What is the idiomatic way of getting the data asso...
# ktor
t
What is the idiomatic way of getting the data associated with a user session in optional routes? Should I wrap all routes in an
authenticate("session-auth", optional=true) { ... }
and then retrieve the principal, or should i check whether the corresponding session is set, get the session (cookie) data and then check if the session is valid in each route?
Both solutions feel a bit suboptimal, the authenticate way, adds another layer of indentation even though the authentication is optional, making the route definitions harder to read IMO. The retrieving the session and checking its validity for each route feels a bit out of place. My Use case is that i am building a site where a user can login. If logged in, i want to show the users name in the header if logged in or a login button if not logged in. I'm using thymeleaf so i have to add the user to the map of thymeleaf params in each route.
a
Can you list the other use cases where the session can be optional?
t
This is the main use case, since I need to do it for basically all routes. Another use case would be adding items to a cart, which for non-loggedin users will be handled with an extra cart session, for logged in users the cart data will be associated via the login session.
a
You can reduce code duplication by encapsulating the common model in a method. Here is an example:
Copy code
fun ApplicationCall.baseTemplateModel(): Map<String, Any?> {
    val user = sessions.get<UserSession>()
    return mapOf("user" to user)
}
Here is a usage example:
Copy code
routing {
    get("/login/{name}") {
        val name = call.parameters["name"] ?: return@get call.respond(HttpStatusCode.BadRequest)
        call.sessions.set(UserSession(name))
    }

    get("/a") {
        call.respond(ThymeleafContent("index", call.baseTemplateModel() + mapOf("body" to 123)))
    }

    get("/b") {
        call.respond(ThymeleafContent("index", call.baseTemplateModel() + mapOf("body" to 456)))
    }
}
When the web context is supported, the session object should be automatically stored in the session attributes.
t
So it's not a smell to access a session, that has been declared to be used by the session authentication provider, by using
call.sessions.get
? 🙂
a
In my example, I don't use the Authentication plugin.
t
First and foremost: Thank you for your suggestion and help! My question was specifically concerned with the authentication plugin though. When using the authentication plugin with a session Auth Provider like so:
Copy code
session<UserSessionData>("auth-session") {
    validate { session ->
        // Check if session is valid by looking the session up in the DB
    }
    challenge {
        call.respondRedirect("/login")
    }
}
I am doing the validation of the session via the plugin. Now I see only three options: • I wrap every route with
authenticate("auth-session", optional = true) { ...
thus making my code pretty deeply nested • Or I do something like you did getting the session with an extension function on ApplicationCall and doing the validation of the session here again. • I could also wrap all my routes in a single authenticate block, and then have all routes where the authentication is optional in a separate function calling the `routing`block. However i feel Option two and three are kind of smelly, because for option two i feel like all authentication logic belongs in the Authentication Plugin installation setup and accessing the session immediately via
call.sessions
feels like I'd go around that by not using
call.principal
. For Option three it feels kind of smelly because I'd pull the authentication further away from the routes (which to my mind is where it belongs) And for Option One I feel it'd clutter code unnecesarily. I was hoping for you to help me either see a fourth option, that I simply dont know yet. Ease my concerns on one of the Options. Or to tell me that this is basically what amounts to a "me-issue" 😅
Or is the Authentication Plugin the wrong construct for my needs?
a
I would reserve the Authentication plugin solely for user authentication. For instance, an endpoint like
/login
would require authentication. Its handler would then set the user's session on successful authentication. All other endpoints would utilize the solution described above.
thank you color 1