API Reference

Contents

API Reference#

This page contains the full API reference for axioms-drf-py, automatically generated from the source code docstrings.

Core Configuration#

The SDK requires the following Django settings to be configured:

Parameter

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

Important

Either AXIOMS_JWKS_URL, AXIOMS_ISS_URL, or AXIOMS_DOMAIN must be configured for token validation.

Configuration Hierarchy:

The SDK uses the following construction order:

  1. AXIOMS_DOMAIN → constructs → AXIOMS_ISS_URL (if not explicitly set)

  2. AXIOMS_ISS_URL → constructs → AXIOMS_JWKS_URL (if not explicitly set)

Example: Setting only AXIOMS_DOMAIN=auth.example.com/oauth results in:

  • AXIOMS_ISS_URL: https://auth.example.com/oauth

  • AXIOMS_JWKS_URL: https://auth.example.com/oauth/.well-known/jwks.json

Security & Algorithm Validation#

The SDK implements multiple security best practices to prevent common JWT attacks:

Algorithm Validation

Only secure asymmetric algorithms are accepted for JWT signature verification. The SDK validates that:

  1. The alg header in the JWT specifies an allowed algorithm

  2. Each key is used with exactly one algorithm

  3. The algorithm validation occurs before cryptographic operations

Supported Algorithms:

  • RSA: RS256, RS384, RS512

  • ECDSA: ES256, ES384, ES512

  • RSA-PSS: PS256, PS384, PS512

Rejected Algorithms:

  • none - No signature (critical security vulnerability)

  • HS256, HS384, HS512 - Symmetric algorithms (prevents key confusion attacks)

  • Any algorithm not in the allowed list above

This prevents algorithm confusion attacks where an attacker might try to:

  • Use the none algorithm to bypass signature verification

  • Substitute an asymmetric algorithm with a symmetric one

  • Use weak or deprecated algorithms

Additional Security Features:

  • Issuer validation (iss claim) to prevent token substitution

  • Automatic public key retrieval and validation from JWKS endpoints

  • Token expiration validation

  • Audience claim validation

  • Key ID (kid) validation

Claim Name Mapping#

Configure custom claim names to support different authorization servers (AWS Cognito, Auth0, Okta, Microsoft Entra). These mapping options provide additional customization for claim names used to extract scopes, roles, and permissions from the JWT token. These mappings can also support RFC 9068 JWT Profile for OAuth 2.0 Access Tokens.

Parameter

Required

Description

AXIOMS_SCOPE_CLAIMS

No

List of scope claim names to check in priority order.

Default: ['scope']

Example: ['scope', 'scp']

AXIOMS_ROLES_CLAIMS

No

List of role claim names to check in priority order.

Default: ['roles']

Example: ['roles', 'cognito:roles']

AXIOMS_PERMISSIONS_CLAIMS

No

List of permission claim names to check in priority order.

Default: ['permissions']

Example: ['permissions', 'cognito:groups', 'groups', 'entitlements']

Important

Namespaced Claims: You can specify namespaced claim names directly in the claim configuration lists.

The SDK will check claims in the order you specify them, using the first non-None value found.

Example: AXIOMS_ROLES_CLAIMS = ['roles', 'https://myapp.com/claims/roles', 'cognito:groups']

Setting Django Settings#

You can configure these settings in your Django settings.py file:

# Required
AXIOMS_AUDIENCE = 'your-api-audience-or-resource-identifier'

# Option 1: Use AXIOMS_DOMAIN (simplest)
AXIOMS_DOMAIN = 'your-domain.axioms.io'

# Option 2: Use AXIOMS_ISS_URL (more control)
# AXIOMS_ISS_URL = 'https://my-auth.domain.com/oauth2'

# Option 3: Use AXIOMS_JWKS_URL (full control)
# AXIOMS_JWKS_URL = 'https://my-auth.domain.com/oauth2/.well-known/jwks.json'

