DEV Community

Cover image for JWT Authentication Best Practices

JWT Authentication Best Practices

Fernando Doglio on December 04, 2020

Microservices are a great tool when it comes to designing scalable and extensible architectures. They can be used to encapsulate different behavior...
Collapse
 
manutopik profile image
Emmanuel Salomon

And what about refresh token?

Collapse
 
branislavlazic profile image
Branislav Lazic • Edited

Now, that's a good point since it brings a crazy amount of complexity. It feels like refresh token mechanism doesn't even pay off if you have a single API service. Let's dive deeper.

So, what is it even? Let's say, a long lived token used strictly to obtain a new access token or to revoke itself!

How does it look? How do I even generate it?
Basically, you can use JWT format here too. Nothing wrong if you have a same format for access and refresh token. If you want the custom one, the safest option is to use a proven library for random strings that cannot be easily guessed (not the time based UUID's or timestamps!) and sign then with e.g. HMAC.

Now, since we know their nature and how they look, let's see how to use them.

If we make refresh token a long lived stateless JWT token, we are in a deep trouble. In case if it gets stolen, there is no effective mechanism to revoke it. The only mechanism is to change a token secret which might also require our API service downtime. Basically, this will log out ALL users since their refresh tokens will fail to verify against a new secret key. And they will have to login again.

What to do?
Save it in the data store. What kind of data store? Ideally the one that can be scaled effectively, but scaling of refresh token data store is not a big deal, since that data store doesn't handle a huge load. Refresh token is sent relatively rarely. Ideally, only after an access token expires. And it means that you have a state you have to maintain.

But wait! There's a potential danger in storing refresh token! Because, you don't want to store refresh token, but rather, its meta data. E.g. ID, user ID, expiry time... Storing the whole refresh token is similar to storing plain text passwords. In case if database leaks, refresh tokens will be compromised. On the other hand, meta data is useless.

So far so good.

What about an authentication flow itself?
Login -> we get both access and refresh token -> after the access token expires, we send refresh token to obtain a new access token
Logout -> we send refresh token -> we confirm it's validity -> if valid, we parse it, extract the ID, and delete it, or mark it as "used" in the data store by its ID -> if not valid (maybe expired), also good, since it can't be used to obtain new access tokens

But again, a tricky situation. What if someone steals a refresh token? It means they could use it to obtain access tokens for quite a long time.

Refresh token rotation to the rescue! Basically, each time we exchange a refresh token for an access token, we respond with a new refresh and access token, and mark the old refresh token in the data store as "used".

Imagine a scenario. An attacker steals a refresh token -> they use it to obtain a new access token and refresh token (old refresh token is marked as "used") -> our user tries to use the same refresh token to prolong their session -> server checks whether the refresh token is used -> BOOM! It is! -> server revokes all refresh tokens for that user.

But that's just one security mechanism... Imagine if an attacker actually waits for a user to close their browser and uses a last active refresh token. It means that a user will not present their refresh token any soon. An attacker can use that users offline time to do whatever they want.

So yeah, it's tricky as hell. For a single service (that even requires scaling to multiple instances), maybe it's way better to use sessions serialized in a data store such as Redis or an external authorization server such as Keycloak if you really want to go with tokens.

Collapse
 
matejko94 profile image
Matej Senožetnik

Very interesting article.

I am wondering what happens if an attacker intrudes between the client app and backend? I believe this could be dangerous if the attacker captures the password and username.

Collapse
 
deleteman123 profile image
Fernando Doglio

Indeed, this is why you use HTTPS, so that a man in the middle attack is not possible.

Collapse
 
sm0ke profile image
Sm0ke

Super content. Ty!

Collapse
 
branislavlazic profile image
Branislav Lazic • Edited

I was thinking the same. Indeed, they can. But stealing a token is still worse than making a request on behalf of a user. With a stolen token, an attacker can make requests not just for a predetermined set of API calls (the ones coded in your client app), but also, on other services that require the stolen access token. In the case if we store an access token in an httpOnly cookie, the attacker can make a request only for a limited set of API calls. Other services could remain isolated.

Collapse
 
cfuehrmann profile image
Carsten Führmann

Nice article! Something about the discussion between session-based tokens is funny though: One could always use a "normal" JWT and add the application server session as a claim. (In fact, this is how our organization does it.)

Kudos for pointing to the problems with the local storage, and the cookie alternative. I came to the same conclusion after lots of research.

 
branislavlazic profile image
Branislav Lazic • Edited

No it won't. That's why we have "Domain" attribute. A cookie will not be sent if our server, and malicious one, don't share the same domain: developer.mozilla.org/en-US/docs/W....

Collapse
 
jesusz0r profile image
Jesus Mendoza

What do you do when you have different microservices that require the user to be authenticated? Do you make a request to the auth microservice with the JWT that comes on the Cookie in every request? or just validate that the JWT is not expired with jwt.verify?

Collapse
 
rafaelcpalmeida profile image
Rafael Almeida

Did you had a look at PASETO already? :)

Collapse
 
vasilisplavos profile image
Vasilis Plavos

Thanks for the article. I have two questions about JWT:

  1. What if somebody steal a not-expired token?
  2. What can we do when the token expired? The user has to login with username and password?
Collapse
 
justden profile image
Denis
  1. He could use it to get secret data
  2. Yes
Collapse
 
jgomes94 profile image
jgomes94

Great Content Fernando, thanks! :)

Collapse
 
gabrielsadaka profile image
Gabriel Sadaka

This is awesome, thanks for sharing. Great amount of detail!

Collapse
 
kasunkoswattha profile image
Kasun

Thanks for the article. I think no application should ever store JWTs in a storage. JWTs are short lived.

Collapse
 
belenot profile image
belenot

Without https not even data encrypted, you can't guarantee that server is actually your trusted server.