JWT Authentication
Stateless auth with JSON Web Tokens in Flask — issue, verify, protect routes
Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.
UpgradeKnowledge Debt detected
You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.
Explanation
enables — no server-side sessions, just a signed token the client sends with every request.
Setup with flask-jwt-extended:
python from flask import Flask from flask_jwt_extended import JWTManager app = Flask(__name__) app.config["JWT_SECRET_KEY"] = "use-a-real-secret-from-env" jwt = JWTManager(app)
Login — issue a token:
python from flask import Blueprint, request, jsonify from flask_jwt_extended import create_access_token, create_refresh_token from datetime import timedelta auth_bp = Blueprint("auth", __name__, url_prefix="/auth") @auth_bp.route("/login", methods=["POST"]) def login(): data = request.get_json() email = data.get("email") password = data.get("password") user = User.query.filter_by(email=email).first() if not user or not user.check_password(password): return jsonify({"error": "Invalid credentials"}), 401 access_token = create_access_token( identity=user.id, expires_delta=timedelta(hours=1) ) refresh_token = create_refresh_token(identity=user.id) return jsonify({ "access_token": access_token, "refresh_token": refresh_token, })
Protecting routes:
python from flask_jwt_extended import jwt_required, get_jwt_identity @app.route("/me") @jwt_required() def me(): user_id = get_jwt_identity() # the identity set at login user = User.query.get(user_id) return jsonify(user.to_dict())
Refresh token flow:
python @auth_bp.route("/refresh", methods=["POST"]) @jwt_required(refresh=True) def refresh(): user_id = get_jwt_identity() new_token = create_access_token(identity=user_id) return jsonify({"access_token": new_token})
Examples
Role-based authorization
Embed role claims at token creation; verify them on protected routes
from flask_jwt_extended import jwt_required, get_jwt
from functools import wraps
from flask import jsonify
def admin_required(fn):
@wraps(fn)
@jwt_required()
def wrapper(*args, **kwargs):
claims = get_jwt()
if claims.get("role") != "admin":
return jsonify({"error": "Admin access required"}), 403
return fn(*args, **kwargs)
return wrapper
# At login, add role to additional claims:
# additional_claims={"role": user.role}
@app.route("/admin/stats")
@admin_required
def admin_stats():
return jsonify({"users": User.query.count()})Password hashing with werkzeug
Never store plaintext passwords — werkzeug hashes with PBKDF2-SHA256 by default
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
def set_password(self, password: str) -> None:
self.password_hash = generate_password_hash(password)
def check_password(self, password: str) -> bool:
return check_password_hash(self.password_hash, password)
# Usage:
user = User(email="alice@example.com")
user.set_password("hunter2")
db.session.add(user)
db.session.commit()
# Login check:
user.check_password("hunter2") # True
user.check_password("wrong") # FalseHow well did you understand this?