Flask JWT Authentication
In this lesson, we'll explore how to implement JWT (JSON Web Token) authentication in Flask applications. JWT has become a standard for securing APIs and managing user sessions in modern web applications.
Introduction to JWT
JWT (JSON Web Token) is an open standard that defines a compact and self-contained way to securely transmit information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
Some key benefits of using JWT:
- Stateless: Servers don't need to store session information
- Portable: Same token can work across multiple backends
- Secure: Tokens are signed and can be encrypted
- Expirable: Tokens can have an expiration time
How JWT Works
A JWT token consists of three parts:
- Header - Contains the algorithm used for signing
- Payload - Contains claims (user data and metadata)
- Signature - Verifies the token hasn't been tampered with
These parts are encoded and concatenated with dots to form a string like:
xxxxx.yyyyy.zzzzz
Setting Up JWT Authentication in Flask
Let's implement JWT authentication in a Flask application step by step.
Step 1: Install Required Packages
First, we need to install the necessary packages:
pip install flask flask-jwt-extended
Step 2: Create a Basic Flask Application
Let's create a simple Flask application with JWT authentication:
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
# Create Flask app
app = Flask(__name__)
# Configure JWT
app.config['JWT_SECRET_KEY'] = 'super-secret-key' # Change this in production!
jwt = JWTManager(app)
# Mock user database
users = {
'user1': {
'password': 'password1',
'role': 'admin'
},
'user2': {
'password': 'password2',
'role': 'user'
}
}
@app.route('/')
def index():
return jsonify(message='Welcome to JWT Authentication API')
# Start the server
if __name__ == '__main__':
app.run(debug=True)
Step 3: Implement Login Endpoint
Now let's create a login endpoint that verifies user credentials and returns a JWT token:
@app.route('/login', methods=['POST'])
def login():
if not request.is_json:
return jsonify({"error": "Missing JSON in request"}), 400
username = request.json.get('username', None)
password = request.json.get('password', None)
if not username or not password:
return jsonify({"error": "Missing username or password"}), 400
# Check if user exists and password is correct
if username not in users or users[username]['password'] != password:
return jsonify({"error": "Invalid username or password"}), 401
# Create access token with user identity
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token), 200
Step 4: Create Protected Endpoints
Let's create endpoints that require JWT authentication:
@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
# Access the identity of the current user
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
@app.route('/admin', methods=['GET'])
@jwt_required()
def admin():
# Access the identity of the current user
current_user = get_jwt_identity()
# Check if user is admin
if users[current_user]['role'] != 'admin':
return jsonify({"error": "Admin access required"}), 403
return jsonify(admin_access=True), 200
Step 5: Testing the Authentication Flow
Here's how to test the authentication flow using cURL:
- Login to get a token:
curl -X POST -H "Content-Type: application/json" \
-d '{"username":"user1","password":"password1"}' \
http://localhost:5000/login
Output:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY0NzM2NjIwMiwianRpIjoiZjdkYzA2M2QtMjFkOC00MDZkLTk5YzktNDU5MmYxMTM4Y2I4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InVzZXIxIiwibmJmIjoxNjQ3MzY2MjAyLCJleHAiOjE2NDczNjcxMDJ9.v41hh71VJpnnZBF9OaPvNVEmOI-qohbqhTdhCEPzmcs"
}
- Access protected endpoint with token:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:5000/protected
Output:
{
"logged_in_as": "user1"
}
Enhanced JWT Authentication Features
Let's explore some advanced features of Flask-JWT-Extended.
Token Expiration
You can configure token expiration time:
# Configure JWT with expiration time
app.config['JWT_SECRET_KEY'] = 'super-secret-key'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 3600 # 1 hour in seconds
jwt = JWTManager(app)
Refresh Tokens
Refresh tokens allow you to issue new access tokens without requiring the user to re-authenticate:
from flask_jwt_extended import create_refresh_token, jwt_refresh_token_required
@app.route('/login', methods=['POST'])
def login():
# Authentication code...
# Create both access and refresh tokens
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
return jsonify(
access_token=access_token,
refresh_token=refresh_token
), 200
@app.route('/refresh', methods=['POST'])
@jwt_required(refresh=True)
def refresh():
current_user = get_jwt_identity()
new_access_token = create_access_token(identity=current_user)
return jsonify(access_token=new_access_token), 200
Token Revocation
You can implement token revocation to invalidate tokens before they expire:
from datetime import datetime
from flask_jwt_extended import get_jwt
# Store for revoked tokens
revoked_tokens = set()
@jwt.token_in_blocklist_loader
def check_if_token_revoked(jwt_header, jwt_payload):
jti = jwt_payload["jti"]
return jti in revoked_tokens
@app.route('/logout', methods=['DELETE'])
@jwt_required()
def logout():
jti = get_jwt()["jti"]
revoked_tokens.add(jti)
return jsonify(message="Successfully logged out"), 200
Real-world Example: User Management API
Let's create a more comprehensive example with user registration, authentication, and protected resources:
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from werkzeug.security import generate_password_hash, check_password_hash
import datetime
app = Flask(__name__)
# Configure JWT
app.config['JWT_SECRET_KEY'] = 'change-this-key-in-production'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = datetime.timedelta(hours=1)
jwt = JWTManager(app)
# In-memory user database (use a real database in production)
users_db = {}
# User registration endpoint
@app.route('/register', methods=['POST'])
def register():
if not request.is_json:
return jsonify({"error": "Missing JSON in request"}), 400
username = request.json.get('username', None)
password = request.json.get('password', None)
email = request.json.get('email', None)
if not username or not password or not email:
return jsonify({"error": "Missing required fields"}), 400
if username in users_db:
return jsonify({"error": "Username already exists"}), 409
# Hash the password for security
hashed_password = generate_password_hash(password)
# Store new user
users_db[username] = {
'password': hashed_password,
'email': email,
'created_at': datetime.datetime.now().isoformat()
}
return jsonify({"message": "User created successfully"}), 201
# User login endpoint
@app.route('/login', methods=['POST'])
def login():
if not request.is_json:
return jsonify({"error": "Missing JSON in request"}), 400
username = request.json.get('username', None)
password = request.json.get('password', None)
if not username or not password:
return jsonify({"error": "Missing username or password"}), 400
# Check if user exists
if username not in users_db:
return jsonify({"error": "Invalid username or password"}), 401
# Check if password is correct
if not check_password_hash(users_db[username]['password'], password):
return jsonify({"error": "Invalid username or password"}), 401
# Create access token with user identity
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token), 200
# User profile endpoint (protected)
@app.route('/profile', methods=['GET'])
@jwt_required()
def profile():
current_user = get_jwt_identity()
if current_user not in users_db:
return jsonify({"error": "User not found"}), 404
# Don't return password hash
user_data = {
'username': current_user,
'email': users_db[current_user]['email'],
'created_at': users_db[current_user]['created_at']
}
return jsonify(user=user_data), 200
if __name__ == '__main__':
app.run(debug=True)
Best Practices for JWT Authentication
When implementing JWT authentication in production applications, follow these best practices:
-
Use HTTPS: Always serve your API over HTTPS to prevent token theft via network sniffing.
-
Secure Secret Keys: Store your JWT secret keys securely, preferably using environment variables.
import os
app.config['JWT_SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY', 'default-dev-key')
-
Short Token Expiration: Use short expiration times for access tokens (minutes to hours).
-
Token Refresh Strategy: Implement refresh tokens to improve user experience without compromising security.
-
Include Only Necessary Claims: Don't store sensitive information in the token payload.
-
Token Revocation: Implement token revocation for logout functionality.
-
Handle Errors Gracefully: Provide clear error messages without exposing sensitive details.
@jwt.expired_token_loader
def expired_token_callback(jwt_header, jwt_payload):
return jsonify({
'status': 401,
'sub_status': 42,
'msg': 'The token has expired'
}), 401
Common JWT Issues and Solutions
Issue: CORS with JWT
When building SPAs with JWT authentication, you might encounter CORS issues:
from flask_cors import CORS
app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}}) # In production, specify origins
Issue: JWT Token Storage on Client Side
Client-side storage recommendations:
- Store in HttpOnly cookies for web apps
- For SPAs, use memory storage with refresh token in HttpOnly cookie
Issue: Handling Expired Tokens
Implement client-side logic to handle token expiration:
- Detect 401 responses
- Use refresh token to get a new access token
- Redirect to login if refresh fails
Summary
In this lesson, we've covered:
- Introduction to JWT and its components
- Setting up JWT authentication in Flask using Flask-JWT-Extended
- Creating login and protected endpoints
- Advanced features like token expiration, refresh tokens, and revocation
- Building a real-world user management API
- Best practices for secure JWT implementation
JWT authentication provides a secure, stateless way to authenticate users in your Flask applications, making it ideal for modern web APIs and single-page applications.
Additional Resources
- Flask-JWT-Extended Documentation
- JWT.io - Decode, verify and generate JWT tokens
- OWASP JWT Cheat Sheet
Exercises
- Modify the example to use a database like SQLAlchemy instead of in-memory storage.
- Add role-based access control to your API endpoints.
- Implement token blacklisting using Redis for better scalability.
- Create a frontend that interacts with the JWT authentication API.
- Add JWT claims customization to include user permissions in the token.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)