A secure single site JWT implementation - security

I read a lot about JWTs and found that it is pretty hard to use them in a secure CSRF and XSS proof way.
Until I realized that my tokens will probably not leave my unique domain...
This led me to this idea of implementation:
Use a refresh token along with an access token. That way I can set short expiring time to the access token and limit the database calls to verify the user and only when the access token expires a simple verification by asking the database that the refresh token is not blacklisted before sending a new access token to the user.
I understood that if I store a token in the local/session storage it would be vulnerable to XSS attacks but if I store it in an HTTP-only cookie it would be vulnerable to CSRF attacks.
As I don't need to access the token inside the JavaScript and that I only need this token in one website, I thought that I could store the access token inside an HTTP-only cookie (that way it is protected from XSS) along with the secure flag and the same site flag set to strict (preventing CSRF).
And regarding the refresh token I could store it inside an HTTP-only cookie that has the same secure flag but without the same site flag this time. Because the server will never execute any action only based on the refresh token, I think that it will therefore not be susceptible to CSRF attacks. The only thing the server will do with a refresh token is to send back a new access token which, if I understood it well, could not be read from the CSRF attacker. The CSRF vulnerability allows the attacker to make a request to the server (which will automatically contain HTTP-only cookies) but he cannot read the response of this request.
I don't know if this implementation of JWTs would be secure or if I missed something...
This is what I'm asking you (JWTs and web security experts) would that be a good JWTs implementation ?

First, a word on JWTs. An oldschool plain server-side session (with a long, random session id, correctly stored in a secure, httpOnly, maybe also SameSite cookie) is "more secure" than any JWT in most cases. The reason is that JWTs store state on the client, where it is more available to an attacker, offline attacks can be performed against the cryptography involved, also JWTs are plain text and only their integrity is protected (ie. they are protected against client-side change, but not against looking at the contents by default, though you could use JWE, and encrypted JWT too). Last but not least, implementation is more complex, and simplicity is great for security.
So you would use a JWT when you need a benefit that it provides.
One argument is that it's stateless. This is very often overrated though. First, in most applications the bottleneck will not be the database lookup needed to find the session data in the database. In some very high profile, high traffic applications it actually might be, but you are probably not developing something like that every day. Usually it's just ok to do a database lookup, and it makes the whole thing a lot simpler. Second, sometimes you need token revocation, ie. you want to be able to force-logout a user. Even if you used a JWT for statelessness, you would have to store something in a database, a list of revoked tokens for example, and checking that would also be a database roundtrip. Revoking JWTs simply cannot be implemented in a purely stateless way.
Another benefit is that you can use it for multiple origins. If you get a httpOnly session cookie, that will be managed by the browser, javascript cannot send it to other origins, like an API or something - that's the whole point, JS not having access. But sometimes you do need that, especially in single sign-on scenarios, where you have an identity provider (a component that provides logon), and services (eg. APIs) that you want to use your access token on. In this case a classic cookie would not work, and JWTs are very handy.
So in short, you would use a JWT when you really need statelessness, or the option to send the token to multiple origins. As a rule of thumb, if you can store the JWT in a httpOnly cookie, you most probably don't need it at all, and could just use a plain old server-side session.
And then let's say you decide to use JWTs, what about refresh tokens. They are another generally misunderstood feature of token-based authentication. The point in using refresh tokens is to be able to use short-lived access tokens, so if an access token get s compromised, it is at least time limited. But if you store the refresh token the same way as an access token, why would an attacker not get that too?
It only makes sense to have a refresh token, if you store it differently, otherwise you could just have long-lived access tokens, and that would not be less secure.
One typical thing is to have an identity provider ("login server", or IdP) that sets a refresh token (or just a plain old session id) in a httpOnly cookie for the identity provider origin, so whenever the client makes a request to the IdP, it "just works" if they are already logged in, and can provide a new access token without user interaction. The access token is then stored in something like localStorage (for the service origin), susceptible to XSS, but at least time limited. If you don't have this separation, a separate refresh token probably doesn't make much sense.
As a final note, all of this should very rarely be implemented by yourself. Any language or framework you use probably already has one or more known good, maintained implementation for authentication. You should just use those, but it's still very much worth to understand it of course.
Please note that in any actual application there might be subtleties and certain circumstances that somewhat change what you want to do, but the above is a general way to think about secure authentication.

Related

How worried should I be about opening up a JWT to an XSS vulnerability?

