24

We are developing an application with Laravel/PHP and we want to use an pay-per-user pricing model. For that we have to ensure that an account can only used by only one concurrent user. We use JWT for authentication and it is stateless so I can't use sessions.

To ensure one concurrent login I can enclose the Browser-agent or IP, but both aren't unique and it is possible that they occur multiple times in e.g. an office. Also I can send the MAC address, but that is not the easiest way.

Are there other solutions to ensure one concurrent login per user with JWT?

hygull
  • 8,464
  • 2
  • 43
  • 52
Gert Timmerman
  • 251
  • 1
  • 2
  • 4
  • You might get some ideas from this RFC proposal: https://tools.ietf.org/html/draft-jones-oauth-token-binding-00 – ingenious Oct 06 '16 at 08:20

5 Answers5

21

I think that the simple answer to this is NO, you cannot do that by JWT and keep the server stateless. However, if you use the setup with an Access Token and a Refresh Token, you can probably achieve something like this:

  1. A user logs in, you store the Refresh Token in a DB
  2. The Access Token expires. Before you issue a new Access Token from your Refresh Token, make the standard check that the account is still OK, but also compare the Refresh Token to the one in your DB. Make sure they match.
  3. A second user logs in with the same account. Store the issues Refresh Token in the DB and over write the old Refresh Token. (One stored Refresh Token per account.)
  4. First users Access Token expires again. This time there is another Refresh Token in the DB, and no new Access Token is issued for that user.

This will result in a login flow where the latest user to login can use your service. This is handy if it actually is the same user that change device or restart browser session. Compare to for example Spotify's "chasing the stream" way of handling concurrent listening.

Andreas Lundgren
  • 12,043
  • 3
  • 22
  • 44
  • how about SPA apps using implicit flow means there is no concept of refresh tokens in this case. ? – Jay Aug 24 '17 at 23:42
  • 1
    If I understood point 3 and 4 correctly, won't there be situation where if first user's token is not expired and second user logs in then both user will stay logged in until first user's token expires ? – hiren Oct 09 '17 at 10:05
  • in SPA the token can also expire, the app must just need to know how to refresh it, and refresh it before it expires in the background. Just remember to set a short enough expire time but enough for every app to refresh it every now and then. – Juan Carrey Feb 27 '20 at 14:25
  • 1
    @Hiren is right, combining this with a reasonable low expiry time depending on the app nature will reduce the probability of 2 simultaneous user logins but again it doesn't eliminate it completely. – Binod Kalathil Jun 15 '20 at 10:21
17

Only answer i can think of:

On login: "User" in your DB has a value = activeJwt

User logs in and JWT token is created, copy the JWT string to value activeJWT in your DB and send it to user. If you login on another device same deal, and the activeJWT value is changed

On all requests that require login match users JWT-string and activeJWT, if they dont match it means another device logged in after making the old token useless.

Peder
  • 171
  • 1
  • 2
  • 1
    Good idea, and it works too, thanks! Since I'm using Spring, I used a `HandlerInterceptorAdapter` in order to test the JWT against the one stored in the DB. – GuiRitter Nov 22 '19 at 11:18
  • 8
    Then you are losing the good thing about jwt, which is that you do not need to access the DB to get the user details and check user is authenticated, for each and every request. – Juan Carrey Feb 27 '20 at 14:24
  • Simple yet elegant – Ritankar Paul Sep 04 '20 at 20:38
1

I now testing without JWT and with OAuth2 authentication with password grant tokens. Within 1 client the user can only use the app with one login, if he logins on another session/device, the other login (token) will not be valid anymore. When i want to allow one user to login multiple times (e.g. web app and mobile app) i can use multiple clients.

Gert Timmerman
  • 251
  • 1
  • 2
  • 4
1

Anyway you need to store token in db.
For that purpose you will need super fast storage. Redis will be perfect.
On new login just replace token with new one.
Every time user makes request check if token matches the one you have in redis. If not then kick him out.

0

The only I can think of to do this without saving the state on the server is to disable the feature to sign new tokens for the life span of the token.

Journeycorner
  • 2,474
  • 3
  • 19
  • 43
  • 4
    This will also prevent a user from logging in again on another device or if browser was restarted during the lifespan of the token. – Andreas Lundgren Oct 11 '16 at 05:54