# Optional: Custom claim names
# AXIOMS_SCOPE_CLAIMS = ['scope', 'scp']
# AXIOMS_ROLES_CLAIMS = ['roles', 'cognito:groups']
# AXIOMS_PERMISSIONS_CLAIMS = ['permissions', 'custom:permissions']

Middleware#

The middleware module provides Django middleware for JWT token extraction and validation.

Django middleware for JWT token extraction and validation.

This module provides middleware that extracts JWT tokens from the Authorization header and validates them before the request reaches the view. It sets attributes on the request object that authentication classes use to determine access.

The middleware must be added to Django’s MIDDLEWARE setting before the request reaches any views that require authentication.

Configuration:

Add to Django settings.py:

MIDDLEWARE = [
    'axioms_drf.middleware.AccessTokenMiddleware',
    # ... other middleware
]

# Required settings
AXIOMS_AUDIENCE = 'your-api-audience'

# Optional settings (one of these is required)
AXIOMS_DOMAIN = 'your-auth-domain.com'
AXIOMS_ISS_URL = 'https://your-auth-domain.com'
AXIOMS_JWKS_URL = 'https://your-auth-domain.com/.well-known/jwks.json'
Classes:

AccessTokenMiddleware: Main middleware for JWT token processing.

class AccessTokenMiddleware(get_response)[source]#

Bases: MiddlewareMixin

Middleware that extracts and validates JWT tokens from Authorization header.

This middleware processes incoming requests to extract JWT tokens from the Authorization header, validates them, and sets request attributes that authentication classes use to grant or deny access.

The middleware sets the following attributes on the request:
auth_jwt (Box|False|None): Validated token payload as Box object, False if

validation failed, None if no token provided.

missing_auth_header (bool): True if Authorization header is missing. invalid_bearer_token (bool): True if Bearer format is invalid.

Token validation includes:
  • Signature verification using JWKS

  • Expiration time check

  • Audience claim validation

  • Issuer claim validation (if configured)

  • Algorithm validation (only secure asymmetric algorithms allowed)

The middleware catches all validation exceptions and sets auth_jwt=False, allowing authentication classes to handle the error appropriately.

Example:

# In settings.py
MIDDLEWARE = [
    'axioms_drf.middleware.AccessTokenMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ... other middleware
]

AXIOMS_AUDIENCE = 'my-api'
AXIOMS_DOMAIN = 'auth.example.com'
Raises:

Exception – If required settings are not configured. AXIOMS_AUDIENCE is always required. At least one JWKS source must be configured: AXIOMS_JWKS_URL, AXIOMS_ISS_URL, or AXIOMS_DOMAIN.

Note

This middleware should be placed early in the middleware stack, before any authentication-dependent middleware.

process_request(request)[source]#

Process incoming request to extract and validate JWT token.

This method is called for every request before it reaches the view. It extracts the JWT token from the Authorization header, validates it, and sets request attributes for use by authentication classes.

Request attributes set:
auth_jwt: Box object with token payload if valid, False if invalid,

None if missing

missing_auth_header: True if Authorization header not present invalid_bearer_token: True if header doesn’t match

Bearer <token> format

Parameters:

request – Django HttpRequest object.

Raises:

Exception – If AXIOMS_AUDIENCE is not configured or if none of the JWKS source settings (AXIOMS_JWKS_URL, AXIOMS_ISS_URL, or AXIOMS_DOMAIN) are configured.

Returns:

This method doesn’t return anything, it modifies the request in-place.

Return type:

None

JWKS Manager#

The apps module provides Django application configurations for initializing the JWKS manager.

Django AppConfig classes for automatic JWKS manager lifecycle management.

These apps handle initialization and shutdown of the JWKS manager automatically. Users can simply add the appropriate app to their INSTALLED_APPS.

For standard Django/WSGI applications:
INSTALLED_APPS = [

‘axioms_drf.apps.JWKSManagerWSGIConfig’, # … other apps

]

For Django ASGI applications:
INSTALLED_APPS = [

‘axioms_drf.apps.JWKSManagerASGIConfig’, # … other apps

]

