JWT (JSON Web Token) automatic prolongation of expiration
Asked Answered
H

17

658

I would like to implement JWT-based authentication to our new REST API. But since the expiration is set in the token, is it possible to automatically prolong it? I don't want users to need to sign in after every X minutes if they were actively using the application in that period. That would be a huge UX fail.

But prolonging the expiration creates a new token (and the old one is still valid until it expires). And generating a new token after each request sounds silly to me. Sounds like a security issue when more than one token is valid at the same time. Of course I could invalidate the old used one using a blacklist but I would need to store the tokens. And one of the benefits of JWT is no storage.

I found how Auth0 solved it. They use not only JWT token but also a refresh token: https://auth0.com/docs/tokens/refresh-tokens

But again, to implement this (without Auth0) I'd need to store refresh tokens and maintain their expiration. What is the real benefit then? Why not have only one token (not JWT) and keep the expiration on the server?

Are there other options? Is using JWT not suited for this scenario?

Haviland answered 4/11, 2014 at 15:41 Comment(4)
Actually there is probably no security issue with many valid tokens at once... There are actually infinite number of valid tokens... So, why to have a refresh token then? I will regenerate them after each request, it should actually be not an issue.Haviland
@Haviland I think having (potentially) hundreds or thousands of unused valid JWTs out there at any given time increases your attack footprint and is a security risk. In my mind, JWTs should be issued carefully as they are access tokens with the keys to the castle in a manner.Jaenicke
Its 10 years since the question was asked, I thought you might be interested in this post JWT Authentication Without Refresh tokens #77886597Squinch
I found a way of generating and renewing JWT's without the need for a separate refresh token. Though it appears JWT's usage is discouraged out of Oauth context. Here's a link to the post describing the approach approach #77886597Squinch
R
753

I work at Auth0 and I was involved in the design of the refresh token feature.

It all depends on the type of application and here is our recommended approach.

Web applications

A good pattern is to refresh the token before it expires.

Set the token expiration to one week and refresh the token every time the user opens the web application and every one hour. If a user doesn't open the application for more than a week, they will have to login again and this is acceptable web application UX.

To refresh the token, your API needs a new endpoint that receives a valid, not expired JWT and returns the same signed JWT with the new expiration field. Then the web application will store the token somewhere.

Mobile/Native applications

Most native applications do login once and only once.

The idea is that the refresh token never expires and it can be exchanged always for a valid JWT.

The problem with a token that never expires is that never means never. What do you do if you lose your phone? So, it needs to be identifiable by the user somehow and the application needs to provide a way to revoke access. We decided to use the device's name, e.g. "maryo's iPad". Then the user can go to the application and revoke access to "maryo's iPad".

Another approach is to revoke the refresh token on specific events. An interesting event is changing the password.

We believe that JWT is not useful for these use cases, so we use a random generated string and we store it on our side.

Reconstruction answered 9/11, 2014 at 23:45 Comment(24)
For the web applications recommended approach, if the token is valid for a week are we not concerned with someone intercepting the token and then being able to use it for such a long time? Disclaimer: I don't quite know what I'm talking about.Gahan
@wbeange yes interception is an issue, even with cookies. You should use https.Jamison
@JoséF.Romaniello Can you clarify what you mean by 'We believe that JWT is not useful for these use cases so we use a random generated string and we store it on our side.' Which use cases and what is the random string for?Shute
@Shute I meant that the refresh token is not a JWT but an opaque token.Jamison
I used an angular interceptor to implement the refresh with each http request.Lenes
Web app: if you don't like refreshing every hour or you want to try an alternative solution, you could make all API requests pass through a server-side proxy that stores in a persistent way the users' refresh tokens and intercepts the 401 responses and exchange tokens before logging the users out. Note that in this way the refresh tokens are not exposed to the client, and the users would experience the same authentication as in native appsPlain
@JoséF.Romaniello, when you discuss generating a random string and storing it. Could you elaborate? Do you mean you store in on the mobile device, or on the server? What happens if they get a hold of the name of the device, or this random string (assuming it is on client) Wouldn't this let them get around any tokens being revoked?Reformism
Also, to clarify...the refresh token you reference..is that meant to not expire in both web and mobile?Reformism
@JoséF.Romaniello In your web application example, everything makes sense to me except having to store the token. I thought the beauty of JWT was stateless authentication - meaning the web application does NOT have to store the token as it is signed. I would think the server could just check the validity of the token, make sure it's within the expiration period, and then issue a renewed JWT token. Could you please elaborate on this? Maybe I just don't understand JWTs enough yet.Albuminuria
@JoséF.Romaniello I have the same question as Lo-Tan. If we are storing tokens in the database how is it different from the traditional web sessions?Yellowlegs
@Yellowlegs Sorry for the confusion, I meant client-side in the browser, like local storage or a cookie.Jamison
Two questions/concerns: 1- Web Application case: why can't expired token be allowed to get a refresh? Say we set short expiration (1 hour) & make renew calls to the backend server when a token expires, as you said. 2- Is there a security concern with storing the hashed (with random salt) password in the token? The idea is that if it's there, the backend server can check against the stored password in the DB when asked for a renewal, & deny request if passwords don't match. This would cover the Mobile/Native app password change, allowing the solution to be extended to Mobile use case.Underlie
@Underlie Your comment realizes the trouble with stolen devices, similar somehow to the logic when password changes. I believe if you already have the refresh token persisted on the server side, for validation when a token needs to be refreshed, there is no need to include in the token the hashed password as you would only simply need to delete all previous refresh tokens of the user after he changes is password. Also you get the benefit that in you backend the user can check all his opened sessions based on the refresh tokens and can invalidate. Like facebook, gmail doesKinser
@Kinser I should have clarified that I'm trying to reach a solution where I don't persist a refresh token on the backend & rely purely on a client JWT, the reason is that I'm trying to allow the backend to scale horizontally without maintaining a common cache (i.e. a fully distributed system)Underlie
But what if someone have refresh token which never expire? He will have access to your account forever?Pebble
@Underlie Some time later, what did you end up with? I'm having the same thoughts as you for the same reasons. By putting a hash of the user PW and scopes in the Access token, this hash could be compared with the current values in the backend each time an Access Token expires. If these values are not changed, and the account is not locked, (and possibly, the Access Token is not in a blacklist), the expired Access Token is enough to issue a new Access Token. This makes the expiry time in an Access Token becomes a "heart beat" describing how often to validate a request towards the backend DB.Tiphane
for web app, I don't think refresh the token before it expires solve any sercurity problem but increase complexity .compare with the JWT token with a short expire time it works the same way. using an unsecure token to get another unsecure token not make sence to me! Although I use Auth0...Chengtu
@AndreasLundgren I still think it's a good solution. I've talked to people who have been very uncomfortable with storing a hashed password in the token though. I'm not sure why exactly, since having the token means having the password (or being able to change it) during its validity period anyway. A safer option would be having a second, randomly generated "password" (similar to the refresh token idea) that's in the payload of the access token, that's what gets checked for validity on refresh. You'd change this "password" when user password changes, or when you revoke access for example.Underlie
@Underlie I agree. (In some services you cannot change your pw with just the token, but a pw change require the user to enter the pw again.) I meant a hash of the "salted and hashed pw"+scope+accountStatus just to avoid bugs about regenerating the random "refresh token" when these values changes. But to explicitly regenerate a random refresh token is a write similar solution.Tiphane
-1 Exposing a public API which blindly re-signs any token to extend its validation period is bad. Now all your tokens have an effective infinite expiry. The act of signing a token should include the appropriate auth checks for each and every claim made in that token at the time of signing.Munmro
From my experience, to refresh the token every time the user open the web application it's not good practice. When using one tab of application it's will be good, but when you use a few tabs or a few browsers it's will be a problem and causes tokens conflicts and disconnect all others application windows you use.Wolsey
with the existence of refresh token, access token's expire date will become useless because it will always get refreshed anyway?Arrowworm
@Arrowworm it does not become useless, it means that if an account was deactivated or highlighted as stolen that an admin could revoke the refresh token which would mean the bad actor who has stolen the account will be unable to use once the access token expires.Mestizo
how about store the username and password in native app and use the username and password to refresh the auth refresh token? @JoséF.RomanielloPullen
C
85

