Examples#
This page provides practical examples of using axioms-drf-py authentication and permission classes to secure your Django REST Framework API views.
AND Logic - Requiring Multiple Claims#
Require users to have ALL specified scopes using AND logic:
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 SecureDataView(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({'message': 'Data created successfully'})
Example JWT Token Payload (Success):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "read:data write:data openid",
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed because the token contains both required scopes.
Example JWT Token Payload (Failure):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "read:data",
"exp": 1735689600,
"iat": 1735686000
}
This request will fail with 403 Forbidden because the token only contains read:data but not write:data.
Mixed OR and AND Logic#
Combine OR and AND logic for complex authorization requirements:
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
class ComplexAuthView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
# User needs: (read:data OR read:all) AND (openid AND profile)
access_token_any_scopes = ['read:data', 'read:all'] # OR logic
access_token_all_scopes = ['openid', 'profile'] # AND logic
def get(self, request):
return Response({'data': 'complex authorization passed'})
Example JWT Token Payload (Success):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "read:data openid profile email",
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed because the token has:
- At least one of read:data or read:all (has read:data)
- Both openid and profile
Example JWT Token Payload (Failure - Missing AND requirement):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "read:data openid",
"exp": 1735689600,
"iat": 1735686000
}
This request will fail because while it has read:data (satisfies OR requirement), it’s missing profile (fails AND requirement).
Multiple Permission Classes#
Combine different permission classes for complex authorization:
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
class MultiPermissionView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes, HasAccessTokenRoles]
access_token_scopes = ['openid', 'profile'] # Needs openid OR profile
access_token_roles = ['admin', 'editor'] # AND needs admin OR editor
def get(self, request):
# User needs: (openid OR profile) AND (admin OR editor)
return Response({'message': 'Multi-permission access granted'})
Example JWT Token Payload (Success):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "openid email",
"roles": ["editor", "viewer"],
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed because the token has both openid scope and editor role.
Method-Level Permissions#
Use properties to define different permissions for each HTTP method on the same view:
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 MethodLevelPermissionView(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)
Example JWT Token Payload for GET (Success):
{
"sub": "user123",
"aud": "your-api-audience",
"permissions": ["sample:read"],
"exp": 1735689600,
"iat": 1735686000
}
This GET request will succeed because the token contains sample:read permission.
Example JWT Token Payload for POST (Success):
{
"sub": "user123",
"aud": "your-api-audience",
"permissions": ["sample:create"],
"exp": 1735689600,
"iat": 1735686000
}
This POST request will succeed because the token contains sample:create permission.
Example JWT Token Payload (Failure):
{
"sub": "user123",
"aud": "your-api-audience",
"permissions": ["sample:create"],
"exp": 1735689600,
"iat": 1735686000
}
This GET request will fail with 403 Forbidden because the token has sample:create permission but GET requires sample:read.
ViewSet Action-Specific Permissions#
ViewSets provide a powerful way to define different permissions for each action (list, retrieve, create, update, destroy) using the @property decorator.
Scope-Based ViewSet Permissions#
Define different scopes for each ViewSet action:
from rest_framework import viewsets
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenScopes
class BookViewSet(viewsets.ModelViewSet):
"""Books API with action-specific scope requirements."""
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
queryset = Book.objects.all()
serializer_class = BookSerializer
@property
def access_token_scopes(self):
"""Return required scopes based on the current action."""
action_scopes = {
'list': ['book:read'], # GET /books/
'retrieve': ['book:read'], # GET /books/{id}/
'create': ['book:create'], # POST /books/
'update': ['book:update'], # PUT /books/{id}/
'partial_update': ['book:update'], # PATCH /books/{id}/
'destroy': ['book:delete'], # DELETE /books/{id}/
}
return action_scopes.get(self.action, [])
Example JWT Token Payload (Success for list/retrieve):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "book:read openid",
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed for GET /books/ (list) and GET /books/1/ (retrieve) because the token contains book:read scope.
Example JWT Token Payload (Success for create):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "book:create openid",
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed for POST /books/ because the token contains book:create scope.
Example JWT Token Payload (Failure):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "book:read openid",
"exp": 1735689600,
"iat": 1735686000
}
This request will fail with 403 Forbidden for POST /books/ (create) because the token only has book:read scope, not book:create.
Role-Based ViewSet Permissions#
Define different roles for each ViewSet action:
from rest_framework import viewsets
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenRoles
class UserViewSet(viewsets.ModelViewSet):
"""User management with role-based permissions."""
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenRoles]
queryset = User.objects.all()
serializer_class = UserSerializer
@property
def access_token_roles(self):
"""Return required roles based on the current action."""
action_roles = {
'list': ['viewer', 'admin'], # Viewers can list
'retrieve': ['viewer', 'admin'], # Viewers can view details
'create': ['admin'], # Only admins can create
'update': ['admin'], # Only admins can update
'partial_update': ['admin'], # Only admins can patch
'destroy': ['admin'], # Only admins can delete
}
return action_roles.get(self.action, [])
Example JWT Token Payload (Success for viewer):
{
"sub": "user123",
"aud": "your-api-audience",
"roles": ["viewer"],
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed for GET /users/ (list) and GET /users/1/ (retrieve) because the token contains viewer role.
Example JWT Token Payload (Failure for viewer trying to create):
{
"sub": "user123",
"aud": "your-api-audience",
"roles": ["viewer"],
"exp": 1735689600,
"iat": 1735686000
}
This request will fail with 403 Forbidden for POST /users/ because the viewer role is not authorized for the create action (requires admin).
Permission-Based ViewSet Permissions#
Define different permissions for each ViewSet action:
from rest_framework import viewsets
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenPermissions
class DocumentViewSet(viewsets.ModelViewSet):
"""Document management with fine-grained permissions."""
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenPermissions]
queryset = Document.objects.all()
serializer_class = DocumentSerializer
@property
def access_token_permissions(self):
"""Return required permissions based on the current action."""
action_permissions = {
'list': ['document:list'],
'retrieve': ['document:read'],
'create': ['document:create'],
'update': ['document:update'],
'partial_update': ['document:update'],
'destroy': ['document:delete'],
}
return action_permissions.get(self.action, [])
Example JWT Token Payload (Success for read operations):
{
"sub": "user123",
"aud": "your-api-audience",
"permissions": ["document:list", "document:read"],
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed for GET /documents/ (list) and GET /documents/1/ (retrieve) because the token contains both required permissions.
Custom ViewSet Actions#
Define permissions for custom ViewSet actions:
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenScopes
class ArticleViewSet(viewsets.ModelViewSet):
"""Article management with custom actions."""
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@property
def access_token_scopes(self):
"""Return required scopes based on the current action."""
action_scopes = {
'list': ['article:read'],
'retrieve': ['article:read'],
'create': ['article:create'],
'update': ['article:update'],
'partial_update': ['article:update'],
'destroy': ['article:delete'],
'publish': ['article:publish'], # Custom action
'archive': ['article:archive'], # Custom action
}
return action_scopes.get(self.action, [])
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""Publish an article (requires article:publish scope)."""
article = self.get_object()
article.published = True
article.save()
return Response({'status': 'published'})
@action(detail=True, methods=['post'])
def archive(self, request, pk=None):
"""Archive an article (requires article:archive scope)."""
article = self.get_object()
article.archived = True
article.save()
return Response({'status': 'archived'})
Example JWT Token Payload (Success for custom action):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "article:publish openid",
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed for POST /articles/1/publish/ because the token contains article:publish scope.
Dynamic Permissions Based on Request Data#
Use request data to dynamically determine required permissions:
from rest_framework import viewsets
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import HasAccessTokenScopes
class ReportViewSet(viewsets.ModelViewSet):
"""Reports with dynamic scope requirements."""
authentication_classes = [HasValidAccessToken]
permission_classes = [HasAccessTokenScopes]
queryset = Report.objects.all()
serializer_class = ReportSerializer
@property
def access_token_scopes(self):
"""Return scopes based on action and request data."""
if self.action == 'create':
# Check if creating a sensitive report
report_type = self.request.data.get('type')
if report_type == 'financial':
return ['report:create:financial']
return ['report:create']
action_scopes = {
'list': ['report:read'],
'retrieve': ['report:read'],
'update': ['report:update'],
'destroy': ['report:delete'],
}
return action_scopes.get(self.action, [])
Example JWT Token Payload (Success for standard report):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "report:create openid",
"exp": 1735689600,
"iat": 1735686000
}
This POST request with {"type": "standard"} will succeed because the token contains report:create scope.
Example JWT Token Payload (Failure for financial report):
{
"sub": "user123",
"aud": "your-api-audience",
"scope": "report:create openid",
"exp": 1735689600,
"iat": 1735686000
}
This POST request with {"type": "financial"} will fail with 403 Forbidden because it requires report:create:financial scope, not just report:create.
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'})
Object-Level Permissions#
Restrict access to resources based on ownership using the sub claim from the JWT token.
Owner-Only Access#
Use IsSubOwner to restrict all operations to the resource owner:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import IsSubOwner
class ArticleViewSet(viewsets.ModelViewSet):
"""Only the owner (matched by sub claim) can access their articles."""
authentication_classes = [HasValidAccessToken]
permission_classes = [IsSubOwner]
owner_attribute = 'author_sub' # Compare token sub with article.author_sub
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def perform_create(self, serializer):
# Automatically set the author from the token's sub claim
serializer.save(author_sub=self.request.user)
Example JWT Token Payload (Success for owner):
{
"sub": "user123",
"iss": "https://your-auth.domain.com",
"aud": "your-api-audience",
"exp": 1735689600,
"iat": 1735686000
}
This request will succeed for operations on articles where article.author_sub == "user123".
Example JWT Token Payload (Failure for non-owner):
{
"sub": "user456",
"iss": "https://your-auth.domain.com",
"aud": "your-api-audience",
"exp": 1735689600,
"iat": 1735686000
}
This request will fail with 403 Forbidden when attempting to access or modify articles where article.author_sub == "user123" because the token’s sub claim (user456) doesn’t match the article’s author_sub (user123).
Public Read, Owner-Only Modify#
Use IsSubOwnerOrSafeOnly to allow anyone to read, but only owners can update/delete:
from rest_framework import viewsets
from axioms_drf.authentication import HasValidAccessToken
from axioms_drf.permissions import IsSubOwnerOrSafeOnly
class ArticleViewSet(viewsets.ModelViewSet):
"""Anyone can read articles, only owners can update/delete."""
authentication_classes = [HasValidAccessToken]
permission_classes = [IsSubOwnerOrSafeOnly]
owner_attribute = 'author_sub'
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def perform_create(self, serializer):
serializer.save(author_sub=self.request.user)
Example JWT Token Payload (Success for GET - any authenticated user):
{
"sub": "user456",
"iss": "https://your-auth.domain.com",
"aud": "your-api-audience",
"exp": 1735689600,
"iat": 1735686000
}
This GET request will succeed for any authenticated user, regardless of ownership.
Example JWT Token Payload (Success for PATCH/DELETE - owner only):
{
"sub": "user123",
"iss": "https://your-auth.domain.com",
"aud": "your-api-audience",
"exp": 1735689600,
"iat": 1735686000
}
This PATCH or DELETE request will succeed only if the token’s sub claim matches the article’s author_sub field.
Example JWT Token Payload (Failure for PATCH/DELETE - non-owner):
{
"sub": "user456",
"iss": "https://your-auth.domain.com",
"aud": "your-api-audience",
"exp": 1735689600,
"iat": 1735686000
}
This PATCH or DELETE request will fail with 403 Forbidden because the token’s sub claim doesn’t match the article’s owner.
Using with APIView#
Object-level permissions also work with standard APIView (not just ViewSets):
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 IsSubOwner
class ArticleDetailView(APIView):
authentication_classes = [HasValidAccessToken]
permission_classes = [IsSubOwner]
owner_attribute = 'author_id'
def get_object(self, pk):
return Article.objects.get(pk=pk)
def get(self, request, pk):
article = self.get_object(pk)
self.check_object_permissions(request, article)
return Response({'title': article.title})
def patch(self, request, pk):
article = self.get_object(pk)
self.check_object_permissions(request, article)
# Update article logic here
return Response({'status': 'updated'})
def delete(self, request, pk):
article = self.get_object(pk)
self.check_object_permissions(request, article)
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Important
When using object-level permissions with APIView, you must:
Set the
owner_attributeon the view to specify which field contains the owner identifierCall
self.check_object_permissions(request, obj)after retrieving the object
ViewSets automatically call check_object_permissions for detail actions (retrieve, update, destroy).
Complete Django REST Framework Application#
For a complete working example, check out the example application in this repository. The sample demonstrates a fully functional Django REST Framework application with authentication and authorization.