DEV Community

Cover image for Django REST Framework Authentication & Permissions: A Simple Guide
Md. Nisan Hossain
Md. Nisan Hossain

Posted on

Django REST Framework Authentication & Permissions: A Simple Guide

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
Enter fullscreen mode Exit fullscreen mode

Authentication identifies the user.

Permissions decide what that user can access.


Setting Up Django REST Framework

First, install Django REST Framework:

pip install djangorestframework
Enter fullscreen mode Exit fullscreen mode

Then add it to your Django project:

INSTALLED_APPS = [
    ...
    "rest_framework",
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now only logged-in users can access this API.

This is useful for endpoints such as:

/api/profile/
/api/orders/
/api/wishlist/
/api/cart/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

A simple API can therefore look like this:

Public
 ├── Product List
 └── Product Details

Authenticated
 ├── Profile
 ├── Orders
 └── Wishlist

Admin
 ├── User Management
 └── Product Management
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
        )
Enter fullscreen mode Exit fullscreen mode

Then use it:

class ProductViewSet(ModelViewSet):
    permission_classes = [IsStaffOrReadOnly]

    queryset = Product.objects.all()
    serializer_class = ProductSerializer
Enter fullscreen mode Exit fullscreen mode

Now the behavior is:

GET     → Anyone
HEAD    → Anyone
OPTIONS → Anyone

POST    → Staff only
PUT     → Staff only
PATCH   → Staff only
DELETE  → Staff only
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then:

class OrderDetailView(RetrieveAPIView):
    permission_classes = [
        IsAuthenticated,
        IsOrderOwner,
    ]

    queryset = Order.objects.all()
    serializer_class = OrderSerializer
Enter fullscreen mode Exit fullscreen mode

Now the API checks:

Is the user authenticated?
        ↓
Does this order belong to the user?
        ↓
Yes → Allow
No  → Deny
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
        )
Enter fullscreen mode Exit fullscreen mode

Then:

class SellerProductViewSet(ModelViewSet):
    permission_classes = [IsSeller]

    queryset = Product.objects.all()
    serializer_class = ProductSerializer
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode

For user-owned resources, this can create security problems.

Instead, when appropriate, use the authenticated user:

user = request.user
Enter fullscreen mode Exit fullscreen mode

The server should determine ownership instead of blindly trusting the client.


Another Important Mistake

This:

permission_classes = [IsAuthenticated]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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)