Django REST Framework Authentication & Permissions: A Simple Guide
If you're building APIs with Django REST Framework (DRF), authentication and permissions are two of the most important things to understand.
In this guide, I'll explain them in a simple and practical way with examples you can use in real Django projects.
Authentication vs Permissions
Let's make it simple.
Authentication answers:
Who is this user?
Permissions answer:
What is this user allowed to do?
For example, imagine an e-commerce API:
Customer → Can view products and create orders
Seller → Can manage products
Admin → Can manage users, products and orders
Authentication identifies the user.
Permissions decide what that user can access.
Setting Up Django REST Framework
First, install Django REST Framework:
pip install djangorestframework
Then add it to your Django project:
INSTALLED_APPS = [
...
"rest_framework",
]
Now you can start building your REST API.
Using IsAuthenticated
One of the most useful DRF permissions is IsAuthenticated.
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
class OrderViewSet(ModelViewSet):
permission_classes = [IsAuthenticated]
queryset = Order.objects.all()
serializer_class = OrderSerializer
Now only logged-in users can access this API.
This is useful for endpoints such as:
/api/profile/
/api/orders/
/api/wishlist/
/api/cart/
Public vs Private APIs
Not every endpoint needs authentication.
For example, anyone might be allowed to view products:
from rest_framework.permissions import AllowAny
class ProductViewSet(ModelViewSet):
permission_classes = [AllowAny]
queryset = Product.objects.all()
serializer_class = ProductSerializer
A simple API can therefore look like this:
Public
├── Product List
└── Product Details
Authenticated
├── Profile
├── Orders
└── Wishlist
Admin
├── User Management
└── Product Management
This makes your API easier to understand and maintain.
Using IsAdminUser
DRF also provides IsAdminUser.
from rest_framework.permissions import IsAdminUser
class UserManagementViewSet(ModelViewSet):
permission_classes = [IsAdminUser]
queryset = User.objects.all()
serializer_class = UserSerializer
Now only admin users can access this endpoint.
This is useful for dashboards and administrative APIs.
Creating a Custom Permission
Real applications often need more specific rules.
For example:
Anyone can view products, but only staff users can create or delete them.
We can create a custom permission:
from rest_framework.permissions import BasePermission
class IsStaffOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in ["GET", "HEAD", "OPTIONS"]:
return True
return (
request.user.is_authenticated
and request.user.is_staff
)
Then use it:
class ProductViewSet(ModelViewSet):
permission_classes = [IsStaffOrReadOnly]
queryset = Product.objects.all()
serializer_class = ProductSerializer
Now the behavior is:
GET → Anyone
HEAD → Anyone
OPTIONS → Anyone
POST → Staff only
PUT → Staff only
PATCH → Staff only
DELETE → Staff only
This is a simple but very useful pattern.
Object-Level Permissions
This is where things become really important.
Imagine this API:
GET /api/orders/25/
A user is authenticated, but that doesn't automatically mean they should be able to see every order.
A customer should normally see only their own orders.
We can create an object-level permission:
from rest_framework.permissions import BasePermission
class IsOrderOwner(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.user == request.user
Then:
class OrderDetailView(RetrieveAPIView):
permission_classes = [
IsAuthenticated,
IsOrderOwner,
]
queryset = Order.objects.all()
serializer_class = OrderSerializer
Now the API checks:
Is the user authenticated?
↓
Does this order belong to the user?
↓
Yes → Allow
No → Deny
This type of authorization is extremely important when working with private user data.
Role-Based Access Control
As an application grows, you may need different roles.
For example:
Admin
Seller
Support
Customer
Each role can have different permissions.
A simple custom permission could check Django Groups:
from rest_framework.permissions import BasePermission
class IsSeller(BasePermission):
def has_permission(self, request, view):
return (
request.user.is_authenticated
and request.user.groups.filter(
name="Seller"
).exists()
)
Then:
class SellerProductViewSet(ModelViewSet):
permission_classes = [IsSeller]
queryset = Product.objects.all()
serializer_class = ProductSerializer
This gives you a simple foundation for Role-Based Access Control (RBAC).
A Common Mistake
One mistake I see in APIs is trusting the user ID sent by the client.
For example:
user = User.objects.get(id=request.data["user"])
For user-owned resources, this can create security problems.
Instead, when appropriate, use the authenticated user:
user = request.user
The server should determine ownership instead of blindly trusting the client.
Another Important Mistake
This:
permission_classes = [IsAuthenticated]
only tells us that the user is authenticated.
It does not automatically mean that the user can access every object.
For example:
User A
└── Order #10
User B
└── Order #20
User A should not automatically be able to access Order #20.
That's why object-level permissions and proper queryset filtering are important.
Simple DRF Permission Strategy
For many projects, you can start with something like this:
Public API
↓
AllowAny
Private API
↓
IsAuthenticated
Admin API
↓
IsAdminUser
Role-based API
↓
Custom Permission
User-owned resources
↓
Object-level Permission
You don't need a complicated authorization system from day one.
Start simple and make it more specific as your application grows.
Best Practices
When building a Django REST API, keep these points in mind:
- Use authentication for private APIs.
- Use permissions for authorization.
- Don't trust user ownership data from the client.
- Use object-level permissions when necessary.
- Keep permission logic reusable.
- Don't expose sensitive information through serializers.
- Use HTTPS in production.
- Keep secret keys and credentials out of source code.
- Test both authorized and unauthorized requests.
- Keep your API structure simple and consistent.
Final Thoughts
Authentication and permissions are fundamental parts of a secure Django REST Framework API.
The easiest way to remember them is:
Authentication → Who are you?
Permission → What can you do?
Object Permission → Can you access this specific object?
Start with DRF's built-in permissions such as AllowAny, IsAuthenticated, and IsAdminUser. As your application becomes more complex, you can introduce custom permissions, roles, and object-level authorization.
A simple permission system that is well designed is usually better than a complicated system that is difficult to maintain.
About Me
I'm Md. Nisan Hossain, a Backend Software Engineer focused on Python, Django, and Django REST Framework.
I enjoy building REST APIs, database-driven applications, authentication systems, and scalable backend solutions.
GitHub: nisan24
Portfolio: nisan24.vercel.app
If you're also learning Django or backend development, I hope this guide helps you build better APIs.
— Nisan Hossain | nisan24
Top comments (0)