In the case where you handle the auth yourself (i.e don't use a provider like Auth0), the following may work:

  1. Issue JWT token with relatively short expiry, say 15min.
  2. Application checks token expiry date before any transaction requiring a token (token contains expiry date). If token has expired, then it first asks API to 'refresh' the token (this is done transparently to the UX).
  3. API gets token refresh request, but first checks user database to see if a 'reauth' flag has been set against that user profile (token can contain user id). If the flag is present, then the token refresh is denied, otherwise a new token is issued.
  4. Repeat.

The 'reauth' flag in the database backend would be set when, for example, the user has reset their password. The flag gets removed when the user logs in next time.

In addition, let's say you have a policy whereby a user must login at least once every 72hrs. In that case, your API token refresh logic would also check the user's last login date from the user database and deny/allow the token refresh on that basis.

Caddell answered 20/2, 2015 at 2:5 Comment(12)
I don't think this would be secure. If I was an attacker and stole your token and sent it to the server, the server would check and see the flag is set to true which is great as it would block a refresh. The problem I think would be if the victim changed their password the flag would be set to false and now the attacker can use that original token to refresh.Prologize
@Prologize no auth solution is perfect, and there will always be tradeoffs. If an attacker is in a position to 'steal your token', then you may have greater issues to worry about. Setting a maximum token lifetime would be a useful tweak to the above.Caddell
instead of having another field in the database, reauth flag, you can include for hash(bcrypt_password_hash) in the token. Then when refreshing token, you just confirm if hash(bcrypt_password_hash) is equal to a value from the token. In order to deny token refresh, one has to just update password hash.Cultism
@bas, thinking of in optimizations and performance, I think the password hash validation would be redundant and have more server implications. Increase the size of the token so signature firm/validation takes more time. additional hash calculations for server for the password. with the extra field approach you just validate in the recalculation with a simple boolean. Db updates is less frequent for the extra field, but is more frequent token refreshes. And you get the optional service of force individual re logins for any existing session (mobile, web, etc).Kinser
@Kinser I didn't suggest password hashing but to hash a string (can be modified_at, password or any data that should trigger logout) with a fast hashing function, let's say md5. Nowadays, doing md5(short_string) is not a performance issue. If you need to force "individual re logins", then reauth won't help here either.Cultism
I think the first comment by user2924127 is actually wrong. When the password is changed, the account is marked as requiring a re-authentication, so any existing expired tokens will be invalid.Flounder
I like this method, its kind a hybrid solution.Ide
@Flounder I think the other way around, if the password is changed, old expired tokens will still be valid. The first hit will require an reauthentication, but then successive calls will pass again. So the existing expired tokens will very much still be valid. Just like user2924127 mentioned.Tiphane
@Cultism This is good unless you ever want to put in more triggers in your hash. Maybe a to force re-login when changing IP to the other side of the world in 1 second or something like that. Then if you change your algorithm for calculation the hash, all your users will need to re-login on the next BE invocation. One solution could be to store the hash in the DB and check agains that hash each time an Access Token needs to get renewed.Tiphane
@Prologize Very much a valid point! No only if an attacker is invloved. If you are logged in on multiple deviced, the end user will have a very odd behaviour. Having to re-login only on the first device after a password update... Let's say you logged in at a friends computer, or a computer at a internet Cafe, work school etc. You would expect a password change to lock the account on your those computers.Tiphane
@AndreasLundgren this is true. Remember that each token has a creation time, so one workaround would be to simply compare the password reset time with the token creation time - tokens created prior to the last password reset would not get renewed.Caddell
in db, store user.passwordUpdatedAt. In token validation, reject any token issued before user.passwordUpdatedAt (of course, prevent them from being refreshed). Then 1. isn't required.Monaco
B
30

Below are the steps to do revoke your JWT access token:

1) When you do login, send 2 tokens (Access token, Refresh token) in response to client .
2) Access token will have less expiry time and Refresh will have long expiry time .
3) Client (Front end) will store refresh token in his local storage and access token in cookies.
4) Client will use access token for calling apis. But when it expires, pick the refresh token from local storage and call auth server api to get the new token.
5) Your auth server will have an api exposed which will accept refresh token and checks for its validity and return a new access token.
6) Once refresh token is expired, User will be logged out.

