OAuth2/OIDC authentication and authorization for Django REST Framework APIs
Project description
axioms-drf-py

OAuth2/OIDC authentication and authorization for Django REST Framework 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 - Authentication classes for standard DRF integration
- Permission classes for claim-based authorization: scopes, roles, and permissions
- Support for both OR and AND logic in authorization checks
- Middleware for automatic token extraction and validation
- Flexible configuration with support for custom JWKS and issuer URLs
- Simple integration with Django REST Framework Resource Server or API backends
- Support for custom claim and/or namespaced claims names to support different authorization servers
Prerequisites
- Python 3.8+
- Django 3.2+
- Django REST Framework 3.12+
- An OAuth2/OIDC authorization server (AWS Cognito, Auth0, Okta, Microsoft Entra, etc.) that can issue JWT access tokens
Installation
Install the package using pip:
pip install axioms-drf-py
Quick Start
1. Add Middleware
Add the middleware to your Django settings:
MIDDLEWARE = [
'axioms_drf.middleware.AccessTokenMiddleware',
# ... other middleware
]
2. Configuration
The SDK supports the following configuration options in your Django settings:
| Setting | Required | Description |
|---|---|---|
AXIOMS_AUDIENCE |
Yes | Expected audience claim in the JWT token. |
AXIOMS_DOMAIN |
No | Axioms domain name. Used as the base to construct AXIOMS_ISS_URL if not explicitly provided. This is the simplest configuration option for standard OAuth2/OIDC providers. |
AXIOMS_ISS_URL |
No | Full issuer URL for validating the iss claim in JWT tokens (e.g., https://auth.example.com/oauth2). If not provided, constructed as https://{AXIOMS_DOMAIN}. Used to construct AXIOMS_JWKS_URL if that is not explicitly set. Recommended for security to prevent token substitution attacks. |
AXIOMS_JWKS_URL |
No | Full URL to JWKS endpoint (e.g., https://auth.example.com/.well-known/jwks.json). If not provided, constructed as {AXIOMS_ISS_URL}/.well-known/jwks.json |
Configuration Hierarchy:
The SDK uses the following construction order:
AXIOMS_DOMAIN→ constructs →AXIOMS_ISS_URL(if not explicitly set)AXIOMS_ISS_URL→ constructs →AXIOMS_JWKS_URL(if not explicitly set)
Note: You must provide at least one of:
AXIOMS_DOMAIN,AXIOMS_ISS_URL, orAXIOMS_JWKS_URL. For most use cases, setting onlyAXIOMS_DOMAINis sufficient.
3. Configure Settings
Option A: Using .env file
Create a .env file in your project root:
AXIOMS_AUDIENCE=your-api-audience
AXIOMS_DOMAIN=your-auth.domain.com
# OR for custom configurations:
# AXIOMS_ISS_URL=https://your-auth.domain.com/oauth2
# AXIOMS_JWKS_URL=https://your-auth.domain.com/.well-known/jwks.json
Then load in your settings.py:
import environ
env = environ.Env()
environ.Env.read_env()
# Required
AXIOMS_AUDIENCE = env('AXIOMS_AUDIENCE')
# Optional - choose based on your auth server setup
AXIOMS_DOMAIN = env('AXIOMS_DOMAIN', default=None) # Simplest option
# AXIOMS_ISS_URL = env('AXIOMS_ISS_URL', default=None) # For custom issuer
# AXIOMS_JWKS_URL = env('AXIOMS_JWKS_URL', default=None) # For custom JWKS endpoint
Option B: Direct Configuration
Configure directly in your settings.py:
# Required settings
AXIOMS_AUDIENCE = 'your-api-audience'
AXIOMS_DOMAIN = 'your-auth.domain.com' # Simplest option - constructs issuer and JWKS URLs
# OR for custom configurations:
# AXIOMS_ISS_URL = 'https://your-auth.domain.com/oauth2'
# AXIOMS_JWKS_URL = 'https://your-auth.domain.com/.well-known/jwks.json'
4. Use Authentication and Permission Classes
Protect your API views using authentication and permission classes:
from rest_framework.views import APIView
from rest_framework.response import Response
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenScopes
class ProtectedView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
access_token_scopes = ['read:data']
def get(self, request):
return Response({'message': 'This is protected'})
Guard Your Django REST Framework API Views
Authentication Classes
| Class | Description |
|---|---|
HasValidAccessToken |
Validates JWT access token from Authorization header. Performs token signature validation, expiry datetime validation, token audience validation, and issuer validation (if configured). |
IsAccessTokenAuthenticated |
Alias for HasValidAccessToken. |
IsAnyPostOrIsAccessTokenAuthenticated |
Allows POST requests without authentication, requires valid token for other methods. |
IsAnyGetOrIsAccessTokenAuthenticated |
Allows GET requests without authentication, requires valid token for other methods. |
Permission Classes
| Class | Description | View Attributes |
|---|---|---|
HasAccessTokenScopes |
Check scopes in scope claim of the access token. |
access_token_scopes or access_token_any_scopes (OR logic)access_token_all_scopes (AND logic) |
HasAccessTokenRoles |
Check roles in roles claim of the access token. |
access_token_roles or access_token_any_roles (OR logic)access_token_all_roles (AND logic) |
HasAccessTokenPermissions |
Check permissions in permissions claim of the access token. |
access_token_permissions or access_token_any_permissions (OR logic)access_token_all_permissions (AND logic) |
OR vs AND Logic
Permission classes support both OR logic (any claim) and AND logic (all claims) through different view attributes. You can also combine both for complex authorization requirements.
OR Logic (Default) - Requires ANY of the specified claims:
from rest_framework.views import APIView
from rest_framework.response import Response
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenScopes
class DataView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
access_token_scopes = ['read:data', 'write:data'] # OR logic
def get(self, request):
# User needs EITHER 'read:data' OR 'write:data' scope
return Response({'data': 'success'})
AND Logic - Requires ALL of the specified claims:
class SecureView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
access_token_all_scopes = ['read:data', 'write:data'] # AND logic
def post(self, request):
# User needs BOTH 'read:data' AND 'write:data' scopes
return Response({'status': 'created'})
Mixed Logic - Combine OR and AND requirements:
class MixedView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
access_token_any_scopes = ['read:data', 'read:all'] # Needs read:data OR read:all
access_token_all_scopes = ['openid', 'profile'] # AND needs BOTH openid AND profile
def get(self, request):
# User needs: (read:data OR read:all) AND (openid AND profile)
return Response({'data': 'complex authorization'})
Examples
Scope-Based Authorization
Check if openid or profile scope is present in the token:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenScopes
class ProfileView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
access_token_scopes = ['openid', 'profile'] # OR logic
def get(self, request):
return Response({'message': 'All good. You are authenticated!'}, status=status.HTTP_200_OK)
Role-Based Authorization
Check if sample:role role is present in the token:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenRoles
class SampleRoleView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenRoles]
access_token_roles = ['sample:role']
def get(self, request):
return Response({'message': 'Sample read.'}, status=status.HTTP_200_OK)
def post(self, request):
return Response({'message': 'Sample created.'}, status=status.HTTP_201_CREATED)
def patch(self, request):
return Response({'message': 'Sample updated.'}, status=status.HTTP_200_OK)
def delete(self, request):
return Response({'message': 'Sample deleted.'}, status=status.HTTP_204_NO_CONTENT)
Permission-Based Authorization
Check permissions at the API method level:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenPermissions
class SamplePermissionView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenPermissions]
@property
def access_token_permissions(self):
method_permissions = {
'GET': ['sample:read'],
'POST': ['sample:create'],
'PATCH': ['sample:update'],
'DELETE': ['sample:delete']
}
return method_permissions[self.request.method]
def get(self, request):
return Response({'message': 'Sample read.'}, status=status.HTTP_200_OK)
def post(self, request):
return Response({'message': 'Sample created.'}, status=status.HTTP_201_CREATED)
def patch(self, request):
return Response({'message': 'Sample updated.'}, status=status.HTTP_200_OK)
def delete(self, request):
return Response({'message': 'Sample deleted.'}, status=status.HTTP_204_NO_CONTENT)
Public Endpoints
Allow unauthenticated access for specific HTTP methods:
from rest_framework.views import APIView
from rest_framework.response import Response
from axioms_drf.authentication import IsAnyGetOrIsAccessTokenAuthenticated
class PublicReadView(APIView):
authentication_classes = [IsAnyGetOrIsAccessTokenAuthenticated]
def get(self, request):
# Anyone can read (no authentication required)
return Response({'articles': []})
def post(self, request):
# Requires valid JWT token to create
return Response({'status': 'created'})
Complete Example
For a complete working example, check out the Django REST Framework sample application on GitHub.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file axioms_drf_py-0.0.5rc11762722450.tar.gz.
File metadata
- Download URL: axioms_drf_py-0.0.5rc11762722450.tar.gz
- Upload date:
- Size: 35.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d0ac72245cce47df5a9a00f79369a0c88306b9e22ab907383a0cd1ab3e5d2ca
|
|
| MD5 |
65ed27ceb19ef80d03797074bcdcac28
|
|
| BLAKE2b-256 |
d4b9163fd0e4a3b3674486f766d9c1b91d630f2b7863f29060ce77179a5966da
|
Provenance
The following attestation bundles were made for axioms_drf_py-0.0.5rc11762722450.tar.gz:
Publisher:
release.yml on abhishektiwari/axioms-drf-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
axioms_drf_py-0.0.5rc11762722450.tar.gz -
Subject digest:
8d0ac72245cce47df5a9a00f79369a0c88306b9e22ab907383a0cd1ab3e5d2ca - Sigstore transparency entry: 685877690
- Sigstore integration time:
-
Permalink:
abhishektiwari/axioms-drf-py@19138490b4a5ebb7948489370c50479ed7db6c04 -
Branch / Tag:
refs/pull/1/merge - Owner: https://github.com/abhishektiwari
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@19138490b4a5ebb7948489370c50479ed7db6c04 -
Trigger Event:
pull_request
-
Statement type:
File details
Details for the file axioms_drf_py-0.0.5rc11762722450-py3-none-any.whl.
File metadata
- Download URL: axioms_drf_py-0.0.5rc11762722450-py3-none-any.whl
- Upload date:
- Size: 16.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95e38f3b7aea4517eed47a6085df7405c0ec06c9cc97ce18b99606d8afb74901
|
|
| MD5 |
cb9bdc24fcaa83c7db5e1e6b364e9564
|
|
| BLAKE2b-256 |
38a93ef3dc03ddaf88bf319f5d40bfa109a1ba8178151b22afa27e06b38b82c6
|
Provenance
The following attestation bundles were made for axioms_drf_py-0.0.5rc11762722450-py3-none-any.whl:
Publisher:
release.yml on abhishektiwari/axioms-drf-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
axioms_drf_py-0.0.5rc11762722450-py3-none-any.whl -
Subject digest:
95e38f3b7aea4517eed47a6085df7405c0ec06c9cc97ce18b99606d8afb74901 - Sigstore transparency entry: 685877691
- Sigstore integration time:
-
Permalink:
abhishektiwari/axioms-drf-py@19138490b4a5ebb7948489370c50479ed7db6c04 -
Branch / Tag:
refs/pull/1/merge - Owner: https://github.com/abhishektiwari
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@19138490b4a5ebb7948489370c50479ed7db6c04 -
Trigger Event:
pull_request
-
Statement type: