Integrating with Python Django API
This guide explains how to implement JWT authentication in your Django REST Framework API using AuthAction’s JWKS (JSON Web Key Set) endpoint.
Example Repository: For a complete working example, check out our example repository.
Prerequisites
Section titled “Prerequisites”Before you begin, ensure you have:
- Python 3.11+: Download from python.org
- AuthAction Account: You’ll need your AuthAction tenant domain and API identifier
Configuration
Section titled “Configuration”1. Install Required Packages
Section titled “1. Install Required Packages”pip install django djangorestframework python-jose[cryptography] httpx python-dotenv2. Configure AuthAction Settings
Section titled “2. Configure AuthAction Settings”Create a .env file in your project root:
AUTHACTION_DOMAIN=your-authaction-tenant-domainAUTHACTION_AUDIENCE=your-authaction-api-identifierDJANGO_SECRET_KEY=your-django-secret-key3. Implement JWT Authentication
Section titled “3. Implement JWT Authentication”Create api/authentication.py:
import osimport httpxfrom jose import JWTError, jwtfrom jose.exceptions import ExpiredSignatureErrorfrom rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailed
AUTHACTION_DOMAIN = os.getenv("AUTHACTION_DOMAIN")AUTHACTION_AUDIENCE = os.getenv("AUTHACTION_AUDIENCE")
_jwks_cache: dict | None = None
def _get_jwks() -> dict: global _jwks_cache if _jwks_cache is None: jwks_uri = f"https://{AUTHACTION_DOMAIN}/.well-known/jwks.json" response = httpx.get(jwks_uri) response.raise_for_status() _jwks_cache = response.json() return _jwks_cache
def _find_rsa_key(token: str) -> dict: jwks = _get_jwks() unverified_header = jwt.get_unverified_header(token) kid = unverified_header.get("kid")
for key in jwks.get("keys", []): if key.get("kid") == kid: return {"kty": key["kty"], "kid": key["kid"], "use": key["use"], "n": key["n"], "e": key["e"]}
# Key not found — could be a rotation; bust cache and retry once global _jwks_cache _jwks_cache = None jwks = _get_jwks() for key in jwks.get("keys", []): if key.get("kid") == kid: return {"kty": key["kty"], "kid": key["kid"], "use": key["use"], "n": key["n"], "e": key["e"]}
raise AuthenticationFailed("Unable to find matching public key")
def verify_token(token: str) -> dict: try: rsa_key = _find_rsa_key(token) return jwt.decode( token, rsa_key, algorithms=["RS256"], audience=AUTHACTION_AUDIENCE, issuer=f"https://{AUTHACTION_DOMAIN}", ) except ExpiredSignatureError: raise AuthenticationFailed("Token has expired") except JWTError as exc: raise AuthenticationFailed(str(exc))
class AuthenticatedToken: def __init__(self, payload: dict): self.payload = payload self.is_authenticated = True
@property def sub(self) -> str: return self.payload.get("sub", "")
class JWTAuthentication(BaseAuthentication): def authenticate(self, request): auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): return None
token = auth_header.split(" ", 1)[1].strip() if not token: raise AuthenticationFailed("Empty token")
payload = verify_token(token) return AuthenticatedToken(payload), token
def authenticate_header(self, request): return "Bearer"4. Configure DRF Settings
Section titled “4. Configure DRF Settings”In settings.py, set JWTAuthentication as the default so all views are protected unless explicitly overridden:
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "api.authentication.JWTAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ],}1. Create Your Views
Section titled “1. Create Your Views”from rest_framework.decorators import api_view, authentication_classes, permission_classesfrom rest_framework.permissions import AllowAny, IsAuthenticatedfrom rest_framework.request import Requestfrom rest_framework.response import Responsefrom api.authentication import JWTAuthentication
@api_view(["GET"])@authentication_classes([])@permission_classes([AllowAny])def public_view(request: Request) -> Response: return Response({"message": "This is a public message!"})
@api_view(["GET"])@authentication_classes([JWTAuthentication])@permission_classes([IsAuthenticated])def protected_view(request: Request) -> Response: return Response({ "message": "This is a protected message!", "sub": request.user.sub, })2. Testing the API
Section titled “2. Testing the API”- Obtain an Access Token:
curl --request POST \ --url https://your-authaction-tenant-domain/oauth2/m2m/token \ --header 'content-type: application/json' \ --data '{ "client_id": "your-authaction-m2m-app-clientid", "client_secret": "your-authaction-m2m-app-client-secret", "audience": "your-authaction-api-identifier", "grant_type": "client_credentials" }'- Call the Public Endpoint:
curl http://localhost:8000/public- Call the Protected Endpoint:
curl --request GET \ --url http://localhost:8000/protected \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'Common Issues
Section titled “Common Issues”Invalid Token Errors
Section titled “Invalid Token Errors”- Verify
AUTHACTION_DOMAINandAUTHACTION_AUDIENCEmatch your AuthAction dashboard exactly - Ensure the token is signed with the RS256 algorithm
Public Key Fetching Errors
Section titled “Public Key Fetching Errors”- Verify your application can reach
https://your-authaction-tenant-domain/.well-known/jwks.json
Unauthorized Access
Section titled “Unauthorized Access”- Ensure the
Authorization: Bearer <token>header is present and the token is not expired - Confirm the token’s audience matches your API identifier