Article originally published here.
In the last years, JWT tokens are widely used as an authentication and authorization method for web application...
For further actions, you may consider blocking this person and/or reporting abuse
If you use CSP to block any inline-script/insecure-script and also enforce that only trusted JS files must be accepted: there's no issue about using LocalStorage.
The article doesn't make sense for me. Yes, using cookies might make your session-value secure. But, let's suppose that your website is vulnerable by XSS in the first place. The attacker can send a request using that session, without the need to steal the session value itself. The "CSRF protection" doesn't protect against XSS, because the requests are executed inside your own website.
Hello Lucas. I agree that this way you would be protected by scripts that are not hosted in your own domain. But in case there is, for example, a node module that you installed while developing it would be considered insecure. I understand your point and it is 100% valid. But with this approach, even in the rare case that a module was vulnerable and you had by accident injected it in your base app, the module could never send the token to an external domain through an Ajax request. It could only hit your internal API, but this would require that the attacker had injected malicious code in a node module just to attack your web application specifically. Let me know about your thoughts.
I don't know. That issue about leak the session can also be fixed with CSP, since you can block external communications too. I never use node modules, and you might guess why, so I can't say anything about it.
What I'm saying is that it is easy to find solutions when you have such a small attack surface, and never tell it. You can also suggest encrypting the local storage value using a random key using, with a random algorithm with a random name... Yes, attackers can extract the key (...), but then use the same argument: "just to attack your web application specifically". The opposite scenario might be valid: since I'm considering that the page is secure against XSS, then I can use LocalStorage.
I think it would be better if it compares all alternatives (SessionStorage, LocalStorage, Cookies, Credential Management API, IndexedDB API) and all kinds of known attacks.
So you suggest serving external js from another domain and set a CSP to disallow these scripts to access local storage or make ajax requests?
That’s correct. But if you serve external scripts through a CDN for example, and set a csp you are secure with the cookie implementation too. I may write a complementary article or extend this one at some point. Thanks for your feedback its really valuable!!!
That is not true. With javascript you can just create a tag with an src to the attacker sites - and as we know the browser always sends the cookie with the requests - so bingo the attacker now has your cookie. Please beware of that
Hey Milebroke,
Not sure which comment you wanted to leave a reply. If you refer to cookies, in the case of Lax and Strict they won't be sent to the attacker's website if we, for example, inject an image with the attackers URL as the source. That's because it's not considered a first level navigation event.
Although, it may be sent if the attacker creates a GET form and sends it by clicking the button through javascript. I have to admit that I have not tested this scenario, but you will be fine if you just use Strict flag, as it will prevent all cross domain cookie sharing.
Also a small comment, I personally think that the endpoints that are related to payments and generally sensitive account actions should require an extra login with a different authentication method, valid for one API call only (especially for payment API calls) and should be served in a page where only vanilla javascript written by the web application developers is used.
Just some thought, but isn't the method of using Cookies just as vulnerable to XSS as that of using localStorage? I'm new to Web Security and have been wondering this all the time. I mean if an XSS attack happens, the attacker can do whatever they want on behalf of the victim, including maliciously sending requests that have totally valid cookies. The cookie-based authentication, from my understanding, can at best introduce a little bit of inconvenience for the attacker, but at the same time making the authentication process much more complex. Therefore, in the end, what you have to do is to make sure you're not (so) vulnerable to XSS or it is your doom.
Nice post btw, especially the part about SameSite policy stuff, very informative.
Hello there,
Thanks for commenting. Cookies are XSS vulnerable in the case that we don’t set an HttpOnly flag in the server when creating the cookie. When the cookie is set with an HttpOnly flag it can’t be accessed by javascript in any way (at least in modern browsers, I don’t know what happens with ie 7 or 8 :p)
I think @quochuytlbk does not suppose that you want to steal the session, but use the session already active on the page. Let's assume that your website is vulnerable by XSS, then I can inject something like:
fetch('api/account', {method: 'DELETE'})
. I still don't know your session/cookie, but I manage to use them to delete your account. The fetch call was performed inside your website, it's not CSRF.Nothing is 100% secure, but that would require that the attacker finds a way to inject code to specifically target my own web application. What you say is 100% valid, my approach is vulnerable to this kind of attack. But it would also be vulnerable if someone was using local storage. I don't know if there is any way that you can prevent this type of attack.
Lucas delivered what I failed to. So yes, that's the point. When an XSS vulnerability is exploited, nothing can save our users. The best bet is to prevent XSS from happening in the first place, or everything is vulnerable.
That being said, why would I go through all the trouble of trying to protect cookies from both CSRF and XSS, just to introduce a little bit of inconvenience to the attacker? At least with localStorage, I don't have to care about protecting against CSRF.
So in my opinion, using XSS as an exclusive drawback of localStorage can be misleading. When I first started to dig into the "localStorage vs Cookies" battle, I unknowingly thought it was "Protecting against XSS vs Protecting against CSRF". That is of course not true. It is "Protecting against XSS vs Protecting against CSRF and XSS".
I think one of the valid use cases of cookies over localStorage (or sessionStorage, IndexedDB, etc.) is SSR (where there is no localStorage).
Edit: Another valid use case of cookies is when you have subdomains because I've heard that localStorage doesn't work across subdomains.
So I have come to my own conclusion that it all depends on your use cases and how you use the methods. There is no "localStorage is totally better than cookies" or vice versa.
A technique I use is to split the JWT into two cookies. The header + payload accessible through JavaScript (for client-side reading of the claims), and the signature is HTTP only (not accessible through JavaScript). Align cookie expiration with JWT expiration for auto-logout.
medium.com/lightrail/getting-token...
I use this.
Header + Payload are stored in LocalStorage and sent in a header with fetch.
Signature is in a cookie with HttpOnly.
The server stitches the header with the cookie and then validates the JWT. This works well in IE 11 which does not support SameSite cookies.
Nice approach
first of all it's a really great post! But I have one question in case for example you have separate authentication server you can not use HttpOnly flag right? or in that case I should use just subdomain?
Can you give a specific example of how you authenticate (the login process) and then how the other APIS validate that the user is authenticated?
I was some time ago already so I don't remember all details to be honest, but the gist is frontend redirected user to authentication server (we used keycloak) then we stored token in the local storage and we reissued it like each minute (or something like that) as I remember. Backend used keycloak adapter which again made request to keycloak server to validate the token.
OK, so I guess that backend was also making a request to keycloak (through redirect) and was then returning the token to the user. If that's the case then you just have to set the token in a Secure HttpOnly Strict cookie instead of just returning it, for example as JSON and saving it to the local storage.
hm not exactly authentication happened exactly on keycloak server and then backend made request to keycloak just to validate the token, so user received token from keycloak server and there for I suppose just cookies solution would not work. As I understand that solution could be migrated from local storate to cookies only if we moved keycloak to some subdomain and used subdomain cookies sharing. But definitely I can confuse something
If it follows the oauth2 flow, you could redirect to the backend of your web application instead of the browser, so then the backend would set the cookie and would redirect back to the frontend page.
yeah it would work I think thank you for the answer!!
First off, great article! I've recently finished building an authentication system that works very similar to the one you've described but, with a distinct difference: I store the JWT across two cookies.
One cookie contains just the JWT header and payload and can be accessed by JavaScript, the other contains the signature but is Secure + HttpOnly. This way I can access the payload on the client without worrying about having the entire token potentially compromised.
Authentication in SPA the right way by Jean-Cristophe Baey describes this approach in a bit more detail.
Thank you very much. That's a very clever approach!!! I will definitely try it on my next projects.
Interesting approach. But would it be wrong if you'd read the payload from a HTTP response instead? IMO, it would be the same thing as reading it from the cookie.
Therein exactly lies the reason why people tend to shy away from them. It means blowing up every request by a few (or, in case of, JWTs, many) bytes, even requests that don’t require authentication like images, CSS, scripts…
Of course you could work around that by using a different domain for static assets or by using a single path prefix for all requests that need authentication and then set the
Path
flag of the cookie to that value but all of these things require infrastructure changes…Hi Raphael,
Thanks for mentioning this issue. It may not seem like a big deal but in some cases it is. Personally, I try to use cookie-free domains if speed is an important factor for my applications.
I remember reading an article talking about people lining in Africa, who could not even access simple pages because of the tons of modules, CSS and javascript that we use on our websites today. It's pretty sad if you think about it, a big part of today's world doesn't have access to the internet because we don't make our own web applications accessible to non-broadband connections.
Nice post George, I've recently integrated the
SameSite
cookie policy to one of my Go packages, the iris web framework one (github.com/kataras/iris) and users loved it so I totally encourage web developers to set a cookie policy throughSameSite
, it's fairly easy-to-understand concept and it looks like 2020One thing missing (from most web site authent. tutorials) is: how many tokens/sessions are currently allowed for a single user?
Because whatever the method [in your post], this does not prevent a whole range of issues.
Let's imagine you lend me your computer so that i can reserve a train, but instead go to DevTools and copy the cookie/localStorage/sessionStorage, i'm done in less than 30s and i can reuse the token whenever i want on my computer :)
If you want more security, you need to store a list of currently allowed signatures on server side.
This way you can:
Note : do not store the whole token, otherwise if your database is compromised, tokens can be used to impersonate anyone...
This can be also completed with:
Hey Thibault,
Thanks for sharing all this info. All these things are really useful. Of course, they cannot be covered in a single tutorial, and it would also be out of the scope of the article.
I haven't implemented a solution that is so complex till now. Do you think that JWT tokens are a good solution for those cases? I mean, if you need to perform actions like checking if a user is logged in, JWT automatically loses a big advantage compared to traditional sessions, the ability to authenticate the user without any query to a DB server. Of course, you keep the authentication stateless but this can be achieved with generated ids that identify the user session (stateless session) too. What's your opinion on these subjects?
Correct.
I'm not experienced enough to give you a definitive answer.
Managing traditional sessions isn't that simple either, especially when you want the "remember me" feature. By implementing things yourself, you're likely to open doors.
Thus, a JWT library with stateless session definitely brings advantages. I also like the encoded payload+signature compared to a lot of things seen in cookies :p
So maybe using JWT and storing only the signature in a table with a foreign key on the user would be interesting.
"verifying the received token with the exact same key that was used to sign it in the first place" -- to be precise, a private key is used to sign the token and a public key to verify the authenticity of the token.
Hi there,
Thank you for commenting!
The way you proposed to perform the signing and validation of the token is also valid but its not the only one. JWT tokens can be signed using HMAC where only a private key is used to sign and verify the token. This is used in most cases where only the backend needs to verify the token, and the frontend just needs to decode it (everyone can decode a jwt as its just a base64 representation of our data).
If you sign JWT tokens and you need your frontend to be able to verify that it was signed by a valid authority, you can use a private key to sign on the backend, and provide the public key to everyone to be able to verify the token.
Is this what you mean?
PS: This article assumes that we use the HMAC way to sign the tokens.
That's perfect, thank you for clarifying the use case where HMAC shines.
If you are storing your JWT tokens in the cookies because they are secure enough... Why do you need JWT to start with? Just store the data in the cookie, no?
Hi there,
If you store data in a cookie, as JSON for example, how would you validate that the data sent to your server is valid? I mean, everybody could just create a JSON representation of a user, send it through an HTTP cookie and then they would be allowed to performed authorized actions in your web application. But I think that you refer to the case that you want to access the data of the JWT in the frontend.
The examples of these articles assume that you don't need access to the JWT in the frontend (I mean access like getting the full name of the user using the JWT or something like this) and that this kind of JWT is used only for authentication purposes, to make it easier for the backend devs to validate that the user is logged in without performing queries or using sessions in the traditional way. For frontend access to the profile of the user, I prefer to get this data from a /me or /profile endpoint, as it's always up to date and I don't have to mess with decoding JWT in the frontend too.
Does this answer make sense to your question, or did I get something wrong?
We use IdentityServer4 in our .NET solution, which also includes Web API and Angular front-end app. There are tons of middleware settings (it's actually an ABP framework-based solution). At the very end, all authentication tokens are stored in Local Storage (I have not found where exactly it's set up, BTW). Anyway, it has worked somehow in our DEV environment. But suddenly the need to use window.open from Angular app popped up. And it causes a lot of headache: now, to identify user in server page, called from window.open, we need to use cookies (URL is not considered of course). Does it mean we have to switch fully from Local Storage to Cookies Storage? How to set it up? My idea was to copy access_token when it is created (in Local Storage) to Cookies and delete when a user logs off and probably under bunch of different conditions, like browser window is closed, etc. (which ones exactly?) Where to find all related info?
Would it be possible/better to split the token between localstorage and a cookie? This would prevent against CSRF and XSS token-stealing attacks at the same time. Part of the token is only accessible by the actual site because it is stored in localstorage, and part of the token in not accessible by JS at all, but could be sent from any site.
Yes, it has been mentioned in other comments too. It’s a nice approach!!
Theeeen what is the point, should I store the token in cookies with Https and only flag?
If you store the cookie with HttpOnly flag, Secure flat and SameSite=Lax|Strict flag you are secure and good to go! This article assumes that you don't need access to the JWT content in the frontend, so it is ok to use the HTTP flag. So by setting the HttpOnly flag, the cookie can not be retrieved from Javascript, and by setting the SecureSite=Lax|Strict it won't be sent in cross-domain requests. Sorry if I was not 100% clear on this point.
Ok, but if i need to get access from the frontend to make some request to my API in the backend, how should I get the token to authenticate my API calls from JS?
If your frontend is on the same domain as your API (or a first-level subdomain), the cookie should be automatically sent through Ajax requests. For example, if your web application is hosted on kevin.dev and your API is hosted on api.kevin.dev, the cookie will be sent in every Ajax request automatically. Of course, you should make your API fetch the cookie in the backend, and verify the token. In case you need to access the payload of the JWT token, that's not possible with HttpOnly cookies. You should make an extra request to your API, something like a /me endpoint, and save the data in a Javascript variable.
Oh :O thnxs a lot, finally a good post to solve my problem!!
"This is achieved by verifying the received token with the exact same key that was used to sign it in the first place" - If I understand this correctly, this would be a symmetric key. It also possible to have an asymmetric key that uses a public and private key. See also stackoverflow.com/questions/329009...
Yes, that's correct. I think it's useful when the client wants to verify that the token was issued by a specific authority. Have you ever used it like this?
"when a user closes their browser, the JWT will disappear" - I believe browsers can also restore sessions these days.
Hi David, this is specific to sessionStorage. If you mean traditional sessions through cookies, of course, they are able to restore them, actually, they never lose them. Based on MDN sessionStorage is cleared when you close the tab or the browser.
Very insightful
Thank you Prithvi
Another issue with the cookie-approach is that you cannot read the associated claims, right?
thx for article George
Q) what you think if #token was saved in #indexeddb ?