class JWKSManagerWSGIConfig(app_name, app_module)[source]#

Bases: AppConfig

AppConfig for JWKS manager with background refresh for WSGI applications.

This app automatically initializes the JWKS manager with background refresh on Django startup and properly shuts it down on application exit.

Usage:

Add to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [

‘axioms_drf.apps.JWKSManagerWSGIConfig’, # … other apps

]

Configuration (optional):

AXIOMS_JWKS_REFRESH_INTERVAL = 3600 # Refresh every 1hr (default) AXIOMS_JWKS_CACHE_TTL = 7200 # Cache for 2hrs (default)

For standard Django/WSGI applications. Use JWKSManagerASGIConfig for ASGI.

name = 'axioms_drf'#
label = 'axioms_drf_jwks_wsgi'#
verbose_name = 'Axioms DRF JWKS Manager (WSGI)'#
ready()[source]#

Initialize JWKS manager on application startup.

class JWKSManagerASGIConfig(app_name, app_module)[source]#

Bases: AppConfig

AppConfig for JWKS manager with background refresh for ASGI applications.

This app automatically initializes the async JWKS manager with background refresh on Django startup and properly shuts it down on application exit.

Usage:

Add to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [

‘axioms_drf.apps.JWKSManagerASGIConfig’, # … other apps

]

Configuration (optional):

AXIOMS_JWKS_REFRESH_INTERVAL = 3600 # Refresh every 1hr (default) AXIOMS_JWKS_CACHE_TTL = 7200 # Cache for 2hrs (default)

For Django ASGI applications. Use JWKSManagerWSGIConfig for WSGI.

name = 'axioms_drf'#
label = 'axioms_drf_jwks_asgi'#
verbose_name = 'Axioms DRF JWKS Manager (ASGI)'#
ready()[source]#

Initialize async JWKS manager on application startup.

Authentication Classes#

The authentication module provides Django REST Framework authentication classes for JWT token validation.

Django REST Framework authentication classes for JWT token validation.

This module provides authentication classes that integrate with Django REST Framework to validate OAuth2/OIDC JWT access tokens. It works in conjunction with the AccessTokenMiddleware to perform token validation.

Configuration:

Configure safe HTTP methods that bypass authentication in Django settings:

# Optional: Configure safe HTTP methods (defaults to HEAD and OPTIONS)
AXIOMS_SAFE_METHODS = ('HEAD', 'OPTIONS', 'GET')
Classes:

HasValidAccessToken: Main authentication class requiring valid JWT token. IsAccessTokenAuthenticated: Alias for HasValidAccessToken. IsAnyPostOrIsAccessTokenAuthenticated: Allows POST without authentication. IsAnyGetOrIsAccessTokenAuthenticated: Allows GET without authentication. MissingAuthorizationHeader: Exception for missing Authorization header. InvalidAuthorizationBearer: Exception for invalid Bearer token format. UnauthorizedAccess: Exception for invalid or expired tokens.

Example:

from rest_framework.views import APIView
from rest_framework.response import Response
from axioms_drf.authentication import HasValidAccessToken

class ProtectedView(APIView):
    authentication_classes = [HasValidAccessToken]

    def get(self, request):
        # User is authenticated with valid JWT token
        return Response({'user': request.user})
class HasValidAccessToken[source]#

Bases: BaseAuthentication

Authentication class that validates JWT access tokens.

This class integrates with AccessTokenMiddleware which performs the actual token validation. The middleware sets request.auth_jwt with the validated token payload, or flags for missing/invalid tokens.

The authentication succeeds when: - A valid JWT token is present in the Authorization header - The token has not expired - The token has valid signature and claims - The token audience matches configured AXIOMS_AUDIENCE

Safe HTTP methods (HEAD, OPTIONS by default) are allowed without authentication to support CORS preflight. Configure AXIOMS_SAFE_METHODS setting to customize.

Raises:
Returns:

(user_identifier, auth_success) where user_identifier is the sub claim.

Return type:

tuple

authenticate(request)[source]#

Authenticate the request using JWT token from middleware.

