2

I'm creating a backend that supports authentication with JWT tokens. I'm using the classic access token / refresh token combo.

  • the access token is valid for 5 minutes and allows the users to perform some actions. It's not checked against the database, it's valid until it expires
  • the refresh token is valid 1 week and can only be used to get a new access token

I'm enquiring about best practices here, when it comes to getting a new access token. As of now, I have a middleware on the backend side. The middleware checks the access token in the header of each request:

  • if the access token is still valid, allow the request
  • if the access token is expired, fetch the refresh token from a cookie (by the way, when the backend tries to access a cookie stored on the browser side, does it result in an additional query?). The refresh token is then checked against the database
  • if a new access token was issued, it's returned to the client for subsequent queries

The advantages of the setup above IMO is that everything happens in one query.

Now, I'm basically wondering if:

  • accessing the refresh token in the cookie results in an additional query?
  • If I'm moving to another kind of client (like Flutter on Android), is it ok (in terms of security, best practices, etc) to send the refresh token in the header of each request?

I saw some examples where people send the refresh token only when they get 401 back from the backend (e.g: Flutter: How to Refresh token when token expires during http call?), but that means two queries.

JPFrancoia
  • 4,866
  • 10
  • 43
  • 73
  • Browsers send the cookies with the request based on the domain and other settings (SameSite, Secure, HttpOnly etc.). So it is not another request. IMO the refresh token should be sent only on HTTP response code 401. This way you will have two requests if the token has expired but that is considered to be normal. – Peter Koltai Apr 26 '23 at 17:54
  • Thanks for the answer. Could you detail the rationale behind "it is considered normal"? From a purely objective point of view, it's 2 requests instead of one – JPFrancoia Apr 28 '23 at 14:05
  • In many cases the access token will be valid and the request will return a valid response. If the access token expired and the refresh token is sent, the next requests for example within an hour will be authenticated. So it's only two requests if the the access token expired. – Peter Koltai Apr 28 '23 at 14:30
  • Access tokens are usually valid for more than five minutes, for example with Firebase Auth it is 1 hour. So for a normal user session you typically need the refresh the access token on the first request and your user is authenticated for the current session. – Peter Koltai Apr 28 '23 at 14:31
  • I am not a JWT expert but that's what I've seen in practice in many examples: use the access / id token, and get a new one with the refresh token if it is expired. – Peter Koltai Apr 28 '23 at 14:32

1 Answers1

1

It's a design decision, there's not a "best way" and there are different "good practices". Auth0 has a lot of documentation and posts about jwt and jwt auth workflow design, if you want to check it out. Anyway, this is how I usually do it:

  1. I don't send the refresh token on every request (a man in the middle attack will have more chances of grabbing your refresh token).
  2. I only send access tokens with every request. If its not valid, throw 401.
  3. Then I have a /refresh endpoint where you need to send the refresh token + current access token to get a new access token

This means that the workflow will go like this:

  1. Browser sends request with access token
  2. Server checks the access token, it's ok, go ahead
  3. Browser sends request with access token (req 1)
  4. Server checks the access token, it's expired or invalid, throw 401
  5. Browser calls /refresh, gets a new access token (req 2)
  6. Browser sends request with the new access token (req 3)
  7. Server checks the access token, it's ok, go ahead

But this isn't ideal, we need to perform 3 requests when the access token has expired.

To avoid this, in the api consumer, I would usually implement some sort of interceptor before calling every protected route of the API. I check the exp in the browser before every request to the API, if the token has expired I do a /refresh and avoid the obvious 401. That means that we only need 2 requests once in a while.

Also, I usually implement a Refresh Token Rotation technique, depending on the project.

Lucas Colombo
  • 513
  • 2
  • 5
  • 17
  • 1
    Yeah that's more or less what I ended up doing. Thanks, I'll accept this answer as it explains the "why" and provides a couple of solutions. – JPFrancoia May 22 '23 at 13:48