Skip to content

Commit b29427e

Browse files
author
Landon Gilbert-Bland
committed
Initial updates to jwt revoking documentation.
1 parent 7795db0 commit b29427e

File tree

4 files changed

+34
-105
lines changed

4 files changed

+34
-105
lines changed
Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,31 @@
11
.. _Blocklist and Token Revoking:
22

3-
Blocklist and Token Revoking
4-
============================
5-
6-
NOTE: THIS DOCUMENTATION HAS NOT YET BEEN UPDATED
7-
8-
9-
This extension supports optional token revoking out of the box. This will
10-
allow you to revoke a specific token so that it can no longer access your endpoints.
11-
12-
You will have to choose what tokens you want to check against the blocklist. In
13-
most cases, you will probably want to check both refresh and access tokens, which
14-
is the default behavior. However, if the extra overhead of checking tokens is a
15-
concern you could instead only check the refresh tokens, and set the access
16-
tokens to have a short expires time so any damage a compromised token could
17-
cause is minimal.
18-
19-
Blocklisting works by is providing a callback function to this extension, using the
3+
JWT Revoking / Blocklist
4+
========================
5+
JWT revoking is a mechanism for preventing an otherwise valid JWT from accessing your
6+
routes while still letting other valid JWTs in. To utilize JWT revoking in this
7+
extension, you must defining a callback function via the
208
:meth:`~flask_jwt_extended.JWTManager.token_in_blocklist_loader` decorator.
21-
This method will be called whenever the specified tokens (`access` and/or `refresh`)
22-
are used to access a protected endpoint. If the callback function says that the
23-
token is revoked, we will not allow the call to continue, otherwise we will
24-
allow the call to access the endpoint as normal.
25-
9+
This function is called whenever a valid JWT is used to access a protected route.
10+
The callback will receive the JWT header and JWT payload as arguments, and must
11+
return `True` if the JWT has been revoked.
2612

2713
Here is a basic example of this in action.
2814

29-
3015
.. literalinclude:: ../examples/blocklist.py
3116

32-
In production, you will likely want to use either a database or in memory store
33-
(such as redis) to store your tokens. In memory stores are great if you are wanting
34-
to revoke a token when the users logs out, as they are blazing fast. A downside
35-
to using redis is that in the case of a power outage or other such event, it's
36-
possible that you might 'forget' that some tokens have been revoked, depending
37-
on if the redis data was synced to disk.
17+
In production, you will want to use some form of persistent storage (database,
18+
redis, etc) to store your JWTs. It would be bad if your application forgot that
19+
a JWT was revoked if it was restarted.
3820

39-
In contrast to that, databases are great if the data persistance is of the highest
40-
importance (for example, if you have very long lived tokens that other developers
41-
use to access your api), or if you want to add some addition features like showing
42-
users all of their active tokens, and letting them revoke and unrevoke those tokens.
21+
If your only requirements are to check if a JWT has been previously revoked,
22+
our recommendation is to use redis, as it is blazing fast. If you need to keep
23+
track of information about revoked JWTs (when it was revoked, who revoked it,
24+
can it be un-revoked, etc), our general recommendation is to utilize your database.
25+
Ultimately though choice of what persistent storage engine to use will depend on
26+
your specific application and tech stack.
4327

44-
For more in depth examples of these, check out:
28+
For more production like examples of toking revoking, check out:
4529

46-
- https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/redis_blocklist.py
47-
- https://github.com/vimalloc/flask-jwt-extended/tree/master/examples/database_blocklist
30+
- `Redis Example <https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/redis_blocklist.py>`_
31+
- `Database Example <https://github.com/vimalloc/flask-jwt-extended/tree/master/examples/database_blocklist>`_

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ Flask-JWT-Extended's Documentation
4141
optional_endpoints
4242
token_locations
4343
refreshing_tokens
44+
blocklist_and_token_revoking
4445
custom_decorators
4546
changing_default_behavior
4647
options
47-
blocklist_and_token_revoking
4848
api

examples/blocklist.py

Lines changed: 13 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,46 @@
11
from flask import Flask
22
from flask import jsonify
3-
from flask import request
43

