Skip to content

Commit 7795db0

Browse files
author
Landon Gilbert-Bland
committed
Update documentation for refreshing tokens and freshness pattern
1 parent e55277f commit 7795db0

11 files changed

+103
-121
lines changed

docs/blocklist_and_token_revoking.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
Blocklist and Token Revoking
44
============================
55

6+
NOTE: THIS DOCUMENTATION HAS NOT YET BEEN UPDATED
7+
8+
69
This extension supports optional token revoking out of the box. This will
710
allow you to revoke a specific token so that it can no longer access your endpoints.
811

docs/changing_default_behavior.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Changing Default Behaviors
22
==========================
33

4+
NOTE: THIS DOCUMENTATION HAS NOT YET BEEN UPDATED
5+
6+
47
Changing callback functions
58
~~~~~~~~~~~~~~~~~~~~~~~~~~~
69

docs/custom_decorators.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Custom Decorators
22
=================
33

4+
NOTE: THIS DOCUMENTATION HAS NOT YET BEEN UPDATED
5+
6+
47
You can create your own decorators that extend the functionality of the
58
decorators provided by this extension. For example, you may want to create
69
your own decorator that verifies a JWT is present as well as verifying that

docs/index.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ Flask-JWT-Extended's Documentation
4242
token_locations
4343
refreshing_tokens
4444
custom_decorators
45-
refresh_tokens
46-
token_freshness
4745
changing_default_behavior
4846
options
4947
blocklist_and_token_revoking

docs/options.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
Configuration Options
44
=====================
55

6+
NOTE: THIS DOCUMENTATION HAS NOT YET BEEN UPDATED
7+
8+
69
You can change many options for how this extension works via
710

811
.. code-block:: python

docs/refresh_tokens.rst

Lines changed: 0 additions & 20 deletions
This file was deleted.

docs/refreshing_tokens.rst

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ Refreshing Tokens
33
In most web applications, it would not be ideal if a user was logged out in the
44
middle of doing something because their JWT expired. Unfortunately we can't just
55
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.
6+
cannot be modified. Lets take a look at some options for solving this problem
7+
by refreshing JWTs.
88

99
Implicit Refreshing With Cookies
1010
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -24,6 +24,59 @@ This is our recommended approach when your frontend is a website.
2424

2525
Explicit Refreshing With Refresh Tokens
2626
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27-
TODO
27+
Alternatively, this extension comes out of the box with refresh token support.
28+
A refresh token is a long lived JWT that can only be used to creating new access
29+
tokens.
30+
31+
You have a couple choices about how to utilize a refresh token. You could store
32+
the expires time of your access token on your frontend, and each time you make
33+
an API request first check if the current access token is near or already
34+
expired, and refresh it as needed. This approach is pretty simple and will work
35+
fine in most cases, but do be aware that if your frontend has a clock that is
36+
significantly off, you might run into issues.
37+
38+
An alternative approach involves making an API request with your access token
39+
and then checking the result to see if it worked. If the result of the request
40+
is an error message saying that your token is expired, use the refresh token to
41+
generate a new access token and redo the request with the new token. This approach
42+
will work regardless of the clock on your frontend, but it does require having
43+
some potentially more complicated logic.
44+
45+
Using refresh tokens is our recommended approach when your frontend is not a
46+
website (mobile, api only, etc).
2847

