Welcome to axioms-flask-py documentation!#
OAuth2/OIDC authentication and authorization for Flask APIs. Supports authentication and claim-based fine-grained authorization (scopes, roles, permissions) using JWT tokens.
Works with access token issued by various authorization servers including AWS Cognito, Auth0, Okta, Microsoft Entra, etc.
Note
Using FastAPI or Django REST Framework? This package is specifically for Flask. For FastAPI applications, use axioms-fastapi. For DRF applications, use axioms-drf-py.
Features#
JWT token validation with automatic public key retrieval from JWKS endpoints
Algorithm validation to prevent algorithm confusion attacks (only secure asymmetric algorithms allowed)
Issuer validation (
issclaim) to prevent token substitution attacksMiddleware support for automatic token extraction and validation on all requests
Authorization decorators for standard claims:
scopes,roles, andpermissionsObject-level permissions for enforcing row-level security and ownership checks
Flexible configuration with support for custom JWKS and issuer URLs
Simple integration with Flask based Resource Server or API backends
Support for custom claim and/or namespaced claims names to support different authorization servers
Prerequisite#
Python 3.9+
An OAuth2/OIDC client which can obtain access token after user’s authentication and authorization and include obtained access token as bearer in
Authorizationheader of all API request sent to Python/Flask application server.
Installation#
Install the package using pip:
pip install axioms-flask-py
Quick Start#
Configure your Flask application:
from flask import Flask
from flask_dotenv import DotEnv
from axioms_flask import setup_token_middleware, init_axioms, register_axioms_error_handler
app = Flask(__name__)
env = DotEnv(app)
# Initialize Axioms
init_axioms(app)
# Optional: Setup middleware for automatic token validation
# Before route handlers are called
setup_token_middleware(app)
# Register global error handler for Axioms errors
register_axioms_error_handler(app)
Create a
.envfile with your configuration (see .env.example for reference):
AXIOMS_AUDIENCE=your-api-audience
AXIOMS_ISS_URL=https://your-auth.domain.com # Recommended - constructs JWKS URL
# Explicit JWKS URL (for non-standard JWKS endpoints)
# AXIOMS_JWKS_URL=https://your-auth.domain.com/.well-known/jwks.json
Use decorators to protect your routes:
from axioms_flask.decorators import has_valid_access_token, has_required_permissions
@app.route('/api/protected')
@has_valid_access_token
def protected_route():
return {'message': 'This is protected'}
@app.route('/api/admin')
@has_valid_access_token
@has_required_permissions(['admin:write'])
def admin_route():
return {'message': 'Admin access'}
Configuration#
At minimum, axioms-flask-py requires the following environment variables to be configured. For full list of configuration options, see Configuration.
AXIOMS_AUDIENCE(required): Your resource identifier or API audienceAXIOMS_ISS_URL(optional): Full issuer URL for validating theissclaim (recommended)AXIOMS_JWKS_URL(optional): Full URL to your JWKS endpointAXIOMS_DOMAIN(optional): Your auth domain - constructs issuer and JWKS URLs (not recommended)
Configuration Hierarchy:
AXIOMS_ISS_URL→ constructs →AXIOMS_JWKS_URL(if not explicitly set)AXIOMS_DOMAIN→ constructs →AXIOMS_ISS_URL→AXIOMS_JWKS_URL(if not explicitly set)
Important
You must provide at least one of: AXIOMS_ISS_URL, AXIOMS_DOMAIN, or AXIOMS_JWKS_URL.
For most use cases, setting only AXIOMS_ISS_URL is recommended. axioms-flask-py will automatically construct the JWKS endpoint URL.
Guard Your Flask API Views#
Use the following decorators to protect your API views:
Decorator |
Description |
Parameters |
|---|---|---|
|
Checks if API request includes a valid bearer access token as authorization header. Performs token signature validation, expiry datetime validation, token audience validation, and issuer validation (if configured). Should be always the first decorator on the protected or private view. |
None |
|
Check any of the given scopes included in |
An array of strings as |
|
Check any of the given roles included in |
An array of strings as |
|
Check any of the given permissions included in |
An array of strings as |
|
Enforces object-level permissions by verifying the authenticated user owns the requested object. Compares a field on the object with a claim in the JWT token. Useful for row-level security. |
|
|
Simpler ownership check when object is already fetched by route handler. Checks ownership of object passed as first argument. |
|
OR vs AND Logic#
By default, authorization decorators use OR logic - the token must have at least ONE of the specified claims. To require ALL claims (AND logic), chain multiple decorators.
OR Logic (Default) - Requires ANY of the specified claims:
@app.route('/api/resource')
@has_valid_access_token
@has_required_scopes(['read:resource', 'write:resource'])
def resource_route():
# User needs EITHER 'read:resource' OR 'write:resource' scope
return {'data': 'success'}
@app.route('/admin/users')
@has_valid_access_token
@has_required_roles(['admin', 'superuser'])
def admin_route():
# User needs EITHER 'admin' OR 'superuser' role
return {'users': []}
AND Logic (Chaining) - Requires ALL of the specified claims:
@app.route('/api/strict')
@has_valid_access_token
@has_required_scopes(['read:resource'])
@has_required_scopes(['write:resource'])
def strict_route():
# User needs BOTH 'read:resource' AND 'write:resource' scopes
return {'data': 'requires both scopes'}
@app.route('/admin/critical')
@has_valid_access_token
@has_required_roles(['admin'])
@has_required_roles(['superuser'])
def critical_route():
# User needs BOTH 'admin' AND 'superuser' roles
return {'message': 'requires both roles'}
Mixed Logic - Combine OR and AND by chaining:
@app.route('/api/advanced')
@has_valid_access_token
@has_required_scopes(['openid', 'profile']) # Needs openid OR profile
@has_required_roles(['editor']) # AND must have editor role
@has_required_permissions(['resource:read', 'resource:write']) # AND read OR write permission
def advanced_route():
# User needs: (openid OR profile) AND (editor) AND (read OR write)
return {'data': 'complex authorization'}
Safe Methods for CORS Support#
All authorization decorators support a safe_methods parameter to bypass authorization checks for specific HTTP methods. This is particularly useful for CORS preflight requests which use the OPTIONS method.
Default Behavior - OPTIONS bypasses authorization:
@app.route('/api/resource', methods=['GET', 'OPTIONS'])
@has_valid_access_token
@has_required_scopes(['read:resource'])
def resource_route():
# OPTIONS requests bypass authentication (for CORS preflight)
# GET requests require valid token with read:resource scope
return {'data': 'success'}
Custom Safe Methods - Specify different methods:
@app.route('/api/resource', methods=['GET', 'HEAD'])
@has_valid_access_token(safe_methods=['HEAD'])
@has_required_scopes(['read:resource'], safe_methods=['HEAD'])
def resource_route():
# HEAD requests bypass authorization
# GET requests require valid token with read:resource scope
return {'data': 'success'}
Configuration - Set default safe methods globally:
# In your .env or Flask config
AXIOMS_SAFE_METHODS = ["OPTIONS", "HEAD"] # Default: ["OPTIONS"]
Note
When using multiple decorators, ensure the safe_methods parameter is consistent across all decorators in the chain for the bypass to work correctly.
Object-Level Permissions#
Enforce row-level security by checking if the authenticated user owns the requested object:
from axioms_flask.decorators import has_valid_access_token, check_object_ownership
def get_article(article_id):
article = Article.query.get_or_404(article_id)
return article
@app.route('/articles/<int:article_id>', methods=['PATCH'])
@has_valid_access_token
@check_object_ownership(get_article, inject_as='article')
def update_article(article_id, article):
# Only owner can update - article.user must match token.sub
article.title = request.json.get('title')
db.session.commit()
return {'id': article.id}
Contents#
Documentation:
Complete Examples#
For complete working examples, check out: