There are a lot of articles around discussing what is the best place to store JWT on the clientside. In short, they're all about -
Http-only secure cookie - no XSS, but vulnarable to XSRF
Header (saved in local storage or DOM) - no XSRF, but vulnarable to XSS
I think I come up with an extremely savvy solution to this, but, since I'm complete noob in security I'm not sure if it's really savvy or stupid.
So, what if to split JWT and save part of it in the cookie and another part in the header? Would it be unbreakable?
This should also solve 'logout' problem - deleting header portion would make browser incapable of logging in.
The JWT needs to remain together, otherwise the signature validation won't work.
Protecting against XSRF is pretty easy, you just need another cookie.
Never use local storage for storing authentication information, it doesn't follow the same domain and origin rules as cookies. Read more here:
https://www.owasp.org/index.php/HTML5_Security_Cheat_Sheet#Storage_APIs
Disclaimer: I work at Stormpath, we have a hosted user management solution and we spend a lot of time on security. I've written two blog posts where I discuss JWTs and front-end auth:
Token Based Authentication for Single Page Apps (SPAs)
https://stormpath.com/blog/build-secure-user-interfaces-using-jwts/
Related
tl;dr
I want to pick your collective brains for my psuedo logic for preventing both XSS and CSRF attacks at the same time when dealing with authenticated sessions in SPA frontends like React (but this applies to any modern frontend pattern where most or all data used in the frontend is populated through incremental XMLHttpRequests that pass API responses to JavaScript logic rather than relying on server side rendering of HTML/backend redirects for the majority of a website).
The reason why I am rehashing this after observing an abundance of answers online is because all the search results on the subject I have encountered suffer one or both of the following problems:
Articles target the aforementioned SSR way of doing things like injecting a CSRF Token as a hidden input in an HTML form
Articles with solutions that only mitigate XSS attacks or CSRF attacks, but not at the same time
I believe there are some pure React/Express based node modules which may or may not solve these problems. But I want to understand a solution at a technical level to be able to implement this in a React SPA + Go backend setup, or any other backend that isn't Node based. I am also making the post because of the lack of public forum to point out security flaws in other approaches I have seen.
tl;dr end
This video argues about why this is might be a non-issue, but is also the starting point of how I thought about the problem.
The problem boils down to the following: an XSS vulnerability is quite trivial to fix by utilizing a HttpOnly cookie inside a modern frontend framework. But this approach alone is still vulnerable to CSRF attacks.
The traditional approach to mitigate CSRF attacks via inserting a hidden input field into server side rendered HTML does not apply to modern SPA's.
The approach I think would prevent both XSS and CSRF is by combining both:
Modern frontend framework + HttpOnly cookie to prevent XSS
CSRF Token passed in every XMLHttpRequest to protected endpoints as a header.
I partially base my logic on this article, but since this project isn't on Github or Medium, I can't find any criticisms of potential security flaws, or anyone praising it as sound.
Here is the detailed approach on how I think you could mitigate XSS and CSRF attacks:
A React rendered login page (/login) you log into via true HTML form submit (not via XMLHttpRequest inside a button onsubmit with a e.preventDefault()), causing the backend to write a JWT httponly cookie, and redirects back to the root (/), and a re-render of the entire SPA DOM with the cookie now stored in the browser.
Frontend needs to request protected data from backend via fetch/axios.
Before sending that request for data, frontend requests the CSRF token from the backend which is validated via JWT httponly cookie.
The actual request to the protected resource is made with both the httponly JWT cookie AND the CSRF Token sent as a header.
Potential flaws to this logic: Because retrieving the CSRF Token only requires the httponly JWT cookie to validate the session, this is potentially the weak link in protecting all calls. However, because the CSRF token is never stored, it adds an extra layer of security.
Alternative approaches:
Store CSRF Token in localStorage. I'm not sure, but I have a bad feeling about this approach since you can just do localStorage.getItem('csrf_token_name') in the browser console to retrieve the CSRF token.
Store CSRF token in JavaScript location. In React terms, either use a CSRFContext (via createContext()), or if you are a masochist, Redux. This approach is cleaner than retrieving the CSRF token before every protected API call. Also, although this is probably more hidden than localStorage, due to production minifying JavaScript logic as well as other pseudo-protection patterns that are afforded to modern compiled frontend code, you probably want to minimize storing anything in JavaScript if possible.
With both of these alternative solutions, you are still stuck with the problem of needing to retrieve the CSRF token from the backend via cookie session validation alone.
Goal:
When using JWT, I need to secure cookies and mitigate XSS attack vector in clients.
I understand that a cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API in the client. The server can access the cookie to for session management (shipping cart, game scores, etc), personalization and, user behavior tracking but JavaScript code can no longer read and write to the cookie in the client.
Reference:https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
Question:
If I am able to enable HttpOnly, is there any practical gain to encrypt the JWT (JWE)? I see that encryption for the JWT is described on https://www.rfc-editor.org/rfc/rfc7516.
I have not seen posts to indicate that JWE has been a practical choice though.
I would appreciate your insight before I explore the deployment of JWE in production.
One potential benefit would be that if the JWT is encrypted, the client won't be able to view and tweak it manually. Even if there aren't any malicious scripts running, there may well still be several inquisitive users who know how to open the developer console and change things - to see what they can break, or gain privileges they shouldn't, or just for the heck of it. If the JWT is encrypted so that only you on the server can decrypt it, that makes things easier for you because the client has no way to change the payload without breaking it entirely (unless they have another JWT saved or found somewhere).
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I'm building a JWT-based (JSON Web Token) authentication mechanism for an browser-based Javascript web app, working with a stateless server (no user-sessions!) and I want to know, once and for all, if using storing my JWT token in a cookie will protect my token from XSS attacks, or if there is no protection, so there's no real advantage over using browser local storage in my Javascript app.
I have seen this question asked and answered in SO and in many blogs, but I've never seen an answer that really satisfies me.
This question was originally held on the basis that it solicits opinion - and given my original wording, rightly so. So let me make it clear here and now that I don't want an opinion based on vague notions of developer laziness or such - that's what the ground rules are meant to eliminate. What I want is an evidence-backed Yes/No answer. Either:
"Yes, cookies can be protected from XSS and CSRF and here's how" or
"No, by protecting your cookies from CSRF, you always open them up to the same kind of XSS attack that made cookies a good idea in the first place"
So I'm going to restate the question, with some simplifying ground-rules, and point out the holes in advance, so that you, the experts, can set me straight.
Ground rules
Your app is a javascript browser app - it might be in AngularJS, but it might be custom-built. It communicates with the server via REST calls. Let's say, jQuery $ajax calls.
The server is stateless: there is no session management.
The app users JWTs as the main authentication token ('access token' in OAuth2 parlance) and validates them on the server using a secret signing key
Ignore other important advantages of cookies: browser management, less chance of coding poorly, etc. For this battle, I want to consider the absolute security, and assume we can competently code either mechanism.
Ignore other disadvantages of cookies, such as non-browser apps, etc. For this battle we are only concerned with a browser-based javascript app.
It doesn't matter whether you use a header or a request body to transmit tokens in the non-cookie approach; nor does it matter if you're using local storage vs session storage - ignore any security differences there. And yes I know that technically Cookies use headers, ignore that.
In a nutshell, we're only interested in comparing browser-handles-tokens vs your-javascript-handles-tokens and the comparative XSS and CSRF security risks.
The contestants
In the red corner, Auth0: Local Storage beats Cookies, because XSS is easier to fix than CSRF
In the blue corner, Stormpath: Cookies beats headers, because actually CSRF is easier to fix than XSS.
(excerpts of both arguments in detail below)
Weapons of choice
XSS and CSRF (we'll use CSRF and XSRF interchangeably: the C seems to be more popular in documentation, the X in code)
Here's my super-simplified summary of the attack types:
Let's assume your stateless, JWT-authenticated, javascript browser app is for online banking and the attacker, "Evil Corp", wants to submit an AJAX REST call that transfers funds to their account by impersonating your users.
XSS (Cross-site scripting)
(As Stormpath points out, there are many attack vectors - I'll pick one)
Evil Corp buys the github account rights for the nifty text field widget you use for password entry. They know your bank site uses it, so they update it to submit AJAX requests to transfer funds to their account when you type in your passord and hit enter. Your build system foolishly pulls the update and puts in production.
CSRF (Cross-Site Request Forgery)
Evil Corp knows your bank site uses JWTs in cookies to authenticate transactions, so they write a web app that submits AJAX requests to transfer funds to their account. They host this on their own evil.com site, and lure you there with an email (phishing) or some other way, when you happen to be logged into your bank site in another tab. The browser submits the request from evil.com, but attaches your JWT becaues it's going to the correct site: the bank.
Standard defences
The defence against XSS is to be very careful about the code in your site so that you never let the browser process something the user types in without sanitizing it (removing javascript and html) and that all the 3rd party libraries (Evil's text field widget) are vetted before being used. As Stormpath rightly points out, this is hard, bordering on impossible.
The defence against CSRF is to use a form of double-submit-cookie. This means our server creates a token (securely random string) and sends it to our Javascript browser app in a readable cookie (call it "XSRF-TOKEN" by convention), and our Javascript sends it back in a header or body with every request.
Actually, double-sumbit-cookie's are only one defence agasint CSRF, but some others require stateful server sessions and no other (I think!) offers any better protection. The statelessness can be achieved by also putting the token in the JWT and comparing it on the server side with the one that comes in the header or body.
But the real CSRF-busting quality of this defence, is that same-origin-policy mean that only the javascript that our app loaded from our domain can read that cookie. So even if the javascript on evilcorp.com can send our cookies with its requests, it can't embed our XSRF-TOKEN because it can't read it in the first place.
To really simplify CSRF:
CSRF attacks work because a browser attaching a cookie depends only on the destination of a request.
CSRF defences work because Javascript access to a cookie depends on the origin of the Javascript.
Auth0's argument
It's easier to deal with XSS than XSRF Cookies have this feature that
allows setting an HttpOnly flag from server side so they can only be
accessed on the server and not from JavaScript. This is useful because
it protects the content of that cookie to be accessed by injected
client-side code (XSS). Since tokens are stored in local/session
storage or a client side cookie, they are open to an XSS attack
getting the attacker access to the token. This is a valid concern, and
for that reason you should keep your tokens expiration low.
But if you
think about the attack surface on cookies, one of the main ones is
XSRF. The reality is that XSRF is one of the most misunderstood
attacks, and the average developer, might not even understand the
risk, so lots of applications lack anti-XSRF mechanism. However,
everybody understands what injection is. Put simply, if you allow
input on your website and then render that without escaping it, you
are open to XSS. So based on our experience, it is easier to protect
against XSS than protecting against XSRF. Adding to that, anti-XSRF is
not built-in on every web framework. XSS on the other hand is easy to
prevent by using the escape syntax available by default on most
template engines.
https://auth0.com/blog/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies#xss-xsrf
Stormpath's argument
Stormpath recommends that you store your JWT in cookies for web
applications, because of the additional security they provide, and the
simplicity of protecting against CSRF with modern web frameworks.
HTML5 Web Storage is vulnerable to XSS, has a larger attack surface
area, and can impact all application users on a successful attack.
https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/
Also:
I see a lot of discussions where cookies are pitted against access
tokens. While we’ve all been burned by systems that store a session ID
in a cookie, and that cookie is not secured and thus gets stolen. That
sucks, but its not a reason to use tokens. Its a reason to avoid
non-secure, non-https cookies.
https://stormpath.com/blog/token-auth-spa/
My take
Stormpath's argument in favour of cookies is pretty convincing, but there's a hole in it that I don't see them addressing clearly:
The double-submit CSRF defence relies on the fact that my CSRF attacker cannot access my cookie: the one with the XSRF-TOKEN in it. But isn't that cookie just as vulnerable in an XSS attack as local storage?
An XSS exploit can run javascript on my domain, so it can read the same cookies my javascript can. (The browser doesn't know that it isn't my Javascript)
To look at it from the other side: local storage is protected by the same-origin-policy just as much as a readable cookie. If I'm using the Auth0 approach, and an XSS attacker knows how to find my JWT in local storage and use it. Can't that same attacker use that same XSS script to grab my XSRF-TOKEN cookie and use that?
Both attacks require them to read and understand my javascript app, but that's out there in their browser.
So what's the difference? Is one really more secure than another, and why?
Abandon all hope unless you can secure against XSS!
Or
Choose the approach that suits you based on other criteria because both are equally secure, equally insecure.
If you use cookies, you should definitely use the double-submit-cookie defence, or something similar, because it does protect you against CSRF in the absence of XSS. That is, if you don't do this, you're definitely open to CSRF attacks - from other domains - that don't even require XSS exploits to work.
But either way, your source code is publicly available (JavaScript in your browser) so for a motivated hacker, there is no significant difference in effort between finding which token to pull from local storage and reading your XSRF-TOKEN cookie. If Evil Corp can get some JavaScript running in your domain - that's XSS - then you're hosed.
Non-security-related criteria you might want to consider for your choice:
Cookies are convenient because you don't have to write JavaScript code to manage the token - only the XSRF.
Redirection becomes a little more automatic too, if you want to use it.
Local storage is easier to adapt to non-browser apps - from the server perspective that is, because if you write say, an Android app in Java that doesn't want to deal with cookies, your server doesn't need to make any distinction between in and the browser, since it's not using cookies.
Anyway, make up your own mind, but be careful about the JavaScript you write and the 3rd party JavaScript you use!
I have read the OWASP information and also a range of articles including Jeff Atwood's Protecting Your Cookies article and I still feel I need to understand HttpOnly cookies better.
This came about because I needed to add some Google Adword tracking code to a site. This javascript needed to set and read a cookie on the website's domain and I assumed that this was an issue. The website is a .Net application with httpOnlyCookies="true" in the web.config, so I assumed that the best approach would be to replace the javascript and write the cookie from the backend to ensure that the generated cookie was HttpOnly. I could then easily read the cookie in the server-side too.
I understand that setting the HttpOnly of a cookie property largely prevents a cookie from being read and manipulated by the client. But what I don't understand is:
Given the example above, would there have been a problem with me using the javascript implementation?
Would it still have been ok to write the cookie using javascript (but still read it using the server-side)? I'm thinking not as the cookie is then not a HttpOnly cookie
If I have done the right thing (moving everything to a server-side implementation), why are Google Analytic cookies always implemented as non-HttpOnly cookies? Surely they pose a security issue too?
So as the title says, I guess what I'm asking is - when (if ever) is it appropriate to have non-HttpOnly cookies on your domain?
So this is much more straightforward than I assumed. According to the comments left by #mah above, flagging a cookie as HttpOnly is redundant when the cookie contains non-sensitive information.
The httpOnly option was response to JavaScript tricks to steal user's session cookie. It was because the session cookie is a temporary credential that allows you access to user's logged-in session on the server.
In case of any other, non-session cookies it really depends on your own risk assessment. If you're not really concerned about slightly increased exposure without httpOnly or you just have a business need to access their values from JavaScript, just ignore this option.
Some security scanners or incompetent pentesting teams will report missing httpOnly flags for each cookie just because it's trivial to spot and allows them to easily inflate their report. But explaining the actual purpose and origin of httpOnly should be sufficient to counter that.
I'm designing the API for a group of sites. The sites are very similar (kind of like StackOverflow, SuperUser and ServerFault), and it makes sense for them to have a shared backend. Hence we decided to try and have a nice REST API as a backend, and a bunch of very-similar-but-different frontends consuming said API. The frontends should preferably be all static, but that's not a hard requirement if it turns out to be borderline impossible.
I'm working on designing that API now, and I'm worried about the security implications, particularly CSRF. From my basic understanding of CSRF attacks, they consist of two important components:
Being able to name the resource and the request body.
Tricking the user/browser into using ambient auth (like sessions) to make a request to that resource that looks authenticated.
A lot of the classic approaches to fixing CSRF attacks are based on the session. Since my REST API doesn't really do sessions, that both prevents a lot of the vectors and also pretty much all of the ways to fix them. For example, double submitting doesn't make sense because there's nothing to double submit.
My initial approach involved attacking part 2 of a CSRF attack. If I authenticate all the requests (say using HTTP Basic Auth), and the browser doesn't keep those credentials stored (e.g. some JS made the request), only the JS that has the credentials can make the request, and we're done. The obvious downside is that the app needs to know the user's credentials. The other slightly less obvious downside is that if I want to store credentials securely on the API end, then verifying a password should take a fixed, non-trivial amount of time. If verifying a password securely takes 100ms, then every other request is going to take at least 100ms + eps, and it's going to take some darn clever clientside trickery to make that not feel slow. I might be able to cache that (since the credentials will always be the same), and if I'm very careful I might manage to do that without introducing a timing vulnerability, but that sounds like a hornet's nest.
OAuth 2.0 seems a bit over the top, but I guess it might be the best solution after all, lest I end up implementing it poorly. I suppose I could do the HTTP Basic Auth thing for now, and move to OAuth when we have third party app developers.
There's a bit of an impedance mismatch with OAuth. OAuth really wants to help apps access stuff on another app, basically. I want users to sign up on one of the frontends, before such an account even exists.
I've also considered attacking point 1 by making the URLs randomized -- ie adding tokens to a query string. This would certainly work and it's very close to how the traditional randomized token in a form works, and given HATEOAS it should even be fairly RESTful, although this raises two questions: 1) where do you start? Is there a mandatory API start point where you do log in using HTTP Basic Auth? 2) How much would it make app developers happy if they can't predict a URL up front, HATEOAS be damned?
I have seen How to prevent CSRF in a RESTful application?, but I disagree with the premise that randomized URIs are necessarily unRESTful. Also, that question doesn't really have any satisfactory answers, and doesn't mention OAuth. Also, the session double submit solution is invalid, as I've mentioned above (different domain for the static frontend than the API endpoint).
I realize that what I'm fundamentally trying to do here is trying to allow cross-site requests from one domain and disallow them from the other, and that's not easy. Surely there has to be some reasonable solution?
A CSRF token is by definition "per user state" and therefore not RESTful. Most API's break their "per user state" requirements for the purposes of security, and require a CSRF token passed as an HTTP header parameter.
There are other ways of preventing CSRF. Checking the referer is not as strong as a CSRF token, but it is RESTful and VERY unlikely to be undermined. Keep in mind that the lack of a referer should be considered a failed request.
XSS can be used to bypass both token based CSRF prevention and referer based CSRF prevention.
The answer depends on what you need to support. If we assume that you want to support a web-app which uses the REST service and use the same REST service for an API that is something different to a web-app that happens to be RESTFUL (you may decide that 'sessions' are for you! ).
For now (/me is rather tired) I think the way you have suggested using javascript with HTTP Basic Auth is a good start.
I have another potential solution, so I'm listing it as an answer. Please feel free to take it apart :)
auth.platform.com takes authentication and sets cookies. If auth.site.com is a CNAME for auth.platform.com, would the request to auth.site.com (ending up at auth.platform.com after resolution) be able to set a cookie for site.com? That way I could double-submit session cookies.
Of course, auth.platform.com will only set cookies for a few whitelisted domains.
EDIT: Except of course this won't work at all, because you'd have to use HTTPS to do the authentication securely, and HTTPS will see right through your trickery.
Designing your API as a true RESTful one, prevents the most common CSRF vectors:
avoiding cookies prevents them to be "stolen",
using GET for "safe" operations prevents img and iframes to trigger unsafe actions without user interaction.
Then you should implement CORS to let users' browsers block requests from origins you don't trust.