Skip to content

Commit b146b3d

Browse files
author
Landon Gilbert-Bland
committed
Clean up database blocklist example
1 parent a2a2637 commit b146b3d

File tree

13 files changed

+105
-349
lines changed

13 files changed

+105
-349
lines changed

docs/blocklist_and_token_revoking.rst

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,26 @@ This function is called whenever a valid JWT is used to access a protected route
1010
The callback will receive the JWT header and JWT payload as arguments, and must
1111
return `True` if the JWT has been revoked.
1212

13-
Here is a basic example of this in action.
14-
15-
.. literalinclude:: ../examples/blocklist.py
16-
1713
In production, you will want to use some form of persistent storage (database,
1814
redis, etc) to store your JWTs. It would be bad if your application forgot that
19-
a JWT was revoked if it was restarted.
15+
a JWT was revoked if it was restarted. We can provide some general recommendations
16+
on what type of storage engine to use, but ultimately the choice will depend on
17+
your specific application and tech stack.
2018

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.
19+
Redis
20+
~~~~~
21+
If your only requirements are to check if a JWT has been revoked, our recommendation
22+
is to use redis. It is blazing fast, can be configured to persist data to disc,
23+
and can automatically clear out JWTs after they expire by utilizing the Time To
24+
Live (TTL) functionality when storing a JWT. Here is an example using redis:
2525

26+
.. literalinclude:: ../examples/blocklist_redis.py
27+
28+
Database
29+
~~~~~~~~
2630
If you need to keep track of information about revoked JWTs our recommendation is
2731
to utilize a database. This allows you to easily store and utilize metadata for
2832
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:
33+
etc. Here is an example using SQLAlchemy:
3434

35-
- `Redis Example <https://github.com/vimalloc/flask-jwt-extended/blob/master/examples/redis_blocklist.py>`_
36-
- `Database Example <https://github.com/vimalloc/flask-jwt-extended/tree/master/examples/database_blocklist>`_
35+
.. literalinclude:: ../examples/blocklist_database.py

examples/blocklist.py

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

examples/blocklist_database.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
from flask_sqlalchemy import SQLAlchemy
8+
9+
from flask_jwt_extended import create_access_token
10+
from flask_jwt_extended import get_jwt
11+
from flask_jwt_extended import jwt_required
12+
from flask_jwt_extended import JWTManager
13+
14+
app = Flask(__name__)
15+
16+
ACCESS_EXPIRES = timedelta(hours=1)
17+
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
18+
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = ACCESS_EXPIRES
19+
jwt = JWTManager(app)
20+
21+
# We are using an in memory database here as an example. Make sure to use a
22+
# database with persistent storage in production!
23+
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite://"
24+
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
25+
db = SQLAlchemy(app)
26+
27+
28+
# This could be expanded to fit the needs of your application. For example,
29+
# it could track who revoked a JWT, when a token expires, notes for why a
30+
# JWT was revoked, an endpoint to un-revoked a JWT, etc.
31+
class TokenBlocklist(db.Model):
32+
id = db.Column(db.Integer, primary_key=True)
33+
jti = db.Column(db.String(36), nullable=False)
34+
created_at = db.Column(db.DateTime, nullable=False)
35+
36+
37+
# Callback function to check if a JWT exists in the database blocklist
38+
@jwt.token_in_blocklist_loader
39+
def check_if_token_revoked(jwt_header, jwt_payload):
40+
jti = jwt_payload["jti"]
41+
token = db.session.query(TokenBlocklist.id).filter_by(jti=jti).scalar()
42+
return token is not None
43+
44+
45+
@app.route("/login", methods=["POST"])
46+
def login():
47+
access_token = create_access_token(identity="example_user")
48+
return jsonify(access_token=access_token)
49+
50+
51+
# Endpoint for revoking the current users access token. Saved the unique
52+
# identifier (jti) for the JWT into our database.
53+
@app.route("/logout", methods=["DELETE"])
54+
@jwt_required()
55+
def modify_token():
56+
jti = get_jwt()["jti"]
57+
now = datetime.now(timezone.utc)
58+
db.session.add(TokenBlocklist(jti=jti, created_at=now))
59+
db.session.commit()
60+
return jsonify(msg="JWT revoked")
61+
62+
63+
# A blocklisted access token will not be able to access this any more
64+
@app.route("/protected", methods=["GET"])
65+
@jwt_required()
66+
def protected():
67+
return jsonify(hello="world")
68+
69+
70+
if __name__ == "__main__":
71+
db.create_all()
72+
app.run()
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = ACCESS_EXPIRES
1717
jwt = JWTManager(app)
1818

19-
# Setup our redis connection for storing the blocklisted tokens
19+
# Setup our redis connection for storing the blocklisted tokens. You will probably
20+
# want your redis instance configured to persist data to disk, so that a restart
21+
# does not cause your application to forget that a JWT was revoked.
2022
jwt_redis_blocklist = redis.StrictRedis(
2123
host="localhost", port=6379, db=0, decode_responses=True
2224
)
@@ -36,14 +38,14 @@ def login():
3638
return jsonify(access_token=access_token)
3739

3840

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.
41+
# Endpoint for revoking the current users access token. Save the JWTs unique
42+
# identifier (jti) in redis. Also set a Time to Live (TTL) when storing the JWT
43+
# so that it will automatically be cleared out of redis after the token expires.
4244
@app.route("/logout", methods=["DELETE"])
4345
@jwt_required()
4446
def logout():
4547
jti = get_jwt()["jti"]
46-
jwt_redis_blocklist.set(jti, "", ACCESS_EXPIRES)
48+
jwt_redis_blocklist.set(jti, "", ex=ACCESS_EXPIRES)
4749
return jsonify(msg="Access token revoked")
4850

4951

examples/database_blocklist/README.md

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

examples/database_blocklist/__init__.py

Whitespace-only changes.

examples/database_blocklist/app.py

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

0 commit comments

Comments
 (0)