There are just too many ways to do JWT wrong. :cry:

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. :flushed:

npm jwt libraries

And +300 on Pypi. :astonished:

Alt Text

Do we need them all? Certainly not. Are they all secure? I won't trust. :confounded:

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. :zzz:

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: :fourleafclover:

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. :innocent:


Well. Back to the real world. :sunglasses:

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. :wink:

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? :raised_hands: :cookie:


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. :stuckouttonguewinkingeye:

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:

  • :computer: Storing in a config file and committing to your Git repo
  • :mega: Sharing with your team on your Drive, Dropbox, Slack, whatever
  • :recycle: Having the same keys for local, test, and production environments


  • :v: Distribute keys for your development team to use only in local and testing environments
  • :thumbsup: Store production keys in a safe place, only available to the production app
  • :closedlockwith_key: Keep the production keys away from prying eyes, load them as environment variables, on-demand, protected against unintended access

Further reading:

Full disclosure: I work as a Developer Advocate at Dashbird.

Image credits:

This post is also available on DEV.