9

I'm trying to wrap my head around JWT (JSON Web Token) based authentication for securing API endpoints using access tokens. I'm protecting these endpoints by requiring the user to have a valid access token in order to obtain said resources.

I'm struggling, however, understanding the use a refresh tokens.

I have a /auth/login endpoint which accepts POST requests:

class UserLogin(Resource):
    def post(self):
        data = parser.parse_args()
        current_user = User.find_by_username(data['username'])
        if not current_user:
            return {'message': 'Unsuccessful. You do not have an account'}

        if User.verify_hash(data['password'], current_user.password):
            access_token = create_access_token(current_user)
            refresh_token = create_refresh_token(current_user)
            return {
                'message': 'Login successful {}'.format(current_user.username),
                'roles': 'Role: {}'.format(current_user.role),
                'access token': access_token,
                'refresh token': refresh_token
            }
        else:
            return {'message': 'Something went wrong'}

The end point essentially checks the incoming credentials to ensure that the user exists in my User store. If they do, I'll return an access and refresh token. Otherwise, an error message to be handled client side.

Now, assume that the user has logged in and tries accessing a private resource from another end point secured by requiring an access token. The client will make a request and the protected end point will return some error message stating they do not have an access token. The client will then attempt to generate a new access token using the refresh token obtained on login:

class TokenRefresh(Resource):
    @jwt_refresh_token_required
    def post(self):
        current_user = get_jwt_identity()
        access_token = create_access_token(identity=current_user)
        return {'access token': access_token}

Hitting the above resource will generate a new access token with some defined expiration time. This is my current flow.

My question is, how should I store refresh tokens to ensure they are being accessed by the authenticated user? Should I have some additional logic in my login requests which store the generated refresh token to that specific user?

If so, should I then generate a new refresh token and overwrite the existing refresh token attached to the user each time the client tries to refresh their access token?

At present, I feel my process can be compromised if an attacker manages to obtain the refresh token.

7

The point of having access tokens is that they can be used without checking for invalidation. You can have 10000 frontend servers users can access with the token without the need to ever ask some database if it is invalid. But after some time, the token expires. The user needs a new access token, sends her refresh token and this refresh token is checked in some database. You never need to check for expired access tokens or have any state, but limit abuse to the lifetime of the token.

If you don't have the requirement to accept the tokens without checking expiration in a database, you don't need the two different tokens. You can just use the refresh token for each access.

Example workflow would be:

  1. User logs in, gets access and refresh token. Access token lifetime 15min, refresh token 5 days.

  2. User accesses the service using the access token. Service only checks signature and lifetime. No database connection.

  3. User logs out, the refresh token is marked expired in the database
  4. User accesses the service using the access token, this still works
  5. 15min pass. Access token expires, user requests a new access token using the refresh token still within its lifetime. The service checks the database and finds the token is expired. User can't get a new access token.

The whole system is a trade-off between the time it takes to invalidate sessions and the amount of connections to a shared/synchronized data source required.

You can replace the refresh token on each refresh, but remember that you need to store all expired refresh tokens until their lifetime is over.

From a security perspective it makes sense to create a new token, but it is a trade off between security and amount of data in your database.

In the worst case (no lifetime for refresh tokens, never do that) you now need to store one token every few minutes in the database for each user instead of one token for each user and you can never remove them again.

2

Refresh tokens are one of those technologies where the practice and the theory don't match, in my experience. In theory, you make a login request, and get back an access token (with a short lifetime) and a refresh token (which has either a long expiry period, no expiry, and can be used to get a new access token at any point). If the client tries to send an expired access token, and gets a rejection from the server, it can send the refresh token, get a new access token, then continue.

In this case, it is very clear that the refresh token is really powerful, and needs to be stored carefully (e.g. in credential storage on a mobile device, rather than a browser cookie, or in a carefully secured server side store). However, the specifications do allow for a refresh token to be invalidated by the authentication server.

In practice, therefore, it's not uncommon for a refresh token to be issued on login, and invalidated on logout or after a period of inactivity. In this case, it can be part of the session data on the server, and hence automatically linked to the current user. Any other user would only be able to use the refresh token from within the authenticated session, meaning that any logic linking it to a specific user would also fail.

From the code snippet above, this is pretty much what you have: the refresh token is generated each login, rather than being retrieved from storage after being generated at account creation, for instance. The only part which is missing is in how the refresh token is queried - it is implied that the refresh token is considered sufficient to get an access token, rather than the combination of an authenticated session and a refresh token. This is, as you suspect, insecure.

The refresh token provides authorization to obtain a new access token, but does not authenticate that the person requesting the access token is the one who should have access. You need to provide the authentication step before accepting the authorization, and ensure this is used every time the refresh token is used - an open session may be sufficient.

You can choose to replace the refresh token on every new access token. This has a side benefit of breaking fairly obviously if multiple users attempt to use the refresh token (the second one fails) hence limiting concurrent use. Avoid issuing new refresh tokens without expiring the old one, however, since this increases the potential for token compromise. It is probably of limited benefit in the case where the refresh token expires with the session (assuming a short session lifetime), but can help with longer sessions (e.g. "remember me" functions).

  • many great insights, thank you! – B12Toaster Jul 1 at 16:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.