I am building a node.js web application with react for the the GUI and graphQL served with Apollo for the back-end connecting to a RDS (MySQL) instance on AWS.
I am authenticating users and then returning JWTs. I have it figured out on how to renew/expire tokens, but now I am being faced with the question where to save it on the client side when a user visits the site...
There are two main concepts with a third being a hybrid model. 1) Store it as localStorage with JavaScript as described on HowToGraphQL 2) Store it in a Cookie with http-only set to true as described in the afore mentioned article as a cationary reference to Randall Degges
There is another alternative to store it in memory only on the client side but then a user would have to login every time the page is refreshed as it would not be persistent anywhere.
Concept 1 is vulnerable to XSS only if there is another XSS vulnerability already exploited. But it is secure to the site only so only scripts running on the site can access it and not scripts on any site. There it a lot of security talk that it should not be stored this way even though it is the common way because a developer cannot trust EVERY JavaScript script they are running on their site and there may be one that reads the localStorage and then sends it offsite.
Concept 2 removes the XSS vulnerable by declaring the http-only to only make it accessible to the server at your site. The problem here lies in that then a separate method has to be created to use the same backend authentication for other uses such as a standard API (for native apps or other sites) where the JWT is sent in the header over https where it is stored securely on another server.
So I researched and found this hybrid method described by Ben Awad 3) use a request token and a refresh token. The request token can then act normally for the standard API but then also on our react app site we can store it only in memory and store a refresh token in a cookie to send back a request token when users refresh or close and reopen browsers.
So theoretically, the best solution is Concept 3 which solves all of the concerns, but it is of course more complicated to setup.
My question: How worried should I be about opening up a JWT to an XSS vulnerability? It is something that down the road I would do the long way when I have more time, but I am pushing for a deadline. My site will be lesser known and not something like Facebook or Sales-Force that hackers would necessarily target. My site is not storing Credit Card data or other highly sensitive data other than a basic CRM and task list. If my site was open to XSS through other code, wouldn't the entire authentication process be vulnerable through keylogging scripts or the likes without even knowing the JWT. I feel like I would be doing a lot of extra work to secure against a possible threat that if occurred, the entire system would be compromised already.
If you are comfortable with your site to not work on Internet Explorer and some older versions of the major browsers, you can take advantage of a new cookies property, called Same-Site (to be more precise, the site will work but the cookie will not be secure).
By defining a cookie as HttpOnly, you are immediately secured from XSS attacks, but you leave yourself open to CSRF attacks.
Now by defining the cookie to have the property Same-Site=Strict, the cookie will be only sent through Http calls and only if the domain matches your site's domain. So for example, if someone creates a form in another site and tries to perform a post request to your own site, the cookie will be never sent.
If you want the cookie to be passed only on GET requests, you can set the Same-Site property to Lax but as you mentioned.
You can find more info about this feature in the following link under the SameSite cookies section:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
You should also check the browser compatibility of the feature by using the following link:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Browser_compatibility
This is the issue I spent a lot of time on. How to store the authorization token securely. People have different strategies in dealing with this so I will share what works for me. Users of my apps were targeted by different attacks, all of them where unsuccessful in stealing anything so far. None used XSS.
Here is what I do
In the end I opted for storing authorization token in local storage. Applications that I work usually have WebSocket connections on top of HTTP routes and I want the token to be saved in one place and act as a single source of truth. They are all web applications running in the browser. Most of the applications I build use JWT.
Why I do it like that
First why I don't use refresh tokens. If they are saved the same way as the actual authorization token would be saved negates the reason for refresh token to exist since the attacker can use the refresh token to get the authorization one.
Storing the token in cookies gives no benefits over local storage assuming that the app is secured against attackers being able to inject JavaScript into your app, mostly through forms and api on your app. Make sure all user inputs are JS injection safe. On top of that with cookies there are issues when using WebSockets that you must go around.
There is also the point of one of the accounts being hacked and you want to invalidate that token as soon as possible. JWT by default has no mechanism of being revoked. Implementing this feature negates the scalability of JWT because checking the JWT would require a call to the database to know if that user can do the specific action. There are 2 ways you can go about this. One is just check the user data if the user is frozen from the database, it is less scalable because of the call but if you already pull the user data in a middleware it is good enough TM. Other is to pull the the "is the user frozen" data from the database just when making changes to the database or when the call from the client is important.
In summary
I would store the token in local storage. Secure the app from code injections. And make a kill switch for the accounts if they get compromised in any way.
EDIT THANKS TO THE COMMENTS BY #JerryCauser
It is more secure to keep your token in a secure http only cookie. Don't expect a storage mechanism choice to automatically save your users from being hacked. There are ways to hijack sessions and other exploits including users using web extensions and approving their request to read protected data.
For the example of the betting website below, you wouldn't require user to write their password (or approve the request via automated email) every time they place a bet, but you would every time they want to take a withdrawal for example.
I use local storage because even if it happens for the token to be stolen, or another person got to your user's laptop (like a kid for example) you should never let the account do critical tasks without approval.
There is no magic bullet of anti hack protection. Try your best to keep your users safe with common sense.
EDIT AS ANSWER TO THE COMMENT FROM THE ASKER #amaster
If you are making a trip to the database on every call, maybe JWT is not the best solution. Point of JWT is to have signed claims and the id of the user without calling the database. In this case, maybe opt in for sessions instead of JWT.
Before I proceed with my answer, you may want to check out OWASP for a set of general guidelines regarding XSS and CSRF since you've mentioned cookies.
Cedomir already covered a good deal of the points with storing JWT client side. One thing that's worth mentioning is that if you have Third-Party scripts running in your web app, they also have access to the Storage API. So if a script you had loaded were to be hijacked, they could conceivably steal the token there. As for XSS with inputs, if you make sure to escape every possible user input, then that is largely mitigated as an attack vector. But you only have to screw up once for someone to take advantage of the hole and steal the JWT at that point. (Refer to this blog post for more details)
Now, if you instead store the JWT in a Http-Only, then you largely sidestep the XSS issue as you've already noted. However, now you introduced a new problem, that being Cross Site Request Forgery. Since cookies are sent with every request, a malicious actor could set up a website to make a fraudulent request on behalf of user and execute actions without the user's consent. Now I won't cover the mitigation in detail here as OWASP and other places have done a pretty good job already, but the short of it can be summed up by installing the most popular and well-maintained Anti-CSRF package for your language :-)
As for invalidating the token as Cedomir brought up, having that mechanism can be quite useful. However, to implement it does mean you give up some of the benefits of using JWT gives you. Whether you store the current JWT assigned to user and validate that or a unique key used to sign the JWT for each user, you now have user state to keep track of, eliminating one of the reasons to use JWTs. Depending on your application, you will need to weigh that tradeoff. A much simpler way could be simply to have short-lived tokens so that any token that is stolen potentially won't have a very useful lifetime. However, as you probably recognize a short lifetime would be a potentially a very annoying user experience. You could have your website periodically poll the server for a new token while your user continues to use the website as a way to improve the experience. You can also balance your security concerns with the lifetime of the token, like a 15 minute token lifetime for a e-commerce app vs. a hour or more for a social application.
I would however advise against the use of a refresh token, at least for a Browser-Based Web App. Typically speaking, the browser is just not considered capable of securing sensitive secrets. By using a refresh token, you're just deferring the stealing of credentials to another layer as by the nature of the refresh tokens, they're 1) long-lived and 2) effectively used as credentials to obtain more JWTs. So if the refresh token were to be stolen, an attacker can just get more valid JWTs on behalf of a user. If you have a mobile or desktop app, you have mechanisms you can use to securely store refresh tokens and this advice does not apply.
...Or you could just use sessions ;-)
When logging in on server set JWT token and a random csrf token in the httpOnly cookie
Also send this csrf token in body response of login back to client
On every future request from client send this csrf token via some header (eg. X-CSRF-TOKEN)
On the backend verify if the csrf tokens coming through the cookie and x-csrf-token are the same.
Then verify your JWT token and continue with your app logic.
Putting JWT token in httpOnly cookie prevents XSS attacks, validating CSRF token prevents CSRF attacks. Double sending csrf token in both cookie and header avoids storing stuff in the backend database.
XSS check
CSRF check
Stateless auth check
Auth doesn’t have to be over complicated. If you have clients that only want to pass JWT token in some header other than cookie then it’s better to just make a separate api endpoint for those programs.
While the question is not actually about OAuth / OpenID Connect I still think you can learn a great deal by checking out this Internet-Draft: OAuth 2.0 for Browser-Based Apps (Best Current Practice)
To sum it up: there simply is no secure way to store an access token on the client. If you develop only the frontend you pretty much have to use and store a token on client side - not because it's great but because you have no other choice. However, if you do have full control over Frontend and Backend you do have that choice and should think about using the same domain for both and use a session cookie as described in the Internet Draft. Basically the React application never even sees the acesss token, because your backend is serving a http page and handling the authentication directly, with the final step being a redirect back to your frontend while setting the session-cookie.
A potential XSS attack is pretty bad as it is and you should be careful not to introduce a vulnerability. The thing is: with the JWT-approach a XSS vulnerability leads pretty much to the worst-case scenario: the attacker is able to steal the user authentication and can impersonate the user - this is basically session hijacking.
The same attack against a regular session-cookie simply does not have the same impact (as long as the cookie uses the HttpOnly Flag which is highly recommended). Even though the vulnerability enables arbitrary JavaScript Code to run on the machine (which is really bad obviously) it's still a lot harder for the attacker to do some damage. He is not able to hijack the session in this case, because he is unable to read the cookie.
Just use HTTP only + SSL only cookies to save your JWT. It will make almost impossible to stole user's jwt via a soft or any type of code injections.
Someone said here, what it is no diff between LocalStorage and Cookies. He is not correct, bcs third party libraries and chrome extensions can easily stole LocalStorage data. But they cannot stole HTTP only cookie.
It will protect against any known and most likely new types of attacks.
JWT itself is completely protected. Just don’t store something there that could compromise your architecture or something like that (do not put a hashed password for example)
Upd: Good article about best practices for JWT strategy: https://ducktypelabs.com/5-mistakes-web-developers-should-avoid-when-using-jwts-for-authentication/

