JWT authentication, as opposed to session-based authentication, is supposed to be less of a hassle to implement in a distributed system. With traditional session auth, you would need a shared cache (which is a single point of failure) or a distributed cache (which comes with its own set of complexities).
Wouldn't adding a revocation service, such as a token blacklist, in order to, for example, "logout" a user, introduce the same hassles of session auth described above?
Yes, it would. Adding a blacklist will cause you to lose some of the advantages of JWT, such as not needing server space, other than that if you use a cache with multiple servers you will need a replication mechanism as you said.
But it is a common assumption to allow tokens to expire instead of using a blacklist and setting a small refresh time.
Take a look here to see some common techniques to invalidate tokens
Related
So my question is about asymmetrically signed JWT tokens. How is it ensured that the signature can't be replicated on JWT tokens? If someone captures the JWT, what prevents him from reusing the same JWT over and over again without being properly authorized? I believe JWTs can have an expiration date, but it doesn't solve the issue since expiration date for most apps are relatively long in order to provide a good user experience. Most simplistic but a naive solution I can think of is having a nonce attached to the user starting from 0 and incrementing every time the jwt is used, and the request is processed.
Preventing replication of the signature of the JWT relies on you storing the key correctly that signed the JWT, either in a secure storage or some other safe place, reuse is a different story.
Now, for a start, I would also be worried if an attacker can easily steal your JWT, which almost implies that you don't use HTTPS/SSL, but sure, let's assume we are running a scenario where someone has exploited your SSL.
JWT's themselves are nothing more than ways of authentication a client, almost similar to how an API key works. It's pretty much a grant that that client can access some resource using that token. It doesn't hold any security value in that regard, other than at some point that client authenticated itself using the correct credentials.
So if it's stolen, you have a problem. This is where the lifetime of the tokens comes in to play. Tokens can expire very quickly, causing the attacker to only be able to access resources for a short while, but still, they did have access to a resource they shouldn't have access to.
In that regard, you can try to mitigate the attack using, like you mentioned, a nonce. Just don't forget to protect that nonce by a key that is related to the identity or session of the JWT. Alternatively you can add an HOTP or TOTP, or some unique signature to the request. This comes at a cost of you having to create extra logic at both your client and server.
So summarised, JWT's themselves don't really provide security, they provide authentication. Generally speaking JWT's are secure if using contemporary security measures such as SSL. If you really want to add additional security to the request, you are left to implement additional logic, such as OTP's or signatures by implementing more logic at the client and server.
I have been researching how best to implement authentication in a React/Next.js application - specifically, how to store authentication tokens in a manner which is practical, while maintaining a needful emphasis on security. There is some existing debate about this topic on SO but as far as I can see, none offer concrete solutions.
Having spent much of yesterday and today trawling the internet for answers, I came across the following:
Local Storage API. I found that some basic guides suggest the use of localStorage (though many rightfully advise against it). I do not like this approach because data stored in localStorage could be accessed in the event of an XSS attack.
Web Workers. If the token is stored in a web worker, the user will not be logged in if a new tab is opened. This makes for a substandard and confusing user experience.
Closures. Same as Web Workers - there is no persistence.
HttpOnly Cookies. On the one hand, such cookies cannot be read by JavaScript so it's not prone to XSS. However, on the other hand, we now have to deal with CSRF, which is a new debate altogether: how does one implement CSRF tokens with an SPA + REST API?
While the use of HttpOnly cookies seems most favourable, implementing CSRF in an SPA seems unconventional and perhaps experimental, contravening the maxim of not "rolling your own" when it comes to security. I'd rather use a proven method if one exists.
With SPAs being all the rage nowadays, I am surprised that it's proving so difficult to find a consensus (or even better, a library) for such a prevalent scenario.
How is everyone else doing it?
Update: After some thought, I wonder if localStorage is really that bad if there is a strong CORS policy? If there happens to be an XSS vulnerability, couldn't the attacker start sending requests from within the browsing context anyway, negating any perceived benefit of using cookies and CSRF protection?
I'm happy that you're asking this question. There are recurring memes regarding oauth2 on the frontend that are really polluting the debate, and finding factual information is difficult.
First, regarding some excluded options which I suggest reconsidering: if you need the same authentication on multiple tabs, you can still use any option that would store tokens in a window scope, but individually manage tokens and get a new one on page refresh (silent refresh, thus standard prompt=none flow). This opens some options: service worker, web workers, closures... True, some of this isn't meant for that originally, but it solves the problem nicely. This also solves a bunch of race conditions about refresh tokens (they can only be used once, so having one for each tab solves a bunch of problems).
That being said, here are the options:
Local storage: in case of successful XSS attacks, tokens can be stolen. XSS=game over anyway IMO (no hacker will care about your token in such a case, it's not needed). It can also be mitigated by having short-lived tokens in comparison with the typicial hours/days validity of cookies. In any case, short-lived tokens are recommended.
Now, stolen tokens in case of XSS seem to be an important issue for some people, so let's look at the other options anyway.
Session storage: same downsides as local storage (XSS can lead to session leakage), introduces its own CSRF issues, but also solves some others (refresh...).
Web workers: this is actually a nice solution. In case of successful XSS in a random part of the application, it won't be able to steal tokens. In theory, if one could inject some script that would run at authentication (auth code or token exchange), it could be exploited too... but that's true for all flows, including cookies/sessions.
Closures: same as web workers. Less isolated (easier to replace by one that would steal your token).
Service worker: ideal solution in my opinion. Easy to implement (you can just intercept fetch requests and add your token in a few lines code). Can't be injected by XSS. You could even kind of argue that it's actually meant for that exact use case. It also solves the case of multiple applications on a page (they share 1 service worker which adds token when required), which none of the other options solves nicely. Only downside: browser can terminate it, you need to implement something to extend it's lifetime (but there is a documented, standard way).
HttpOnly Cookies: in short, you're then transitioning to a traditional server-side web application (at least for some parts), it's not an independent SPA with standard oidc or aouth2 anymore. It's a choice (it's not been mine for some years now), but it shouldn't be motivated by token storage, as there are options for that which are even secure and arguably better.
Conclusion: my recommendation is to just use local/session storage. Successful XSS will probably cost you your job or your customer anyway (hint: nobody is interested in your tokens when they can call the pay(5000000, lulzsecAccount) API).
If you're picky about token storage, service worker is the best choice IMO.
The solution is a HttpOnly SameSite Cookie.
In the question, you correctly note that HttpOnly protects from XSS. SameSite in turn protects from CSRF. Both options are supported by all modern browsers.
This is orders of magnitude simpler and safer than other solutions. It's easy to set up on the API and completely transparent to the SPA. Security is built-in.
Concrete solution:
The actual authentication can be done by your API or by an external provider that redirects to your API. Then, when logged-in, your API creates a JWT token and stores it in a HttpOnly SameSite Cookie. You can see this at work with NestJS at nestjs-starter as explained in: OAuth2 in NestJS for Social Login (Google, Facebook, Twitter, etc).
One limitation is that API and SPA have to be on the same domains.
For rather storing the token client-side, this other article is very comprehensive.
Your comparison is a bit confusing... When you talk about SPA authentication, you need a solution that alows you to store authentication tokens. The main purpose of Web Workers and Closures is to run code, not for to store authentication tokens. You can write it down as a hard coded variable but it's not its purpose and this is why a new tab won't identify it.
So we're left with Local Storage and Session Cookies.
Before SPA and client side rendering, we used to only have server side rendering and cookies. This is when HTTPOnly was invented to make it harder to steal session IDs and users' identities. When client side rendering was invented, stateless APIs were invented. These APIs do not use session IDs but Tokens instead, such as JWT. It means that the server do not save any information about the user in a session that is identified by a session ID. Instead, JWT tokens contains the signed information within the token itself, and the server do not remember anything and reauthenticate the user in each request. There is also a hybrid approach of tokens that are saved in NoSQL DBs in the server side such as Redis, to make the authentication process faster.
But the bottom line is that HTTPOnly cookies are used with stateful APIs that use sessions, and LocalStorage are used with stateless APIs that use tokens. You can also use it the other way around (cookies with tokens, LocalStorage with sessions) but its "less natural".
As for XSS, HttpOnly mechanism in cookies makes attackers' life a bit harder, but even if it is used, attackers can still do a lot of damage with phishing for example. So its not critical as this is just a compensating control and not a main mitigation in any way, so you can safely use LocalStorage. Furthermore, cookies are prone to CSRF attacks where LocalStorage does not.
So both options are valid.
You can read about it some more in here:
What is the difference between localStorage, sessionStorage, session and cookies?
I am struggling to understand how JWT refresh tokens are safer than just using normal JWT access tokens with a long lifespan. I understand that by keeping the lifespan of JWT access tokens short, it limits the window of opportunity for attackers to abuse it. This assumes that the SSL layer of HTTPS has somehow been bypassed by the attacker in order to gain the JWT access token in the first place.
How do JWT refresh tokens solve this problem exactly? Once the access token expires, you would have to transfer the refresh token, which can also be hijacked if we assume that HTTPS is not secure enough. If the attacker gains control of the refresh token, then he now has access to a large supply of access tokens since refresh tokens typically have a long lifespan. By extension we could also say that the initial username/password authentication can be stolen if the HTTPS protocol is compromised.
Since the refresh token must be kept in the frontend (I am building a Angular/Spring boot application), we must take extra care that the refresh token can not be stolen client side as well. LocalStorage is clearly unsuitable to store the refresh token since it is not meant to be a secure storage. They are also unsuitable to be sent every request since they would be stolen together with the access token otherwise, which defeats the purpose of having short lifespan access tokens in the first place. Where should one store the refresh token?
If I wish to provide remember-me functionality at the loginpage, can I simply set the refresh token with an infinite lifespan?
I already went through several well written answers from the following links (and more):
What if JWT is stolen?
SPA best practices for authentication and session management
https://security.stackexchange.com/questions/119371/is-refreshing-an-expired-jwt-token-a-good-strategy
But I am left unsatisfied regarding these 3 questions.
I shall attempt to answer all the points in your question
Do not use JWT refresh tokens. Use Opaque refresh tokens. Typically, JWTs are to have very short life times. The reason for this is that revoking them may not be possible if you do not have blacklisting
You can store refresh tokens in HttpOnly, secure cookies. If you want to avoid CSRF and XSS, then you can split the access token and store half in cookies, and other half in localstorage
If you assume that https is compromised (which is actually possible), the best defence here is to have measures in place to detect stolen refresh tokens. You can do do by implementing rotating refresh tokens. This can also be used to implement remember me functionality quite easily and with the highest level of security.
In general, this topic is quite complex and it would be impossible for me to explain everything here. So here is a blog post I like that explain everything todo with session security. They also have an open source library called SuperTokens you can use that is by far the most secure implementation I have seen. They have it in various tech stacks, and can also implement one for your tech stack.
You've already received an answer and have selected it, but I thought I'd add another perspective.
I'll start by pointing out a bit of myth with one of your assumptions:
LocalStorage is clearly unsuitable to store the refresh token since it
is not meant to be a secure storage.
I'm sure some will disagree with me on this, but to me LocalStorage is just as secure as Cookie storage, if not more.
Cookies are susceptible to CSRF attacks, while LocalStorage not so much. And both LocalStorage and Cookies are susceptible to XSS attacks (even httpOnly cookies, since injected code could perform any operation withCredentials).
So from that perspective, Cookies offer a greater attack surface than LocalStorage.
Therefor I don't see any problem in storing access NOR refresh tokens in LocalStorage from purely a security perspective.
Beyond the security concern, you may need to store them in LocalStorage (or a non-Cookie store) depending on the platform(s) you deploy to, ex: some mobile frameworks don't support Cookies.
Conversely, if you plan to run a JS Web app that does server-side rendering, you may need Cookies since typically the server process will not have access to LocalStorage.
So the issue is not entirely one of security.
As for the main gist of your question, which I understood as:
If access tokens are susceptible to attacks, what makes refresh tokens helpful, as they too must be susceptible to the same attacks?
You're right. Both access tokens and refresh tokens can be compromised. The question is... what can your server do about it once it finds out?
The idea of access tokens and refresh tokens is that access tokens are short lived and refresh tokens are long lived.
Personally, I see little use in refresh tokens unless you're using JWTs as your access token, which is what you eluded to in your post.
As you probably know, JWTs are stateless (albeit you can implement white/black lists which would make them stateful, but that sort of defeats the purpose). Therefor there is nothing the server can do to disable a stateless JWT.
Due to this fact, some consider it risky to have long expirations on JWTs because they can't easily be disabled if compromised. I agree with this.
So to get the "best of both" worlds, one can use short-expiry JWTs (10 minutes or so) with long-expiry refresh tokens (many OAuth implementations never expire the refresh tokens).
This strategy gives your server some control back by allowing it to deny issuing new refresh tokens, thereby denying new access tokens, while also benefiting from some of the selling points of JWTs.
I'm building an electron desktop app, and in the app it will call the remote API with JWT token. However, where should I persist this JWT token safely without the threats like XSS, CSRF, man in the middle attack etc... and cannot be accessible by other applications
I've tried using node-keytar package, which uses an encryption key derived from the user’s login.
But according to this issue(https://github.com/atom/node-keytar/issues/88), the credential(JWT in our scenario) can still be compromised if the user's environment got a malware installed.
The code of node-keytar is fairly easy, here's the add secret
keytar.addPassword('KeytarTest', 'AccountName', 'secret');
and here's the get secret
const secret = keytar.getPassword('KeytarTest', 'AccountName');
console.log(secret); // "supersecret"
I'm thinking about just storing JWT into the memory might be the safest way, but will require user to re-login and get JWT token everytime they reopen the electron desktop app.
Any suggestions or thoughts are welcomed. Thanks!
In many use cases involving JWT, you would not need to necessarily do any additional encryption/obfuscation of the JWT before you send it to the API, because:
When you send the JWT to the API, you would be doing so via SSL or HTTPS, which encrypts the entire payload. This in theory would eliminate most chances of man-in-the-middle attacks.
Even if someone managed to sniff your JWT token, they would lack the server's key which is required to unlock it. Also, even if they managed to unlock the JWT, it would be almost impossible to alter its contents without also altering the checksum, which is contained within the JWT itself. This eliminates the chance of injection attacks by inserting something into the JWT.
So in general, the JWT pattern is a way of pushing server side session state outside of the application. And it does it in such a way that this state is protected from tampering on the outside. If it were possible to easily tamper with a JWT on the outside, the entire pattern would fall apart.
I have implemented JWT based security in a test Core Web API REST project, it is working fine but I am not sure that I see the benefit of this. The web says JWT is good because it's lightweight and can be used to verify that the source of data but in my implementation:
The client first provides a username and password to authenticate
If user + pwd is ok the a token is returned and every subsequent call to the api uses that jwt token (instead of the username and password) to authenticate.
This is fine but why not just use the username + password on every call to the api (and skip the complication of managing the token)?
In fact in my case there's additional complications because I now have to factor in an expiry date (of the token) that resides outside of my system.
Can someone explain what I'm missing here?
One of the main benefits and motivations for using JWT is that it allows your server side application to push all session state information outside of the application. That is, in a theoretical limit, a JWT implementation is actually stateless.
To directly answer your question, we can compare the workflows for what happens when username/password is submitted in every request versus submitting a JWT.
First, a JWT contains a claims section, which is typically written by the issuer of the token, i.e. the server side application. One of the fields is called exp, and contains the expiry time of the token. One property of JWT is that it is not possible for the user to tamper with them. This is enforced via a checksum, which would change if any part of the JWT changes. Taken together, this means that the user cannot alter the expiry time (or any other claim), and the server can implicitly trust this time. When the user submits a request with a JWT, in theory all the server has to do is just check exp to see if the token still be valid. That is, the session state actually lives outside the application, at least in theory.
In contrast, when the user submits a username/password each time, the server has no way of knowing what to do just based on that information. Rather, the server has to maintain the session state itself, and this can be costly both in terms of memory and performance.
In practice, JWT is never completely stateless, but, using a good implementation, it is usually possible to get the memory footprint very small, requiring only a bit of space in a cache (e.g. Redis or a similar tool).