Parameters:

request – Django REST Framework request object with auth_jwt attribute set by AccessTokenMiddleware.

Returns:

(user_identifier, True) if authentication succeeds, where

user_identifier is the subject claim from the token.

None: If no authentication is required (safe HTTP methods).

Return type:

tuple

Raises:
authenticate_header(request)[source]#

Return the WWW-Authenticate header value for 401 responses.

Parameters:

request – Django REST Framework request object.

Returns:

WWW-Authenticate header value following RFC 6750.

Return type:

str

class IsAccessTokenAuthenticated[source]#

Bases: HasValidAccessToken

Alias for HasValidAccessToken.

This class provides the same functionality as HasValidAccessToken. Use this if you prefer the naming style.

authenticate(request)[source]#

Authenticate using parent class implementation.

Parameters:

request – Django REST Framework request object.

Returns:

Same as HasValidAccessToken.authenticate().

Return type:

tuple

class IsAnyPostOrIsAccessTokenAuthenticated[source]#

Bases: HasValidAccessToken

Authentication class that allows POST requests without authentication.

Useful for public endpoints that accept unauthenticated POST requests (e.g., user registration, password reset) but require authentication for other methods.

Example:

class RegisterView(APIView):
    authentication_classes = [IsAnyPostOrIsAccessTokenAuthenticated]

    def post(self, request):
        # Anyone can register (no auth required)
        return Response({'status': 'registered'})

    def get(self, request):
        # Requires valid JWT token
        return Response({'user': request.user})
authenticate(request)[source]#

Authenticate request, allowing POST without token.

Parameters:

request – Django REST Framework request object.

Returns:

(None, True) for POST requests, otherwise delegates to parent.

Return type:

tuple

class IsAnyGetOrIsAccessTokenAuthenticated[source]#

Bases: HasValidAccessToken

Authentication class that allows GET requests without authentication.

Useful for public read endpoints that don’t require authentication for viewing but require authentication for modifications.

Example:

class ArticleView(APIView):
    authentication_classes = [IsAnyGetOrIsAccessTokenAuthenticated]

    def get(self, request):
        # Anyone can read articles (no auth required)
        return Response({'articles': []})

    def post(self, request):
        # Requires valid JWT token to create
        return Response({'status': 'created'})
authenticate(request)[source]#

Authenticate request, allowing GET without token.

Parameters:

request – Django REST Framework request object.

Returns:

(None, True) for GET requests, otherwise delegates to parent.

Return type:

tuple

exception MissingAuthorizationHeader(detail=None, code=None)[source]#

Bases: APIException

Exception raised when Authorization header is missing from request.

This exception is raised when a protected endpoint is accessed without providing the Authorization header.

Follows RFC 6750 OAuth 2.0 Bearer Token Usage standard.

status_code#

HTTP 401 Unauthorized

default_detail#

Error message dict with RFC 6750 compliant error and description

default_code#

invalid_token

status_code = 401#
default_detail = {'error': 'invalid_token', 'error_description': 'Missing Authorization Header'}#
default_code = 'invalid_token'#
exception InvalidAuthorizationBearer(detail=None, code=None)[source]#

Bases: APIException

Exception raised when Bearer token format is invalid.

This exception is raised when the Authorization header is present but doesn’t follow the Bearer <token> format.

Follows RFC 6750 OAuth 2.0 Bearer Token Usage standard.

status_code#

HTTP 401 Unauthorized

default_detail#

Error message dict with RFC 6750 compliant error and description

default_code#

invalid_token

status_code = 401#
default_detail = {'error': 'invalid_token', 'error_description': 'Invalid Authorization Bearer'}#
default_code = 'invalid_token'#
exception UnauthorizedAccess(detail=None, code=None)[source]#

Bases: APIException

Exception raised when JWT token validation fails.

This exception is raised when: - Token signature is invalid - Token has expired - Token audience doesn’t match configured AXIOMS_AUDIENCE - Token issuer doesn’t match configured AXIOMS_ISS_URL - Token algorithm is not in allowed algorithms - Token is missing required claims

