CodingBowl

Django REST Part 2 - Public vs. Private URLs: Securing Django with Token Auth

Published on 3 Jan 2026 Tech Development
image
Photo by Ante Hamersmit on Unsplash

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'),
]

        

Meow! AI Assistance Note

This post was created with the assistance of Gemini AI and ChatGPT.
It is shared for informational purposes only and is not intended to mislead, cause harm, or misrepresent facts. While efforts have been made to ensure accuracy, readers are encouraged to verify information independently. Portions of the content may not be entirely original.

image
Photo by Yibo Wei on Unsplash