A deep dive into creating a secure API system with public access, private access, and a custom login/logout flow.
1. Enabling Token Authentication
Open your settings.py and add the authtoken app to your project. Then, configure the global REST Framework settings to require authentication by default.
# 1. Add to INSTALLED_APPS
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken',
'api',
]
# 2. Set Global Security
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
After saving, run the migration command to create the necessary token tables:
python manage.py migrate
Why define the REST_FRAMEWORK settings?
Defining the REST_FRAMEWORK dictionary in settings.py sets the global "House Rules" for your API. By setting the default permission to IsAuthenticated, you ensure that your API is "Secure by Default." This means every new URL you create will be private unless you explicitly mark it as public, preventing accidental data leaks.
2. The Custom Login View
By default, DRF only returns a token. We can customize it to return user data as well.
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from django.contrib.auth import login as django_login
class CustomLogin(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
# 1. This creates the Session Cookie for the Browser/Admin
django_login(request, user)
# 2. This creates/gets the Token for Mobile/Postman
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})
How to use the Login Endpoint
To get your access token, send a POST request to /api/login/ with the following JSON body:
{
"username": "your_django_username",
"password": "your_django_password"
}
If the credentials are correct, the server will respond with your unique token and user details:
{
"token": "7a3b...",
"user_id": 1,
"email": "user@example.com"
}
Note: You must save this token in your application (e.g., LocalStorage) to use it for future private requests.
3. Public, Private, and Logout Views
Use permission classes to control access to your data.
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
@api_view(['GET'])
@permission_classes([AllowAny])
def public_view(request):
return Response({"msg": "Public Access"})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def private_view(request):
return Response({"msg": "Private Access for " + request.user.username})
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def logout_view(request):
# 1. Delete the Token (for Mobile/Postman)
if request.auth:
request.auth.delete()
# 2. Clear the Session (for Browser/Admin)
django_logout(request)
return Response({"message": "Successfully logged out from both Token and Session."})
Where to place your code
All authentication logic and content endpoints should live in api/views.py. Here is the complete structure:
# api/views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from django.contrib.auth import login as django_login, logout as django_logout
class CustomLogin(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
# Dual Login: Session + Token
django_login(request, user)
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def logout_view(request):
if request.auth:
request.auth.delete()
django_logout(request)
return Response({"message": "Logged out successfully."})
@api_view(['GET'])
@permission_classes([AllowAny])
def public_view(request):
return Response({"message": "Public access allowed!"})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def private_view(request):
return Response({"message": f"Hello {request.user.username}, this is private!"})
By placing this in api/views.py, you keep your project organized and make it easy to import these classes into your urls.py.
4. The Completed URL Config
from django.urls import path
from api import views as api_views
urlpatterns = [
# 1. CustomLogin is a CLASS, so it needs .as_view()
path('api/login/', api_views.CustomLogin.as_view(), name='api-login'),
# 2. These are FUNCTIONS, so call them via the prefix 'api_views'
path('api/logout/', api_views.logout_view, name='api-logout'),
path('api/public/', api_views.public_view, name='api-public'),
path('api/private/', api_views.private_view, name='api-private'),
]