Token Authentication and Refreshing using Flask-JWT-Extended
In a previous blog post, we talked about a Flask extension, Flask-JWT
, which allows us to create JWTs (JSON Web Tokens) in our Flask apps. Flask-JWT is handy and provides a minimal set of features we would need for token based authentication. However, as our app grows more complex, we may find it a little bit restricting.
Read on for a more powerful alternative!
Introduction
In this post we introduce a new Flask extension: Flask-JWT-Extended
. It has a more advanced set of features and enables us to design a more practical authentication workflow.
Flask-JWT-Extended
has many advantages compared to Flask-JWT
. For example, it supports token refreshing, which could result in a much more practical and user-friendly authentication workflow. It also has a much more active community that maintains and upgrades the project, so it is more likely to introduce new features and remain stable. We will be focusing on the authentication workflow in this post.
A recommended authentication workflow
Token based authentication
Very much like in Flask-JWT
, we can perform a token-based authentication using Flask-JWT-Extended
. The user gets authenticated and their info gets encrypted and returned as an access token (JWT).
Whenever the user wants to tell us who they are, they send the access token along with their request. The access token gives us some level of trust on the user's identity, and improved security since all the user's info is encrypted and unlikely to be compromised.
Token refreshing
For security purposes, each access token must have an expiration time. Normally this is set to between 5 and 15 minutes, after which the user must re-authenticate.
In Flask-JWT
, the re-authentication would require the user to enter their username and password again. That can be very tedious for users!
Token refreshing serves exactly this purpose. When authenticating via credentials the first time, we not only return an access token that contains the user's account info—we also return a refresh token that only serves to refresh the access token.
When an access token has expired we provide the refresh token, and Flask-JWT-Extended
verifies it and returns a new, valid access token. That way the user can keep using that access token for accessing the protected services.
This process repeats every time the original access token expires... So does this mean the user never has to enter their credentials again?
Token Freshness
Given the above description, one may ask what is the difference between using the token refreshing workflow and having an access token that never expires?
It is true that it does not make a difference if we simply allow token refreshing with no further restrictions. However, there is a solution to make our authentication workflow more robust.
Here's a common use case: once we've signed in, we are normally able to continue using an app without entering credentials. However, if we try to perform a "critical" operation—such deleting some data or completing a bank transaction—we are often asked for the credentials (or at least a password).
How does that work?
Enter, token freshness. As a rule of thumb, any access token acquired via credentials is marked as fresh
, while access tokens acquired via the refresh mechanism are marked as non-fresh
.
Going back to our previous authentication workflow, the first time a user logs in with his credentials, he would get a fresh
access token and a refresh token. If he tries to log back in and finds out his current access token has expired, he uses his refresh token to get a new access token. This new access token would be non-fresh
, since it's not acquired with credentials.
We still have some trust in the user when he presents the non-fresh
access token. However, when he tries to perform a critical action, such as a transaction, the application would not accept this access token. Instead, he would need to enter his credentials again and get a new fresh
access token to proceed with the critical action.
Let's have a look at some code.
Flask-JWT-Extended in action
Authentication
A code snippet for the user login endpoint is shown below using class-based views from Flask-RESTful
. If you've taken our REST API course, this will look very familiar.
from models.user import UserModel
from flask_restful import Resource, reqparse
from flask_jwt_extended import (
create_access_token,
create_refresh_token
)
from werkzeug.security import safe_str_cmp
class UserLogin(Resource):
# defining the request parser and expected arguments in the request
parser = reqparse.RequestParser()
parser.add_argument('username',
type=str,
required=True,
help="This field cannot be blank."
)
parser.add_argument('password',
type=str,
required=True,
help="This field cannot be blank."
)
def post(self):
data = self.parser.parse_args()
# read from database to find the user and then check the password
user = UserModel.find_by_username(data['username'])
if user and safe_str_cmp(user.password, data['password']):
# when authenticated, return a fresh access token and a refresh token
access_token = create_access_token(identity=user.id, fresh=True)
refresh_token = create_refresh_token(user.id)
return {
'access_token': access_token,
'refresh_token': refresh_token
}, 200
return {"message": "Invalid Credentials!"}, 401
Token refreshing
The following code snippet shows how the token refreshing endpoint works.
class TokenRefresh(Resource):
@jwt_refresh_token_required
def post(self):
# retrive the user's identity from the refresh token using a Flask-JWT-Extended built-in method
current_user = get_jwt_identity()
# return a non-fresh token for the user
new_token = create_access_token(identity=current_user, fresh=False)
return {'access_token': new_token}, 200
Endpoints decorated with @jwt_refresh_token_required
require that an Authorization: Bearer {refresh_token}
header is included in the request.
Define different protection levels using token freshness
We can then protect our endpoints and define different protection levels like this:
# An endpoint that requires a valid access token (non-expired, either fresh or non-fresh)
@jwt_required
def get(self):
pass
# An endpoint that requires a valid fresh access token (non-expired and fresh only)
@fresh_jwt_required
def post(self):
pass
Acknowledgements
Flask-JWT-Extended is an open source python project maintained by an active and awesome community. You may refer to the original documentations as well as source code on their GitHub repo: https://github.com/vimalloc/flask-jwt-extended.
Want to learn much more about developing REST APIs with authentication, data storage, relational databases, and deployments? Check out our flagship course!