Skip to content

Commit e55277f

Browse files
author
Landon Gilbert-Bland
committed
Documentation around implict token refreshing with cookies
1 parent efa7dd1 commit e55277f

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Flask-JWT-Extended's Documentation
4040
add_custom_data_claims
4141
optional_endpoints
4242
token_locations
43+
refreshing_tokens
4344
custom_decorators
4445
refresh_tokens
4546
token_freshness

docs/refreshing_tokens.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Refreshing Tokens
2+
=================
3+
In most web applications, it would not be ideal if a user was logged out in the
4+
middle of doing something because their JWT expired. Unfortunately we can't just
5+
change the expires time on a JWT on each request, as once a JWT is created it
6+
cannot be modified, only replaced with a new JWT. Lets take a look at how we can
7+
simulate these behaviors.
8+
9+
Implicit Refreshing With Cookies
10+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11+
One huge benefit to storing your JWTs in cookies (when your frontend is a website)
12+
is that the frontend does not have to handle any logic when it comes to refreshing
13+
a token. It can all happen implicitly with the cookies your Flask application sets.
14+
15+
The basic idea here is that at the end of every request, we will check if there
16+
is a JWT that is close to expiring. If we find a JWT that is nearly expired,
17+
we will replace the current cookie containing the JWT with a new JWT that has a
18+
longer time until it expires.
19+
20+
This is our recommended approach when your frontend is a website.
21+
22+
.. literalinclude:: ../examples/implicit_refresh.py
23+
24+
25+
Explicit Refreshing With Refresh Tokens
26+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27+
TODO
28+
29+
.. literalinclude:: ../examples/refresh_tokens.py

examples/implicit_refresh.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from datetime import datetime
2+
from datetime import timedelta
3+
from datetime import timezone
4+
5+
from flask import Flask
6+
from flask import jsonify
7+
8+
from flask_jwt_extended import create_access_token
9+
from flask_jwt_extended import get_jwt
10+
from flask_jwt_extended import get_jwt_identity
11+
from flask_jwt_extended import jwt_required
12+
from flask_jwt_extended import JWTManager
13+
from flask_jwt_extended import set_access_cookies
14+
from flask_jwt_extended import unset_jwt_cookies
15+
16+
app = Flask(__name__)
17+
18+
# If true this will only allow the cookies that contain your JWTs to be sent
19+
# over https. In production, this should always be set to True
20+
app.config["JWT_COOKIE_SECURE"] = False
21+
app.config["JWT_TOKEN_LOCATION"] = ["cookies"]
22+
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this in your code!
23+
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
24+
25+
jwt = JWTManager(app)
26+
27+
28+
# Using an `after_request` callback, we refresh any token that is within 30
29+
# minutes of expiring. Change the timedeltas to match the needs of your application.
30+
@app.after_request
31+
def refresh_expiring_jwts(response):
32+
try:
33+
exp_timestamp = get_jwt()["exp"]
34+
now = datetime.now(timezone.utc)
35+
target_timestamp = datetime.timestamp(now + timedelta(minutes=30))
36+
if target_timestamp > exp_timestamp:
37+
access_token = create_access_token(identity=get_jwt_identity())
38+
set_access_cookies(response, access_token)
39+
return response
40+
except (RuntimeError, KeyError):
41+
# Case where there is not a valid JWT. Just return the original respone
42+
return response
43+
44+
45+
@app.route("/login", methods=["POST"])
46+
def login():
47+
response = jsonify({"msg": "login successful"})
48+
access_token = create_access_token(identity="example_user")
49+
set_access_cookies(response, access_token)
50+
return response
51+
52+
53+
@app.route("/logout", methods=["POST"])
54+
def logout():
55+
response = jsonify({"msg": "logout successful"})
56+
unset_jwt_cookies(response)
57+
return response
58+
59+
60+
@app.route("/protected")
61+
@jwt_required()
62+
def protected():
63+
return jsonify(foo="bar")
64+
65+
66+
if __name__ == "__main__":
67+
app.run()

0 commit comments

Comments
 (0)