Auth, APIs & ProjectPro· 55 min read

DRF in Depth: Auth, Permissions & Pagination

Take your API from a toy to job-ready — routers, token/JWT auth, permissions, pagination, filtering and throttling.

What you will learn

  • Wire viewsets with a router and secure them with permissions
  • Authenticate API clients with tokens / JWT
  • Add pagination, filtering and throttling

From intro to production API

The previous lesson built a working API with a serializer and a viewset. Real backend jobs need more: clients must log in to the API, some endpoints must be protected, long lists must be paginated, and the API must be searchable and protected from abuse. This lesson adds each of those, building on the same Post API.

Routers — URLs for free

Writing a URL pattern for every endpoint is tedious. A router looks at a viewset and generates all its URLs automatically (list, detail, create, update, delete). You register the viewset once:

A router builds all the endpoint URLs
# urls.py
from rest_framework.routers import DefaultRouter
from .views import PostViewSet

router = DefaultRouter()
router.register("posts", PostViewSet)   # generates /posts/ and /posts/<id>/

urlpatterns = router.urls

Note: Output: registering one viewset gives you /posts/ (list + create) and /posts/5/ (read + update + delete) with no hand-written URL patterns. The DefaultRouter even adds a browsable API root page.

Authentication — who is calling the API?

A web page uses cookies and sessions, but an API client (a React app, a phone, another server) usually sends a token instead — a long secret string that proves identity on every request. Two common styles:

StyleHow it worksGood for
Token authserver stores one token per user; client sends it in a headersimple internal APIs
JWTa signed, self-contained token that expires; nothing stored server-sidemost modern apps / SPAs

JWT (JSON Web Token) is the popular choice. The package djangorestframework-simplejwt gives you ready-made endpoints: the client posts a username and password and gets back a token, then sends that token on future requests. Set it up in two steps:

JWT login and refresh endpoints with SimpleJWT
# settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ],
}

# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns += [
    path("api/token/", TokenObtainPairView.as_view()),     # log in -> get token
    path("api/token/refresh/", TokenRefreshView.as_view()),# get a fresh token
]

How a client uses JWT, step by step:

  1. The client POSTs its username and password to /api/token/.
  2. The server checks them and returns an access token (short-lived) and a refresh token.
  3. On every protected request the client sends the access token in a header: Authorization: Bearer <token>.
  4. DRF decodes the token, identifies the user, and lets the request through.
  5. When the access token expires, the client POSTs the refresh token to /api/token/refresh/ to get a new one — no re-login needed.

Note: Output (from /api/token/): { "access": "eyJhbG...", "refresh": "eyJhbG..." } The client stores these and sends the access one as Authorization: Bearer eyJhbG... on future calls.

Permissions — what is each caller allowed to do?

A permission class decides, per request, whether a caller may proceed. DRF ships several; you set them on a view or globally. The most useful: IsAuthenticated (must be logged in) and IsAuthenticatedOrReadOnly (anyone may read, only logged-in users may write):

IsAuthenticatedOrReadOnly: public reads, protected writes
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]   # read = public, write = login

Note: Output: an anonymous client can GET /posts/ (read), but a POST to create a post returns 401 Unauthorized until they send a valid token. That single line is the difference between a public demo and a controlled API.

Pagination — never dump 10,000 rows

Pagination splits a long list into pages so the API returns, say, 10 items at a time instead of everything at once. Turn it on globally in settings:

Page the list into chunks of 10
# settings.py
REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS":
        "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 10,
}

Note: Output (GET /posts/): { "count": 57, "next": "http://.../posts/?page=2", "previous": null, "results": [ ...10 posts... ] } The response now carries the total count plus next/previous page links — the client requests ?page=2 for the next chunk.

Filtering, search & throttling

Two more job-ready touches. Filtering/search lets clients narrow results with query parameters (?search=django). Throttling limits how many requests a caller can make, protecting the API from abuse:

Search the list and throttle abusive callers
# views.py — add search to the viewset
from rest_framework import filters

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    filter_backends = [filters.SearchFilter]
    search_fields = ["title", "body"]      # GET /posts/?search=django

# settings.py — throttle anonymous callers
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.AnonRateThrottle"],
    "DEFAULT_THROTTLE_RATES": {"anon": "100/day"},
}

Note: Output: GET /posts/?search=django returns only posts whose title or body contains "django". And an anonymous client that exceeds 100 requests in a day gets 429 Too Many Requests until the next day.

Tip: The DRF job-ready checklist: router for URLs, JWT for auth, a permission class on every viewset, pagination on every list, plus search and throttling. This combination is exactly what most Django backend roles hire for.

Q. You want anyone to be able to read your API’s posts, but only logged-in users to create or edit them. Which permission class fits?

Answer: IsAuthenticatedOrReadOnly allows safe read requests (GET) for anyone but requires authentication for write requests (POST/PUT/DELETE) — exactly public reads with protected writes.

✍️ Practice

  1. Register your viewset with a DefaultRouter and add JWT login/refresh endpoints with SimpleJWT.
  2. Add IsAuthenticatedOrReadOnly permissions, turn on pagination, and add search_fields to your viewset.

🏠 Homework

  1. Build a secured API for one model: router URLs, JWT auth, a permission class, pagination, and search. Test it by logging in for a token and calling a protected endpoint.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →