Django REST Framework
Authentication in DRF — JWT
JSON Web Tokens (JWT) provide a stateless method for authenticating users across distributed systems in Django REST Framework. By encoding user identity into a cryptographically signed token, the server avoids storing session state in the database for every request. This approach is ideal for decoupled front-end applications, mobile clients, and microservices where traditional session cookies are impractical or inefficient.
Understanding the Stateless Architecture
Traditional Django authentication relies on sessions, where the server stores a session ID in a database and a corresponding cookie in the browser. In contrast, JWT authentication is entirely stateless. The server generates a token containing user data and signs it using a secret key. Because the server does not need to look up a session record in the database for every incoming request, this architecture scales significantly better in distributed environments. When a request arrives, the server simply verifies the cryptographic signature of the token to confirm the user's identity. If the signature matches, the server trusts the payload within the token. This mechanism shifts the burden of state management from the server to the client, which must store the token and present it in the Authorization header for every protected interaction, allowing the backend to remain blissfully unaware of individual session lifecycles.
# settings.py configuration for simple-jwt
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}The Token Structure and Lifecycle
A JWT consists of three parts: a header, a payload, and a signature, all encoded as Base64 strings separated by dots. The header specifies the signing algorithm, the payload contains claims like user IDs and expiration times, and the signature ensures that the data has not been tampered with. Because the payload is visible to anyone who decodes the base64 string, you must never include sensitive information like passwords or PII in the token. The lifecycle begins when a user submits their credentials to the authentication endpoint. If valid, the server returns an access token and a refresh token. The access token is short-lived to minimize damage if stolen, while the refresh token is long-lived and used to obtain new access tokens without requiring the user to re-authenticate, balancing security requirements with a seamless user experience.
# views.py: Exposing endpoints for token generation
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]Securing API Endpoints
Once the authentication class is globally defined in settings, every API endpoint becomes protected by default. To allow public access to specific views, such as registration or documentation, you must explicitly override the authentication classes on those specific view sets. When a client makes a request to a protected endpoint, the JWTAuthentication backend automatically parses the 'Authorization: Bearer <token>' header. If the token is missing or malformed, the system raises an AuthenticationFailed exception, resulting in a 401 Unauthorized response. If the token is expired, it raises an InvalidToken exception. This approach keeps your view logic clean, as you can trust that any request reaching your method is already authenticated. By utilizing DRF's permission classes alongside JWT, you can restrict access based on user roles or ownership while ensuring that the identity verification process remains centralized and consistent throughout your entire application architecture.
# views.py: Explicitly requiring authentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
class SecureDataView(APIView):
permission_classes = [IsAuthenticated] # Ensures only token-holders access
def get(self, request):
return Response({"data": "Sensitive information accessed."})Handling Token Expiration and Refresh
Short access token lifetimes are a critical security feature, but they create a problem: how do we keep a user logged in without forcing them to re-enter their password every fifteen minutes? The answer is the refresh token mechanism. When an access token expires, the client uses the stored refresh token to call the token refresh endpoint. The server verifies that the refresh token is still valid, check against a blacklist if necessary, and issues a fresh, valid access token. This creates a secure, sliding-window session. If a user logs out or a device is lost, the refresh token can be invalidated or blacklisted, effectively cutting off the user's ability to generate new access tokens. Managing this logic properly on the client side ensures that users maintain a continuous experience while maintaining the high security standards of a stateless system.
# settings.py: Customizing lifetimes
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
}Security Best Practices
Security is not just about having tokens; it is about protecting them. Since JWTs are typically stored in client-side storage like localStorage or cookies, they are vulnerable to cross-site scripting (XSS) attacks. To mitigate this, always serve tokens over HTTPS to prevent interception. If storing tokens in the browser, consider using HttpOnly, Secure cookies to prevent JavaScript access, which drastically reduces the risk of token theft via XSS. Furthermore, implement an efficient token blacklisting strategy for logout functionality. Without a blacklist, a JWT remains valid until its natural expiration, regardless of whether the user has logged out. By registering the 'rest_framework_simplejwt.token_blacklist' app, you gain the ability to revoke tokens instantly. Always ensure your secret key is managed via environment variables and never committed to your version control system, as it is the foundation of your entire authentication security.
# settings.py: Enabling the blacklist app
INSTALLED_APPS = [
# ...
'rest_framework_simplejwt.token_blacklist',
]
# Then run: python manage.py migrateKey points
- JWT authentication provides a stateless alternative to traditional session-based systems.
- Tokens are composed of a header, payload, and cryptographic signature for verification.
- The access token should have a short lifespan to limit the impact of potential theft.
- Refresh tokens are used to obtain new access tokens without requiring user re-authentication.
- Statelessness improves scalability by eliminating the need for server-side session lookups.
- Sensitive data should never be stored in the JWT payload because it is base64 encoded.
- The token blacklist app is necessary for implementing robust logout and security revocation.
- Transporting tokens over HTTPS is mandatory to prevent interception by malicious actors.
Common mistakes
- Mistake: Storing the JWT inside a browser's localStorage. Why it's wrong: It makes the application vulnerable to Cross-Site Scripting (XSS) attacks. Fix: Store the JWT in an HttpOnly, Secure, and SameSite cookie.
- Mistake: Manually validating the JWT signature in every view. Why it's wrong: DRF's authentication classes are designed to handle this automatically, and manual logic leads to security holes. Fix: Use the standard SimpleJWT authentication classes in settings.py.
- Mistake: Setting an extremely long expiration time for access tokens. Why it's wrong: If a token is stolen, the attacker has permanent access until the token expires. Fix: Use short-lived access tokens and implement a rotation mechanism for refresh tokens.
- Mistake: Including sensitive user information like passwords in the JWT payload. Why it's wrong: JWTs are Base64 encoded, not encrypted, meaning the payload is easily readable by anyone. Fix: Only include non-sensitive identifiers like the user ID in the claims.
- Mistake: Forgetting to protect views using permissions. Why it's wrong: Relying only on authentication just identifies the user; it doesn't restrict their access to specific endpoints. Fix: Always pair authentication with IsAuthenticated or custom Permission classes.
Interview questions
What is JWT authentication in Django REST Framework and why do we use it?
JWT stands for JSON Web Token. In Django REST Framework, it is a stateless authentication mechanism that allows the server to verify a user's identity without storing session data in the database. When a user logs in, the server generates a cryptographically signed token containing user information. Subsequent requests include this token in the Authorization header. We use it because it is highly scalable for distributed systems and mobile applications, as the server doesn't need to look up session IDs, reducing database overhead significantly compared to traditional session-based authentication.
How does the authentication flow work when using the SimpleJWT library?
The flow begins when a user sends their credentials to the token obtain view, usually provided by SimpleJWT. If the credentials are valid, the server returns an access token and a refresh token. The client stores these tokens, typically in local storage or secure cookies. For subsequent requests, the client adds the access token to the Authorization header as 'Bearer <token>'. Django REST Framework intercepts this, validates the signature and expiration using the secret key, and then authenticates the user, allowing access to protected API endpoints.
What is the purpose of the refresh token in Django REST Framework authentication?
The refresh token is a long-lived credential used specifically to obtain a new access token once the original one expires. Access tokens are kept short-lived for security reasons; if an access token is intercepted, it only provides a narrow window of vulnerability. By keeping the access token lifespan short, we limit damage. When it expires, the client sends the refresh token to a dedicated endpoint to request a fresh access token without forcing the user to re-enter their password, balancing high security with a smooth, continuous user experience.
Compare session-based authentication with JWT authentication in Django.
Session-based authentication is stateful, meaning the server creates a record in the database or cache, like Redis, to keep track of the user session. It is tightly coupled to the browser, relying on cookies. In contrast, JWT is stateless; the server holds no record of the session, relying entirely on the information embedded within the token itself. JWT is superior for cross-domain requests, mobile apps, and microservices where sharing a central session store is difficult, whereas sessions are generally safer against token theft because they can be instantly invalidated on the server side.
How do you protect specific Django views or viewsets using JWT?
To protect views, you apply permission classes. In your view or viewset, you set the 'permission_classes' attribute to '[IsAuthenticated]'. You also need to ensure 'JWTAuthentication' is included in the 'DEFAULT_AUTHENTICATION_CLASSES' setting in your 'settings.py' file. For example: 'class ProfileView(APIView): permission_classes = [IsAuthenticated]'. When a request hits this view, Django REST Framework will automatically invoke the JWT authentication backend, parse the Bearer token, verify its integrity against your SECRET_KEY, and raise a 401 Unauthorized response if the token is invalid, missing, or expired.
How can you implement custom claims in a JWT, and why might you need them?
Custom claims allow you to embed extra user-specific data into the JWT payload, such as a user role, tenant ID, or subscription level. In SimpleJWT, you create a custom token serializer by overriding the 'get_token' method in the 'TokenObtainPairSerializer'. Inside this method, you add data to 'token['custom_field'] = user.some_attribute'. We need this because it allows frontend applications to access user details, like 'is_premium', immediately upon decoding the token without making an extra API call to the user profile endpoint. This pattern significantly reduces the total number of network requests and improves application performance.
Check yourself
1. When a client sends a JWT in the 'Authorization' header, where does Django Rest Framework store the user object after successful authentication?
- A.request.user
- B.request.session.user
- C.request.auth
- D.context.user
Show answer
A. request.user
request.user is the standard attribute where the authenticated user is populated. request.session is not used in stateless JWT authentication, request.auth holds the token object itself, and context.user is not a standard DRF request attribute.
2. What is the primary architectural benefit of using JWTs instead of traditional session-based authentication in a DRF API?
- A.It eliminates the need for a database lookup for every request.
- B.It makes the application stateful by storing tokens on the server.
- C.It removes the need for HTTPS encryption.
- D.It prevents all forms of CSRF attacks automatically.
Show answer
A. It eliminates the need for a database lookup for every request.
JWTs allow the server to verify the user without querying the database for session data on every call. It does not make the server stateful, it still requires HTTPS, and it does not inherently prevent CSRF.
3. If you need to revoke a specific JWT before its expiration time, what is the best practice approach?
- A.Delete the user from the database.
- B.Maintain a blacklist or whitelist of JTI (JWT ID) claims in a fast-access cache like Redis.
- C.Change the user's password to immediately invalidate all tokens.
- D.Change the SECRET_KEY in settings.py.
Show answer
B. Maintain a blacklist or whitelist of JTI (JWT ID) claims in a fast-access cache like Redis.
Using a blacklist/denylist with JTI claims is the standard way to revoke tokens without destroying the user account or affecting the entire system globally like a SECRET_KEY rotation would.
4. Why is it important to use 'IsAuthenticated' as a permission class alongside JWT authentication?
- A.JWT only verifies that the token is valid, but does not stop unauthenticated users from hitting endpoints.
- B.It is required to decrypt the JWT signature.
- C.It forces the client to use a refresh token.
- D.Without it, the server will crash on unauthorized requests.
Show answer
A. JWT only verifies that the token is valid, but does not stop unauthenticated users from hitting endpoints.
Authentication identifies the user, but permissions authorize access. Without IsAuthenticated, your view would allow anonymous users to reach the logic, which is a common security oversight.
5. What happens if the 'SECRET_KEY' used to sign a JWT is compromised?
- A.Only refresh tokens are invalidated.
- B.The server will automatically generate a new one.
- C.Any attacker can forge valid tokens for any user in the system.
- D.The database records are automatically deleted.
Show answer
C. Any attacker can forge valid tokens for any user in the system.
The SECRET_KEY is the foundation of the signature. If compromised, an attacker can sign their own tokens and impersonate any user. It does not trigger automatic deletion or rotation.