How to secure your apps properly with JWT and keep your users logged in for longer.
A user logs in to your site with a password and email, or perhaps using their social platforms. You want them to have headless access to your api, so your database isn’t pummelled on every request checking if the user is logged in. So you distribute them a JWT access token. This gives the user headless access for x amount of time, mitigating the issues surrounding sessions. Sorted? Not quite!
The problem storing access tokens in web local storage.
Dependent on where this access token is stored, which is usually in web local storage, makes it susceptible for hijacking. Any scripts running in a browser have access to local storage, and could potentially take the access token and start making requests on the users behalf. This could be solved with a short expiry, say 15 minutes, which would constrict the time in which a hijacker would be able to act on the users behalf. However the problem that arises is that the user will be logged out every 15 minutes! Far from ideal.
We’ll introduce another JWT into the mix, a refresh token.
- The most important difference between the tokens is that the refresh token must be stored in an HttpOnly cookie. This means that the token can only be used for http requests, and they can be restricted for your domain. This solves our hijacking issue, but we’re not there just yet.
- Why can’t i just put the access token in an http only cookie you ask, because it’s not accessible by your code in the browser. So there is still a purpose for an access token.
- To tie the two tokens together, we must implement some functionality to refresh the access token by distributing a new access token with a new expiry to the user. The important step here is to only allow the access token to be refreshed if the request sent contains a paired refresh token. Now, if your access token is hijacked, they would indeed have the time until expiry acting on behalf of the user as before. However they will not be able to refresh the access token, because they cannot hijack the refresh token. The user on the other hand, can now use access tokens with short, safe expiry times and keep refreshing it when needed.
- When refreshing access tokens, we can do any logic we need; hit the database, check some activity, whatever meets your products needs. This setup opens up an opportunity to be headless for the majority of requests reducing the database workload, and a scattered and periodic opportunity to do a thorough check on the user by whatever means necessary.
- Another advantage of this approach is that the user can stay logged in infinitely and safely. For example if the access token has an expiry of 15 minutes and a refresh token has an expiry of 7 days. If you refresh both access and refresh tokens when the access token expires, the user will only be logged out if they do not refresh their tokens for 7 days (the refresh token expiry). So with constant activity, the user can stay logged in infinitely. Obviously the times of these expiries, and if you want to refresh the refresh token it is a decision per application.
In summary, this approach produces a healthy and configurable equilibrium of workload and security for your products.