Storing extra data about a user's session

A number of our users have been suffering from a nasty bug recently, which I’ve finally figured out the cause of:

Discourse uses two cookies for storing data about a user’s session: an authentication token cookie (which determines who is logged in) and a session store cookie (which holds encrypted data about a user’s session). I’ve been storing data about a user’s Mozilla IAM session within the session cookie.

Unfortunately, there’s an inconsistency with the expiry dates of the cookies: the session cookie is set to clear at the end of a browser session, whereas the token cookie is set to expire a month from the last rotation (which happens regularly while a user is logged in). So, if the browser’s session is cleared, the token cookie persists - so a user remains logged into discourse - but the session cookie is cleared - meaning the extra IAM data is also cleared, and so some users (NDA/staff) get logged out because their authentication method isn’t deemed sufficiently secure.

I feel a bit silly for misinterpreting the session store as being within the context of a Discourse session and not a browser session. But, with it storing data for that kind of session, there isn’t any simple method for storing arbitrary data about a user’s Discourse session for as long as they are logged in with that session.

At least I hope there isn’t (aside from some horrendous monkey patching of the UserAuthToken class) I spent quite a while when developing this originally trying to find if there was such a method.

Could we add something like a JSON column to the UserAuthToken class, and logic to persist it across refreshes? This also had the added benefit of making it super easy for plugins to extend the “Recently Used Devices” user preferences section with their own data (like, in my case, whether a user used 2FA to log in with that session).

What are people’s thoughts. Have I missed something obvious? I’m happy to submit a PR adding this functionality if not.

2 Likes

Hmm this is in @sam’s wheelhouse I think.

1 Like

Could you simply have a plugin put another table in place and use it for this extra data key on the auth token id? I don’t feel comfortable over generalising a solution here quite yet. Granted the plugin you are describing is not super easy to build, but it is doable.

4 Likes

Oh crap, the id on the user token table persists between refreshes doesn’t it? :facepalm:

I assumed I would have to patch the rotate method to keep track of the new token id somehow, but since it stays the same using my own table seems much easier.

I had, thanks for pointing it out!

4 Likes

For anyone following along at home, @sam’s suggested approach was pretty simple to implement:

The crux of the solution is a find_or_create method on the extra auth token data model, which takes a user’s session and cookies returns the extra data, creating the record and clearing the session if need-be.

This approach is necessary because the Authenticator doesn’t have access to a user’s cookies, only their session, so that extra data has to be placed there at login, and then written to db on the first request after a user has been authenticated. The nice thing about this method is it migrates existing sessions to the new store without requiring users to re-authenticate.

I briefly considered storing this extra data in both the session cookie and the db, and only fetching it from db when the session cookie had been cleared, but it seemed like more complexity than it was worth for only avoiding one extra (pretty quick) SQL query each request.

4 Likes