This release is a pre-release and may not be stable for production use.
axioms-flask-py

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 tokens issued by various authorization servers including AWS Cognito, Auth0, Okta, Microsoft Entra, etc.
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 attacks - Authorization decorators for standard claims: scopes, roles, and permissions
- 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.7+
- 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.
Install SDK
Install axioms-flask-py in you Flask API project,
pip install axioms-flask-py
Documentation
Prerequisite
- Python 3.7+
- An Axioms 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.
Install SDK
Install axioms-flask-py in you Flask API project,
pip install axioms-flask-py
Add environment variables
Create a .env file and add following configs (see .env.example for reference)
Option 1: Using AXIOMS_DOMAIN (Recommended - simplest configuration)
AXIOMS_AUDIENCE=<your-axioms-resource-identifier-or-endpoint>
AXIOMS_DOMAIN=<your-axioms-slug>.axioms.io
Option 2: Custom issuer URL (when your issuer includes a path)
AXIOMS_AUDIENCE=<your-axioms-resource-identifier-or-endpoint>
AXIOMS_ISS_URL=https://my-auth.domain.com/oauth2
Option 3: Explicit JWKS URL (for non-standard JWKS endpoints)
AXIOMS_AUDIENCE=<your-axioms-resource-identifier-or-endpoint>
AXIOMS_JWKS_URL=https://my-auth.domain.com/.well-known/jwks.json
For more information see API documentation.
Load environment variables
In your Flask app file (where flask app is declared) add following.
from flask_dotenv import DotEnv
env = DotEnv(app)
Register Error
In your Flask app file (where flask app is declared) add following.
from flask import jsonify
from axioms_flask.error import AxiomsError
@app.errorhandler(AxiomsError)
def handle_auth_error(ex):
response = jsonify(ex.error)
response.status_code = ex.status_code
if ex.status_code == 401:
response.headers[
"WWW-Authenticate"
] = "Bearer realm='{}', error='{}', error_description='{}'".format(
app.config["AXIOMS_DOMAIN"], ex.error["error"], ex.error["error_description"]
)
return response
Guard Your Flask API Views
Use following decorators to guard you API views.
| Decorators | Description | Parameter |
|---|---|---|
axioms_flask.decorators.has_valid_access_token |
Checks if API request includes a valid bearer access token as authorization header. Check performed includes: 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. |
|
axioms_flask.decorators.has_required_scopes |
Check any of the given scopes included in scope claim of the access token. Should be after has_valid_access_token. |
An array of strings as conditional OR representing any of the allowed scope or scopes for the view as parameter. For instance, to check openid or profile pass ['profile', 'openid'] as parameter. |
axioms_flask.decorators.has_required_roles |
Check any of the given roles included in roles claim of the access token. Should be after has_valid_access_token. |
An array of strings as conditional OR representing any of the allowed role or roles for the view as parameter. For instance, to check sample:role1 or sample:role2 roles you will pass ['sample:role1', 'sample:role2'] as parameter. |
axioms_flask.decorators.has_required_permissions |
Check any of the given permissions included in permissions claim of the access token. Should be after has_valid_access_token. |
An array of strings as conditional OR representing any of the allowed permission or permissions for the view as parameter. For instance, to check sample:create or sample:update permissions you will pass ['sample:create', 'sample:update'] as parameter. |
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'}
Examples
- Check
openidorprofilescope present in the token
from axioms_flask.decorators import has_valid_access_token, has_required_scopes
private_api = Blueprint("private_api", __name__)
@private_api.route('/private', methods=["GET"])
@has_valid_access_token
@has_required_scopes(['openid', 'profile'])
def api_private():
return jsonify({'message': 'All good. You are authenticated!'})
- Check
sample:rolerole present in the token
from axioms_flask.decorators import has_valid_access_token, has_required_roles
role_api = Blueprint("role_api", __name__)
@role_api.route("/role", methods=["GET", "POST", "PATCH", "DELETE"])
@has_valid_access_token
@has_required_roles(["sample:role"])
def sample_role():
if request.method == 'POST':
return jsonify({"message": "Sample created."})
if request.method == 'PATCH':
return jsonify({"message": "Sample updated."})
if request.method == 'GET':
return jsonify({"message": "Sample read."})
if request.method == 'DELETE':
return jsonify({"message": "Sample deleted."})
- Check permission present in the token at API method level
from axioms_flask.decorators import has_valid_access_token, has_required_permissions
permission_api = Blueprint("permission_api", __name__)
@permission_api.route("/permission", methods=["POST"])
@has_valid_access_token
@has_required_permissions(["sample:create"])
def sample_create():
return jsonify({"message": "Sample created."})
@permission_api.route("/permission", methods=["PATCH"])
@has_valid_access_token
@has_required_permissions(["sample:update"])
def sample_update():
return jsonify({"message": "Sample updated."})
@permission_api.route("/permission", methods=["GET"])
@has_valid_access_token
@has_required_permissions(["sample:read"])
def sample_read():
return jsonify({"message": "Sample read."})
@permission_api.route("/permission", methods=["DELETE"])
@has_valid_access_token
@has_required_permissions(["sample:delete"])
def sample_delete():
return jsonify({"message": "Sample deleted."})
Flask Sample
To see a complete working example download Flask sample from our Github repository or simply deploy to heroku by clicking following button. You will need to provide Axioms domain and Axioms audience to complete deployment.
Metadata
Release files for axioms-flask-py 0.0.18rc81762699385
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| axioms_flask_py-0.0.18rc81762699385.tar.gz | 35.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| axioms_flask_py-0.0.18rc81762699385-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 49.9 kB
Release files / axioms_flask_py-0.0.18rc81762699385.tar.gz
| Download URL | axioms_flask_py-0.0.18rc81762699385.tar.gz |
|---|---|
| Size | 35.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8a34daa9bc6df38599e783d373d697102f4434a84d8fcd85e2d07265b7a641d0
|
|
BLAKE2b-256 checksum How to use checksums |
8adac6ff75be77a6e21d2bbc8780d9995d31bf87dd11e71e455c2ca3b287e53a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2025.
Transparency logRelease files / axioms_flask_py-0.0.18rc81762699385-py3-none-any.whl
| Download URL | axioms_flask_py-0.0.18rc81762699385-py3-none-any.whl |
|---|---|
| Size | 14.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ffa76864ad12ad1eef8f595f73dd2e8d2c22c590449dc18e4e809dcd6b2113de
|
|
BLAKE2b-256 checksum How to use checksums |
6507807ec48cfe5744bb631fc41cb75df7b6afed97473335d3a2644042b572f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2025.
Transparency log