Skip to content

Commit a2a2637

Browse files
author
Landon Gilbert-Bland
committed
Update redis blocklist example
1 parent b29427e commit a2a2637

File tree

2 files changed

+34
-109
lines changed

2 files changed

+34
-109
lines changed

docs/blocklist_and_token_revoking.rst

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@ In production, you will want to use some form of persistent storage (database,
1818
redis, etc) to store your JWTs. It would be bad if your application forgot that
1919
a JWT was revoked if it was restarted.
2020

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.
27-
28-
For more production like examples of toking revoking, check out:
21+
If your only requirements are to check if a JWT has been revoked, our
22+
recommendation is to use redis. It is blazing fast, can be configured to persist
23+
data to disc, and can automatically clear out JWTs after they expire by utilizing
24+
the Time To Live (TTL) functionality when storing a JWT.
25+
26+
If you need to keep track of information about revoked JWTs our recommendation is
27+
to utilize a database. This allows you to easily store and utilize metadata for
28+
revoked tokens, such as when it was revoked, who revoked it, can it be un-revoked,
29+
etc.
30+
31+
Ultimately the choice of what persistent storage engine to use will depend on
32+
your specific application and tech stack. For some examples of JWT revoking using
33+
redist or a database, check out:
2934

3035
- `Redis Example <https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/redis_blocklist.py>`_
3136
- `Database Example <https://github.com/vimalloc/flask-jwt-extended/tree/master/examples/database_blocklist>`_

examples/redis_blocklist.py

Lines changed: 21 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,57 @@
1-
# Redis is a very quick in memory store. The benefits of using redis is that
2-
# things will generally speedy, and it can be (mostly) persistent by dumping
3-
# the data to disk (see: https://redis.io/topics/persistence). The drawbacks
4-
# to using redis is you have a higher chance of encountering data loss (in
5-
# this case, 'forgetting' that a token was revoked), when events like
6-
# power outages occur.
7-
#
8-
# When does it make sense to use redis for a blocklist? If you are blocklisting
9-
# every token on logout, and not doing nothing besides that (such as keeping
10-
# track of what tokens are blocklisted, providing options to un-revoke
11-
# blocklisted tokens, or view tokens that are currently active for a user),
12-
# then redis is a great choice. In the worst case, a few tokens might slip
13-
# between the cracks in the case of a power outage or other such event, but
14-
# 99.99% of the time tokens will be properly blocklisted.
15-
#
16-
# Redis also has the benefit of supporting an expires time when storing data.
17-
# Utilizing this, you will not need to manually prune down the stored tokens
18-
# to keep it from blowing up over time. This code includes how to do this.
19-
#
20-
# If you intend to use some other features in your blocklist (tracking
21-
# what tokens are currently active, option to revoke or unrevoke specific
22-
# tokens, etc), data integrity is probably more important to you then
23-
# raw performance. In this case a database solution (such as postgres) is
24-
# probably a better fit for your blocklist. Check out the "database_blocklist"
25-
# example for how that might work.
261
from datetime import timedelta
272

283
import redis
294
from flask import Flask
305
from flask import jsonify
31-
from flask import request
326

337
from flask_jwt_extended import create_access_token
34-
from flask_jwt_extended import create_refresh_token
35-
from flask_jwt_extended import get_jti
368
from flask_jwt_extended import get_jwt
37-
from flask_jwt_extended import get_jwt_identity
38-
from flask_jwt_extended import jwt_refresh_token_required
399
from flask_jwt_extended import jwt_required
4010
from flask_jwt_extended import JWTManager
4111

42-
app = Flask(__name__)
43-
app.secret_key = "ChangeMe!"
12+
ACCESS_EXPIRES = timedelta(hours=1)
4413

45-
# Setup the flask-jwt-extended extension. See:
46-
ACCESS_EXPIRES = timedelta(minutes=15)
47-
REFRESH_EXPIRES = timedelta(days=30)
14+
app = Flask(__name__)
15+
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
4816
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = ACCESS_EXPIRES
49-
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = REFRESH_EXPIRES
5017
jwt = JWTManager(app)
5118

