Why does using JWT refresh tokens protect against CSRF during authentication? - security

I have read a few articles regarding JWT refresh tokens, and how/why they are used. One thing i have seen mentioned here: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/#persistance and here: https://dev.to/cotter/localstorage-vs-cookies-all-you-need-to-know-about-storing-jwt-tokens-securely-in-the-front-end-15id
is that using refresh tokens mitigates against CSRF attacks. The first article states:
The refresh token is sent by the auth server to the client as an HttpOnly cookie and is automatically sent by the browser in a /refresh_token API call.
Because client side Javascript can't read or steal an HttpOnly cookie, this is a little better at mitigating XSS than persisting it as a normal cookie or in localstorage.
This approach is also safe from CSRF attacks, because even though a form submit attack can make a /refresh_token API call, the attacker cannot get the new JWT token value that is returned.
The second article says something similar:
Although a form submit to /refresh_token will work and a new access token will be returned, the attacker can't read the response if they're using an HTML form
I am struggling to see how this would prevent CSRF attacks as I am thinking the following:
A request to /refresh token from another domain to the users will return new JWT token to the user. I am going to assume this is stored in a HttpOnly cookie (as is done in the first article)
As CSRF does not involve any injection of javascript and the cookie it httpOnly, the attacker can't read the value of the new JWT token.
However, if the JWT token is stored in a cookie again, surely a CSRF attacker can just send another request using this new cookie, with the new JWT token sinde?
If my understanding is correct, I am struggling to see how CSRF attacks are prevented by using refresh tokens. Can someone please explain exactly why refresh tokens prevent CSRF attacks, and why the CSRF attacker can't just use the new JWT the user would receive for future attacks?
It seems to me that the thing that would actually be preventing a CSRF attack would be the use of a sameSite cookie, or maybe using some sort of anti-forgery token.

The new jwt would not be returned from the identity provider as a cookie. That would not make much sense as the client on a different origin would not be able to read it. Instead, the token is passed in the response body, or even the url (usually not the token in that case, but let's not delve into that).
So the idp has its httpOnly cookie to authenticate the user, issues a new token in some way that is not a cookie, and the client stores the token for the appropriate origin (not the idp) in say localstorage. This way, any call to the resource server is not vulnerable to csrf, because the token needs to be explicitly be added from localstorage. The idp can be called by attacker.com to issue a new token ("csrf"), but attacker.com will not have access to the token due to the same origin policy, so it's not exploitable.
Note that even if the new token is returned as a cookie for the idp and read from there by the client, it's still ok, because the idp will do nothing with that token, and the resource server (~api) will not receive it automatically.

Related

Token rotation refresh implementation how to persist token?

I have implemented all of the code to support token rotation/ refresh with one last hitch. How are we supposed to persist the token on the frontend to make authenticated backend calls beyond our login?
My Current workflow goes as follows:
User logs in with email and password and is returned the refresh token and the access token in an HTTP-only secure cookie.
A MongoDB (body) document along with the refresh Token and Access Token in HTTP-only, secure cookies is returned with the response.
Now moving on from here how can we persist using these cookies? Especially since we cannot access these. I am using HTTP-only cookies as that is what is recommended as it is most secure but I am struggling to see how it is possible with only HTTP cookies.
In case anyone stumbles across this. HTTP cookies persist by default. They were not persisting in my case as I had the secure property set to true and I was not over an https connection.

If the JWT token for auth is saved in the HTTP-Only cookie, how do you read it from the cookie so that I can include it in request header?

I am building a Node web app using JWT for user auth.
My server sends JWT in HTTP-Only cookie via 'Set-Cookie' when user submits correct id and password, but I am stuck on how to access that JWT stored within the cookie so that I can include it in the Authorization header when making authorized API requests.
How can I access the HTTP-Only cookie from client-side to include it when sending API request to server?
Or, would it be safe to just not use the cookie at all by having the server send the JWT in response body? So that I use the JWT by putting it in a client-side variable? That way, I believe the variable is only alive until the user closes the browser.
I have looked for many resources, but was not able to find clear answer to this issue that I am stuck with.
While there are numerous ways to solve this problem, I will suggest the method where the client sends the JWT twice on each request: in an HttpOnly cookie, and also an an Authentication: header.
Let's examine what security issue each is solving:
HttpOnly Cookie
From the Mozilla documentation:
To help mitigate cross-site scripting (XSS) attacks, HttpOnly cookies are inaccessible to JavaScript's Document.cookie API; they are only sent to the server.
This is to mitigate cross-site scripting risk; imagine your website has an XSS vuln -- for example I can put <script>some_code{..}</script> into a comment, and any user who views my comment will have my code running in their browser. Yes, the attacker's code is running inside the victim's logged-in browser, but because of the HttpOnly flag, it can't extract the session cookie and send it to the attacker.
Authentication header
The problem with cookie authentication is that the browser will automatically attach them to any request to the domain that the cookie belongs to, this allows cross-site request forgery attacks. To prevent this, the usual method is to send the session token (in your case a JWT) to the client in a method other than a cookie, ie in a different header, or maybe in a hidden HTML field.
If the client is able to echo the JWT token back to you on their next request, that means they were able to read the previous response, and are not an attacker doing a blind injection CSRF attack.
Overall Suggestion
Combine the two!
The two methods serve different purposes: HttpOnly cookies protect against XSS attacks while Authentication headers protect against CSRF attacks.
There are many ways to combine them, but they all boil down to putting the JWT in some sort of authentication header, and putting a sessionID in the cookie, and having the server check that these belong to the same session. Important: remember that an attacker who achieves XSS on your site will be able to read the JWT, so for the cookie to be doing its job, the cookie should be a separate value that is not contained in the JWT. (ie if the attacker can figure out the right cookie value by looking at the JWT, then the cookie is not providing any security).

Is there any benefit for storing JWT in both a cookie and local storage?

I'm attempting write an authentication system for a node.js api using express. I've noticed that if I am going to use a JWT for authentication tokens, I have two options...
1.) Store the token in a cookie, and add CSRF protection.
2.) Have the client send the token in the Auth Header and add XSS protection.
My question is, is there any benefit to storing the auth token in a cookie, and having the client send it in the Auth Header for authentication? This way if for some reason the CSRF protection fell through, the request would fail if there was no authentication token in the header. Also, if XSS protection fell through, the request would still require the auth token in a cookie. I guess my thought is that this would provide more protection, and the only way it could fail is through a successful XSS attack followed by a successful CSRF attack.
Follow up question: Are CSRF tokens a full proof protection technique against CSRF attacks?
Firstly I recommend that you go through this answer first. I hope I've bern able to address your queries about CSRF and XSS here and why and how we should use cookie.
Secondly, your approach of using localstorage along with cookie is good. The only problem I see is localstorage cannot be used across sub domains. If you use cookie and set the cookie domain as example.com (replace example with your organization domain), it will be valid across all sub domains. Thus a user authenticated by your authorization server can seamlessly login to app1.example.com and app2.example.com. You won't be able to do this with localstorage.

