Search code examples
kotlinktor

Redirect back to initial page after form login in KTOR?


I have a protected resource in KTOR which requires authentication via auth-session.

authenticate("auth-session") {
     get("/list") {...}

route("/login") {
     get { call.respond(FreeMarkerContent("login.ftl", model = null)) }
     
     authenticate("auth-form") {
     post {
        val loginUser = call.principal<UserIdPrincipal>()?.name.toString()
            val session = UserSession(loginUser, AccessLevel.USER )
            call.sessions.set(session)
            call.respondRedirect("/")
           }
     }

Is it possible to capture the initial resource (i.e. "/list") and redirect back there instead of "/"? I have other resources protected by auth-session and all require different redirect URIs. I appreciate help as I could not find a solution elsewhere.

I tried to capture referer header but it gets lost after get/post operations.


Solution

  • You can pass the original requested URL down to the /login POST route through a query parameter. In the challenge of the session authentication provider, the client can be redirected to the /login page with the query parameter, which can then be inserted into the form's action in the login.ftl template. If the authentication is successful, the value of the query parameter can be used for the final redirection. Here is a complete code snippet:

    import freemarker.cache.ClassTemplateLoader
    import io.ktor.http.*
    import io.ktor.server.application.*
    import io.ktor.server.auth.*
    import io.ktor.server.engine.*
    import io.ktor.server.freemarker.*
    import io.ktor.server.netty.*
    import io.ktor.server.request.*
    import io.ktor.server.response.*
    import io.ktor.server.routing.*
    import io.ktor.server.sessions.*
    
    fun main() {
        embeddedServer(Netty, port = 8080) {
            install(FreeMarker) {
                templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
            }
            install(Sessions) {
                cookie<UserSession>("user_session") {
                    cookie.path = "/"
                    cookie.maxAgeInSeconds = 60
                }
            }
    
            install(Authentication) {
                session<UserSession>("auth-session") {
                    validate { session ->
                        if (session.name.startsWith("jet")) {
                            session
                        } else {
                            null
                        }
                    }
                    challenge {
                        call.respondRedirect("/login?origin=${call.request.uri}")
                    }
                }
    
                form("auth-form") {
                    userParamName = "username"
                    passwordParamName = "password"
                    validate { credentials ->
                        if (credentials.name == "jetbrains" && credentials.password == "foobar") {
                            UserIdPrincipal(credentials.name)
                        } else {
                            null
                        }
                    }
                    challenge {
                        call.respond(HttpStatusCode.Unauthorized, "Credentials are not valid")
                    }
                }
            }
    
            routing {
                authenticate("auth-session") {
                    get("/list") { call.respondText { "List" } }
                }
                route("/login") {
                    get { call.respond(FreeMarkerContent("login.ftl", model = mapOf("origin" to call.request.queryParameters["origin"]))) }
    
                    authenticate("auth-form") {
                        post {
                            val loginUser = call.principal<UserIdPrincipal>()?.name.toString()
                            val session = UserSession(loginUser)
                            call.sessions.set(session)
    
                            val origin = call.request.queryParameters["origin"]
    
                            if (!origin.isNullOrBlank()) {
                                call.respondRedirect(origin)
                                return@post
                            }
    
                            call.respondRedirect("/")
                        }
                    }
                }
            }
        }.start(wait = true)
    }
    
    data class UserSession(val name: String) : Principal
    

    The login.ftl:

    <html>
    <body>
        <form action="/login?origin=${origin}" method="post">
            <input type="text" name="username" value="jetbrains">
            <input type="password" name="password" value="foobar">
            <input type="submit">
        </form>
    </body>
    </html>