5219
# Setup our redis connection for storing the blocklisted tokens
53-
revoked_store = redis.StrictRedis(
20+
jwt_redis_blocklist = redis.StrictRedis(
5421
host="localhost", port=6379, db=0, decode_responses=True
5522
)
5623

5724

58-
# Create our function to check if a token has been blocklisted. In this simple
59-
# case, we will just store the tokens jti (unique identifier) in redis
60-
# whenever we create a new token (with the revoked status being 'false'). This
61-
# function will return the revoked status of a token. If a token doesn't
62-
# exist in this store, we don't know where it came from (as we are adding newly
63-
# created tokens to our store with a revoked status of 'false'). In this case
64-
# we will consider the token to be revoked, for safety purposes.
25+
# Callback function to check if a JWT exists in the redis blocklist
6526
@jwt.token_in_blocklist_loader
66-
def check_if_token_is_revoked(decrypted_token):
67-
jti = decrypted_token["jti"]
68-
entry = revoked_store.get(jti)
69-
if entry is None:
70-
return True
71-
return entry == "true"
27+
def check_if_token_is_revoked(jwt_header, jwt_payload):
28+
jti = jwt_payload["jti"]
29+
token_in_redis = jwt_redis_blocklist.get(jti)
30+
return token_in_redis is not None
7231

7332

74-
@app.route("/auth/login", methods=["POST"])
33+
@app.route("/login", methods=["POST"])
7534
def login():
76-
username = request.json.get("username", None)
77-
password = request.json.get("password", None)
78-
if username != "test" or password != "test":
79-
return jsonify({"msg": "Bad username or password"}), 401
80-
81-
# Create our JWTs
82-
access_token = create_access_token(identity=username)
83-
refresh_token = create_refresh_token(identity=username)
84-
85-
# Store the tokens in redis with a status of not currently revoked. We
86-
# can use the `get_jti()` method to get the unique identifier string for
87-
# each token. We can also set an expires time on these tokens in redis,
88-
# so they will get automatically removed after they expire. We will set
89-
# everything to be automatically removed shortly after the token expires
90-
access_jti = get_jti(encoded_token=access_token)
91-
refresh_jti = get_jti(encoded_token=refresh_token)
92-
revoked_store.set(access_jti, "false", ACCESS_EXPIRES * 1.2)
93-
revoked_store.set(refresh_jti, "false", REFRESH_EXPIRES * 1.2)
94-
95-
ret = {"access_token": access_token, "refresh_token": refresh_token}
96-
return jsonify(ret), 201
35+
access_token = create_access_token(identity="example_user")
36+
return jsonify(access_token=access_token)
9737

9838

99-
# A blocklisted refresh tokens will not be able to access this endpoint
100-
@app.route("/auth/refresh", methods=["POST"])
101-
@jwt_refresh_token_required
102-
def refresh():
103-
# Do the same thing that we did in the login endpoint here
104-
current_user = get_jwt_identity()
105-
access_token = create_access_token(identity=current_user)
106-
access_jti = get_jti(encoded_token=access_token)
107-
revoked_store.set(access_jti, "false", ACCESS_EXPIRES * 1.2)
108-
ret = {"access_token": access_token}
109-
return jsonify(ret), 201
110-
111-
112-
# Endpoint for revoking the current users access token
113-
@app.route("/auth/access_revoke", methods=["DELETE"])
114-
@jwt_required
39+
# Endpoint for revoking the current users access token. Set a Time to Live (TTL)
40+
# when storing the token so that it will automatically be cleared out of redis
41+
# after the token expires.
42+
@app.route("/logout", methods=["DELETE"])
43+
@jwt_required()
11544
def logout():
11645
jti = get_jwt()["jti"]
117-
revoked_store.set(jti, "true", ACCESS_EXPIRES * 1.2)
118-
return jsonify({"msg": "Access token revoked"}), 200
119-
120-
121-
# Endpoint for revoking the current users refresh token
122-
@app.route("/auth/refresh_revoke", methods=["DELETE"])
123-
@jwt_refresh_token_required
124-
def logout2():
125-
jti = get_jwt()["jti"]
126-
revoked_store.set(jti, "true", REFRESH_EXPIRES * 1.2)
127-
return jsonify({"msg": "Refresh token revoked"}), 200
46+
jwt_redis_blocklist.set(jti, "", ACCESS_EXPIRES)
47+
return jsonify(msg="Access token revoked")
12848

12949

13050
# A blocklisted access token will not be able to access this any more
13151
@app.route("/protected", methods=["GET"])
132-
@jwt_required
52+
@jwt_required()
13353
def protected():
134-
return jsonify({"hello": "world"})
54+
return jsonify(hello="world")
13555

13656

13757
if __name__ == "__main__":

0 commit comments

Comments
 (0)