There are just too many ways to do JWT wrong. 😢
And I fell for some... Don't panic, but it's likely to be your case as well.
Check these 3 commonly overlooked security areas on JWT implementations. It will take only a few minutes.
1) Broken libraries
There are +1,600 libraries matching "jwt" on npm. 😳
And +300 on Pypi. 😲
Do we need them all? Certainly not. Are they all secure? I won't trust. 😖
There are several ways your JWT library of choice might be compromised.
Can we cut to a simple solution?
Yes, I am also bored with security blah, blah, blah. 💤
Go to this resource and double-check which libraries follow practices proven to be safe. Most will by now. But better safe than sorry.
2) Unsafe token generation and distribution
The joyfull implementation: 🍀
a. Frontend requests user authentication
b. Backend authenticates and generates JWT
c. JWT is sent in the response body payload
d. Frontend store JWT in the
localStorage
Ah, yes... The world would be beautiful without bad guys and if ugly things could not happen. 😇
Well. Back to the real world. 😎
Avoid following the above outline.
To help with items (a) & (b), make sure you selected a JWT library that follows best practices. Or that you implemented correctly on your own. Not that difficult really. Just care enough.
It's also good practice to log every authentication attempt (success and failures) and all contextual data you may possibly have.
JWT is frequently used in Serverless environments (because both are stateless, niiice!).
If that's your case, make sure you have professionals monitoring your logs and alerting you proactively. If that's not your case, the advice still holds. 😉
To address items (c) & (d):
Do not send JWT in the response body payload
Do not store JWT in
localStorage
Problem is: any JavaScript code in your frontend is able to access the JWT. And do whatever it wants.
And that's dangerous.
Imagine what can happen if someone manages to inject malicious code in your frontend... and get all your users' JWTs?... Hum... Houston...
No. Instead, the backend should set the JWT as a cookie in the user browser. Make sure you flag it as Secure
and httpOnly
cookie. And SameSite
cookie. Boy, that's a multi-flavor cookie.
This way, the JWT will be available to the backend in all subsequent requests, while remaining outside the reach of potentially dirty JS hands.
In your response body payload, send only what's necessary for the frontend to provide the features expected by the user. Did I mention to not include anything sensitive here? Should not.
I know. A cookie is not as cool as localStorage
. But, look, they can be colorful! And SAFE. He's our friend. Let's treat him well. Deal? 🙌 🍪
3) Not handling secret keys securely
Any JWT implementation will involve some sort of secret data. Regardless of using a symmetric (one secret key) or an asymmetric (public/private keys) way to sign tokens.
Personally, I prefer symmetric implementations with HMAC. Simplifies things. But sometimes I use asymmetric RSA. Lately, I have been using the latter only. Well, they'll never know which one I really use. 😜
No one should ever know how YOU implement JWT either. Not to mention your secret or private keys.
Things you should avoid doing with your secret/private key when possible:
- 💻 Storing in a
config
file and committing to your Git repo - 📣 Sharing with your team on your Drive, Dropbox, Slack, whatever
- ♻️ Having the same keys for local, test, and production environments
Instead:
- ✌️ Distribute keys for your development team to use only in local and testing environments
- 👍 Store production keys in a safe place, only available to the production app
- 🔐 Keep the production keys away from prying eyes, load them as environment variables, on-demand, protected against unintended access
Further reading:
- Auth0 blog post about vulnerabilities on JWT libraries
- OWASP cheatsheet about JWT
- OWASP cheatsheet on managing security keys
- Critical security logs on Serverless applications
Full disclosure: I work as a Developer Advocate at Dashbird.
Image credits:
- Cover image: Vincent van Zalinge on Unsplash
- Cupcakes: Viktor Forgacs on Unsplash
- Scorpion: Shayna Take on Unsplash
- Cookies (not really, they're actually macarrons): Mockaroon on Unsplash
Latest comments (41)
how to add layers of security, which will protect the integrity and, optionally, the content of the message? Vasiyam Specialist in Kerala
Thanks for the tips.
You're welcome Otu! Glad to have helped!
I believe this has already been discussed in the comments.
I'm not here to convince you of anything or prove anyone wrong. It's obvious that anyone can store anything in localstorage. If you're confident that storing sensitive credentials in it is perfectly fine, then it's not my responsibility to prove anything wrong or right... Just go for it.
Nice post. Anyway, just one question, how can I get the user information from the token that is stored in the cookies?
Hi Renato, thanks for the article but even more so for following up on all the comments. I may have learned more from the discussion than from the article itself.
Hi Samuel, it's very good to know the discussion was really helpful to you! Kudos to everyone that brought their thoughts and questions to enrich it. 👏 🍻
This is perhaps the main value in DEV: the ability to have friendly, respectful and intelligent conversations around tech issues that are open for everyone to learn from. 🙌
Awesome post, thanks for sharing!
Question if i may:
I am working on legacy project that has sessions already in use. I have to add authorization part and i decided to use JWT, to avoid making extra request to session store.
Is it a good idea to use sessionId as secret when ever is needed to verify/sign JWT.
What you think about this idea, just to use sessionId as secret which is always unique for each user. In theory, this should make things more secure?
Hi, glad the article was helpful!
Two of the main advantages of JWT are:
How do you plan to implement #1 only with a session ID?
Unless your session ID is unique for each user and is permanent across time, you will still need to map each session ID to a real user in your database, defeating #2.
Great read! I have a question for you. Since storing credentials in the source code or cofig files is a big NO for all sorts of reasons, what do YOU do in cases where there is a SPA application that needs to authenticate with the backend (via JWTs) without any user interaction? I mean, lets say that we want only this app to be able to access specific back end resources, hence we must somehow provide the sourcecode of this app with some credentials to communicate to the back end to gain access (retrieve a JWT for future calls). For example, you have an application that accesses a backed web api and you want only this application to be able to access that particular API.
Hi Nikolaos, a critical topic you raised! This could easily render a dedicated article. But, as a quick overview:
It really depends on your infrastructure, environment and required security level.
Loading secrets on demand from an external, secure place, is recommended. Once it's loaded, the app can share it internally as an environment variable or keep it short-lived in a contextual variable to avoid leakage inside the app.
Once a secret is in the machine RAM and unencrypted, it's already exposed, though. Depending on how much security you need, it might be worth having secrets for different application areas or endpoints. If one secret is compromised, the damaged area is reduced.
There are basically two routes for implementation:
1. Managed services
This is my preferred choice, when financially feasible.
AWS, for instance, provides Secrets Manager:
It's a great service. There are two downsides:
Similarly, Azure has Key Vault and GCP has Key Management Service.
2. In-house secrets management
This will be tricky and I don't have enough expertise to advise on the best implementation from a security standpoint.
Nevertheless, one solution is using AWS Lambda to isolate my secrets and serve other parts of the application stack. A plugin for the Serverless framework makes it a bit easier.
Instead of calling a service like AWS Secrets Manager, I'd call my own AWS Lambda function, which will provide the secrets on-demand. It will be 10x cheaper: $0.004 per 10,000 requests.
Using IAM to control who has access to the secrets Lambda will provide reasonable level of security.
If I would work in a team using this approach, I'd completely isolate the Secrets Lambda from the rest of my stack (repo, infra-as-code, CI/CD workflow, everything). This would contribute to preventing leakage of secrets used in production.
You can get way fancier with this implementation, but it's already getting too lengthy... Hope this helps shed some light. 💡 😉
Hi, I think you built your
localstorage
critique on a wrong claim:"Problem is: any JavaScript code in your frontend is able to access the JWT. And do whatever it wants."
The
localstorage
is isolated from other domains and only accessbile to scripts from the same domain. Those can manipulate thelocalstorage
, as they can manipulate cookies from the same domain.Cookies on the other hand can be used for CSRF attacks if CORS headers are not set correctly (see: stackoverflow.com/a/37635977/1337474 ). They do have advantages though - you can set expiration/max-age times and they are sent also with links where you possibly cannot set
Authorization
headers like with file downloads.Hi Johannes, those are very important concerns! I'm glad you raised them in this discussion!
You are absolutely right that other domains wouldn't be able to access your
localStorage
implementation. Nonetheless, this would be possible in case of an XSS attack to your site. This was the scenario I pointed out in the article:CSRF is indeed a real threat to cookies. That's why I suggested setting them with the
sameSite
property:This protects your JWT against some CSRF attack vectors, but not your entire implementation. There are additional measures you should take, such as:
Keeping JWT token in localStorage is fine. The only concern is XSS which should be avoided at all cost.
Once your site is vulnerable to XSS you got more bigger problem rather than just stealing JWT token.
So store the JWT token in localStorage and make sure your website is battle tested against XSS.
It can be "ok" and acceptable in some cases, but definitely not the best practice from a security standpoint.
A good analogy here would be our house. We need to secure doors and windows against unauthorized access. If a malicious actor gets in, we've got big problems, yes. But that doesn't mean we shouldn't hide our valuables. We may still store jewelry, money and other values in a safe. That practice can mitigate the losses in case someone breaks in the house.
What is develop advocate?
Hey Vladimir, I like this definition, by Wassim Chegham:
Nowadays it's also commonly referred to as "Developer Relations".
You'll also see "Technology Evangelist". I particularly dislike this title, because evangelism is about converting someone into a faith, which is a disservice in the tech world - not questioning other's beliefs, just think faith-based decisions are dangerous in tech.
Gotcha. Personally i think there is a way too many titles nowadays and hard to follow up all of them. People just get with some title and it goes viral. Take this as example, so we have at least 3 titles for the same thing :) Is it that team lead as well? Well, however, i appreciate the time to reply to my question :) All the best!
I agree, we need simplification and clarification of concepts.
The Tech Lead role is geared towards internal stuff within a company/project. There is relations to it, but internally.
Involves some level of:
This type of advice will sadly not help the reader. Here's why:
Hi Sandrino, thanks for taking the time to contribute to the discussion!
The whole point of the discussion is when we have a backend securely authenticating a user. JWT won't apply to Frontend-only scenarios.
A large portion of web apps nowadays have a backend, so I wouldn't agree that the discussion here is useless...
About your second topic, it's a discussion beyond the purpose of my article. It's fair for you to prefer using Session IDs, but many people have a different view.
On whether JWT is a good authentication solution or not. I'm going to need to differ on this statement:
First, sessions are just cookies, they're no different. Storing a Session ID or a JWT as a cookie in the browser is essentially the same thing.
The advantage of a JWT is that, once the backend receives it in the request header as a cookie, it doesn't need to go anyplace get more info. It's self-contained.
This is a property called
stateless
and it plays very well in distributed environments designed for variable demand and smoother scalability.Whereas with a Session ID, the backend still needs to ask a storage engine to authenticate who is the owner of that session. It's a design that is more difficult to scale.
I'm not referring to frontend only scenarios, there is indeed no such thing as frontend/client-side authentication. What I'm referring to is modern applications where the frontend is hosted on a static file server and the backend is an API often running on a different (sub)domain.
An application could be running on AWS S3 on acme.com which then needs to call an API (eg: Heroku) an api.acme.com. In that case a JWT can be used to serve as a bearer token, but the site running on acme.com has no backend which can set cookies. A backend and an API can be the same or different concepts.
I'm not talking about Session IDs, I'm talking about sessions. Session use cookies and they can contain a session ID where a backend data store is used or they can be self contained, where the data is stored in a cookie and then encrypted.
express-sessions
is a good example of this.Sessions in every platform already have all the required tools to make them work for many different use cases:
It's not:
We're getting way way beyond the purpose of my article, but let's cover it quickly.
I see there are maybe some conceptual confusions.
When api.domain.com sets a cookie, it doesn't need to be available for domain.com. When domain.com fires a subsequent request to API, the cookie will be sent along with it. It's the API that needs the JWT, not domain.com. This can work if you set the API to accept CORS.
What I mean is that a session uses a cookie the same way its proposed for JWT to use it. Obviously they are different implementations.
The problem with sessions is not encryption, but what can be extracted from them. JWT can store minimal user object, so that API doesn't necessarily have to reach a database for that.