Refreshing Tokens¶
In most web applications, it would not be ideal if a user was logged out in the middle of doing something because their JWT expired. Unfortunately we can’t just change the expires time on a JWT on each request, as once a JWT is created it cannot be modified. Lets take a look at some options for solving this problem by refreshing JWTs.
Explicit Refreshing With Refresh Tokens¶
Alternatively, this extension comes out of the box with refresh token support. A refresh token is a long lived JWT that can only be used to creating new access tokens.
You have a couple choices about how to utilize a refresh token. You could store the expires time of your access token on your frontend, and each time you make an API request first check if the current access token is near or already expired, and refresh it as needed. This approach is pretty simple and will work fine in most cases, but do be aware that if your frontend has a clock that is significantly off, you might run into issues.
An alternative approach involves making an API request with your access token and then checking the result to see if it worked. If the result of the request is an error message saying that your token is expired, use the refresh token to generate a new access token and redo the request with the new token. This approach will work regardless of the clock on your frontend, but it does require having some potentially more complicated logic.
Using refresh tokens is our recommended approach when your frontend is not a website (mobile, api only, etc).
from datetime import timedelta
from flask import Flask
from flask import jsonify
from flask_jwt_extended import create_access_token
from flask_jwt_extended import create_refresh_token
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
jwt = JWTManager(app)
@app.route("/login", methods=["POST"])
def login():
access_token = create_access_token(identity="example_user")
refresh_token = create_refresh_token(identity="example_user")
return jsonify(access_token=access_token, refresh_token=refresh_token)
# We are using the `refresh=True` options in jwt_required to only allow
# refresh tokens to access this route.
@app.route("/refresh", methods=["POST"])
@jwt_required(refresh=True)
def refresh():
identity = get_jwt_identity()
access_token = create_access_token(identity=identity)
return jsonify(access_token=access_token)
@app.route("/protected", methods=["GET"])
@jwt_required()
def protected():
return jsonify(foo="bar")
if __name__ == "__main__":
app.run()
Making a request with a refresh token looks just like making a request with an access token. Here is an example using HTTPie.
$ http POST :5000/refresh Authorization:"Bearer $REFRESH_TOKEN"
Warning
Note that when an access token is invalidated (e.g. logging a user out), any corresponding refresh token(s) must be revoked too. See Revoking Refresh Tokens for details on how to handle this.
Token Freshness Pattern¶
The token freshness pattern is a very simple idea. Every time a user authenticates
by providing a username and password, they receive a fresh
access token that
can access any route. But after some time, that token should no longer be considered
fresh
, and some critical or dangerous routes will be blocked until the user
verifies their password again. All other routes will still work normally for
the user even though their token is no longer fresh
. As an example, we might
not allow users to change their email address unless they have a fresh
token,
but we do allow them use the rest of our Flask application normally.
The token freshness pattern is built into this extension, and works seamlessly with both token refreshing strategies discussed above. Lets take a look at this with the explicit refresh example (it will look basically same in the implicit refresh example).
from datetime import timedelta
from flask import Flask
from flask import jsonify
from flask import request
from flask_jwt_extended import create_access_token
from flask_jwt_extended import create_refresh_token
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
jwt = JWTManager(app)
# We verify the users password here, so we are returning a fresh access token
@app.route("/login", methods=["POST"])
def login():
username = request.json.get("username", None)
password = request.json.get("password", None)
if username != "test" or password != "test":
return jsonify({"msg": "Bad username or password"}), 401
access_token = create_access_token(identity="example_user", fresh=True)
refresh_token = create_refresh_token(identity="example_user")
return jsonify(access_token=access_token, refresh_token=refresh_token)
# If we are refreshing a token here we have not verified the users password in
# a while, so mark the newly created access token as not fresh
@app.route("/refresh", methods=["POST"])
@jwt_required(refresh=True)
def refresh():
identity = get_jwt_identity()
access_token = create_access_token(identity=identity, fresh=False)
return jsonify(access_token=access_token)
# Only allow fresh JWTs to access this route with the `fresh=True` arguement.
@app.route("/protected", methods=["GET"])
@jwt_required(fresh=True)
def protected():
return jsonify(foo="bar")
if __name__ == "__main__":
app.run()
We also support marking a token as fresh for a given amount of time after it
is created. You can do this by passing a datetime.timedelta
to the fresh
option when creating JWTs:
create_access_token(identity, fresh=datetime.timedelta(minutes=15))