Follows RFC 6750 OAuth 2.0 Bearer Token Usage standard.

status_code#

HTTP 401 Unauthorized

default_detail#

Error message dict with RFC 6750 compliant error and description

default_code#

invalid_token

status_code = 401#
default_detail = {'error': 'invalid_token', 'error_description': 'Invalid access token'}#
default_code = 'invalid_token'#

Permission Classes#

The permissions module provides Django REST Framework permission classes for claim-based authorization.

Django REST Framework permission classes for JWT claim-based authorization.

This module provides permission classes that perform authorization based on claims in validated JWT tokens (scopes, roles, permissions). These classes work with the authentication classes and middleware to provide fine-grained access control.

Permission Logic:

Each permission class supports both OR and AND logic through different attributes: - _any_ attributes: User needs ANY ONE of the specified claims (OR logic) - _all_ attributes: User needs ALL of the specified claims (AND logic)

Configuration:

Configure custom claim names in Django settings:

# Optional: Configure custom claim names for roles
AXIOMS_ROLES_CLAIMS = ['roles', 'https://example.com/claims/roles']

# Optional: Configure custom claim names for permissions
AXIOMS_PERMISSIONS_CLAIMS = ['permissions', 'https://example.com/claims/permissions']

# Optional: Configure custom claim names for scopes
AXIOMS_SCOPE_CLAIMS = ['scope', 'scp']
Classes:
  • HasAccessTokenScopes: Check scopes (supports both OR and AND logic).

  • HasAccessTokenRoles: Check roles (supports both OR and AND logic).

  • HasAccessTokenPermissions: Check permissions (supports both OR and AND logic).

  • IsSubOwner: Object-level permission for token subject ownership.

  • IsSubOwnerOrSafeOnly: Object-level permission allowing safe methods or owner access.

  • IsSafeOnly: Permission allowing only safe HTTP methods.

  • InsufficientPermission: Exception raised when authorization fails.

Example:

from rest_framework.views import APIView
from rest_framework.response import Response
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenScopes, HasAccessTokenRoles

# OR logic - user needs ANY ONE scope
class DataView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenScopes]
    access_token_scopes = ['read:data', 'write:data']  # OR logic (backward compatible)
    # OR use: access_token_any_scopes = ['read:data', 'write:data']

    def get(self, request):
        return Response({'data': 'protected'})

# AND logic - user needs ALL scopes
class SecureView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenScopes]
    access_token_all_scopes = ['read:data', 'write:data']  # AND logic

    def post(self, request):
        return Response({'status': 'created'})
class HasAccessTokenScopes[source]#

Bases: BasePermission

Permission class that checks if user has required scopes.

Supports both OR logic (any scope) and AND logic (all scopes) through different view attributes:

  • access_token_scopes or access_token_any_scopes: User needs ANY ONE (OR logic)

  • access_token_all_scopes: User needs ALL (AND logic)

access_token_scopes#

List of scopes (OR logic, backward compatible).

access_token_any_scopes#

List of scopes (OR logic, explicit).

access_token_all_scopes#

List of scopes (AND logic).

Example:

# OR logic - user needs read OR write
class DataView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenScopes]
    access_token_scopes = ['read:data', 'write:data']

# AND logic - user needs BOTH read AND write
class SecureView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenScopes]
    access_token_all_scopes = ['read:data', 'write:data']

# Method-level scopes - different scopes for each HTTP method
class MethodLevelView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenScopes]

    @property
    def access_token_scopes(self):
        method_scopes = {
            'GET': ['read:data'],
            'POST': ['write:data'],
            'DELETE': ['delete:data']
        }
        return method_scopes[self.request.method]

    def get(self, request):
        return Response({'data': []})

    def post(self, request):
        return Response({'status': 'created'})

# ViewSet with action-specific scopes
from rest_framework import viewsets

