Automatic User LoadingΒΆ
In most web applications it is important to have access to the user who is accessing a protected route. We provide a couple callback functions that make this seamless while working with JWTs.
The first is user_identity_loader()
, which
will convert any User
object used to create a JWT into a string.
On the flip side, you can use user_lookup_loader()
to automatically load your User
object when a JWT is present in the request.
The loaded user is available in your protected routes via current_user
.
Lets see an example of this while utilizing SQLAlchemy to store our users:
from hmac import compare_digest
from flask import Flask
from flask import jsonify
from flask import request
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import create_access_token
from flask_jwt_extended import current_user
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite://"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
jwt = JWTManager(app)
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Text, nullable=False, unique=True)
full_name = db.Column(db.Text, nullable=False)
# NOTE: In a real application make sure to properly hash and salt passwords
def check_password(self, password):
return compare_digest(password, "password")
# Register a callback function that takes whatever object is passed in as the
# identity when creating JWTs and converts it to a JSON serializable format.
@jwt.user_identity_loader
def user_identity_lookup(user):
return user.id
# Register a callback function that loads a user from your database whenever
# a protected route is accessed. This should return any python object on a
# successful lookup, or None if the lookup failed for any reason (for example
# if the user has been deleted from the database).
@jwt.user_lookup_loader
def user_lookup_callback(_jwt_header, jwt_data):
identity = jwt_data["sub"]
return User.query.filter_by(id=identity).one_or_none()
@app.route("/login", methods=["POST"])
def login():
username = request.json.get("username", None)
password = request.json.get("password", None)
user = User.query.filter_by(username=username).one_or_none()
if not user or not user.check_password(password):
return jsonify("Wrong username or password"), 401
# Notice that we are passing in the actual sqlalchemy user object here
access_token = create_access_token(identity=user)
return jsonify(access_token=access_token)
@app.route("/who_am_i", methods=["GET"])
@jwt_required()
def protected():
# We can now access our sqlalchemy User object via `current_user`.
return jsonify(
id=current_user.id,
full_name=current_user.full_name,
username=current_user.username,
)
if __name__ == "__main__":
db.create_all()
db.session.add(User(full_name="Bruce Wayne", username="batman"))
db.session.add(User(full_name="Ann Takamaki", username="panther"))
db.session.add(User(full_name="Jester Lavore", username="little_sapphire"))
db.session.commit()
app.run()
We can see this in action using HTTPie.
$ http POST :5000/login username=panther password=password
HTTP/1.0 200 OK
Content-Length: 281
Content-Type: application/json
Date: Sun, 24 Jan 2021 17:23:31 GMT
Server: Werkzeug/1.0.1 Python/3.8.6
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTYxMTUwOTAxMSwianRpIjoiNGFmN2ViNTAtMjk3Yy00ZmY4LWJmOTYtMTZlMDE5MWEzYzMwIiwibmJmIjoxNjExNTA5MDExLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoyLCJleHAiOjE2MTQxMDEwMTF9.2UhZo-xo19NXaqKLwcMz0NBLAcxxEUeK4Ziqk1T_9h0"
}
$ export JWT="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTYxMTUwOTAxMSwianRpIjoiNGFmN2ViNTAtMjk3Yy00ZmY4LWJmOTYtMTZlMDE5MWEzYzMwIiwibmJmIjoxNjExNTA5MDExLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoyLCJleHAiOjE2MTQxMDEwMTF9.2UhZo-xo19NXaqKLwcMz0NBLAcxxEUeK4Ziqk1T_9h0"
$ http GET :5000/who_am_i Authorization:"Bearer $JWT"
HTTP/1.0 200 OK
Content-Length: 57
Content-Type: application/json
Date: Sun, 24 Jan 2021 17:31:34 GMT
Server: Werkzeug/1.0.1 Python/3.8.6
{
"id": 2,
"full_name": "Ann Takamaki",
"username": "panther"
}