Force Logout of user on multiple devices using JWT - rest

We have an existing REST API that is currently using JWT.
Client came with the requirement that users can only use 1 device at a time. For example, if user logs in from iOS device then logs in Android device, then the iOS device should be "forced" to logout.
Since we are using JWT, we are not keeping track of tokens, except a Token Blacklist when user click Log Out.
I researched on how to "force" log out the user and it seems we would need to keep track of the last token used by the user, then invalidate that once we detect a new log-in.
Is there no cleaner / alternative way to achieve above?

Here are the steps to implement your requirement:
step 1: save the timestamp of last user activity performed for password update or logout from all device
add a column lastSessionResetDate to store last password update date in your user table
While performing forget password/change password apis or at logout-from-all-device, update lastSessionResetDate
step 2: set lastSessionResetDate in JWT Claims, when generating JWT token
example of setting claim while token generation
//make a claims map (`Claim extends Map<String, Object>`)
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, user.getUserName());
claims.put(CLAIM_KEY_AUDIENCE, "web");
claims.put(CLAIM_KEY_CREATED, new Date());
claims.put(CLAIM_KEY_LAST_SESSION_RESET, user.lastSessionResetDate());
//set claims and build JWT
return Jwts.builder().setClaims(claims).setSubject(user.getUserName())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + yourTokenValidity))
.signWith(SignatureAlgorithm.HS512,"yourSecretEncodedBase64")
.compact();
You can get any of claim value at time of parsing JWT token.
Step 3: Time to validate user and session
After getting User entity by JWT, check if lastSessionResetDate is valid till time, else unauthorized the request.
You must be already fetching user from database, so you don't need to make any other query because our new added column lastSessionResetDate will be a part of the same query result. (You just have to add one if-else block).
Note: You can also get prompt value in response from users for log-me-out-from-other-devices
Hope you have got an idea, Happy Coding!!