54
from flask_jwt_extended import create_access_token
6-
from flask_jwt_extended import create_refresh_token
75
from flask_jwt_extended import get_jwt
8-
from flask_jwt_extended import get_jwt_identity
9-
from flask_jwt_extended import jwt_refresh_token_required
106
from flask_jwt_extended import jwt_required
117
from flask_jwt_extended import JWTManager
128

9+
# In production make sure to use persistent storage, such as a database or redis
10+
blocklist = set()
1311

14-
# Setup flask
1512
app = Flask(__name__)
16-
17-
# Enable blocklisting and specify what kind of tokens to check
18-
# against the blocklist
1913
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
2014
jwt = JWTManager(app)
2115

22-
# A storage engine to save revoked tokens. In production if
23-
# speed is the primary concern, redis is a good bet. If data
24-
# persistence is more important for you, postgres is another
25-
# great option. In this example, we will be using an in memory
26-
# store, just to show you how this might work. For more
27-
# complete examples, check out these:
28-
# https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/redis_blocklist.py
29-
# https://github.com/vimalloc/flask-jwt-extended/tree/master/examples/database_blocklist
30-
blocklist = set()
31-
3216

33-
# For this example, we are just checking if the tokens jti
34-
# (unique identifier) is in the blocklist set. This could
35-
# be made more complex, for example storing all tokens
36-
# into the blocklist with a revoked status when created,
37-
# and returning the revoked status in this call. This
38-
# would allow you to have a list of all created tokens,
39-
# and to consider tokens that aren't in the blocklist
40-
# (aka tokens you didn't create) as revoked. These are
41-
# just two options, and this can be tailored to whatever
42-
# your application needs.
17+
# The `jti` claim in the jwt_payload is a unique identifier (string) for the JWT.
4318
@jwt.token_in_blocklist_loader
44-
def check_if_token_in_blocklist(decrypted_token):
45-
jti = decrypted_token["jti"]
19+
def check_if_token_in_blocklist(jwt_header, jwt_payload):
20+
jti = jwt_payload["jti"]
4621
return jti in blocklist
4722

4823

49-
# Standard login endpoint
5024
@app.route("/login", methods=["POST"])
5125
def login():
52-
username = request.json.get("username", None)
53-
password = request.json.get("password", None)
54-
if username != "test" or password != "test":
55-
return jsonify({"msg": "Bad username or password"}), 401
56-
57-
ret = {
58-
"access_token": create_access_token(identity=username),
59-
"refresh_token": create_refresh_token(identity=username),
60-
}
61-
return jsonify(ret), 200
62-
26+
access_token = create_access_token(identity="example_user")
27+
return jsonify(access_token=access_token)
6328

64-
# Standard refresh endpoint. A blocklisted refresh token
65-
# will not be able to access this endpoint
66-
@app.route("/refresh", methods=["POST"])
67-
@jwt_refresh_token_required
68-
def refresh():
69-
current_user = get_jwt_identity()
70-
ret = {"access_token": create_access_token(identity=current_user)}
71-
return jsonify(ret), 200
7229

73-
74-
# Endpoint for revoking the current users access token
30+
# On logout, add the token to our blocklist.
7531
@app.route("/logout", methods=["DELETE"])
76-
@jwt_required
32+
@jwt_required()
7733
def logout():
7834
jti = get_jwt()["jti"]
7935
blocklist.add(jti)
80-
return jsonify({"msg": "Successfully logged out"}), 200
81-
82-
83-
# Endpoint for revoking the current users refresh token
84-
@app.route("/logout2", methods=["DELETE"])
85-
@jwt_refresh_token_required
86-
def logout2():
87-
jti = get_jwt()["jti"]
88-
blocklist.add(jti)
89-
return jsonify({"msg": "Successfully logged out"}), 200
36+
return jsonify(msg="Successfully logged out")
9037

9138

92-
# This will now prevent users with blocklisted tokens from
93-
# accessing this endpoint
39+
# A revoked token will not be able to access this route.
9440
@app.route("/protected", methods=["GET"])
95-
@jwt_required
41+
@jwt_required()
9642
def protected():
97-
return jsonify({"hello": "world"})
43+
return jsonify(hello="world")
9844

9945

10046
if __name__ == "__main__":

tests/test_headers.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ def access_protected():
2323

2424

2525
def test_default_headers(app):
26-
app.config
2726
test_client = app.test_client()
2827

2928
with app.test_request_context():

0 commit comments

Comments
 (0)