Javalin: How to get started with user auth and session management

Created on 10 Apr 2019  路  5Comments  路  Source: tipsy/javalin

Hi,

I've started using Javalin and found it really useful so far. However I've gotten stuck when adding real authentication and session handling. I've not used Jetty before, which I think is why I find the existing docs on that topic a bit on the short side.

I added an AccessManager according to tutorial https://javalin.io/tutorials/auth-example, and a SessionManager according to tutorial https://javalin.io/tutorials/jetty-session-handling-kotlin.

I also need to make sure that SessionManager does something useful to my requests. Now, I see that the Context keeps a Request object, which has a lot of methods that I suspect I should use.

So, in my login endpoint, I added this:

BasicAuthCredentials creds = ctx.basicAuthCredentials();
ctx.req.login(creds.getUsername(), creds.getPassword());`

As it is, the above gives me
[qtp1635546341-22] WARN io.javalin.core.ExceptionMapper - Uncaught exception org.eclipse.jetty.server.Authentication$Failed: Authenticated failed for username 'user1'. Already authenticated as null

What do I need to set up to make login() work?

I also figure I should use a SessionManager to remember who is logged in. Does it also handle reading session cookies for me? The tutorial at tells me how to _configure_ the SessionManager, but not what it will do, or how to use it.
I notice I can probably log out a session with ctx.req.getSession().invalidate() but what do I do to for example check whether the Context is for a logged in Session is for a certain user or Role?

HttpSession sess = ctx.req.getSession();

I looked around at the Jetty docs but they are very focused on configuring a web.xml and not so useful for a Javalin user.

It would have been really cool to see an example project that implements Session handling!

INFO QUESTION

Most helpful comment

I don't really know much about remoteUser and isUserInRole. Why do you need to use them? This is how I usually make my access managers:

fun manage(handler: Handler, ctx: Context, roles: Set<Role>) = when {
    roles.contains(Role.ANY) -> handler.handle(ctx)
    ctx.currentUser == null -> redirectToLogin(ctx)
    ctx.currentUser != null && roles.contains(Role.LOGGED_IN) -> handler.handle(ctx)
    ctx.currentUser != null && userHasMatchingRole(ctx, roles) -> handler.handle(ctx)
    else -> throw UnauthorizedResponse()
}

Edit: ctx.currentUser is just ctx.attribute<String>("current-user")

All 5 comments

I'm not all that familiar with how login is handled in Servlets/Jetty either, but luckily this isn't required for auth in Javalin. Once you have a session manager configured (the default one is fine too), you can trust Jetty to keep track of the session cookie which binds the user to the server side session. Handle login however you want (basic auth, google signing, etc), then do ctx.sessionAttribute("logged-in-user", loggedInUser). If logged-in-useris set, then the user is logged in.

That sound very simple indeed. So, from my example above, I would instead do:

BasicAuthCredentials creds = ctx.basicAuthCredentials();
if (credentialsAreCorrect(creds)) {
     ctx.sessionAttribute("logged-in-user", creds.getUsername());
}

And after that I should see the session added to the database table, and a cookie set automatically?

Yes, that's correct. Anything you put in ctx.sessionAttribute() should be added to your database if you have one configured. Jetty will handle both that and the cookie.

OK, I see that it also returns a cookie called JSESSIONID. The raw header is

Set-Cookie: JSESSIONID=node0wqa4mf7chne2ots5ouyxoe2s1.node0;Path=/

I can work with that...

This is what's stored in Postgres:

sessions=# \d
             List of relations
 Schema |     Name      | Type  |  Owner
--------+---------------+-------+----------
 public | jettysessions | table | postgres
(1 row)

sessions=# select * from jettysessions;
            sessionid             | contextpath | virtualhost | lastnode |  accesstime   | lastaccesstime |  createtime   | cookietime | lastsavedtime | expirytime | maxinterval |        map
----------------------------------+-------------+-------------+----------+---------------+----------------+---------------+------------+---------------+------------+-------------+--------------------
 node01bzaiac35kiu218im1jn4yxukv0 |             | 0.0.0.0     | node0    | 1554899734977 |  1554899733548 | 1554899540517 |          0 | 1554899734984 |          0 |          -1 | \xaced [truncated]
(1 row)

I would have loved it if I could get the expected results from ctx.req.isUserInRole():

private fun isPermitted(ctx:Context, permittedRoles: Set<Role>): Boolean {
    if (ctx.req.remoteUser == null) { //Not logged in
        return (permittedRoles.contains(ApiRole.ANYONE))
    }
    for (role in permittedRoles){
        if (ctx.req.isUserInRole(role.toString())) {
            return true
        }
    }
    return false
}

I suppose I should be inferring this mapping from session to a Role using the sessionAttribute I stored, right? And forget about the member functions on ctx.

I don't really know much about remoteUser and isUserInRole. Why do you need to use them? This is how I usually make my access managers:

fun manage(handler: Handler, ctx: Context, roles: Set<Role>) = when {
    roles.contains(Role.ANY) -> handler.handle(ctx)
    ctx.currentUser == null -> redirectToLogin(ctx)
    ctx.currentUser != null && roles.contains(Role.LOGGED_IN) -> handler.handle(ctx)
    ctx.currentUser != null && userHasMatchingRole(ctx, roles) -> handler.handle(ctx)
    else -> throw UnauthorizedResponse()
}

Edit: ctx.currentUser is just ctx.attribute<String>("current-user")

Was this page helpful?
0 / 5 - 0 ratings