Assuming you use JWTs as Bearer tokens, depending on your requirements, it might not be sufficient enough "to keep track of the last token used by the user, then invalidate that once we detect a new log-in.".
As Bearer tokens (independent on whether it's JWT or not) are send by the client, the client is also aware of the token, which allows the sender to also copy and paste tokens from one device to another (or issue requests including the same token from various devices).
Your requirements sound like a standard use case for proper session management that allows e.g. for user-agent binding.

Related

Next-auth (JWT) logging sessions

According to Next-auth documentation as we are using credentials provider to connect to our users collection for usernames and passwords Next-Auth does not use the session database to check if a session is active.
If you use a custom credentials provider user accounts will not be persisted in a database by NextAuth.js (even if one is configured). The option to use JSON Web Tokens for session tokens (which allow sign in without using a session database) must be enabled to use a custom credentials provider.
I am wanting to add a _middleware that will allow me to store and check that the latest JWT sessions inside our session database matches the latest one that the user is currently using.
Reason being is that if I have two devices technically I would be able to login on both devices and at the moment their is no real way to discern if the user from PC2 is also login on PC1.
So my theory and not sure if this will work is to add the following.
callbacks: {
jwt: async ({ token, user }) => {
console.log("running JWT - because of custom login")
user && (token.user = user)
(ADD CODE HERE TO SAVE TOKEN & CHECK IF TOKEN IS LATEST TOKEN + VALID - INSIDE SESSION DATABASE)
(IF OLD-TOKEN IS NO LONGER VALID OR THE LATEST TOKEN LOG THE USER OUT)
console.log("TOKEN IS "+ JSON.stringify(token))
return token
},
session: async ({ session, token, user }) => {
console.log(JSON.stringify(session) +" / "+ JSON.stringify(token) +"/"+ JSON.stringify(user));
session.user.tokenID = token //ADD CODE HERE TO SAVE TOKEN TO SESSION COOKIE
session.user = user
return session
}
},
Then if I create a middleware that checks this tokenID and matches it with the session database and if it is the latest result from said user.
For example.
Say PC1 (user1) login here
{
_id: 1
tokenID: 918171-918171-81716-0887
userid: 00-00-00-001
expire: "2022-05-23T12:47:04.593Z"
}
But then PC2 also (user1) login again and created a new session
{
_id: 2
tokenID: 71888-651777-616666-0117
userid: 00-00-00-001
expire: "2022-05-24T12:47:04.593Z"
}
What I would need the middleware to do (which a simple mongodb query could do) is check if their is an older session stored for the same userID if so then logout from PC1.
Now there are a few things that I can see going wrong with this idea.
Other Provider Sessions (which use session DB) making it harder to validate
Every time you call the signup page or session it seems to re-run the JWT section - which in theory is fine, as we could use a findOne Update function which if token is in session then just update the expiry - however that would cause say PC1 refreshes after PC2 logged in then PC1 expire time might be longer then PC2 expire time (but a simple sort function would allow us to see if the ID was older then PC2 if so logout).
JWT changes token every time you reload the page
How would this help privacy and the user data?
By not storing the user details inside a session cookie we would not be exposing the data to hackers or other plugins like FB or Google as the user data would only be linked to a token ID. Which to request the user data you would have to make sure the tokenID was valid first and then be allowed to fetch user data.
I understand that Next-Auth may not want too do this, and this is why I ask the question what is the best practice to do what I am wanting to achieve.
This answer is based on the confirmation that the issue is that you want to be able to only have users able to be signed in to one computer/device at time and that you are using usernames and passwords to authenticate them.
In that scenario you also need to have a database that records a token in each JWT issued. It is not possible to solve for that problem without a database.
Here is how you can solve for it it using JWT and a database:
On every new sign in, you would need to use the jwt callback to add something like a UUID to each JWT and then record that UUID , the User ID and the time the JWT expires in a database.
In that callback, if there are other entries in the database for the same User ID you should mark them as invalid (or delete them from the database).
Everytime an existing JWT is read in that same callback you would need to check to see if the UUID in the database was still valid (i.e. still exist / doesn't point to a UUID that corresponds to a JWT flagged as expired) and if it is no longer valid, don't return a valid JWT.
You might also want to add special handling in the session callback that does something similar to improve the user experience by gracefully handling it in the User Interface of the computer they are being signed out of.
Effectively this has all the downsides of a JWT with all the downsides of a session database (although there are niche cases where this is a preferable approach).
I would not recommend using usernames and passwords or limiting users to only being able to sign in to one computer at at time, however given those unusually specific constraints (which also necessitates a solution that has a negative impact on performance) you may want to consider a different authentication solution and/or think how else you could address the underlying need this is trying to address (and if it's worth the cost and complexity).

How to properly use refresh token on different devices?

After successful authorization on the client, two tokens are saved in cookies, one of them is access the other is refresh, when the access token expires, the client sends a refresh and the server issues two new tokens to the client, the point is, the fact is that if the user logs in from another device, for example, from a phone , then the refresh token on the first device will already be invalid. Refresh token is stored in the database table users, in the table fields: email, password, refresh_token.
That is, when authorizing on another device, the refresh_token field will be updated and on the first device, the refresh token is already invalid in the cookies. How to make it possible to use the refresh token, not only on one device? I saw that I needed to do something with the device identifier, but did not understand, I also did not understand where to get this device identifier. I would be grateful if you describe in detail how to properly organize this process.

Blocking JWT tokens AdonisJS

In my application, I am using JWT for authentication on the backend created in Adonis. But, I am facing an issue.
Since, JWT is stateless, it can only be logged out by deleting it from client-side. But, I require a feature in which I am trying to logout a user from server side in a case without client interaction. So, I read a few blogs and found out, the best way to make this happen is black listing the used JWT token.
But, now the issue is that if I am trying to black list, AdonisJS just saves the token, how can I blacklist it? I mean how can I compare the Authorization header that contains the complete JWT and the token that is encoded within it?
Basically how can I generate the JWT from the token column of the record that is saved by AdonisJS?
If any other way is possible please suggest.
TL;DR
You can simply run await auth.logout(). The token will be deleted automatically.
Long Answer:
You can define a route e.g. /user/logout
Route.get('/user/logout, 'UsersController.logout')
Then open the controller and implement the logout method. Just put this in there:
return await auth.logout()
This 1LoC will deletes the current token for the corresponding user.
However, you can do this manually:
await Database.from('api_tokens').where('id', tokenId).delete()
where tokenId is the user's token.

Does I understand access and refresh token technique for authentication correctly?

After doing some research in using JWT with Access Token and Refresh Token for authentication. I understand this in this way.
After login, return to user Access Token and Refresh Token (using same technique JWT for both).
Saving Refresh Token in Database (one User can have multiple Refresh Tokens for multiple devices).
Whenever user sends a request with invalid Access Token, check Refresh Token and call another api to get new Access Token (doing this in client side). After that, call api to get data again with new Access Token.
If Refresh Token is invalid, deleting its record in database and user must to login again to get new Refresh Token.
Does I understand Access and Refresh Token technique correctly? Please give me some advices. Thank in advance.
Of the 4 steps you listed, some look more or less correct while others do not. I will begin this answer by giving the premise for why refresh tokens were created and what is their main purpose.
Using the JWT pattern with only access tokens, there is a potential usability problem when the JWT token expires. Consider as an example a banking website. When a user logs in, he receives a JWT token with a certain expiry (typically stored under the exp key in the claims section of the token). If the token is given say a 5 minute expiry, then from a usability point of view, it means that the website would have to force the user to manually login every 5 minutes. Obviously, this is not the best user experience, because it means that a user who happens to be in the middle of some business process when the token expires might lose all that work. This is where refresh tokens step in to alleviate this problem.
Using the JWT pattern with refresh tokens means that the user receives both an access and a refresh token. A typical workflow here might be:
After login, return to user Access Token and Refresh Token (using same technique JWT for both). The receiver notes when the access token is set to expire (say 15 minutes).
As the expiry of the access token approaches (e.g. 10 minutes), the UI will send the refresh token to the backend to obtain a new access token (and refresh token). This could be done explicitly, e.g. on a website which displays a popup asking if the user wants to continue. Or it could be done in stealth mode, with a REST call being made under the hood to get the new access token.
For the edge case where the refresh token cannot be used to obtain a new access token, then the very next user action which requires authentication would fail. In this case, the user would have to redirected to the login page. But, as this case should generally be rare, it does not disqualify the refresh token pattern.
I would also point out that storing the access/refresh tokens in the database largely defeats the purpose of the JWT pattern. One major reason for using JWT is that it pushes the user session state out of the application and onto the user. By storing tokens in your database, you are totally making your user sessions very stateful, which has all sorts of potential drawbacks. Consider using the suggested workflow above to avoid doing this.
The way I see it, your refresh token needs to be stored and associated with the device and the user.
Example:
User Logs In in Device A
Call Login endpoint
Validate user is valid
If valid, generate a refresh token associated with the userid & device
id
store required data to your table or storage engine (user_sessions..etc)
user_id | device_id | refresh_token | expires_at
Return the payload with access_token, refresh_token , access_token_expires_at, refresh_token_expires_at
Front-end, store the payload
when consuming a resource, check the following
If refresh_token_expires_at > now then logs them out , show your session is timeout (or you can have a never expired refresh_token.. ex. refresh_token_expires_at can be 0)
if access_token_expires_at > now then call refresh token endpoint along with your payload.
on the refresh endpoint, validate the call and check the refresh token against the data stored.
if refresh token is valid for this user+device, generate a new access_token
return the access_token and its expires_at
If the refresh token is INvalid , return invalid
front end will log the user out.
** in any case, if a refresh token was compromised, it will be only for that particular device/user. A user can then deactivate or remove the device from their list. This action will invalidate the refresh_token on their next refresh call.

Tracking multiple logins from the same user with Stormpath

I'm working on a mobile application that uses Stormpath on the server side for authentication and authorization. I need to support the same user signing in on more than one device, but I want to be able to keep track of it and limit it if I want to.
My application currently uses Stormpath to sign in the user using email/password or MDN/password and upon successful login returns a JWT token to use for API access to the server.
I'm thinking of the following approach:
Keep a list of sessions in the user's account. Every time the user signs in, a new entry is added with the device_id and the JWT provided. When the user signs off, the entry is removed or marked inactive.
When a user tries to sign in on another device, if I want to restrict to only one active device, I would set the other entry to disabled and expire the JWT so the application can detect it and require login again.
If I wanted to restrict the user to a maximum of n sessions, I could just count the entries and force the user to sign off on one of the other sessions before allowing her/him to sign in on the new device
Is this a good approach? Is there a better way to do it? What are the issues with this method?
I work at Stormpath on the mobile SDKs. You can use the access / refresh token feature that we have to accomplish this.
Every time the user signs in, an access and refresh token are created. When the user signs off, the refresh token is deleted, as well as the access token.
When a user tries to sign in on another device, if you want to restrict to only one active device, you can delete all of the other access & refresh tokens.
If you wanted to restrict the user to a maximum of n sessions, I could just count the entries and force the user to delete one of the refresh tokens before allowing her/him to sign in on the new device. You would then go through the access tokens, and delete ones with a matching "rti" (refresh token ID)
Several notes about implementing this:
If you're using a Stormpath Framework integration, the default is to verify an access token locally (instead of sending it to Stormpath to validate). This is because they have a signature that can be validated by the SDK. However, to log out a user, you'll either have to set this to remote validation, or use a short access token life (and use the refresh tokens to control each "session")
Refresh tokens can't store "customData", so you'll have to maintain metadata about the refresh token in either the account's customData, or in your own database.
Alternatively, you could "create" API Keys for each user, and use that instead of sessions for each user. You can use the API Key name or description attributes to keep track of where the user signed in from / etc.