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:
# 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.urlsNote: 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:
| Style | How it works | Good for |
|---|---|---|
| Token auth | server stores one token per user; client sends it in a header | simple internal APIs |
| JWT | a signed, self-contained token that expires; nothing stored server-side | most 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:
# 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:
- The client POSTs its username and password to
/api/token/. - The server checks them and returns an
accesstoken (short-lived) and arefreshtoken. - On every protected request the client sends the access token in a header:
Authorization: Bearer <token>. - DRF decodes the token, identifies the user, and lets the request through.
- 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):
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 = loginNote: 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:
# 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:
# 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?
✍️ Practice
- Register your viewset with a
DefaultRouterand add JWT login/refresh endpoints with SimpleJWT. - Add
IsAuthenticatedOrReadOnlypermissions, turn on pagination, and addsearch_fieldsto your viewset.
🏠 Homework
- 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.