2948
.. literalinclude:: ../examples/refresh_tokens.py
49+
50+
Making a request with a refresh token looks just like making a request with
51+
an access token. Here is an example using `HTTPie <https://httpie.io/>`_.
52+
53+
.. code-block :: bash
54+
55+
$ http POST :5000/refresh Authorization:"Bearer $REFRESH_TOKEN"
56+
57+
58+
Token Freshness Pattern
59+
~~~~~~~~~~~~~~~~~~~~~~~
60+
The token freshness pattern is a very simple idea. Every time a user authenticates
61+
by providing a username and password, they receive a `fresh` access token that
62+
can access any route. But after some time, that token should no longer be considered
63+
`fresh`, and some critical or dangerous routes will be blocked until the user
64+
verifies their password again. All other routes will still work normally for
65+
the user even though their token is no longer `fresh`. As an example, we might
66+
not allow users to change their email address unless they have a `fresh` token,
67+
but we do allow them use the rest of our Flask application normally.
68+
69+
The token freshness pattern is built into this extension, and works seamlessly
70+
with both token refreshing strategies discussed above. Lets take a look at this
71+
with the explicit refresh example (it will look basically same in the implicit
72+
refresh example).
73+
74+
.. literalinclude:: ../examples/token_freshness.py
75+
76+
We also support marking a token as fresh for a given amount of time after it
77+
is created. You can do this by passing a `datetime.timedelta` to the `fresh`
78+
option when creating JWTs:
79+
80+
.. code-block :: python
81+
82+
create_access_token(identity, fresh=datetime.timedelta(minutes=15))

docs/token_freshness.rst

Lines changed: 0 additions & 18 deletions
This file was deleted.

docs/token_locations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ out as invalid too. Lets look at how to do that:
102102
async function makeRequestWithJWT() {
103103
const options = {
104104
method: 'post',
105+
credentials: 'same-origin',
105106
headers: {
106107
'X-CSRF-TOKEN': getCookie('csrf_access_token'),
107108
},

examples/refresh_tokens.py

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,43 @@
1+
from datetime import timedelta
2+
13
from flask import Flask
24
from flask import jsonify
3-
from flask import request
45

56
from flask_jwt_extended import create_access_token
67
from flask_jwt_extended import create_refresh_token
78
from flask_jwt_extended import get_jwt_identity
8-
from flask_jwt_extended import jwt_refresh_token_required
99
from flask_jwt_extended import jwt_required
1010
from flask_jwt_extended import JWTManager
1111

1212
app = Flask(__name__)
1313

1414
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
15+
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
16+
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
1517
jwt = JWTManager(app)
1618

1719

1820
@app.route("/login", methods=["POST"])
1921
def login():
20-
username = request.json.get("username", None)
21-
password = request.json.get("password", None)
22-
if username != "test" or password != "test":
23-
return jsonify({"msg": "Bad username or password"}), 401
24-
25-
# Use create_access_token() and create_refresh_token() to create our
26-
# access and refresh tokens
27-
ret = {
28-
"access_token": create_access_token(identity=username),
29-
"refresh_token": create_refresh_token(identity=username),
30-
}
31-
return jsonify(ret), 200
32-
33-
34-
# The jwt_refresh_token_required decorator insures a valid refresh
35-
# token is present in the request before calling this endpoint. We
36-
# can use the get_jwt_identity() function to get the identity of
37-
# the refresh token, and use the create_access_token() function again
38-
# to make a new access token for this identity.
22+
access_token = create_access_token(identity="example_user")
23+
refresh_token = create_refresh_token(identity="example_user")
24+
return jsonify(access_token=access_token, refresh_token=refresh_token)
25+
26+
27+
# We are using the `refresh=True` options in jwt_required to only allow
28+
# refresh tokens to access this route.
3929
@app.route("/refresh", methods=["POST"])
40-
@jwt_refresh_token_required
30+
@jwt_required(refresh=True)
4131
def refresh():
42-
current_user = get_jwt_identity()
43-
ret = {"access_token": create_access_token(identity=current_user)}
44-
return jsonify(ret), 200
32+
identity = get_jwt_identity()
33+
access_token = create_access_token(identity=identity)
34+
return jsonify(access_token=access_token)
4535

4636

4737
@app.route("/protected", methods=["GET"])
48-
@jwt_required
38+
@jwt_required()
4939
def protected():
50-
username = get_jwt_identity()
51-
return jsonify(logged_in_as=username), 200
40+
return jsonify(foo="bar")
5241

5342

5443
if __name__ == "__main__":

0 commit comments

Comments
 (0)