Hi,
I got some very good answers in issue #541, thanks for that.
You might consider adding some more info about sessions to the Javalin docs. As it is, the doc mentions that attribute and sessionAttribute exist, but it doesn't explain any of their behaviors, or what the differences are.
I got the authentication and role handling down now, however I'm not quite there yet with Sessions.
This table stores the sessions in my Postgres:
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)
Questions:
Will Javalin/Jetty ensure that sessions are removed from this table after a certain time, or do I need to write some housekeeping code? It doesn't seem to be setting any expiry time so I'm a little worried.
How do I make a session persistent ("remember me")? The default implementation seems to be to forget about the client when the browser is restarted. Javalin doesn't seem to provide any entry point for manipulating the session cookie directly, unless I'm mistaken.
Likewise, how can I set the HttpOnly attribute on the JSESSIONID cookie? It should be useful to protect the session ID from being stolen by XSS or similar. (Ref. https://www.owasp.org/index.php/HttpOnly#Mitigating_the_Most_Common_XSS_attack_using_HttpOnly)
Thanks for your continued efforts in bringing us this excellent library!
Joel
You might consider adding some more info about sessions to the Javalin docs. As it is, the doc mentions that attribute and sessionAttribute exist, but it doesn't explain any of their behaviors, or what the differences are.
It would be a lot of work to replicate all of the documentation for the underlying Servlet concepts, but I will consider expanding on the Session tutorial. The good news is that the Servlet javadocs are excellent, so you can cmd/ctrl+click to the source and read more there.
how can I set the HttpOnly attribute on the JSESSIONID cookie
SessionHandler().apply { httpOnly = true }
How do I make a session persistent ("remember me")? The default implementation seems to be to forget about the client when the browser is restarted
That should be the default implementation. The JSESSIONID cookie is set with an expiry of 1969-12-31T23:59:59.000Z (never). If you have disabled cookies (ex by using incognito mode), you will have to log in every time you restart the browser.
Will Javalin/Jetty ensure that sessions are removed from this table after a certain time, or do I need to write some housekeeping code? It doesn't seem to be setting any expiry time so I'm a little worried.
All of this is handled by Jetty, you can read detailed information about it here: https://www.eclipse.org/jetty/documentation/9.4.x/session-management.html
My thinking has been that people should check the jetty docs for how to configure session handling, as Jetty is responsible for everything.
Thanks, I'll try SessionHandler().apply tomorrow.
About Jetty and sessions: Thing is, I read through the Jetty chapter you linked (it's linked from the doc, so of course I did). But I only find info on how to set up various config: the web.xml of a Jetty server, the .ini files for various persistency backends, details on session cache behavior, etc, all of which are kind of useless for a beginner Javalin user.
I'm rather in need a programmer's guide. What classes to use and what methods I can call. Perhaps that is in there somewhere, but I'm not finding it in the Sessions chapter. Not a single code example either.
I would have been completely lost if not for your Sessions tutorial, but even that tutorial doesn't tell me what to do once I have created a SessionHandler. Maybe inform me that I can call SessionHandler().apply{} (and what I can apply, or what a Servlet is. Maybe link to the most relevant Servlet javadocs, or something?
About the JSESSIONID cookie expiry time, I'll check what date was set, but for sure I've tested several times now and every time I close Firefox, my app is setting me a new session ID, so something isn't working right. Not sure if it's my code - can you double check the behavior on your side?
About the JSESSIONID cookie expiry time, I'll check what date was set, but for sure I've tested several times now and every time I close Firefox, my app is setting me a new session ID, so something isn't working right. Not sure if it's my code - can you double check the behavior on your side?
I have several apps running in production with this config:
private fun sqlSessionHandler(dbConfig: DataSourceFactory) = SessionHandler().apply {
httpOnly = true
sessionCache = NullSessionCache(this).apply {
sessionDataStore = JDBCSessionDataStoreFactory().apply {
setDatabaseAdaptor(DatabaseAdaptor().apply {
setDriverInfo(dbConfig.driverClass, "${dbConfig.url}?user=${dbConfig.user}&password=${dbConfig.password}")
})
}.getSessionDataStore(sessionHandler)
}
}
Maybe inform me that I can call SessionHandler().apply{} (and what I can apply, or what a Servlet is. Maybe link to the most relevant Servlet javadocs, or something?
apply{} is a general purpose Kotlin function. SessionHandler().apply { httpOnly = true } is the same as:
SessionHandler sessionHandler = new SessionHandler();
sessionHandler.setHttpOnly(true)
To teach Kotlin, Servlets, and Jetty (and Javalin) for all topics will take a lot of time and effort, I don't think I'm up for that. I will expand on the session tutorial though, as it's a pretty important topic.
I know the Jetty docs can be a pain to work with, but everything in my session tutorial comes from reading the docs I linked to (I'll admit it took a few hours though).
Thanks @tipsy for explaining.
I get this in Firefox:

I get this in Chrome, it seems to behave the same way:

As you can see it gets expired at "session" and "N/A" respectively, which I guess explains the behavior I've been seeing.
I have no code setting up the cookie specifics in my project right now. The relevant code is basically the below. I've based it heavily on the session tutorial:
Main.kt:
val app = Javalin.create()
.enableCorsForOrigin("*")
.sessionHandler {
SessionHandlerUtil.getSqlSessionHandler()
}
SessionHandlerUtil.java:
package com.ericsson.cdzm.ws;
import org.eclipse.jetty.server.session.*;
public class SessionHandlerUtil {
public static SessionHandler getSqlSessionHandler() {
SessionHandler sessionHandler = new SessionHandler();
SessionCache sessionCache = new DefaultSessionCache(sessionHandler);
sessionCache.setSessionDataStore(makeJdbcDataStoreFactory().getSessionDataStore(sessionHandler));
sessionHandler.setSessionCache(sessionCache);
return sessionHandler;
}
private static JDBCSessionDataStoreFactory makeJdbcDataStoreFactory() {
DatabaseAdaptor databaseAdaptor = new DatabaseAdaptor();
databaseAdaptor.setDriverInfo(WsKt.getApplicationProperties().getDbDriver(), WsKt.getApplicationProperties().getSessionDbUrl());
JDBCSessionDataStoreFactory jdbcSessionDataStoreFactory = new JDBCSessionDataStoreFactory();
jdbcSessionDataStoreFactory.setDatabaseAdaptor(databaseAdaptor);
return jdbcSessionDataStoreFactory;
}
}
So you see, I don't know why I wouldn't get a good default cookie expiry...
My colleague is observing the same behavior, the cookie "expires" when he closes Chrome, and he gets a new Session ID from the server.
I added the log statement logger.info("sessionHandler maxCookieAge: {}, getMaxInactiveInterval: {}", sessionHandler.getMaxCookieAge(), sessionHandler.getMaxInactiveInterval()); and it prints -1:
[main] INFO SessionHandlerUtil - sessionHandler maxCookieAge: -1, getMaxInactiveInterval: -1
I'm not saying your docs need to "teach Kotlin, Servlets, and Jetty (and Javalin) for all topics", of course that's a big ask. I'm just saying that on this particular topic, I hit a brick wall because there is not even a hint that I should google for "servlet", it's not a prominent term in the Javalin docs...
Reading this javaDoc: https://www.eclipse.org/jetty/javadoc/9.4.9.v20180320/org/eclipse/jetty/server/session/SessionHandler.html
getMaxCookieAge
@ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
public int getMaxCookieAge​()
Jetty docs say that it should default to -1,
which together looks like it supports your argument that it should have infinite expiry time if not set.
So what is happening that I get "session" cookie expiry?
Also, how do I set it? It's a managed attribute which means a Jetty user would configure it using org.eclipse.jetty.servlet.MaxAge property in their web.xml... setMaxCookieAge doesn't exist according to my compiler.
I'm seeing this message in the logs on startup:
[main] WARN io.javalin.Javalin - Failed to look up ID in sessionDataStore. SessionHandler might be misconfigured.
Looking at the code in JettyServerUtil.kt, I don't see what might have gone wrong. The session persistence layer must be up and running, because I am seeing sessions persisted in the SQL DB as I open new browser sessions.
I've added some example code now: https://github.com/tipsy/javalin-jetty-sessions-example/commit/800c87089bd51c0aad7a8473207d5b9e8e1726fe
As well as a new section at the end of the tutorial: https://javalin.io/tutorials/jetty-session-handling-kotlin#usage-in-javalin
As for why your cookie has a bad expiry date, it's hard for me to help you, as I don't really know much more than you. This is all handled by Jetty, and my knowledge about the topic comes from reading Jetty's docs. I would advise that you google "problem + jetty", or "problem + embedded jetty" (more code examples).
If you can publish a repo which reproduces the cookie having "Session" as expiry date, I will have a look.
Thanks, that's a very helpful update.
Any more help and I'll have to credit you as a co-author of my app! =)
I'll try to write a small reproducer but it'll have to be next week. I can open a bug ticket if I am really sure by then how to reproduce it.
(Edit: The below didn't work after all)
For now, I thought I was able to get the desired result with this workaround in my "/login" endpoint:
} else if (credentialsAreCorrect(creds)) {
ctx.sessionAttribute("current-user", creds.getUsername());
ctx.status(201);
ctx.req.changeSessionId(); //change the session id on login, to protect against session fixation attacks
Cookie cookie = new Cookie("JSESSIONID", ctx.req.getSession().getId());
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(Integer.MAX_VALUE);
ctx.cookie(cookie); //will overwrite the default session cookie
It's a bit of a kludge but it seemed to work in my integration tests. The cookie was set with an expiry time in 2087.
The only time I care about setting an expiry time is when a login succeeds, so it would have been good enough to do it there.
However, when running this in a real browser, I see the login response has no less than three cookie entries, one with the old session ID, one autogenerated, and one manually generated. Of course, this means my problem is not solved...
HTTP/1.1 201 Created
Server: nginx/1.14.2
Date: Wed, 17 Apr 2019 12:24:08 GMT
Content-Type: text/plain
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost:30000
Access-Control-Allow-Credentials: true
Set-Cookie: JSESSIONID=node01g7vyi2m5zv8qgrjcsoetpy4o13.node0;Path=/;HttpOnly
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: JSESSIONID=node0hwetdy9iwpkyzeopk9ym0qz114.node0;Path=/;HttpOnly
Set-Cookie: JSESSIONID=node0hwetdy9iwpkyzeopk9ym0qz114;Path=/;Expires=Mon, 05-May-2087 15:38:15 GMT;Max-Age=2147483647;HttpOnly
Gah...
I tested using a FileSessionStore but same result.
You're welcome. I would advice against trying to manipulating the cookie manually, let Jetty handle that. I'll close the issue now, but please feel free to keep posting in it. I try to close question-issues to keep the tracker clean, but closed doesn't mean locked.
Hello, i don't want to reopen the issue and "mess up" the tracker, but I have the same issue and @Jolter's workarround does not work for me either in a real browser.
So I comment to ask you if you find a fix or a workarround for this problem please share it!
And thanks Tipsy for your good support :)
@Cadiducho, I didn't have time yet to make a small, self-contained reproduction example. Should have time in a couple of weeks. Hopefully I will find the root cause in the process.
@tipsy I think I can point to one behavior that doesn't seem right to me.
I posted a reproduction example at https://github.com/Jolter/javalin-session-issue
Clearly the cookie response header is not setting any expiry time:
Set-Cookie: JSESSIONID=node0a7hcpncqe4ia16sapi1lepx1s1.node0;Path=/;HttpOnly
What I would want to see is that it would include an Expires setting that I could customize:
Set-Cookie: JSESSIONID=node0a7hcpncqe4ia16sapi1lepx1s1.node0;Path=/;HttpOnly;Expires=<later_date>
I'm far from any HTTP expert by this is how I'm interpreting the docs at for example https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
Session cookies will get removed when the client is shut down. They don't specify the Expires or Max-Age directives.
Set-Cookie: sessionid=38afes7a8; HttpOnly; Path=/
[...]
Instead of expiring when the client is closed, permanent cookies expire at a specific date (Expires) or after a specific length of time (Max-Age).Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
OK, so writing a reproduction project is always a good learning experience...
This solves my problem:
fun fileSessionHandler() = SessionHandler().apply { // create the session handler
sessionCache = NullSessionCache(this).apply { // attach a cache to the handler
sessionDataStore = FileSessionDataStore().apply { // attach a store to the cache
val baseDir = File(System.getProperty("java.io.tmpdir"))
this.storeDir = File(baseDir, "javalin-session-store").apply { mkdir() }
}
}
sessionCookieConfig.maxAge = 86400
httpOnly = true
}
Found this by digging in the SessionHandler class.
@tipsy I guess I solved that one - but my next problem is, it's configured per SessionHandler instance, not per Session.
I would like my users to be able to check a "remember me" box on the login form, and have the cookie expire on session end if it's not selected. If checked, I would set something like two weeks or other "long enough" time for my users.
it's configured per SessionHandler instance, not per Session.
What do you mean by this? If you want to share a session across multiple instances you need to use a database, not a FileSessionDataStore.
@tipsy I'm sure you know what I mean by a "remember me" checkbox on a login page?
I can't find a way to change the variable sessionCookieConfig.maxAge other than globally, before I start the app, and in particular, I cannot set different Max-Ages on different sessions. (At least, that's what I think after doing some digging in the Javalin code.)
I know I need a database store, but the same conclusion holds for RAM or File store - MaxAge is only configured per SessionHandler, isn't it?
I think I'm starting to understand the issue. In my experience, it usually works like this:
The session expiry is not inherently related to "logged in" vs "not logged in", it's is just something describing the relationship between a specific client and the server. You don't need to change expiry time based on if the user is logged in or not, you can instead manage this with business logic (people usually check a JWT to see if the login is still valid).
That being said, managing it via expiry isn't a bad idea, I'll ask the Jetty devs.
Aha! I knew people must have some way to manage this. =)
I was thinking to use the cookie expiry because I've seen that mechanism in use in a few web services, like Gerrit.
I'll look into JWT, it might be a fine solution for us once I understand it.
Thanks again for the pointer!