
JWT Authentication in Django
So you’ve built a Django app, and now you want to add secure, stateless authentication for your APIs or frontend clients. You’ve probably heard about JSON Web Tokens (JWT)—a popular, modern approach to handle user sessions without server-side storage. In this guide, I’ll walk you through what JWT is, why it’s useful, and how you can implement it in your Django project step by step.
What is JWT and Why Use It?
JWT is an open standard (RFC 7519) that defines a compact, self-contained way to securely transmit information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair (like RSA or ECDSA).
Unlike traditional session-based authentication, where the server stores session data, JWT is stateless. The token itself contains all the necessary information about the user, so your server doesn’t need to keep a session store. This makes scaling easier and works beautifully with distributed systems or microservices.
A JWT consists of three parts: a header, a payload, and a signature, each base64-encoded and separated by dots. Here’s a quick example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The payload often includes "claims" about the user (like user ID or roles) and other metadata. Since the token is signed, you can verify that it hasn’t been tampered with.
Key benefits of using JWT in Django:
- Statelessness: No need to store session data on the server.
- Decentralized verification: Microservices can independently verify tokens.
- Flexibility: Tokens can contain custom claims and are easy to use with frontend frameworks.
However, it’s important to note that JWTs have downsides too—such as larger payload sizes and the complexity of token invalidation. But for many use cases, they’re a great fit.
Setting Up JWT in Your Django Project
To get started with JWT authentication in Django, we’ll use the popular djangorestframework-simplejwt
package. It’s well-maintained, integrates seamlessly with Django REST Framework (DRF), and provides a solid implementation of JWT standards.
First, make sure you have Django and DRF installed. Then, install the package:
pip install djangorestframework-simplejwt
Add it to your INSTALLED_APPS
in settings.py
:
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt',
...
]
Next, configure DRF to use JWT authentication by adding this to your settings.py
:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
Now, include the JWT routes in your urls.py
:
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
That’s the basic setup! You now have endpoints to obtain and refresh JWTs.
Obtaining and Using JWTs
With the setup above, you can obtain a JWT by sending a POST request to /api/token/
with a username and password. Here’s an example using curl
:
curl -X POST -H "Content-Type: application/json" -d '{"username":"your_username","password":"your_password"}' http://localhost:8000/api/token/
If the credentials are valid, you’ll receive a response like:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI...",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI..."
}
The access
token is short-lived (e.g., 5 minutes) and must be included in the Authorization
header for protected requests:
Authorization: Bearer <your_access_token>
The refresh
token is used to obtain a new access token once it expires, by sending a POST to /api/token/refresh/
with the refresh token:
curl -X POST -H "Content-Type: application/json" -d '{"refresh":"your_refresh_token"}' http://localhost:8000/api/token/refresh/
This returns a new access token, keeping the user authenticated without requiring them to log in again.
Customizing Token Claims
Often, you’ll want to include additional user information in the token payload, such as user ID, email, or permissions. You can do this by customizing the token class.
Create a serializers.py
file in your app and define a custom token serializer:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
token['username'] = user.username
token['email'] = user.email
return token
Then, create a custom view to use this serializer:
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
Update your urls.py
to use this custom view:
from .views import CustomTokenObtainPairView
urlpatterns = [
path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Now, when you obtain a token, it will include the extra claims you specified.
Securing Your JWT Implementation
While JWT is secure by design, how you implement it matters. Here are some best practices:
- Use HTTPS: Always serve your API over HTTPS to prevent token interception.
- Set reasonable expiration times: Short-lived access tokens reduce the risk of misuse.
- Store tokens securely: On the client side, avoid storing tokens in local storage (which is vulnerable to XSS). Consider using httpOnly cookies, though this requires CSRF protection.
- Implement token blacklisting: If you need to revoke tokens before they expire, consider using a blacklist.
djangorestframework-simplejwt
supports this through itsBlacklist
app.
To enable token blacklisting, first add 'rest_framework_simplejwt.token_blacklist'
to INSTALLED_APPS
, then run migrations:
python manage.py migrate
Then, in your settings, configure the token blacklist:
SIMPLE_JWT = {
'BLACKLIST_AFTER_ROTATION': True,
}
This will automatically add refresh tokens to the blacklist after they are used to obtain a new access token.
Handling JWT in Django Views and Permissions
Once JWT authentication is set up, you can protect your API views easily. In DRF, you can use permission classes to restrict access:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
class ProtectedView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({"message": "This is a protected view!"})
You can also access the user from the token in your views via request.user
.
If you need to customize permissions further—for example, to allow only users with a specific role—you can create custom permission classes:
from rest_framework.permissions import BasePermission
class IsAdmin(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_staff
class AdminView(APIView):
permission_classes = [IsAuthenticated, IsAdmin]
def get(self, request):
return Response({"message": "Hello, admin!"})
Comparing JWT to Session Authentication
It’s useful to understand when to use JWT and when to stick with traditional sessions. Here’s a quick comparison:
Aspect | JWT Authentication | Session Authentication |
---|---|---|
Statefulness | Stateless | Stateful |
Server Storage | None (token holds data) | Server stores session data |
Scalability | Excellent for distributed systems | Requires sticky sessions or shared storage |
Token Size | Larger (contains payload) | Smaller (session ID only) |
Revocation | Complex (requires blacklisting) | Simple (delete session) |
Use Case | APIs, microservices | Traditional web apps |
In summary, JWT is ideal for API-based architectures where you want to avoid server-side state, while session authentication may be simpler for classic web applications with server-rendered views.
Troubleshooting Common JWT Issues
As you work with JWT, you might run into some common issues. Here are a few and how to resolve them:
- Token not working: Ensure you’re including the token in the
Authorization
header asBearer <token>
. - Token expired: Use the refresh token to get a new access token.
- Custom claims not appearing: Double-check your token serializer and view customization.
- Blacklist not working: Verify that the
token_blacklist
app is inINSTALLED_APPS
and migrations are applied.
If you need to debug, you can decode your JWT using online tools like jwt.io (just remember not to use sensitive tokens in public tools) or write a simple Python script:
import jwt
token = "your.jwt.token"
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded)
This will show you the payload without verifying the signature—useful for debugging, but never use this in production without verification!
Final Thoughts
JWT authentication is a powerful tool in your Django toolkit, especially for building scalable, stateless APIs. With djangorestframework-simplejwt
, the setup is straightforward, and customization is flexible enough for most needs.
Remember to always follow security best practices, like using HTTPS and setting appropriate token expiration times. If you need to revoke tokens, leverage the blacklist feature.
I hope this guide helps you implement JWT authentication smoothly in your next project. Feel free to experiment, and happy coding!