DEV Community

Zeba
Zeba

Posted on

Session-Based Authentication in Django (Complete Internal Flow)

Complete Internal Flow (From Signup to Request Lifecycle)

Authentication is one of the most fundamental parts of any web application.
In Django, authentication feels simple β€” you call authenticate() and login() and everything works.

But internally, several layers coordinate:

  • Database tables
  • Password hashing
  • Session engine
  • Cookies
  • Middleware
  • Request lifecycle

Understanding this deeply gives you architectural clarity and debugging power.
This guide explains the complete internal flow of session-based authentication in Django β€” step by step.

Big Picture: What Django Uses by Default

Django uses server-side session-based authentication by default via django.contrib.auth.
That means:

  • The server stores session data.
  • The browser only stores a session ID.
  • Every request is validated using middleware.

JWT or token-based authentication is not the default β€” it must be added separately.

Core Components Involved

Before diving into the flow, understand the building blocks.

πŸ”Ή A- Database Tables
auth_user(table)
Stores:
- id (primary key)
- username
- email
- hashed password
- is_active, is_staff, etc
Passwords are never stored in plain text.

django_session(table)
Stores:
- session_key
- session_data (encoded)
- expire_date

This table connects session keys to user IDs.

πŸ”Ή B. Browser (Client)
The browser:

  • Stores the sessionid cookie
  • Automatically sends it with every request

Important:
The browser never knows

  • The password
  • The session data
  • The user ID directly It only knows a random session key.

πŸ”Ή C. Middleware
Two critical middleware classes:
- SessionMiddleware
- AuthenticationMiddleware
These run before your view executes.

Let’s understand what happens internally when:

  • A user signs up
  • A user logs in
  • The browser makes future requests

1. User Signup (Creating a User)

What Your Signup View Does

You create a user using Django’s built-in User model:

from django.contrib.auth.models import User
from django.http import JsonResponse

def signup(request):
    username = request.POST.get("username")
    password = request.POST.get("password")

    User.objects.create_user(
        username=username,
        password=password
    )

    return JsonResponse({"message": "User created successfully"})

Enter fullscreen mode Exit fullscreen mode

What Django Does Internally

When you call create_user():

  • The password is automatically hashed
  • The user is stored in the auth_user table
  • The plain-text password is never saved

Important:
At signup time, no session is created.
The user is not logged in automatically.

Signup only creates a database record.

2. User Login (Authentication Step)

Login View

from django.contrib.auth import authenticate, login
from django.http import JsonResponse

def login_view(request):
    username = request.POST.get("username")
    password = request.POST.get("password")

    user = authenticate(username=username, password=password)

    if user is not None:
        login(request, user)
        return JsonResponse({"message": "Login successful"})
    else:
        return JsonResponse(
            {"error": "Invalid credentials"},
            status=401
        )

Enter fullscreen mode Exit fullscreen mode

This flow has two important parts:

  • authenticate() β†’ validates credentials
  • login() β†’ creates the session

Let’s break them down.

2.A: What authenticate() Actually Does

authenticate(username, password)
Enter fullscreen mode Exit fullscreen mode

Internally, Django:

  • Looks up the user in the auth_user table
  • Hashes the input password
  • Compares it with the stored hashed password

It returns:

  • A User object if credentials are valid
  • None if credentials are invalid

Important:
authenticate() does not create a session.
It only verifies identity.

2.B What login(request, user) Actually Does

This is where session-based authentication begins.
When you call:

login(request, user)
Enter fullscreen mode Exit fullscreen mode

Django performs three key steps.

Step 1: Create a New Session

Django generates a random session key, for example:

 ```
 abc123xyz
 ```
Enter fullscreen mode Exit fullscreen mode

Step 2: Store the Session in the Database
In the djnago_session table:

_auth_user_id stores the logged-in user’s ID.

Step 3: Send Session ID to the Browser

Django sends this header:

```
Set-Cookie: sessionid=abc123xyz

```
Enter fullscreen mode Exit fullscreen mode

The browser stores this cookie automatically. From this point
onward,browser includes sessionID in every request.

3. What Happens on Every Request After Login

For every future request, the browser sends:

   Cookie: sessionid=abc123xyz
Enter fullscreen mode Exit fullscreen mode

Now middleware processing begins.

3A. SessionMiddleware

The first important middleware is SessionMiddleware.
It:

  1. Reads sessionid from cookies
  2. Queries the django_session table
  3. Loads the session data
  4. Attaches it to request.session

Now:

request.session["_auth_user_id"] = 5

Enter fullscreen mode Exit fullscreen mode

3B. AuthenticationMiddleware

Next, AuthenticationMiddleware runs.

It:

  • Reads _auth_user_id from session
  • Queries auth_user
  • Fetches User object
  • Attaches it to request

Now:

request.user
Enter fullscreen mode Exit fullscreen mode

This is how request.user becomes available in every view.
Complete Request Lifecycle Diagram

Browser Request
Cookie: sessionid=XYZ
        ↓
SessionMiddleware
        ↓
Load session from django_session
        ↓
Attach request.session
        ↓
AuthenticationMiddleware
        ↓
Load user from auth_user
        ↓
Attach request.user
        ↓
Your View Executes

Enter fullscreen mode Exit fullscreen mode

4. AnonymousUser Behavior

Django does not automatically block unauthenticated users.

Even if the user is not logged in:

  • The view will still execute
  • request.user will still exist

However:

request.user.is_authenticated  # False
Enter fullscreen mode Exit fullscreen mode

In that case, request.user is an AnonymousUser.

Middleware attaches the user to the request.
It does not enforce access control.

5. Why This View Is Public

def profile(request):
return JsonResponse({"profile": "data"})

This view has no restriction.
Django will not prevent unauthenticated users from accessing it.
Security must be explicitly enforced.

6.How to Restrict Access Properly

Option 1: Using login_required (Recommended)

from django.contrib.auth.decorators import login_required

@login_required
def profile(request):
    return JsonResponse({"profile": "private data"})

Enter fullscreen mode Exit fullscreen mode

Behavior:

  • If not logged in β†’ redirect to login page
  • If logged in β†’ view executes normally

Option 2: Manual Check

def profile(request):
    if not request.user.is_authenticated:
        return JsonResponse(
            {"error": "Unauthorized"},
            status=401
        )

    return JsonResponse({"profile": "private data"})
Enter fullscreen mode Exit fullscreen mode

Mental Model

Think of the authentication flow like this:

Signup              β†’ Creates user (no session)
Authenticate        β†’ Validates credentials
Login               β†’ Creates session + sends cookie
SessionMiddleware   β†’ Loads session
AuthenticationMiddleware β†’ Loads user
View execution      β†’ Always happens
Access control      β†’ Must be enforced by you

Enter fullscreen mode Exit fullscreen mode

🏁 Final Deep Mental Model

Think in layers:

Layer 1 β†’ Database (auth_user, django_session)
Layer 2 β†’ Session Engine
Layer 3 β†’ Middleware
Layer 4 β†’ View
Layer 5 β†’ Access Control Logic

Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
sreekailas_vs_25539329436 profile image
Sreekailas VS

simply and briefly explained

Some comments may only be visible to logged-in visitors. Sign in to view all comments.