Please let me know if you need more details , I can share the code (Java + Spring boot) as well.

Beleaguer answered 26/1, 2019 at 12:26 Comment(8)
Could you please share your project link if you have it in GitHub?Bufford
click here for the linkBeleaguer
Hi @BhupinderSingh. My question is why you set less expiry for access token?! You could set long expiry of refresh token to access token And you didn't use refresh token at all. Am I right?Spirogyra
@AliSohrabi : short expiry time for access token is needed so that you force your application to continually refresh them. You giving the service a chance to revoke application access. It just secure your api more. If someone has an access token , after short time duration it will expire so he needs to have refresh token also to get new access. It just increases the chances of security. The more complex ways you make of your application’s access , more security you get.Beleaguer
@BhupinderSingh I think most of answers or google results get similar opinions with you, but my opinion is bit of different. Refresh token, can help to make JWT/stateless access token expire in a short time which make logout work. But if a hacker want to hack your resources, they will use refresh token to keep getting new access tokens. So it does not really help on security. It does help on achieving traditional logout.Samarium
LocalStorage of tokens is not secureMoschatel
but some apps never logout, how did they do this? make refresh token never expire?@BhupinderSinghPullen
@Pullen I find your question interesting. I think when Access Token expires, and we try to get new one with Refresh Token, we can also check in this step how old is Refresh token. For example, if Access Token lasts for 20 minutes and Refresh Token last for 30 days, when reissuing new Access Token, we can also include Refresh Token if it's older than 20 days for example. In that way, both Refresh and Access Token can be replaced. However, the previous Refresh token has to be blacklisted.Ism
M
22

I was tinkering around when moving our applications to HTML5 with RESTful apis in the backend. The solution that I came up with was:

  1. Client is issued with a token with a session time of 30 mins (or whatever the usual server side session time) upon successful login.
  2. A client-side timer is created to call a service to renew the token before its expiring time. The new token will replace the existing in future calls.

As you can see, this reduces the frequent refresh token requests. If user closes the browser/app before the renew token call is triggered, the previous token will expire in time and user will have to re-login.

A more complicated strategy can be implemented to cater for user inactivity (e.g. neglected an opened browser tab). In that case, the renew token call should include the expected expiring time which should not exceed the defined session time. The application will have to keep track of the last user interaction accordingly.

I don't like the idea of setting long expiration hence this approach may not work well with native applications requiring less frequent authentication.

Maurene answered 21/5, 2015 at 3:0 Comment(3)
What if the computer was suspended/sleep. The timer will still count until the expiry but the token was actually already expired. Timer doesn't work in this situationsHabitude
@AlexParij You would compare against a fixed time, something like this: https://mcmap.net/q/48531/-what-happens-to-settimeout-when-the-computer-goes-to-sleepBalmacaan
Allowing the client to request a new token with a preferred expiration date smells like a security risk to me.Jaenicke
V
21