JWT advantages over simple randomly-generated tokens in database?

Let's say I have a typical CRUD API for a web application. I need to authorize users by a token, check user roles, etc.
Is there any reason why I should consider JWT over storing a randomly-generated token in a table like tokens(token, refresh_token, expiration_date)?
In my opinion, JWT is adding more complexity here:
Additional code to handle encoding/decoding
Need to store JWT secrets and keys
Token revocation problem
I have to hit a database to check user roles(although I can include them in a payload, there's also other stuff that I should check in my application), so no advantages here. The only benefit I can see here is that I can check token expiration data without hitting a database.
At the same time storing a randomly-generated token in a database is a dead-simple solution.
Am I missing something?
JWTs are often misunderstood. The main benefit they provide is statelessness. If you go to your database to query privileges upon each request anyway, that is pretty much lost, if not from a theoretical but from a practical point of view.
They are typically not stored in http-only cookies, which makes them vulnerable to XSS, but at the same time allows Javascript clients to read the payload (eg. who is logged in, what privileges they have and so on). Not being stored in cookies also allows them to be sent to different origins, which is pretty much the only reason they should not be stored in a http-only cookie (if and only if you understand and accept the risks of this).
JWTs are in no way better or magically more secure than plain old random session tokens - quite the opposite in most cases, especially that it is often overlooked that as opposed to server-side sessions, JWT payload is plaintext. It is protected against tampering by message authentication, but not protected against the user having a look, which sometimes might become an issue.
If you don't need the features above (statelessness, access from javascript), you should just not have the additional complexity of a JWT, you just need a plain old session then.
First thing you need to consider is who will generate the token.
In case of JWT, a valid OAUTH provider will generate the token. The benefits are as follows,
you can validate the legitimacy of the token, which will include checks for following,
a. audience
b. expiry
c. issued at
d. not before date
e. issuer
you can check the issuer based on just the public key which would avoid a network trip to another remote server such as database.
you can inject any payload which can include a userinfo, email, role or any custom attributes.
for a standard OAUTH JWT, you can test using common providers such as 'Okta'. As opposed to your custom generator, where you will have to provide a user a mechanism to retrieve such a token.
as far as the code to check, the JWT is quite straightforward with 3 parts separated by a "period". But there are libraries in java and other languages which can do the parsing for you - com.auth0, specifically jwt and rsa which will let you do the parsing and verification.
your code will be compliant and easily portable to another provider.

Where should a SPA keep a OAuth 2.0 access token?

In a Authorization Code Grant flow, once a public client such as a Single Page Application (SPA) obtains a OAuth 2.0 access token, where should the SPA keep it?
Storing the access token in locale storage or session storage opens up to cross-site scripting (XSS) attacks, so that should be avoided.
Storing the access token in a non-httpOnly cookie also opens up to XSS attacks, so that should be avoided as well.
Storing the access token in a httpOnly cookie is not technically possible because that is the point of httpOnly.
So the only secure remaining option that I see is to keep it in memory. Is it actually secure? Is it the only secure way?
It's all about the risk you want to accept.
If you store it in a cookie, you potentially open up your application to CSRF. While it may make sense to exchange XSS for CSRF by storing the token in a httponly cookie, it doesn't make much sense to do so with a non-httponly cookie that besides CSRF is also vulnerable to XSS.
Storing it in localStorage or sessionStorage is ok in many cases. With choosing that, you accept the risk of XSS having access to tokens. To mitigate this risk, you might want to implement mitigations, like for example static security scanning with a suitable tool, regular pentesting and so on - security is not just code, it's also processes around how you create that code. With mitigations in place, you can decide to accept the residual risk.
You can also store tokens in memory, like for example in IIFEs I guess, from where it's somewhat harder to read in an XSS attack. Storing it in a plain variable doesn't help (javascript from XSS would still have access), and I'm not entirely sure about what the latest JS can do to securely make it inaccessible from outside a given object. It's probably not possible in a way that is actually secure.
Or you can go down a different route. You can store very short-lived access tokens in localStorage, accepting the risk of XSS having access. However, your IdP can issue refresh tokens in httponly cookies for the IdP domain. This way even if an access token is compromised, it is only valid for a limited amount of time, and then the attacker will not be able to renew it. This may make sense in some applications, and probably not in others.

Is it dangerous to store a JWT in a Javascript-accessible cookie

In other words, is it dangerous to have a JWT, which doesn't contain any sensitive info, in a cookie without the httponly flag? I understand the main security concern is XSS attacks. So since Javascript can access the cookie, attackers can potentially access the token. But since there isn't any sensitive info in the JWT, is the only harm session hijacking?
The crux of the issue is that I would like the JWT to be in a Javascript-accessible cookie because I want to be able to access the token claims to limit the functionality of the user within the ui.
So is it a better idea to use a non-httponly cookie for the JWT, or make the JWT in a httponly cookie and just do a separate non-httponly cookie for storing the user's credentials?
Although the JWT does not contain any sensitive information, the token itself is sensitive. JWTs are usually used as bearer tokens, which means they are a (possibly time limited) credential that can be used by anyone in possession of the token to access whatever resources the token is issued for.
An attacker who was able to obtain the token via a successful XSS would therefore be able to impersonate the victim to make requests to your server. This is what you describe as "only" session hijacking. I'm not sure why you say "only". Usually session hijacking is pretty serious ;o)
A more secure approach in my opinion, as you say, to make the JWT cookie HTTP-Only and have a separate cookie containing the information used to limit the functionality in the UI.
As an aside, limiting the UI in this way should not be considered as an effective security measure on it's own, since it would almost certainly be possible to bypass the UI restrictions unless they were also enforced on the server side. It is fine to use it for personalisation of the UI though.

Is there a more secure means of authentication than Forms authentication

I was wondering if there is a more secure way of authenticating than forms authentication? I don't like the fact that when you login to a site using forms authentication, you can copy the cookie to another computer, and log in. I realise that if the site is browsed to over ssl there can be no man in the middle attack, however I still don't like the fact my site is vulnerable in this way.
Is there a good alternative available, other than windows authentication?
Thanks
You can look into token-based stuff, but what you will find is that there is always something that is stored on the client and passed for authentication instead of the actual user credentials (username and password). Whether it's a session id or a token does not really matter if you assume the attacker has the ability to access stuff on the client.
When token-based auth is used, the tokens are most of the times not kept in a cookie, but for example in browser memory (Javascript objects), or even worse, in places like localStorage. That is actually less secure than a good old session id in an httpOnly cookie, which is secure against cross-site scripting, as opposed to pretty much anything else in a browser.
So do realize that with almost any solution you choose, basically what happens is you exchange your user credentials for some kind of a token which you then use for authentication, and for the session the token is equivalent to your credentials, and there is not much difference between traditional sessions and tokens. Even Windows auth does about the same, but a layer below, on the OS level, which makes it less susceptible to attacks like XSS, but makes it vulnerable to attacks based on automatically sent session ids (like CSRF).
If you want to mitigate the threat of an attacker stealing session ids, you can for example design your application in a way that sessions are bound to client identifiers like the client ip address. That way even if a session id is stolen, the attacker still cannot use it. As another mitigation to that threat, you can prevent concurrent sessions, practically meaning logic around terminating all other sessions of a user that logs in. Note that these things are not specific to traditional sessions, the same applies to token-based auth, only the implementation is different.

Resources