class ArticleViewSet(viewsets.ModelViewSet):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenScopes]
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    @property
    def access_token_scopes(self):
        action_scopes = {
            'list': ['article:read'],
            'retrieve': ['article:read'],
            'create': ['article:create'],
            'update': ['article:update'],
            'partial_update': ['article:update'],
            'destroy': ['article:delete'],
        }
        return action_scopes.get(self.action, [])
Raises:
  • InsufficientPermission – If user doesn’t have required scopes.

  • ImproperlyConfigured – If no scope attribute is defined on the view.

message = 'Permission Denied'#
has_permission(request, view)[source]#

Check if user has required scopes.

Parameters:
  • request – Django REST Framework request with auth_jwt attribute.

  • view – View instance with scope attributes.

Returns:

True if user has required scopes.

Return type:

bool

Raises:

InsufficientPermission – If authorization fails.

class HasAccessTokenRoles[source]#

Bases: BasePermission

Permission class that checks if user has required roles.

Supports both OR logic (any role) and AND logic (all roles) through different view attributes:

  • access_token_roles or access_token_any_roles: User needs ANY ONE (OR logic)

  • access_token_all_roles: User needs ALL (AND logic)

access_token_roles#

List of roles (OR logic, backward compatible).

access_token_any_roles#

List of roles (OR logic, explicit).

access_token_all_roles#

List of roles (AND logic).

Example:

# OR logic - user needs admin OR moderator
class AdminView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenRoles]
    access_token_roles = ['admin', 'moderator']

# AND logic - user needs BOTH admin AND superuser
class SuperAdminView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenRoles]
    access_token_all_roles = ['admin', 'superuser']

# Method-level roles - different roles for each HTTP method
class MethodLevelView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenRoles]

    @property
    def access_token_roles(self):
        method_roles = {
            'GET': ['viewer', 'editor'],
            'POST': ['editor', 'admin'],
            'DELETE': ['admin']
        }
        return method_roles[self.request.method]

    def get(self, request):
        return Response({'data': []})

    def post(self, request):
        return Response({'status': 'created'})

# ViewSet with action-specific roles
from rest_framework import viewsets

class UserViewSet(viewsets.ModelViewSet):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenRoles]
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @property
    def access_token_roles(self):
        action_roles = {
            'list': ['viewer', 'editor', 'admin'],
            'retrieve': ['viewer', 'editor', 'admin'],
            'create': ['admin'],
            'update': ['editor', 'admin'],
            'partial_update': ['editor', 'admin'],
            'destroy': ['admin'],
        }
        return action_roles.get(self.action, [])
Raises:
  • InsufficientPermission – If user doesn’t have required roles.

  • ImproperlyConfigured – If no role attribute is defined on the view.

message = 'Permission Denied'#
has_permission(request, view)[source]#

Check if user has required roles.

Parameters:
  • request – Django REST Framework request with auth_jwt attribute.

  • view – View instance with role attributes.

Returns:

True if user has required roles.

Return type:

bool

Raises:

InsufficientPermission – If authorization fails.

class HasAccessTokenPermissions[source]#

Bases: BasePermission

Permission class that checks if user has required permissions.

Supports both OR logic (any permission) and AND logic (all permissions) through different view attributes:

  • access_token_permissions or access_token_any_permissions: User needs ANY ONE (OR logic)

  • access_token_all_permissions: User needs ALL (AND logic)

access_token_permissions#

List of permissions (OR logic, backward compatible).

access_token_any_permissions#

List of permissions (OR logic, explicit).

access_token_all_permissions#

List of permissions (AND logic).

Example:

# OR logic - user needs read OR admin permission
class UserView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenPermissions]
    access_token_permissions = ['user:read', 'user:admin']

# AND logic - user needs BOTH write AND delete
class CriticalView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenPermissions]
    access_token_all_permissions = ['user:write', 'user:delete']

# Method-level permissions - different permission for each HTTP method
class MethodLevelView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenPermissions]

    @property
    def access_token_permissions(self):
        method_permissions = {
            'GET': ['user:read'],
            'POST': ['user:create'],
            'PATCH': ['user:update'],
            'DELETE': ['user:delete']
        }
        return method_permissions[self.request.method]

    def get(self, request):
        return Response({'message': 'User read.'})

    def post(self, request):
        return Response({'message': 'User created.'})

