UPDATE: JWT can fit as an authentication system with a blacklist technique
JWT (JSON Web Token) is a very popular way to authenticate users. It's a way to securely exchange data between client and server through a token. Here is how it works:
- User sends their credentials (i.e. username and password) to the server.
- The server checks if the credentials are correct.
-
If valid, server creates a JWT token as follows:
-
To make a JWT token, server will use 3 things: Header, Payload, and Secret.
-
Header: It contains type of token and hashing algorithm used to sign a signature.
{ "alg": "HS256", "typ": "JWT" }
-
Payload: It contains data that we want to send. For example, user's ID, username, email, etc.
{ "id": 1, "name": "John Doe", "admin": true }
Secret: It's a secret key that is used to sign a signature. It's only known by server.
"MySecretKeyThatNoBodyKnowsButTheServer"
- The server will use header (base64UrlEncode), payload (base64UrlEncode), and secret in order to create a signature.
signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
- The output structure of JWT token will be like this:
base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + signature
-
-
The server sends JWT token to client.
And that's it. Client will send JWT token in header of each request to server.
Very important notes:
- JWT token is not encrypted, it's just base64UrlEncoded. So, don't put any sensitive information in payload. Meaning, if for some reason an access token is stolen, an attacker will be able to decode it and see information in payload.Check it here.
- Keep access token expiry short (e.g., 15 minutes) to limit attacker misuse if stolen.
- Because access token is kept on client side, it's vulnerable to XSS attacks. Server can't set HttpOnly flag on it since client needs access to include it in requests.
- Dont use an access token to generate a new access token when old one expires. If access token is stolen, an attacker will be able to generate new access tokens forever. Instead, use a refresh token to generate new access tokens.
- Refresh tokens should have a long expiration time, and they are stored in HttpOnly cookies to prevent XSS attacks (only server can access them).
- Protect a refresh token from CSRF attacks by using
SameSite
attribute in cookie. Check it here so a browser will only send a cookie to server if a request originated from same site that set cookie in first place (server). - As a security measure for refresh tokens. Create a table for refresh tokens in database and store refresh token's related information (user ID, expiration time, last used time, device, user agent, etc) to be able to revoke refresh token if it's stolen or check if refresh token was used before or not.
Big Drawbacks of JWT:
- Logout doesn't really log you out:
- Once a JWT token is created, it's valid until it expires. There is no way to invalidate it. So, if a user logs out, you can delete the access token from client side, but it's still valid until it expires.
- Blocking a user is not possible:
- If a user is blocked, there is no way to block them from accessing the system until their access token expires. Imagine you are a bank and one of the users's access token is stolen, you can't block it or do anything about it until it expires which is a big security risk for a bank that deals with money.
- Changes by system are not reflected in real time:
- If a user's role is changed from admin to regular user, there is no way to reflect this change in real time. The user will still have access to admin features until their access token expires.
- You can't know how many current users are logged-in:
- Since JWT tokens are stateless, there is no way to know how many users are logged in at any given time in the system.
My conclusion:
- Use traditional session-based authentication. It's more secure and flexible than JWT.
- JWT is a good fit for cases/situations where you want to issue a one-time token to be used for a specific purpose. what is JWT good for, then?
Resources:
Top comments (19)
I store JWTs in httpOnly cookies and forward them with requests, works fine.
I'd agree that you need more than JWT tokens to cover log outs and invalidations on users.
Given that an invalidation of a token is a very short lived operation, even if the expiry is set to days, you can manage invalidated tokens in Redis easily and quickly with O(1) lookup. No need for session storage.
Storing JWT access token in httpOnly is NOT SECURE. It will still be be subjected to CSRF attacks.
Better to store the access token in a local variable (i.e Vuex, Redux). When the token is stored in a local variable, it helps protect the system from common attacks like CSRF and XSS. CSRF is prevented because the token isn't automatically sent with cookies but is instead sent as a header (bearer header) with each request. XSS is avoided because the token isn't stored in Session or LocalStorage.
For invalidation of a access tokens, If you setup a Redis lookup for your JWT authentication process. You basically defeat the propose of JWT in the first place.. just use Redis sessions at this point!?
You really don't defeat the purpose, you have to properly consider the scale. You are storing only tokens that are invalidated, a tiny number, incredibly fast to look up - not a database of sessions to read and parse. You have to properly consider scale when you look at these things.
It may still be subject to CSRF attacks, though most types are naturally prevented, sure if you can trick the page into making a request the cookie will go with it. That's not so hard to defend against.
I can accept this as a work around to invalidate the tokens. But the drawbacks that were mentioned are still applicable:
Lets say I blocked a user "John Doe" from an admin dashboard.
lets say I changed the user role from admin to normal user.
lets say I want to know how many users are currently logged-in. (this we may use third party solutions to find out)
Well it's not so bad, you block the token, this requires an attempt at a refresh, the new token has the new permissions. Of course if you've banned the user, they can't refresh either.
Do not use JWT for authorization.
Use JWT only for authentication.
JWT payload should be minimal because it will be re-send on each request. Stuffing it full of permission and roles is a bad idea.
JWT has use cases where it should contain permissions for authorization. It has many others where a trade off would not to be to do that.
These kinds of "Don't do this do that" comments just aren't very helpful, these tools have a reason to exist and a reason to work in the way they do. You have to keep an open mind and use the right tool for the right job.
If I have a system with 4.3 million users, of which between 100,000 and 1 million are roughly online at the same time, and users can purchase upgraded service elements then I'll use JWT for both. I'm not paying to manage a database and system with those concurrent sessions, they can live in a JWT and I'll use Redis to invalidate their current token if they buy an upgrade.
My current project, with around 10,000 b2b users each with fine-grained permissions, the JWT exists, but it only has an email and an ID in it. Used to look up a settings record - a session if you like. That's the right choice here.
So, use your brain each time and come up with the right answer. Try to avoid silver bullet answers.
I've always seen someone saying like this:
Better to store the access token in a local variable
If the user refreshes the page (website) all the variable will reset to default so the jwt token that you stored in the variable (memory) is gone then the user will need to login again, So how is this the possible solution?
JWT are very useful in service-to-service communication which is short-lived. Other than authentication we can use them to formulate a data transmission contract between two parties.
Surely changes are not reflected in JWT token but the example of the user from admin to regular user, should depend on the architecture of the system. I believe JWT are only a token format the authorization is still with the Authorization Server. So if a user is revoked of a role (from admin) the scoped token would also have limited privileges.
I agree with this fully. If we use Redis to invalidate/blacklist tokens (although this goes against the JWT) as @miketalbot said. And let the JWT only responsible for authentication not authorization. It does now makes sense to use JWT as an authentication process.
I was under the impression that user's roles can be used/stored in the token for some reason.
JWT are complicated to implement Vs the traditional session based authentication (easy to setup out of the box). I would say if you are a Reddit/X kind of platform then using JWT can help you to scale big. Otherwise, for small, medium platforms traditional session are the way to go.
Well argued, thank you 😄
IMO the most important part of making decisions like this is to examine your own systems use cases and consider if you need/expect-to-need the particular benefits of JWTs (or any token-based approach), namely: scalability (100k+ active), federation (especially b2b, OIDC); then weigh the cost of providing those using traditional sessions vs. the risks token introduce, as above: invalidation periods, token leakage risk, non-real time token changes, lack of real-time stats.
In my case a few years ago we had mainly other businesses as customers, who managed their own users internally, so federation (using OIDC or SAML) was attractive to remove user & credential management (eg: leavers and joiners processes) and reduce call center costs for us (no more password resets - 80% of our call center time!)
We estimated that the time required for us to detect account misuse or a for a contract ending would far exceed normal access token validity periods, so we did not need to invalidate normal access tokens. Refresh / [re]authentication was handled through a single database, thus accounts could be managed there once. Contract changes resulting in new roles would take hours/days, again beyond access token lifetimes, besides a customer could always re-authenticate to obtain new roles immediately. We issued long-term access tokens for some customers (don't ask!), so needed an invalidation mechanism for the unlikely events of token leakage or contract / legal issues - we chose to include any such list of invalidated tokens in the hourly updates of the token signing keys that all web components collected from the authentication service. We also looked at our legal position, and concluded that customers leaking their own keys only impacted themselves and remained liable for costs incurred, including abuse by other parties, so our financial risk was minimal.
Note, we chose not to offer any administrative or privileged access using JWTs, this all went via traditional web sessions.
But session based systems require some kind of storage, for example a redis instance so it's more expensive and complicate
Also, people using session approach tend to store lot of information into it increasing the complexity and readability of the code
To me jwt is perfect for a microservice architecture and session for typical monolithic applications
hmmm Whether Redis is "expensive" or not largely depends on your specific use case, infrastructure, scale, and whether you're willing or able to manage it yourself. But in the end its a trade off that I am willing to take to make my system more secure.
As for the "store lot of information". Redis can handle a lot and a lot of data based on your RAM limits, if you ever reached the limit just scale it horizontally. dev.to/irakan/redis-scaling-with-r...
Don't get me wrong, I like JWT and I know its solves the Redis session lookup problem which makes request response faster and we don't need to maintain a Redis instance/s. But, from my point of view it is not a good approach for authentication for the points that shown in the post.
Correct me if I'm wrong, but the only thing a refresh token does is it forces the attacker to steal 2 things from the exact same place. I refresh it on the server side based on recent activity instead. I usually use 60-90 minutes expiry on the token. Having a "last_action" timestamp in the claims is also valuable.
I found it valuable to store some user details that are displayed on most pages to begin with(like username, user id, personal status, maybe email), so I don't have to query a database on every single request.
Any change of permissions or the items in the token MUST trigger its refresh or logout.
the drawbacks you mention seems to ignore the existence of program logic to deal with these shortcomings. Just because a token contains certain information and has a validity time period does not mean the application should not check this at every occasion presented. You're inviting a security risk in of itself by not checking.
You CAN know how many people are logged in, Web-sockets are one way to validate a user connection to a system or specific parts of a system.
You CAN enforce access limitations when the user access has been invalidated. A session table can enforce the user state and cause program code to forcefully log you out and invalidate the provided token server side.
You CAN block users by invalidating access on your session table, or wherever you decide to administrate this. Again, using program logic.
JWT doesn't work in isolation, if it would you would correct in assertions.
Not sure about the drawbacks that you added.
Logout doesn't really log you out:
At the backend, you can put the logic with a boolean key and invalidate the request.
Blocking a user is not possible
It sure is possible when you put logic at the backend and if you have set up layers for verification with device fingerprints.
Changes by system are not reflected in real time
You can immediately replace the JWT token in the response of changes in the role. Plus, the role information should not be there; the role should be checked at run time in the backend by the user's ID.
You can't know how many current users are logged-in
If you have set up device fingerprints then you can surely know that number.
Why you assume the refresh token won't be stolen? access token and refresh token are just the same jwt token with just different expiry time.
Server can set httponly flag on cookies and browser will automatically send cookies regardless.
Also refresh token is meant to be kept on client side too.
99% of candidates can answer what is JWT to the interviewer and only 1% can answer what is the bad and good of JWT and how to secure it. Thanks for your great post.
Hi, I love your articles. We have build out site in react: upride.in , We use JWT extensively. I must say it is a time tested framework.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.