I'm using JWTs for authenticating users for my app. When a user logs in they are given an access token and a refresh token. To keep the refresh token safe, I don't store it on the client-side, but save it on the back-end with their account so it's not easy to access. I'm confused about the security of refresh tokens though, here's the logic that I'm understanding when I read online resources on how to use refresh tokens:
- authenticate
- store access token + refresh token somewhere (in my case, access token on the front-end and refresh token on the back-end)
- when performing an api request, validate the access token on the api side
- if the access token is expired, use the refresh token to generate a new access token + new refresh token, send access token back to client
- store tokens as before... and repeat
The security issue I'm worried about is if someone else (hacker) got a hold of the access token and they send a request to the api with it, if the token is expired the api will use the refresh token to get a new access token + new refresh token and return at least the access token to the hacker.
I read this article about 5-6 times and I read this article a few times, as well as some other articles on the subject, they all say something along the lines of
make sure to store the refresh token securely because it's long lived, the access_token is short lived so not as big of a deal
But according to the flow I described above, it doesn't matter if the access token is short lived, the refresh token will be used to get a new access token and have access forever.
Is there something I'm missing? How would the api know who is sending the request if a hacker got a hold of the expired access token? it will still send a new one using the refresh token. Am I supposed to somehow validate who is sending the request?
UPDATE
So I do understand that when a new access token is requested, I need to send over the refresh token, the client ID, and the client secret. The issue I have with that is, like before, the hacker can send a request to my API server, the server gets the hijacked access token from the hacker, it will see that it's expired, so it will send the refresh token, along with the clientID/client secret (which are stored as environment variables) to the Auth API and get back a new access token / refresh token, which brings us back to the same issue.
UPDATE 2
some interesting questions on the subject:
- Why Does OAuth v2 Have Both Access and Refresh Tokens?
- https://security.stackexchange.com/questions/87119/how-secure-are-expiring-tokens-and-refresh-tokens
according to the second question and answer, it seems like the refresh token is not a more secure way to maintain access, it's just that it's easier to detect a hacker because auth/refresh tokens keep getting requested and invalidating the other's tokens. The issue with this is this will only happen if 2 users are simultaneously trying to access resources - if only the hacker happens to be active at a given time period, he will have unlimited access to the original users data until the original user tries to use the app and access protected resources
Basically, it seems like a bad idea to use refresh tokens with browser based apps. Refresh tokens are meant for mobile apps where the refresh token can be stored securely on the phone - phones have some sort of secure storage mechanism, whereas browsers do not.
There is a good document OAuth 2.0 for Browser-Based Apps which discusses best practices for these applications.
I would choose between keeping tokens on client or server. Mixing it (keeping refresh tokens on your server and access tokens in browser), you create your own protocol with its own vulnerabilities.
If the browser application needs the access token just to access its backend, you could consider using your backend as an OAuth2 client (which receives an auth code), get the user's indentity, issue a cookie which will maintain a session between the browser and the backend. It's much easier than exchanging, refreshing and validating OAuth2 tokens.
If you really want to keep your browser application as an OAuth2 client which receives tokens, you should use PKCE extension (so the auth code kept in network caches and browser history cannot be used to get tokens) and get a new refresh token with each new access token - take a look at the chapter about refresh tokens:
Authorization servers SHOULD NOT issue refresh tokens to browser-based applications.
If an authorization server does choose to issue refresh tokens to browser-based applications, then it MUST issue a new refresh
token with every access token refresh response. Doing this
mitigates the risk of a leaked refresh token, as a leaked refresh
token can be detected if both the attacker and the legitimate
client attempt to use the same refresh token.
Your browser application can keep its tokens in sessionStorage
to survive page reloads.
You shouldn't store the token on the server. A client authenticates and gets the token. You store the token in the browsers in a cookie or localStorage. Each request is authorised with the token. If you send it over an unencrypted channel without ssl it is open to being intercepted. A hacker getting the token does allow them to impersonate the user. Expired tokens should not allow reauthentication without reentering the users credentials again. Expired tokens should be ignored.
In the second article you linked it is sayed that to refresh token you have to post the refresh token and client_id and client_secret so basically you re-authenticate user when you refresh the access token.
To use the refresh token, make a POST request to the service’s token endpoint with grant_type=refresh_token, and include the refresh token as well as the client credentials.
If you are storing the refresh token on the server, your server should include a secure session cookie in the authentication response to identify the user. You can prevent attackers from extracting secure session cookies by setting the cookies with the HttpOnly
flag.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies
The cookie wouldn't be a refresh token. It would be some other kind of session cookie. One application flow in which a refresh token is not returned to the user is a single-page application flow.
https://auth0.com/docs/flows/concepts/single-page-login-flow
In this flow, token refreshing is done through "Silent Authentication."
https://auth0.com/docs/api-auth/tutorials/silent-authentication#initiate-a-silent-authentication-request
A successful authentication response if the user already has a valid session in Auth0 and no consent or other prompts are needed.
So we need to maintain a session by storing some user identifier.
The access token & refresh token are meant to be used is as follows:
- Generate expiring access & refresh token on user login and send to front-end app ( Android, IOS, Web App).
- Front-end App securely stored refresh token in its db.
- Front-end App sends access token with every request and JWT verifies it
without hitting database.
- Authentication works for defined time of access token.
- When it expires, Front-end app sends refresh token to your server,
additionally you verify it using JWT and also check it in database
for equality.
- Server generates new access token and so on.
PS: Whole communication should take place over HTTPS.
I have my implementation based on above logic with access token expiring every 30 mins and refresh token with a year validity.
Also the thing with verification of refresh token with database is that you have control over user login process and you can limit the number of devices able to use your apps with same account.
You simply need to update refresh token on server whenever user sends a login request again.