2
votes

I'm running a website + native apps that communicate via HTTPS with my backend. The following requirements must be fulfilled:

  1. Sliding session on the website. That means if the user interacted with the website within the last xx Minutes, he must not be logged out
  2. Remember me on the website. When this is checked, the user must not be logged out (or after a very long time)
  3. The user must not be logged out on the app
  4. Access can be revoked, either by the user (currently logged in) or specific events (password changes).

What I currently have is the following: A refresh token endpoint generates a JWT when password hash and username match in the database. Every refresh token has a jti that is stored in the database, as well as expiration (for DB cleanup only), device_id and a revoked flag. Another endpoint can be hit with the refresh token, which returns a JWT access token. The access token is valid for 15 minutes. The access token cannot be revoked.

My problems arise with requirement 1. I do not want the user to reauthenticate when he's interacting with the website. This means I need the refresh token. However, the refresh token must only be valid for e.g. last user interaction + xx Minutes. I cannot extend the access token with every request, as there is no way to blacklist access tokens. This would mean that a leaked access token is like a master key forever (as long as you constantly hit the api in 15-minute intervals). But I also do not know what the expiration for the request token could be.

The second problem is (2) with incognito modes or multiple devices. Assuming the user opens 20 private tabs and uses remember me on all of them. Then I have to store 20 tokens in the database. I could, of course, set a limit for type "web" to say 5 and "app" to 3 and remove the oldest last accessed one from the database (and therefore invalidate it). But this would log him out on the "main" browser if he opens 5 private tabs somewhere. It would also limit the number of phones to e.g. 2.

Different PCs/laptops would also generate many refresh tokens of type web. How would I best identify the corresponding device so access can be revoked, but I also do not store hundreds of refresh tokens over the application's lifetime? Best would be one refresh token per device (windows+firefox, iPhoneA, iPhoneB, windows2+firefox). But identifying desktop PC's is super hard.

What it comes down to is:

  1. How can I store refresh tokens in the DB so they are identifiable to the end-user (e.g. Whatsapp webs "Safari started in New York last used at xxx-xxx-xxx"
  2. How do I avoid having hundreds of tokens per user in the DB (as refresh token basically never expire, and the user can open as many private tabs as he likes without logging off)
  3. How can I implement sliding windows with the refresh/access token pattern? So no unlimited refresh token on the client-side, but also no logoff after the access token expires as long as there is any usage. I could have it in the session storage, but then it still clutters my database and shows to the user as "currently logged in" (which displays all refresh tokens) as it's basically still valid.
1

1 Answers

1
votes

Sliding session on the website. That means if the user interacted with the website within the last xx Minutes, he must not be logged out

To solve this problem you can use a refresh token, i.e when the user login for the first time, the application will return a access token (JWT format), with an expiration date set to the amount that you want. When the user will browse the application, your backend will return a X-Refresh-Token header valid for your xx amount of time (i.e you'll need to return this header for each backend call).

If the acess token is expired (the backend will read the access token used, and perform check on expiration date token field), the backend will return a 401 Unauthorized error, and your client must call the authentication endpoint, providing the last refresh token stored, to issue a new access token.

With this implementation your requirement #1 is satisfied.

Remember me on the website. When this is checked, the user must not be logged out (or after a very long time)

To solve this one, you'll just need to generate a long lived access token (i.e with an expiration date set to the amount of time you want).

The user must not be logged out on the app

I don't understand this one

Access can be revoked, either by the user (currently logged in) or specific events (password changes).

This one is really tricky. Since backend is stateless, revoking access token is a really complex topic. Hopefully a lot of pattern existing to solve this one, we just need to discuss about it.