An alternative solution for invalidating JWTs, without any additional secure storage on the backend, is to implement a new jwt_version integer column on the users table. If the user wishes to log out or expire existing tokens, they simply increment the jwt_version field.

When generating a new JWT, encode the jwt_version into the JWT payload, optionally incrementing the value beforehand if the new JWT should replace all others.

When validating the JWT, the jwt_version field is compared alongside the user_id and authorisation is granted only if it matches.

Viewpoint answered 31/5, 2017 at 9:19 Comment(6)
This has problems with multiple devices. Essentially if you log out on one device, it logs out everywhere. Right?Grouse
Hey, that may not be a "problem" depending on your requirements, but you're right; this doesn't support per-device session management.Viewpoint
Doesn't this mean the jwt_version has to be stored server side such that the authentication scheme becomes "session-like" and defeats the fundamental purpose of JWTs?Deflagrate
There is more discussion and some other options for revoking JWTs here, via the devise-jwt readme.Viewpoint
Also, the jwt_version concept is better specified as a unique code via the (reserved name) jti or "JWT ID" claim - see specificationViewpoint
One of the whole points of JWT is being to authenticate it without needing to lookup its validity in the DB. if you have to lookup the jwt_version for the user in the DB every time you validate the JWT you lose this benefit.Duntson
B
10

jwt-autorefresh

If you are using node (React / Redux / Universal JS) you can install npm i -S jwt-autorefresh.

This library schedules refresh of JWT tokens at a user calculated number of seconds prior to the access token expiring (based on the exp claim encoded in the token). It has an extensive test suite and checks for quite a few conditions to ensure any strange activity is accompanied by a descriptive message regarding misconfigurations from your environment.

Full example implementation

import autorefresh from 'jwt-autorefresh'

/** Events in your app that are triggered when your user becomes authorized or deauthorized. */
import { onAuthorize, onDeauthorize } from './events'

/** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */
const refresh = () => {
  const init =  { method: 'POST'
                , headers: { 'Content-Type': `application/x-www-form-urlencoded` }
                , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token`
                }
  return fetch('/oauth/token', init)
    .then(res => res.json())
    .then(({ token_type, access_token, expires_in, refresh_token }) => {
      localStorage.access_token = access_token
      localStorage.refresh_token = refresh_token
      return access_token
    })
}

/** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */
const leadSeconds = () => {
  /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */
  const jitter = Math.floor(Math.random() * 30)

  /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */
  return 60 + jitter
}

let start = autorefresh({ refresh, leadSeconds })
let cancel = () => {}
onAuthorize(access_token => {
  cancel()
  cancel = start(access_token)
})

onDeauthorize(() => cancel())

disclaimer: I am the maintainer

Byword answered 27/5, 2016 at 7:43 Comment(3)
Question about this, I saw the decode function it uses. Does it assume the JWT can be decoded without using a secret? Does it work with JWT that were signed with a secret?Nunci
Yes, the decode is a client-only decode and should not be aware of the secret. The secret is used to sign the JWT token server-side to verify that your signature was used to generate the JWT originally and should never be used from the client. The magic of JWT is that its payload can be decoded client-side and the claims inside can be used to build your UI without the secret. The only thing jwt-autorefresh decodes it for is to extract the exp claim so it can determine how far out to schedule the next refresh.Byword
Oh good to know, something didn't make sense but now it does. Thanks for the answer.Nunci
H
10

Today, lots of people opt for doing session management with JWTs without being aware of what they are giving up for the sake of perceived simplicity. My answer elaborates on the 2nd part of the questions:

What is the real benefit then? Why not have only one token (not JWT) and keep the expiration on the server?

Are there other options? Is using JWT not suited for this scenario?

JWTs are capable of supporting basic session management with some limitations. Being self-describing tokens, they don't require any state on the server-side. This makes them appealing. For instance, if the service doesn't have a persistence layer, it doesn't need to bring one in just for session management.

However, statelessness is also the leading cause of their shortcomings. Since they are only issued once with fixed content and expiration, you can't do things you would like to with a typical session management setup.

Namely, you can't invalidate them on-demand. This means you can't implement a secure logout as there is no way to expire already issued tokens. You also can't implement idle timeout for the same reason. One solution is to keep a blacklist, but that introduces state.

I wrote a post explaining these drawbacks in more detail. To be clear, you can work around these by adding more complexity (sliding sessions, refresh tokens, etc.)

As for other options, if your clients only interact with your service via a browser, I strongly recommend using a cookie-based session management solution. I also compiled a list authentication methods currently widely used on the web.

Hakeem answered 22/2, 2020 at 12:19 Comment(2)
thanks for the excellent-simple authentication guide linked / and for authoring it :) Would using a combo of JWT+Cookies (save the accessToken to a cookie) be a good solution?Marsha
Saving the JWT to a cookie would work well. It would give your cookie value integrity protection, but you still need some way to blacklist tokens on-demand, if you need to support more advanced scenarios like idle timeout. I would opt for a simple session-id in a cookie.Hakeem
G
8

Good question- and there is wealth of information in the question itself.

The article Refresh Tokens: When to Use Them and How They Interact with JWTs gives a good idea for this scenario. Some points are:-

  • Refresh tokens carry the information necessary to get a new access token.
  • Refresh tokens can also expire but are rather long-lived.
  • Refresh tokens are usually subject to strict storage requirements to ensure they are not leaked.
  • They can also be blacklisted by the authorization server.

Also take a look at auth0/angular-jwt angularjs

For Web API. read Enable OAuth Refresh Tokens in AngularJS App using ASP .NET Web API 2, and Owin

Goldy answered 26/8, 2016 at 17:50 Comment(1)
Maybe I read it wrong... But article with a title that starts as "Refresh Tokens..." contains nothing about refresh tokens, except what you mentioned here.Backslide
D
8

I actually implemented this in PHP using the Guzzle client to make a client library for the api, but the concept should work for other platforms.

Basically, I issue two tokens, a short (5 minute) one and a long one that expires after a week. The client library uses middleware to attempt one refresh of the short token if it receives a 401 response to some request. It will then try the original request again and if it was able to refresh gets the correct response, transparently to the user. If it failed, it will just send the 401 up to the user.

If the short token is expired, but still authentic and the long token is valid and authentic, it will refresh the short token using a special endpoint on the service that the long token authenticates (this is the only thing it can be used for). It will then use the short token to get a new long token, thereby extending it another week every time it refreshes the short token.

This approach also allows us to revoke access within at most 5 minutes, which is acceptable for our use without having to store a blacklist of tokens.

Late edit: Re-reading this months after it was fresh in my head, I should point out that you can revoke access when refreshing the short token because it gives an opportunity for more expensive calls (e.g. call to the database to see if the user has been banned) without paying for it on every single call to your service.

Demilune answered 13/12, 2016 at 21:0 Comment(0)
L
7

I solved this problem by adding a variable in the token data:

softexp - I set this to 5 mins (300 seconds)

I set expiresIn option to my desired time before the user will be forced to login again. Mine is set to 30 minutes. This must be greater than the value of softexp.

When my client side app sends request to the server API (where token is required, eg. customer list page), the server checks whether the token submitted is still valid or not based on its original expiration (expiresIn) value. If it's not valid, server will respond with a status particular for this error, eg. INVALID_TOKEN.

If the token is still valid based on expiredIn value, but it already exceeded the softexp value, the server will respond with a separate status for this error, eg. EXPIRED_TOKEN:

(Math.floor(Date.now() / 1000) > decoded.softexp)

On the client side, if it received EXPIRED_TOKEN response, it should renew the token automatically by sending a renewal request to the server. This is transparent to the user and automatically being taken care of the client app.

The renewal method in the server must check if the token is still valid:

jwt.verify(token, secret, (err, decoded) => {})

The server will refuse to renew tokens if it failed the above method.

Logsdon answered 4/8, 2017 at 21:29 Comment(2)
This strategy looks good. But I think should be complemented with a kind of "max amount of renews" because (maybe) a user sessión can be alive forever.Woodchuck
You can set a hardExp variable in the token data to set a max date to force expire the token, or maybe a counter which is decremented whenever the token is renewed, limiting the amount of total token renews.Logsdon
T
4

How about this approach:

  • For every client request, the server compares the expirationTime of the token with (currentTime - lastAccessTime)
  • If expirationTime < (currentTime - lastAccessedTime), it changes the last lastAccessedTime to currentTime.
  • In case of inactivity on the browser for a time duration exceeding expirationTime or in case the browser window was closed and the expirationTime > (currentTime - lastAccessedTime), and then the server can expire the token and ask the user to login again.

We don't require additional end point for refreshing the token in this case. Would appreciate any feedack.

Teresita answered 10/5, 2016 at 21:31 Comment(3)
Is it a good choice in this day, It's look pretty much easy for implementation.Dolhenty
In this case, where do you store lastAccessedTime? You have to do it on backend and per request, so it becomes a not desired stateful solution.Algae
I'm afraid you cannot change the expiration time of a jwt during authorization checks because setting the expiration time is part of authentication at loginBabyblueeyes
T
3

Ref - My blog post - Refresh Expired JWT Example

Another alternative is that once the JWT has expired, the user/system will make a call to another url suppose /refreshtoken. Also along with this request the expired JWT should be passed. The Server will then return a new JWT which can be used by the user/system.

enter image description here

Tecumseh answered 14/8, 2020 at 13:6 Comment(2)
I don't think authenticating with an expired token like this makes much sense. Tokens might leak, so the safer thing to do is periodically renew with a still valid JWT before it expires.Disequilibrium
if you are periodically refreshing the JWT before it expires, what happens then when there are multiple tokens for the user that haven not expired?Cohla
C
2

Here's what worked for me, without needing to generate new token on every api call. The approach is to use a timer at the client and force logout after token expires.

Use two tokens:

  1. Access token (for making API calls)
  2. Refresh token (for renewing Access token)

Steps to implement JWT that prolong

  1. If the session is timed for 1 hour duration then set Access Token expiry to 1 Hr and refresh token expiry to 2 Hr.
  2. Maintain 1 Hr timer on each api call and if the time exceeds 1Hr, then send the refresh token in the Auth header. The backend figures out the type of token (AT/RT) and it will check for its expiry and accordingly generate new token.
  3. If the client timer exceeds 1 hour since the last call, the client will call the logout api which will remove these tokens from the whitelist category or add them to blacklist whichever methods suits you.(I chose whitelist because clean up is not required).
  4. If it is a access token, server will simply serve the request and if the token is a refresh token, the backend will generate new access token and refresh token and send these two in the response headers.

Note :

Step 2 can be done differently, once the timer exceeds 1 Hr threshold, identify the diff between the last api call and current time ,if it exceeds 1 Hr then force logout, else at the 59th minute send the last api call time and generate new tokens with expiry time (last call time + 1 hr). In this case you don't need any refresh token.

And for whitelisting or blacklisting tokens, you may use redis(instead of db), for lookup.

Caucasia answered 31/3, 2023 at 10:12 Comment(0)
D
1

The idea of JWT is good, you put what you need in JWT and go stateless. Two problems:

  1. Lousy JWT standardization.
  2. JWT is impossible to invalidate or if created fast-expiring it forces the user to log in frequently.

The solution to 1. Use custom JSON:

 {"userId": "12345", "role": "regular_user"}

Encrypt it with a symmetric (AES) algorithm (it is faster than signing with an asymmetric one) and put it in a fast-expiring cookie. I would still call it JWT since it is JSON and used as a token in a Web application. Now the server checks if the cookie is present and its value can be decrypted.

The solution to 2. Use refresh token:

Take userId as 12345, encrypt it, and put it in the long-expiring cookie. No need to create a special field for the refresh token in DB.

Now every time an access token (JWT) cookie is expired server checks the refresh token cookie, decrypts, takes the value, and looks for the user in DB. In case the user is found, generate a new access token, otherwise (or if the refresh token is also expired) force the user to log in.

The simplest alternative is to use a refresh token as an access token, i.e. do not use JWT at all.

The advantage of using JWT is that during its expiration time server does not hit DB. Even if we put an access token in the cookie with an expiration time of only 2 min, for a busy application like eBay it will results in thousands of DB hits per second avoided.

Deviltry answered 29/12, 2022 at 18:19 Comment(0)
V
0

I know this is an old question, but I use a hybrid of both session and token authentication. My app is a combination of micro-services so I need to use token-based authentication so that every micro-service doesn't need access to a centralized database for authentication. I issue 2 JWTs to my user (signed by different secrets):

  1. A standard JWT, used to authenticate requests. This token expires after 15 minutes.
  2. A JWT that acts as a refresh token that is placed in a secure cookie. Only one endpoint (actually it is its own microservice) accepts this token, and it is the JWT refresh endpoint. It must be accompanied by a CSRF token in the post body to prevent CRSF on that endpoint. The JWT refresh endpoint stores a session in the database (the id of the session and the user are encoded into the refresh JWT). This allows the user, or an admin, to invalidate a refresh token as the token must both validate and match the session for that user.

This works just fine but is much more complicated than just using session-based auth with cookies and a CSRF token. So if you don't have micro-services then session-based auth is probably the way to go.

Vide answered 27/12, 2021 at 16:6 Comment(0)
W
-1

If you are using AWS Amplify & Cognito this will do the magic for you:

Use Auth.currentSession() to get the current valid token or get new if the current has expired. Amplify will handle it As a fallback, use some interval job to refresh tokens on demand every x minutes, maybe 10 min. This is required when you have a long-running process like uploading a very large video which will take more than an hour (maybe due to a slow network) then your token will expire during the upload and amplify will not update automatically for you. In this case, this strategy will work. Keep updating your tokens at some interval. How to refresh on demand is not mentioned in docs so here it is.

import { Auth } from 'aws-amplify';

try {
  const cognitoUser = await Auth.currentAuthenticatedUser();
  const currentSession = await Auth.currentSession();
  cognitoUser.refreshSession(currentSession.refreshToken, (err, session) => {
    console.log('session', err, session);
    const { idToken, refreshToken, accessToken } = session;
    // do whatever you want to do now :)
  });
} catch (e) {
  console.log('Unable to refresh Token', e);
}

Origin: https://github.com/aws-amplify/amplify-js/issues/2560

Wilsonwilt answered 11/4, 2022 at 9:9 Comment(0)
P
-3

services.Configure(Configuration.GetSection("ApplicationSettings"));

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 

        services.AddDbContext<AuthenticationContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));

        services.AddDefaultIdentity<ApplicationUser>()
            .AddEntityFrameworkStores<AuthenticationContext>();

        services.Configure<IdentityOptions>(options =>
        {
            options.Password.RequireDigit = false;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireLowercase = false;
            options.Password.RequireUppercase = false;
            options.Password.RequiredLength = 4;
        }
        );

        services.AddCors();

        //Jwt Authentication

        var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:JWT_Secret"].ToString());

        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(x=> {
            x.RequireHttpsMetadata = false;
            x.SaveToken = false;
            x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                ClockSkew = TimeSpan.Zero
            };
        });
    }
Pinebrook answered 12/4, 2021 at 20:35 Comment(1)
It would be helpful if you could add some explanationDorseydorsiferous

© 2022 - 2024 — McMap. All rights reserved.