# ViewSet with action-specific permissions
from rest_framework import viewsets

class DocumentViewSet(viewsets.ModelViewSet):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [HasAccessTokenPermissions]
    queryset = Document.objects.all()
    serializer_class = DocumentSerializer

    @property
    def access_token_permissions(self):
        action_permissions = {
            'list': ['document:read'],
            'retrieve': ['document:read'],
            'create': ['document:create'],
            'update': ['document:update'],
            'partial_update': ['document:update'],
            'destroy': ['document:delete'],
        }
        return action_permissions.get(self.action, [])
Raises:
  • InsufficientPermission – If user doesn’t have required permissions.

  • ImproperlyConfigured – If no permission attribute is defined on the view.

message = 'Permission Denied'#
has_permission(request, view)[source]#

Check if user has required permissions.

Parameters:
  • request – Django REST Framework request with auth_jwt attribute.

  • view – View instance with permission attributes.

Returns:

True if user has required permissions.

Return type:

bool

Raises:

InsufficientPermission – If authorization fails.

class IsSubOwner[source]#

Bases: BasePermission

Object-level permission that checks if the token subject matches the object owner.

This permission class checks if the sub (subject) claim from the JWT token matches a specified attribute on the object being accessed. This is useful for ensuring users can only access their own resources.

owner_attribute#

Name of the object attribute to compare with token sub. Defaults to 'user'.

Example:

# Basic usage - compares token sub with object.owner
class ArticleDetailView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [IsSubOwner]
    owner_attribute = 'author_id'  # Compare with object.author_id

    def get_object(self):
        return Article.objects.get(pk=self.kwargs['pk'])

    def get(self, request, pk):
        article = self.get_object()
        self.check_object_permissions(request, article)
        return Response({'title': article.title})

# Using with ViewSet
class ArticleViewSet(viewsets.ModelViewSet):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [IsSubOwner]
    owner_attribute = 'user_id'
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
Raises:
  • InsufficientPermission – If token subject doesn’t match object owner.

  • ImproperlyConfigured – If owner_attribute is not defined.

message = 'Permission Denied - Not the owner'#
has_object_permission(request, view, obj)[source]#

Check if token subject matches object owner attribute.

Parameters:
  • request – Django REST Framework request with auth_jwt attribute.

  • view – View instance with owner_attribute.

  • obj – Object being accessed.

Returns:

True if token subject matches object owner.

Return type:

bool

Raises:
  • InsufficientPermission – If authorization fails.

  • ImproperlyConfigured – If owner_attribute is not set or object doesn’t have the attribute.

class IsSubOwnerOrSafeOnly[source]#

Bases: BasePermission

Object-level permission for safe methods or owner-only modifications.

Allows safe HTTP methods (GET, HEAD, OPTIONS by default) for all authenticated users, but restricts unsafe methods (POST, PUT, PATCH, DELETE) to the object owner. Owner is determined by comparing the token sub claim with a specified object attribute.

owner_attribute#

Name of the object attribute to compare with token sub. Defaults to 'owner'.

safe_methods#

Tuple of HTTP methods considered safe. Defaults to ('GET', 'HEAD', 'OPTIONS').

Example:

# Allow anyone to read, but only owner can update/delete
class ArticleDetailView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [IsSubOwnerOrSafeOnly]
    owner_attribute = 'author_id'
    safe_methods = ('GET', 'HEAD', 'OPTIONS')

    def get_object(self):
        return Article.objects.get(pk=self.kwargs['pk'])

    def get(self, request, pk):
        # Anyone can read
        article = self.get_object()
        self.check_object_permissions(request, article)
        return Response({'title': article.title})

    def put(self, request, pk):
        # Only owner can update
        article = self.get_object()
        self.check_object_permissions(request, article)
        # Update logic
        return Response({'status': 'updated'})

