Skip to content

Integrating with Python Django API

Terminal window
pip install "authaction-python-sdk[django]"
settings.py
AUTHACTION = {
"DOMAIN": os.getenv("AUTHACTION_DOMAIN"),
"AUDIENCE": os.getenv("AUTHACTION_AUDIENCE"),
}
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["authaction.django.AuthActionAuthentication"],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
}
# views.py — request.user.sub and all JWT claims available as attributes
@api_view(["GET"])
def protected(request):
return Response({"sub": request.user.sub, "email": request.user.email})

Full SDK reference: github.com/authaction/authaction-python-sdk


This guide explains how to implement JWT authentication in your Django REST Framework API using AuthAction’s JWKS endpoint with python-jose.

Example Repository: For a complete working example, check out our example repository.

Before you begin, ensure you have:

  1. Python 3.11+: Download from python.org
  2. AuthAction Account: You’ll need your AuthAction tenant domain and API identifier
Terminal window
pip install django djangorestframework python-jose[cryptography] httpx python-dotenv

Create a .env file in your project root:

Terminal window
AUTHACTION_DOMAIN=your-authaction-tenant-domain
AUTHACTION_AUDIENCE=your-authaction-api-identifier
DJANGO_SECRET_KEY=your-django-secret-key

Create api/authentication.py:

import os
import httpx
from jose import JWTError, jwt
from jose.exceptions import ExpiredSignatureError
from rest_framework.authentication import BaseAuthentication
from 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"

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",
],
}
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from 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,
})
  1. Obtain an Access Token:
Terminal window
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"
}'
  1. Call the Public Endpoint:
Terminal window
curl http://localhost:8000/public
  1. Call the Protected Endpoint:
Terminal window
curl --request GET \
--url http://localhost:8000/protected \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  • Verify AUTHACTION_DOMAIN and AUTHACTION_AUDIENCE match your AuthAction dashboard exactly
  • Ensure the token is signed with the RS256 algorithm
  • Verify your application can reach https://your-authaction-tenant-domain/.well-known/jwks.json
  • Ensure the Authorization: Bearer <token> header is present and the token is not expired
  • Confirm the token’s audience matches your API identifier