Search code examples
flaskjwtbackendflask-restful

Access and refresh token implementation on backend side, security and performance concerns



I have implemented jwt access_token and refresh token authorithation system on Flask backend where for each protected URI, I have a decorator @login_required that checks whether the access_token is available inside the HTTP Authorization header as bearer, and whether the refresh_token is available as an HTTP-only cookie.:
if auth_header:
        access_token = auth_header.split(" ")[1]
        # Get refresh token from httponly cookie
        refresh_token = request.cookies.get('refresh_token')
        if not access_token or not refresh_token:
            return jsonify({'message': 'Tokens are missing'}), 400

Once I have confirmed that both tokens are present, I decode the JWT access token and extract the user_id and access_level. If decoding fails due to expiration, the access_token is automatically refreshed, but only if the refresh_token has not expired as well

     try:
            # Decode access token and get user ID and access level
            payload = jwt.decode(access_token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
            user_id = payload['sub']
            access_level = payload['role']
        except jwt.ExpiredSignatureError:
            # If access token is expired, try to refresh it
            try:
                token = Account.verify_refresh_token(refresh_token)
                if not token:
                    return jsonify({'error': 'Refresh token expired'}), 401
                user = db_session.query(Account).options(selectinload(Account.token)).filter_by(token=token).first()
                access_token = user.generate_access_token()
                resp = make_response(jsonify({'message': 'Access token refreshed'}), 202)
                resp.headers['Authorization'] = f'Bearer {access_token}'
                resp.set_cookie('refresh_token', token.refresh_token, samesite='lax', httponly=True)
                return resp
            except Exception as e:
                return jsonify({'error': str(e)}), 401
        except jwt.InvalidTokenError:
            return jsonify({'error': 'Invalid access token'}), 401
        return f(user_id=user_id, access_level=access_level, *args, **kwargs)

I have read several articles about token authentication, and most of them suggest that when the access_token expires and the backend responds with an authorization error code, the frontend should send another request to the /refresh URI on the backend to obtain a new access_token. The frontend can then use the new access_token to try to access the protected URI again.
Please advise which approach is more secure and provides smoother performance, and also point out any potential issues with my approach.


Solution

  • The standard approach is to send only the jwt and not the refresh one. This is also the most correct method since, jwt, is specifically introduced to not send credentials within each http request to make communication more secure.

    if with the jwt we also send the refresh token with each request, in case someone manages to steal the contents of the http packets directed to your server they would be able to take possession of both the authentication token and the login token, thus allowing him to be able to authenticate himself at any time.

    If, on the other hand, you send only the jwt with each request and someone steals the information, they will only be able to register until the jwt expires.