# Using with ViewSet
class ArticleViewSet(viewsets.ModelViewSet):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [IsSubOwnerOrSafeOnly]
    owner_attribute = 'user_id'
    safe_methods = ('GET', 'HEAD', 'OPTIONS', 'LIST')
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
Raises:
  • InsufficientPermission – If non-safe method and token subject doesn’t match owner.

  • ImproperlyConfigured – If owner_attribute is not defined.

message = 'Permission Denied - Safe methods only or must be owner'#
has_object_permission(request, view, obj)[source]#

Check if request method is safe or token subject matches owner.

Parameters:
  • request – Django REST Framework request with auth_jwt and method.

  • view – View instance with owner_attribute and safe_methods.

  • obj – Object being accessed.

Returns:

True if method is safe or user is owner.

Return type:

bool

Raises:

InsufficientPermission – If authorization fails.

class IsSafeOnly[source]#

Bases: BasePermission

Permission that only allows safe HTTP methods.

Restricts access to safe HTTP methods only (GET, HEAD, OPTIONS by default). Useful for read-only endpoints where authenticated users can view but not modify.

safe_methods#

Tuple of HTTP methods considered safe. Defaults to ('GET', 'HEAD', 'OPTIONS').

Example:

# Read-only access for all authenticated users
class ArticleListView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [IsSafeOnly]
    safe_methods = ('GET', 'HEAD', 'OPTIONS')

    def get(self, request):
        articles = Article.objects.all()
        return Response({'articles': list(articles.values())})

    def post(self, request):
        # This will be denied by IsSafeOnly permission
        return Response({'status': 'created'})

# Custom safe methods including LIST
class CustomReadOnlyView(APIView):
    authentication_classes = [HasValidAccessToken]
    permission_classes = [IsSafeOnly]
    safe_methods = ('GET', 'HEAD', 'OPTIONS', 'LIST')

    def get(self, request):
        return Response({'data': 'read-only'})
Raises:

InsufficientPermission – If request method is not in safe_methods.

message = 'Permission Denied - Safe methods only'#
has_permission(request, view)[source]#

Check if request method is safe.

Parameters:
  • request – Django REST Framework request with method.

  • view – View instance with optional safe_methods attribute.

Returns:

True if method is safe.

Return type:

bool

Raises:

InsufficientPermission – If method is not safe.

exception InsufficientPermission(detail=None, code=None)[source]#

Bases: APIException

Exception raised when user lacks required scopes, roles, or permissions.

This exception is raised by permission classes when a user’s JWT token doesn’t contain the required claims for accessing a protected endpoint.

Follows RFC 6750 OAuth 2.0 Bearer Token Usage standard.

status_code#

HTTP 403 Forbidden

default_detail#

Error message dict with RFC 6750 compliant error and description

default_code#

insufficient_scope

Example:

# Automatically raised by permission classes
class ProtectedView(APIView):
    permission_classes = [HasAccessTokenScopes]
    access_token_scopes = ['admin']

    def get(self, request):
        # InsufficientPermission raised if user lacks 'admin' scope
        return Response({'data': 'protected'})
status_code = 403#
default_detail = {'error': 'insufficient_scope', 'error_description': 'Insufficient role, scope or permission'}#
default_code = 'insufficient_scope'#

Helper Functions#

The helper module provides utility functions for token validation and claim extraction.

Token validation helpers for Axioms DRF.

This module provides helper functions for JWT token validation with support for algorithm validation, issuer validation, and configuration hierarchy.

build_config_from_django_settings()[source]#

Build configuration dict from Django settings for axioms-core-py.

Returns:

Configuration dictionary compatible with axioms-core-py.

Return type:

dict

has_valid_token(token)[source]#

Validate JWT token with algorithm and issuer validation.

Parameters:

token – JWT token string.

Returns:

Immutable (frozen) Box containing validated JWT payload. The returned Box

cannot be modified to prevent tampering with validated token claims.

Return type:

Box

Raises:

UnauthorizedAccess – If token is invalid.