JWT Based Authentication in Django using djangorestframework-simplejwt
A JSON Web Token (JWT) is like a secure, digital ID card for web apps. Instead of checking a database every single time a user clicks a page, the server gives the user a token when they log in. The user's browser attaches this token to every future request. The server looks at the token, instantly knows who the user is, and lets them in, saving a lot of database work.
With django, we can easily add JWT based authentication to our project using the djangorestframework-simplejwt package.
A JWT consists of three Base64URL-encoded parts separated by dots:
header.payload.signature
- Header: token type and hashing algorithm (e.g., HS256)
- Payload: claims: user ID, expiry time, roles, etc.
- Signature: ensures the token hasn't been tampered.
Create a django project with dependencies
Create a virtual environment, and use it to created project in django. I assume readers knows basics of django and django rest framework.
For this article, we will use djangorestframework-simplejwt, the most popular and actively maintained JWT library for Django Rest Framework(DRF).
# create project folder
mkdir jwt_auth_demo
cd jwt_auth_demo
# create virtual environment
python -m venv .venv
# OR
python3 -m venv .venv
# activate virtual env
source .venv/bin/activate # linux based os & macos
# OR
.venv\Scripts\activate # windows
# install dependencies
pip install django
pip install django_rest_framework
pip install djangorestframework-simplejwt
# create django project inside jwt_auth_demo, [. tells this]
django-admin startproject core .
# create an app
django-admin startapp account
Configure Django Settings
Open core/settings.py and update it:
INSTALLED_APPS = [
# ...
'rest_framework',
'rest_framework_simplejwt',
'account' # our app
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
}
Optionally, customize token lifetimes and behavior in core/settings.py.
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'AUTH_HEADER_TYPES': ('Bearer',),
}
Tip: Keep
ACCESS_TOKEN_LIFETIMEshort (5–15 minutes) and rely on refresh tokens for long-lived sessions.
Don't forget to migrate default table requried for django
python manage.py migrate
Set Up URLs
Update your path in core/urls.py:
from django.urls import path
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
This gives you three endpoints out of the box:
| Endpoint | Method | Purpose |
|---|---|---|
/api/token/ |
POST | Login: returns access + refresh token |
/api/token/refresh/ |
POST | Get a new access token using refresh token |
/api/token/verify/ |
POST | Checks validity of a token |
Create View
Now, we will create a ProfileView class on account/views.py:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
class ProfileView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
return Response({
'id': user.id,
'username': user.username,
'email': user.email,
})
Create account/urls.py and add path for view. And, also update core/urls.py to make sure it finds account/urls.py
account/urls.py
from django.urls import path
from .views import ProfileView
urlpatterns = [
path("profile", ProfileView.as_view(), name="profile"),
]
core/urls.py
from django.contrib import admin
from django.urls import path, include # include added
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
urlpatterns = [
# ...
path("account/", include("account.urls")), # for account/urls.py
]
Test the Flow
Create a superuser in django and now we will test the views. We are using curl here, you can use Postman, Bruno or any other tools of your choice.
Obtain Tokens (Login)
curl -X POST http://localhost:8000/api/token/ \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin"}'
Response:
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Access a Protected Endpoint
curl -X GET http://localhost:8000/account/profile \
-H "Authorization: Bearer <your_access_token>"
You will get user info in json as response
Refresh an Expired Access Token
curl -X POST http://localhost:8000/api/token/refresh/ \
-H "Content-Type: application/json" \
-d '{"refresh": "<your_refresh_token>"}'
You will get new token as response
Customize the Token Payload
By default, the access token only includes the user ID. You can add extra claims (like roles or email) by customizing the serializer. Create a new file account/serializers.py and add this:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['email'] = user.email
token['is_staff'] = user.is_staff
token['full_name'] = f'{user.first_name} {user.last_name}'.strip()
return token
Register the custom view on account/views.py. Add new class.
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
Update account/urls.py
from .views import CustomTokenObtainPairView
urlpatterns = [
# ...
path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
]
Enable Token Blacklisting
For logout to invalidate(blacklist) tokens server-side, enable the blacklist app in core/settings.py
INSTALLED_APPS = [
# ...
'rest_framework_simplejwt.token_blacklist',
]
Run migrations:
python manage.py migrate
Add a logout view on account/views.py
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
class LogoutView(APIView):
authentication_classes = []
permission_classes = [AllowAny]
def post(self, request):
try:
refresh_token = request.data['refresh']
token = RefreshToken(refresh_token)
token.blacklist()
return Response({'detail': 'Successfully logged out.'}, status=status.HTTP_200_OK)
except Exception:
return Response({'detail': 'Invalid token.'}, status=status.HTTP_400_BAD_REQUEST)
Update account/urls.py to add path for LogoutView
from django.urls import path
from .views import ProfileView, CustomTokenObtainPairView, LogoutView
urlpatterns = [
# ...
path("logout/", LogoutView.as_view(), name="log_out"),
]
Comment out CsrfViewMiddleware in core/settings.py if you get Forbidden (403) CSRF verification failed.
Commenting
CsrfViewMiddlewareshouldn't be done on real projects. There are other ways to deal with403 CSRF failed.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Complete Project Structure
jwt_auth_demo/
|-core/
| |- settings.py # JWT config here
| |- urls.py # Token endpoints here
|-account/
| |- views.py # Protected views + LogoutView
| |- serializers.py # CustomTokenObtainPairSerializer
| |- urls.py
|- manage.py
Summary
JWT authentication in Django is really simple with djangorestframework-simplejwt. Workflow of JWT authentication:
- User POSTs credentials -> receives
access+refreshtokens - Client includes
Authorization: Bearer <access_token>on each request - When the access token expires, POST the refresh token to get a new one
- On logout, blacklist the refresh token server-side
Use JWT based authentication with short token lifetimes, token rotation, and HTTPS, and you will have a good authentication foundation for any Django REST API.
For detailed information, you can visit djangorestframework-simplejwt official docs.
Leave a comment
Comments