While some of your points are correct, suggesting to use sessions and cookies instead of JWT is missing the point.
People using JWTs are most probably running a SPA and communicating with a pure stateless API. We don’t want to hear about sessions in this case.
Storing a JWT in the cookies is perfectly OK and it has the advantage of not needing custom JS code to pass it to each HTTP request to your backend.
But in some situations, like when your API is also used by your mobile app and it requires the "Authorization Bearer xxx" header instead of a cookie or when you’re making HTTP requests to multiple backends but with same JWT, it’s convenient to have your JWT in localStorage instead.
Also, most modern JS frameworks (React, Angular, ...) nowadays produce concatenated and minified vendor scripts so you don’t have to include single scripts from third party locations.
Here's a question: in what contexts do SPAs need to use bearer token auth, and is this truly an ideal?
There's only one class of SPAs which CAN'T use cookie auth -- namely, SPAs using a statically served application shell. This is an architectural decision with a lot of tradeoffs to it. On the plus side S3 is cheap, there's a certain theoretical purity to having your web frontend go #serverless, and you only need to maintain one form of API authentication. On the minus side, you have to greenspin literally everything that the browser gives you for free.... like the nice security properties of cookie auth.
There's nothing wrong with maintaining a simple, stupid edge service that exists to maintain sessions & proxy requests to your API. It feels pretty oldschool, but it works pretty well. And if (when) your web frontend winds up wanting a sliiiiightly different API than your mobile app, having an edge service already in place makes that trivial! Most existing SSR frameworks (Next, Nuxt...) can serve this purpose without too much modification of an existing app-shell-style SPA.
You can work around statically served apps and authentication if you use a concept of an API gateway like Kong. I my case statically served apps are only allowed to go through a Kong like service...in which we can control authentication, rate limiting etc
There is another class of SPAs that cannot use cookies: Those that are supposed to work inside an iframe. Third party cookies are denied in any third party http request in Safari already and Chrome is coming. Firefox did the right thing and still allow them while preventing unwanted tracking.
Considering JWT are most likely used for security, it is pretty much urging not to use them. Unless you need a token for accessing public data.
Many people don't realize that JWTs are essentially the same thing as a username/password.
This one is a stretch and if you go this way, then that is true for pretty much everything used for security nowadays. It is true for cookies. It is true for SSH keys, etc. Any written access that replaces your password can be exploited in a way or another.
Your example with keys is interesting because even if it is not bulletproof, then you are better off typing your password on each request. Except it's not practical.
Don't get me wrong, the article is interesting and everybody should understand the danger of all these practices. But then people will be waiting for the alternative. And mentioning an alternative is the worst thing to do because people will prove you wrong, because nothing is unbreakable.
You're better off warning and help them make sure they are making their best to secure the way they use the current tool.
I'm a Software Engineer based in Australia. I enjoy Type Safety, Functional Programming, Event Driven Systems and building good Team Culture. People first.
Let's say you want to store a JWT in a cookie -- that's fine. BUT: the purpose of JWTs is to be stateless, right? Cookies are capped out at 4k, which means the JWT needs to be < 4k for this to work.
Most stateless JWTs are > 4kb (this is quite easy to test, serialize a user from your DB into a compact'd JWT and look at the byte size -- I've done this on a lot of sample apps and in almost every case except the most simple the JWT is > 4kb). In this scenario, you've basically got to use local storage.
Instead of doing that: why not just use a session cookie as I recommended? The downside is that you need to manage a cache on the API side, but this is easily doable. If you're using JWTs anyway (with local storage, let's say), you STILL NEED to have centralized sessions in some way to manage revocation.
JWTs are insecure by design: they cache authentication/authorization data, so in order to work around their speed-vs-security tradeoff you've got to manage a revocation list centrally no matter what: otherwise you end up in situations where revoked permissions/data are being allowed through -- a poor scenario.
Now, let's talk about your point about using the Authorization header. I hear this a lot: I think I will write another article about it as it's a common misconception.
There is NO DIFFERENCE between sending auth data to a server in the Authorization header (with a bearer token) vs sending it to the server using a cookie. COokies are just HTTP headers as well. The difference is a few characters:
Authorization: Bearer <token>
vs Cookie: session=<signed(id)>
Any server can parse either header. While you may not want to for some reason, there is nothing technical or bad about using a cookie for this purpose.
I'd argue that the ONLY reason you need to use a JWT for auth is if you're using OpenID Connect, as for some reason the authors of the spec decided to standardize on JWTs for the ID token. In this case, using the JWT and putting it into an Authorization is actually required -- outside this case? Not at all.
And re: scripts being minified/included... This does not apply to the biggest use case I mentioned in the article: third party tools. Things like GA, Optimizely, Kissmetrics, and all the other marketing/ad network tools that most companies use.
There are definitely scenarios in which you can make yourself safe from XSS, but those situations are increasingly rare.
As always, this article is only meant to provide guidance to the 99% of people who do this stuff blindly without thinking about it. In your case, you likely know the risk profile for what you're doing and can decide whether or not you want to take the risk.
Content-security-policy for XSS + subresource integrity hashes for third party hosted libraries, or just compile in the third party tool like google analytics, e.g. npmjs.com/package/analytics
JWTs aren't inherently unsafe. I like that JWTs are signed by a server side secret, and I like the flow that creates during auth. Depending on how secure the app needs to be, I've even stored user agent and remote address info in a JWT, signing it with a user specific secret. I had the JWT checked for all of that as accurate against headers and then refreshed the sign/verify secret if the JWT had data that was bad in it.
Most apps can get away with an expiry on a JWT and increase performance, this decreasing energy usage, this decreasing environmental impact of your code.
The CIA of information (Confidentiality, Integrity, Availability) is held strong. OP only focuses on Confidentiality for some reason.
JWT is inherently unsafe for the sole purpose it cannot be immediately invalidated. The only way to force invalidation is to change a signing key. If you’re blacklisting JWT’s, then you’re using it wrong. Blacklisting requires preserving a state and checking against it. If you’re saving them, then you’re using them wrong (there’s no a plus in scalability over sessions). If you’re using refresh tokens, then again, you have a state. Plus, it leads to number of bad practices, such as storing them in local storage which can be exposed to XSS attacks, use of long lived JWT’s, or storing sensitive informations in their payload. What would be the correct way to use them? Probably by having a dedicated authentication server like Keycloak which can use short lived JWT’s in combination with refresh tokens and key rotation. But, do you really need such a complicated architecture in the end of the day?
I don't think you understand that APIs don't scale with sessions maintained on server side. That's why we no longer use session cookies. Storing JWT in http only cookies is not perfect, but ok from scalability perspective.
First of all, you’ll need hundreds, if not even thousands of requests per second until your API stops scaling. Second thing, sessions can be serialized and stored in an external data store such as Redis or even Postgres. This way, you may easily scale up or down your API since you moved session state outside of your service. Yes, you will need to take a look at that data store each time when you want to check session validity, but this is very fast. The issue is that session data store becomes a single point of failure, but so is modern authentication service such as Keycloak. If Keycloak goes down, users will start to log out as soon as their JWT’s start expiring since it will be unable to issue new JWT’s. Third and final thing, a common sense. As I already wrote, it will take hundreds or thousands of requests per second before you need to scale to additional instance. In my experience, an app which handles 200-500 requests generates so much money that you can afford to rewrite your authentication layer from scratch implementing your fancy stateless tokens.
This might be true for Google or Amazon but is not actually true for the majority of applications out there I would argue.
You find out a token is compromised? Just regenerate your signing key. Yes every application user will have to log in again but that is a perfectly acceptable action for most applications.
One problem with the article is that the words 'never' and 'always' are used way too often.
Also, the article says that localStorage is insecure quite often but doesn't give any evidence or examples.
I would argue that localStorage is as secure as cookies (including httpOnly cookies).
localStorage uses essentially the same security policy as cookies; one of its core principles is that a domain cannot access localStorage data that was created under a different domain so there is no chance that a website could steal data from a different website.
Also, httpOnly cookies do not make your site any less vulnerable to XSS attacks; if the attacker manages to inject a malicious script into your front end, then they can use that script to make HTTP requests to your server (directly from the victim's browser) and your precious httpOnly cookie (containing the user's valid session ID) will be attached to every request so the server will service them without suspecting anything.
The only real difference is that if the token (e.g. JWT) is in localStorage then the attacker can steal the token to use later (same goes for regular non-httpOnly cookies BTW)... Which is hardly a convenience because it's more advantageous for the attacker to carry-out the attack in-place from the victim's browser rather than from the attacker's own machine (thus allowing their IP to be traced directly).
Also, with JWTs, it's good practice to set short expiry dates. If you're using WebSockets you can even issue JWTs with 10 minute expiry (for example) and re-issue a new one automatically every 8 minutes if the user is still connected and logged in; then when the user logs out or becomes disconnected; their last issued JWT will become invalid in only 10 minutes (at which point it becomes completely useless to an attacker).
Also, it's not recommended practice to store large amounts of data inside a JWT because of the overhead of having to send it with every request/connection.
Indeed the original article totally misses the point that local storage is by no means less secure than any other part of your website. If you have an XSS, you are flawed. That's actually the reason why XSS attacks are so serious/bad.
If you fetch from the browser, you don't get cookies. You have to add {credentials: "include"}. And that requires a whitelisting on the server. So no, it's no so easy to get httpOnly cookie content in browser as you describe. It requires a TRACE method or other known vulnerability or bug to expose them. Pls. prove me wrong if you think otherwise.
Other than that, I agree with your point. When an app has an XSS injection there are tons of attack vectors a hacker can take. This article is mostly a FUD, unfortunately.
While some of your points are correct, suggesting to use sessions and cookies instead of JWT is missing the point.
People using JWTs are most probably running a SPA and communicating with a pure stateless API. We don’t want to hear about sessions in this case.
Storing a JWT in the cookies is perfectly OK and it has the advantage of not needing custom JS code to pass it to each HTTP request to your backend.
But in some situations, like when your API is also used by your mobile app and it requires the "Authorization Bearer xxx" header instead of a cookie or when you’re making HTTP requests to multiple backends but with same JWT, it’s convenient to have your JWT in localStorage instead.
Also, most modern JS frameworks (React, Angular, ...) nowadays produce concatenated and minified vendor scripts so you don’t have to include single scripts from third party locations.
Here's a question: in what contexts do SPAs need to use bearer token auth, and is this truly an ideal?
There's only one class of SPAs which CAN'T use cookie auth -- namely, SPAs using a statically served application shell. This is an architectural decision with a lot of tradeoffs to it. On the plus side S3 is cheap, there's a certain theoretical purity to having your web frontend go #serverless, and you only need to maintain one form of API authentication. On the minus side, you have to greenspin literally everything that the browser gives you for free.... like the nice security properties of cookie auth.
There's nothing wrong with maintaining a simple, stupid edge service that exists to maintain sessions & proxy requests to your API. It feels pretty oldschool, but it works pretty well. And if (when) your web frontend winds up wanting a sliiiiightly different API than your mobile app, having an edge service already in place makes that trivial! Most existing SSR frameworks (Next, Nuxt...) can serve this purpose without too much modification of an existing app-shell-style SPA.
You can work around statically served apps and authentication if you use a concept of an API gateway like Kong. I my case statically served apps are only allowed to go through a Kong like service...in which we can control authentication, rate limiting etc
There is another class of SPAs that cannot use cookies: Those that are supposed to work inside an iframe. Third party cookies are denied in any third party http request in Safari already and Chrome is coming. Firefox did the right thing and still allow them while preventing unwanted tracking.
I think you’re misreading him. He doesn’t say “don’t use JWT” he’s just saying “don’t stick your JWT in local storage”.
Yes, it’s convenient. It’s also convenient to just leave the key to my house in the lock, but it means anyone who can get to the door can get in.
Considering JWT are most likely used for security, it is pretty much urging not to use them. Unless you need a token for accessing public data.
This one is a stretch and if you go this way, then that is true for pretty much everything used for security nowadays. It is true for cookies. It is true for SSH keys, etc. Any written access that replaces your password can be exploited in a way or another.
Your example with keys is interesting because even if it is not bulletproof, then you are better off typing your password on each request. Except it's not practical.
Don't get me wrong, the article is interesting and everybody should understand the danger of all these practices. But then people will be waiting for the alternative. And mentioning an alternative is the worst thing to do because people will prove you wrong, because nothing is unbreakable.
You're better off warning and help them make sure they are making their best to secure the way they use the current tool.
Another great resource to look at is the BFF pattern, worth looking into to explore the idea in this post further.
docs.duendesoftware.com/identityse...
youtube.com/watch?v=UBFx3MSu1Rc
Hey, this is a great point. But hear me out.
Let's say you want to store a JWT in a cookie -- that's fine. BUT: the purpose of JWTs is to be stateless, right? Cookies are capped out at 4k, which means the JWT needs to be < 4k for this to work.
Most stateless JWTs are > 4kb (this is quite easy to test, serialize a user from your DB into a compact'd JWT and look at the byte size -- I've done this on a lot of sample apps and in almost every case except the most simple the JWT is > 4kb). In this scenario, you've basically got to use local storage.
Instead of doing that: why not just use a session cookie as I recommended? The downside is that you need to manage a cache on the API side, but this is easily doable. If you're using JWTs anyway (with local storage, let's say), you STILL NEED to have centralized sessions in some way to manage revocation.
JWTs are insecure by design: they cache authentication/authorization data, so in order to work around their speed-vs-security tradeoff you've got to manage a revocation list centrally no matter what: otherwise you end up in situations where revoked permissions/data are being allowed through -- a poor scenario.
Now, let's talk about your point about using the Authorization header. I hear this a lot: I think I will write another article about it as it's a common misconception.
There is NO DIFFERENCE between sending auth data to a server in the Authorization header (with a bearer token) vs sending it to the server using a cookie. COokies are just HTTP headers as well. The difference is a few characters:
Authorization: Bearer <token>
vs
Cookie: session=<signed(id)>
Any server can parse either header. While you may not want to for some reason, there is nothing technical or bad about using a cookie for this purpose.
I'd argue that the ONLY reason you need to use a JWT for auth is if you're using OpenID Connect, as for some reason the authors of the spec decided to standardize on JWTs for the ID token. In this case, using the JWT and putting it into an Authorization is actually required -- outside this case? Not at all.
And re: scripts being minified/included... This does not apply to the biggest use case I mentioned in the article: third party tools. Things like GA, Optimizely, Kissmetrics, and all the other marketing/ad network tools that most companies use.
There are definitely scenarios in which you can make yourself safe from XSS, but those situations are increasingly rare.
As always, this article is only meant to provide guidance to the 99% of people who do this stuff blindly without thinking about it. In your case, you likely know the risk profile for what you're doing and can decide whether or not you want to take the risk.
Content-security-policy for XSS + subresource integrity hashes for third party hosted libraries, or just compile in the third party tool like google analytics, e.g. npmjs.com/package/analytics
There are many improvements being made to the security of single page applications (HSTS en.wikipedia.org/wiki/HTTP_Strict_..., certificate transparency en.wikipedia.org/wiki/Certificate_...) that make storing sensitive information in JS about as safe as storing them in a mobile app.
JWTs aren't inherently unsafe. I like that JWTs are signed by a server side secret, and I like the flow that creates during auth. Depending on how secure the app needs to be, I've even stored user agent and remote address info in a JWT, signing it with a user specific secret. I had the JWT checked for all of that as accurate against headers and then refreshed the sign/verify secret if the JWT had data that was bad in it.
Most apps can get away with an expiry on a JWT and increase performance, this decreasing energy usage, this decreasing environmental impact of your code.
The CIA of information (Confidentiality, Integrity, Availability) is held strong. OP only focuses on Confidentiality for some reason.
JWT is inherently unsafe for the sole purpose it cannot be immediately invalidated. The only way to force invalidation is to change a signing key. If you’re blacklisting JWT’s, then you’re using it wrong. Blacklisting requires preserving a state and checking against it. If you’re saving them, then you’re using them wrong (there’s no a plus in scalability over sessions). If you’re using refresh tokens, then again, you have a state. Plus, it leads to number of bad practices, such as storing them in local storage which can be exposed to XSS attacks, use of long lived JWT’s, or storing sensitive informations in their payload. What would be the correct way to use them? Probably by having a dedicated authentication server like Keycloak which can use short lived JWT’s in combination with refresh tokens and key rotation. But, do you really need such a complicated architecture in the end of the day?
I don't think you understand that APIs don't scale with sessions maintained on server side. That's why we no longer use session cookies. Storing JWT in http only cookies is not perfect, but ok from scalability perspective.
First of all, you’ll need hundreds, if not even thousands of requests per second until your API stops scaling. Second thing, sessions can be serialized and stored in an external data store such as Redis or even Postgres. This way, you may easily scale up or down your API since you moved session state outside of your service. Yes, you will need to take a look at that data store each time when you want to check session validity, but this is very fast. The issue is that session data store becomes a single point of failure, but so is modern authentication service such as Keycloak. If Keycloak goes down, users will start to log out as soon as their JWT’s start expiring since it will be unable to issue new JWT’s. Third and final thing, a common sense. As I already wrote, it will take hundreds or thousands of requests per second before you need to scale to additional instance. In my experience, an app which handles 200-500 requests generates so much money that you can afford to rewrite your authentication layer from scratch implementing your fancy stateless tokens.
This might be true for Google or Amazon but is not actually true for the majority of applications out there I would argue.
You find out a token is compromised? Just regenerate your signing key. Yes every application user will have to log in again but that is a perfectly acceptable action for most applications.
One problem with the article is that the words 'never' and 'always' are used way too often.
Also, the article says that localStorage is insecure quite often but doesn't give any evidence or examples.
I would argue that localStorage is as secure as cookies (including httpOnly cookies).
localStorage uses essentially the same security policy as cookies; one of its core principles is that a domain cannot access localStorage data that was created under a different domain so there is no chance that a website could steal data from a different website.
Also, httpOnly cookies do not make your site any less vulnerable to XSS attacks; if the attacker manages to inject a malicious script into your front end, then they can use that script to make HTTP requests to your server (directly from the victim's browser) and your precious httpOnly cookie (containing the user's valid session ID) will be attached to every request so the server will service them without suspecting anything.
The only real difference is that if the token (e.g. JWT) is in localStorage then the attacker can steal the token to use later (same goes for regular non-httpOnly cookies BTW)... Which is hardly a convenience because it's more advantageous for the attacker to carry-out the attack in-place from the victim's browser rather than from the attacker's own machine (thus allowing their IP to be traced directly).
Also, with JWTs, it's good practice to set short expiry dates. If you're using WebSockets you can even issue JWTs with 10 minute expiry (for example) and re-issue a new one automatically every 8 minutes if the user is still connected and logged in; then when the user logs out or becomes disconnected; their last issued JWT will become invalid in only 10 minutes (at which point it becomes completely useless to an attacker).
Also, it's not recommended practice to store large amounts of data inside a JWT because of the overhead of having to send it with every request/connection.
woow this is one of the best comments in this post.
Indeed the original article totally misses the point that local storage is by no means less secure than any other part of your website. If you have an XSS, you are flawed. That's actually the reason why XSS attacks are so serious/bad.
If you
fetch
from the browser, you don't get cookies. You have to add{credentials: "include"}
. And that requires a whitelisting on the server. So no, it's no so easy to gethttpOnly
cookie content in browser as you describe. It requires a TRACE method or other known vulnerability or bug to expose them. Pls. prove me wrong if you think otherwise.Other than that, I agree with your point. When an app has an XSS injection there are tons of attack vectors a hacker can take. This article is mostly a FUD, unfortunately.
His point was that you don't need to read the cookie if you can send requests when the user is on the compromised website.