Skip to main content

OAuth2/OIDC authentication and authorization for Django REST Framework APIs

Project description

axioms-drf-py PyPI Pepy Total Downloads

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.

** Using Flask or FastAPI?** This package is specifically for Django REST Framework. For Flask applications, use axioms-flask-py. For FastAPI applications, use axioms-fastapi.

GitHub Release GitHub Actions Test Workflow Status PyPI - Version Python Wheels Python Versions GitHub last commit PyPI - Status License PyPI Downloads

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 (iss claim) 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:

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

Note: You must provide at least one of: AXIOMS_DOMAIN, AXIOMS_ISS_URL, or AXIOMS_JWKS_URL. For most use cases, setting only AXIOMS_DOMAIN is 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)

Method-Level Authorization: All permission classes support method-level authorization using Python's @property decorator. This allows you to define different authorization requirements for each HTTP method (GET, POST, PATCH, DELETE) on the same view. See the Method-Level Permissions section for implementation details.

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)

Method-Level Permissions

Check permissions at the API method level using properties:

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

axioms_drf_py-0.0.6rc21762725241.tar.gz (37.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

axioms_drf_py-0.0.6rc21762725241-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file axioms_drf_py-0.0.6rc21762725241.tar.gz.

File metadata

File hashes

Hashes for axioms_drf_py-0.0.6rc21762725241.tar.gz
Algorithm Hash digest
SHA256 884f1f2615d5649afd7159a47957e2f4280505c3e46159f141f1b01d1905e205
MD5 1fe65daec4909122dd0e289af0c82b3b
BLAKE2b-256 8c9565af9f6e660c3b21777f1c31418778b1432e7fac8a0f2b93728df3858edc

See more details on using hashes here.

Provenance

The following attestation bundles were made for axioms_drf_py-0.0.6rc21762725241.tar.gz:

Publisher: release.yml on abhishektiwari/axioms-drf-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file axioms_drf_py-0.0.6rc21762725241-py3-none-any.whl.

File metadata

File hashes

Hashes for axioms_drf_py-0.0.6rc21762725241-py3-none-any.whl
Algorithm Hash digest
SHA256 b31a784cee950b743fa5168119651183dc928ef0c1bce51c7b86279545b436c0
MD5 314fcb45ddcc2210af7233261d5e8ad4
BLAKE2b-256 498d7fa107ed581d1342191a78e20f87af3a16345a23329fe292bec1e7f1b3e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for axioms_drf_py-0.0.6rc21762725241-py3-none-any.whl:

Publisher: release.yml on abhishektiwari/axioms-drf-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page