February 14, 2025 by Appler LABS
Flask and Keycloak for Single Sign On

Flask and Keycloak for JWT based Authentication
To secure your Flask API endpoints using Keycloak and JWT tokens, you can create a custom decorator that validates the JWT token before allowing access to the endpoint. Below is a step-by-step guide to achieve this:
1. Install Required Libraries
You need the following Python libraries:
Flask
: For building the API.PyJWT
: For decoding and validating JWT tokens.requests
: For fetching the public key from Keycloak to validate the token.
Install them using pip:
pip install Flask PyJWT requests
2. Set Up Keycloak
- Create a realm, client, and roles in Keycloak.
- Ensure your Keycloak server is running and accessible.
- Note down the following details:
- Keycloak server URL (e.g.,
http://localhost:8080/auth
). - Realm name.
- Client ID.
- Client secret (if using confidential clients).
- Keycloak server URL (e.g.,
3. Fetch Keycloak’s Public Key
Keycloak signs JWT tokens using a private key, and you can validate them using the corresponding public key. Fetch the public key from Keycloak’s certs
endpoint:
GET http://<keycloak-url>/auth/realms/<realm-name>/protocol/openid-connect/certs
You can fetch this programmatically in your Flask app.
4. Create the Decorator
Here’s how you can create a decorator to validate the JWT token:
from functools import wraps
import jwt
from flask import request, jsonify
import requests
# Keycloak configuration
KEYCLOAK_URL = "http://localhost:8080/auth"
REALM_NAME = "your-realm"
CLIENT_ID = "your-client-id"
PUBLIC_KEY_URL = f"{KEYCLOAK_URL}/realms/{REALM_NAME}/protocol/openid-connect/certs"
# Fetch the public key from Keycloak
def get_public_key():
response = requests.get(PUBLIC_KEY_URL)
response.raise_for_status()
jwks = response.json()
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(jwks['keys'][0])
return public_key
# Decorator to validate JWT token
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = None
# Get the token from the Authorization header
if 'Authorization' in request.headers:
token = request.headers['Authorization'].split(" ")[1]
if not token:
return jsonify({"message": "Token is missing!"}), 401
try:
# Decode and validate the token
public_key = get_public_key()
data = jwt.decode(token, public_key, algorithms=["RS256"], audience=CLIENT_ID)
# Optionally, you can add more checks here (e.g., roles, expiration, etc.)
except jwt.ExpiredSignatureError:
return jsonify({"message": "Token has expired!"}), 401
except jwt.InvalidTokenError:
return jsonify({"message": "Invalid token!"}), 401
# Attach the decoded token data to the request object
request.user_data = data
return f(*args, **kwargs)
return decorated
5. Use the Decorator in Your Flask App
Apply the @token_required
decorator to your API endpoints:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/protected', methods=['GET'])
@token_required
def protected():
return jsonify({"message": "This is a protected endpoint!", "user": request.user_data})
@app.route('/unprotected', methods=['GET'])
def unprotected():
return jsonify({"message": "This is an unprotected endpoint!"})
if __name__ == '__main__':
app.run(debug=True)
6. Disable Flask’s Default Session Management
Flask uses browser sessions by default. To disable this and rely solely on JWT tokens, ensure you don’t use flask.session
or any session-related functionality in your app.
7. Test Your API
- Use a tool like Postman or
curl
to test your API. - Obtain a JWT token from Keycloak (e.g., via the
/protocol/openid-connect/token
endpoint). - Include the token in the
Authorization
header of your requests:Authorization: Bearer <your-jwt-token>
8. Optional: Add Role-Based Access Control
If you want to enforce role-based access, you can extend the decorator to check for specific roles in the token’s realm_access
or resource_access
claims.
Example:
def role_required(role):
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'realm_access' not in request.user_data or role not in request.user_data['realm_access']['roles']:
return jsonify({"message": "Access denied! Role required."}), 403
return f(*args, **kwargs)
return decorated
return decorator
@app.route('/admin', methods=['GET'])
@token_required
@role_required('admin')
def admin():
return jsonify({"message": "Welcome, admin!"})
This setup ensures your Flask API is secured using Keycloak and JWT tokens, without relying on Flask’s default session management.