What is the workflow for validating a refresh token and issuing a new bearer token?

This is not a coding question, but a conceptual question for the correct handling and processing of a refresh token.
I have a single page app which issues a jwt token when logging in. The token works just fine. Now, I want to set the expiration to a low value, and use a refresh token to refresh the bearer token.
The question is, what claims should be stored in the refresh token? And what steps are supposed to be done to validate the refresh token before issuing a new token?
For example, right now my refresh token is a jwt which stores an expiration, so the client knows when the refresh token expires, and a username claim so that I know what user the refresh token is associated with.
So then when the refresh token is recieved:
Check that it is not expired.
Check that it has not been revoked.
Use the UserName in the refresh token to issue a new short-lived bearer token.
Is this the correct workflow for this? I just want to make sure I am not missing any security checks.
If your application is a single page application, you should not use long lived refresh tokens as you have no way of securely storing them.
OAuth2 defines a number of grant flows for different types of clients (which I've described here). Refresh tokens are only meant for confidential clients (like web applications living on a secured server).
Your refresh token is just as vulnerable to theft as your access token, since both are bearer tokens stored on the client.
Some OAuth libraries allow SPA or other non-confidential clients to get a new access token by talking to the token endpoint of the authorization server using a session token in a cookie. As long as the cookie is valid, the user can get a new access token. After that, the user will need to re-authenticate. Of course cookies can be marked secure and http only, making them harder to steal.
If you issue the JWT tokens from the same service endpoint that consumes the access tokens, you could have the client include a nonce in the token request that you hash and include as a claim in the token. The client can send the JWT in the Authorization header and the nonce in a custom header. Your token verification would hash the nonce again and compare it to the claim in the JWT. That way, if your token is stolen is harder to use without the nonce value. Of course, in a targeted attack, your nonce could also be stolen.

JWT Refresh Token

A common scenario for user authentication follows these steps:
User registers and logs in using its credentials (username/password)
The server verifies the user's credentials and, if valid, returns an access token and a refresh token
The access token is sent on further requests and, if it is valid, the server responds with the requested resource
When the access token is no longer valid, the server requests the client to provide a refresh token in order to issue a new access token
The server receives the refresh token and two things may happen:
if the refresh token is valid, a new access token is issued to the client
if not, the server requests the user to authenticate
For the client to be able to send the access token in every request, the token should be stored either on browser storage (local/session storage) or cookies, which makes it vulnerable to XSS (Cross-Site Scripting) attacks. This problem may be mitigated if we set a short lifetime to the access token.
My question, however, is regarding to the refresh token. This token should have a long lifetime and since we use it to get new access tokens, it would be a problem if an attacker would intercept it. So storing this token on the client side might not be a good idea, right?
But, what are the alternatives?
Can we store it in a cookie set with the "httpOnly" flag? But wouldn't this make it vulnerable to CSRF (Cross-Site Request Forgery) attacks?
Is it safe to encrypt the token and still save it on browser storage?
Thank you in advance. Best regards!
which makes it vulnerable to XSS (Cross-Site Scripting) attacks.
To clarify, the cookie is only vulnerable to a XSS attack should there be a vulnerability on your site that makes this possible.
Can we store it in a cookie set with the "httpOnly" flag? But wouldn't
this make it vulnerable to CSRF (Cross-Site Request Forgery) attacks?
Although the httpOnly flag cannot be used in some forms of CSRF protection due to the need for it to be accessed client-side, flagging a cookie as httpOnly does not increase the risk to your system in any way - httpOnly is more restrictive.
Is it safe to encrypt the token and still save it on browser storage?
Not really because anyone or anything that can access browser storage can access the cookie value and present it. It doesn't matter what form it takes - encrypted or not - if it can be used directly without an external key. Don't worry about this too much - put trust in the browser to keep this secure, however ensure the rest of your site is as secure as possible.
You are right that the refresh token is viewed as more sensitive than the access token. You can store this in a cookie, however make sure that it is set to httpOnly and has the secure flag set too to ensure it is only transmitted over encrypted HTTPS connections.

Resources