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