Django Sessions and Cookies Explained

Django Sessions and Cookies Explained

Understanding sessions and cookies in Django is one of those topics that can feel simple at first and then suddenly become confusing the moment you try to build something real. On paper, the idea sounds easy: you log a user in, remember them, and show their data again later. In practice, though, there are many moving parts behind the scenes. Some data should live in a cookie, some should live in a session, some should never be stored in the browser at all, and security should always be part of the conversation. That is exactly why this topic matters so much.

When developers first start with Django, they often learn how to create models, views, templates, and forms. Then they reach the point where they need a login system, a shopping cart, a language preference, a theme setting, or a “remember me” feature. Suddenly, they need a way to keep information across requests. Since HTTP is stateless by design, the server does not automatically remember who the user is from one request to the next. Django solves this problem with a very elegant combination of cookies and sessions.

In this article, we will go through the entire concept in a clear and practical way. We will explain what cookies are, what sessions are, how Django uses them, when to use each one, how to store and read session data, how to create and inspect cookies, how to secure them properly, and how to avoid common mistakes. We will also build simple code examples so the ideas feel more real and less abstract. By the end, the whole subject should feel much more comfortable.

Why browsers need cookies and sessions

The web is built on requests and responses. A browser sends a request to a server, the server responds, and then that conversation ends. If the same browser sends another request a second later, the server does not automatically know whether it is the same person or a completely new visitor. This is why state management exists.

Imagine a user logs into your site. On the next page load, you want to show their name, keep them authenticated, and maybe remember the cart items they selected earlier. Without some kind of memory mechanism, the site would behave like it had never seen them before. That would make modern websites almost impossible to use.

Cookies and sessions provide that memory, but they do it in different ways.

A cookie is a small piece of data stored in the user’s browser. It is sent back to the server with every request to the same domain, depending on its settings. A session is server-side storage for user-specific data. In Django, the browser usually holds only a session ID in a cookie, while the actual session data stays on the server or in a backend storage system.

That distinction is incredibly important. Many beginners think “session data is stored in cookies,” but that is not quite true in Django’s default setup. By default, Django stores session information on the server and keeps only the session key in a cookie named sessionid.

Cookies in simple words

A cookie is like a tiny note the browser keeps for a website. The site can write the note, and the browser returns it later. Cookies are commonly used for things like:

  • remembering login state

  • storing language preferences

  • saving theme choices

  • tracking analytics

  • keeping short-lived identifiers

A cookie has a name and a value, and it may also include settings such as expiration time, domain, path, secure flag, and HTTP-only flag.

A cookie can be extremely useful, but it should not be treated like a safe place for sensitive data. Browser cookies are visible to the client, and users can inspect them, modify them, or delete them. That is why you should never store passwords, secret API keys, or highly sensitive private information in plain cookies.

Sessions in simple words

A session is a way to remember a user across multiple requests without keeping all the data in the browser. In Django, when a session is created, the server generates a unique session key. This key is saved in the user’s browser inside a cookie, and the actual session data is stored separately.

Think of it like this:

  • the cookie holds a ticket number

  • the server holds the real record linked to that ticket number

When the browser comes back, it sends the ticket number again, and the server uses it to find the matching session data.

This approach is much safer and more flexible than putting everything directly into cookies.

How Django handles sessions by default

Django has built-in support for sessions. It comes with a session framework that you can enable and use without installing third-party packages.

By default, Django uses the database-backed session engine. That means session data is stored in your database in a table called django_session. The browser receives a cookie containing a session ID, and Django uses that ID to look up the corresponding session row.

The default flow is:

  1. User visits your site.

  2. Django creates or loads a session.

  3. The browser stores a sessionid cookie.

  4. The server stores session data in the session backend.

  5. On later requests, the browser sends sessionid back.

  6. Django retrieves the session data and makes it available in the request.

This design gives you a secure and practical way to store temporary state.

Enabling sessions in Django

In most Django projects, sessions are already available because the app is included in the default configuration. Still, it is useful to know the setup.

In settings.py, make sure these apps are enabled:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Also ensure the session middleware is included:

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

The SessionMiddleware is what makes session support available in requests and responses. Without it, session data will not work properly.

After confirming this configuration, run migrations if needed:

python manage.py migrate

This creates the session table in the database if you are using the default session backend.

The session object in Django views

Inside a Django view, you can access session data through request.session. This object behaves a lot like a Python dictionary, which makes it easy to understand.

Setting session data

from django.shortcuts import render

def set_session(request):
    request.session['username'] = 'Hassan'
    request.session['role'] = 'admin'
    return render(request, 'session_set.html')

In this example, we are storing two values in the session: username and role.

Reading session data

from django.shortcuts import render

def get_session(request):
    username = request.session.get('username', 'Guest')
    role = request.session.get('role', 'visitor')
    return render(request, 'session_get.html', {
        'username': username,
        'role': role,
    })

Using .get() is safer than directly accessing the key because it lets you provide a default value.

Removing a single session value

def remove_session_key(request):
    if 'role' in request.session:
        del request.session['role']
    return render(request, 'session_removed.html')

Clearing the full session

def clear_session(request):
    request.session.flush()
    return render(request, 'session_cleared.html')

flush() removes all session data and creates a new session key. This is often useful during logout.

Working with cookies in Django

Cookies are handled through the response object in Django. You can set a cookie by using response.set_cookie().

Setting a cookie

from django.http import HttpResponse

def set_cookie_view(request):
    response = HttpResponse("Cookie has been set")
    response.set_cookie('favorite_color', 'blue')
    return response

Reading a cookie

from django.http import HttpResponse

def read_cookie_view(request):
    favorite_color = request.COOKIES.get('favorite_color', 'unknown')
    return HttpResponse(f"My favorite color is {favorite_color}")

Deleting a cookie

from django.http import HttpResponse

def delete_cookie_view(request):
    response = HttpResponse("Cookie deleted")
    response.delete_cookie('favorite_color')
    return response

This is the basic pattern. Cookies are set on the response and read from the request.

Cookies vs sessions: what is the difference?

This is one of the most important concepts to understand.

A cookie is client-side storage. A session is server-side storage. In Django, sessions usually depend on cookies only to store the session key.

Here is the practical difference:

  • Cookies are good for small, non-sensitive preferences.

  • Sessions are better for logged-in user state, temporary application data, and information you do not want fully exposed to the browser.

For example:

  • storing theme=dark in a cookie is fine

  • storing a user ID in a session is better

  • storing a password in either is a bad idea

  • storing a shopping cart in a session is usually reasonable

The question is not just “can I store this?” but “where should this data live to be secure and manageable?”

How Django sessions actually work under the hood

When you set session data in Django, the framework does more than simply keep a Python dictionary in memory. The session system tracks changes and persists them based on the configured backend.

A typical session flow looks like this:

def add_to_session(request):
    request.session['cart_items'] = ['book-1', 'book-2']
    request.session['customer_type'] = 'regular'
    return HttpResponse("Saved in session")

Django marks the session as modified and stores it when the response is returned. On the next request, the session data is loaded again using the session key in the browser cookie.

This is one of the reasons Django sessions feel so natural to use. You work with them almost like a dictionary, and Django handles the persistence.

Session expiration and lifetime

Sessions should not last forever. In fact, one of the reasons sessions are useful is that they can expire automatically.

Django gives you several ways to control session age.

Default session age

In settings.py, there is a setting called SESSION_COOKIE_AGE that controls how long the session cookie lasts in seconds.

SESSION_COOKIE_AGE = 1209600

The default is usually two weeks. That means the session cookie will stay valid for about 14 days.

Expire at browser close

SESSION_EXPIRE_AT_BROWSER_CLOSE = True

If this is enabled, the session ends when the browser closes. This can be useful for more secure environments, though it may be annoying for users who expect to stay logged in.

Set a custom expiration for a session

def custom_session_expiry(request):
    request.session['user_id'] = 42
    request.session.set_expiry(300)  # expires in 5 minutes
    return HttpResponse("Session will expire in 5 minutes")

You can also use a datetime, a timedelta, or 0 to control the expiration logic.

Make a session last for the browser session only

request.session.set_expiry(0)

This means the session expires when the browser closes.

Secure cookie settings in Django

Cookies are small, but they can become a major security issue if misconfigured. Django provides several useful settings to help protect them.

SESSION_COOKIE_SECURE

SESSION_COOKIE_SECURE = True

This ensures the cookie is only sent over HTTPS. In production, this should usually be enabled.

SESSION_COOKIE_HTTPONLY

SESSION_COOKIE_HTTPONLY = True

This makes the cookie inaccessible to JavaScript in the browser. It helps reduce the risk of cookie theft through XSS attacks.

SESSION_COOKIE_SAMESITE

SESSION_COOKIE_SAMESITE = 'Lax'

This helps control when cookies are sent with cross-site requests. Common values are Lax, Strict, and None.

CSRF_COOKIE_SECURE

If your site uses forms and cookies in production over HTTPS, you should also think about CSRF cookie settings.

CSRF_COOKIE_SECURE = True

These settings are part of good security hygiene. They may not feel exciting, but they matter a lot in real-world applications.

Practical example: login state with session

Let’s create a simple example where we store a username in session after a mock login.

View for login

from django.shortcuts import redirect, render

def login_view(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        request.session['username'] = username
        return redirect('dashboard')
    return render(request, 'login.html')

Dashboard view

from django.shortcuts import render

def dashboard_view(request):
    username = request.session.get('username', 'Guest')
    return render(request, 'dashboard.html', {
        'username': username
    })

Logout view

from django.shortcuts import redirect

def logout_view(request):
    request.session.flush()
    return redirect('login')

Simple login template

<form method="post">
    {% csrf_token %}
    <label for="username">Username:</label>
    <input type="text" name="username" id="username">
    <button type="submit">Login</button>
</form>

Dashboard template

<h2>Welcome, {{ username }}</h2>
<a href="{% url 'logout' %}">Logout</a>

This is not a full authentication system, but it demonstrates how session data can be used to remember the user between requests.

Practical example: storing preferences in cookies

Now let’s use cookies to store a user preference, such as the site theme.

Set the theme cookie

from django.http import HttpResponse
from django.shortcuts import render

def set_theme(request):
    response = render(request, 'theme.html')
    response.set_cookie('theme', 'dark', max_age=60 * 60 * 24 * 30)
    return response

Read the theme cookie

from django.shortcuts import render

def home(request):
    theme = request.COOKIES.get('theme', 'light')
    return render(request, 'home.html', {
        'theme': theme
    })

Template example

<body class="{{ theme }}">
    <h1>Welcome to the site</h1>
</body>

This is a perfect use case for cookies because the value is small, harmless, and useful across visits.

Practical example: shopping cart with sessions

A shopping cart is one of the most common examples for session storage.

Add item to cart

from django.shortcuts import redirect

def add_to_cart(request, product_id):
    cart = request.session.get('cart', [])
    cart.append(product_id)
    request.session['cart'] = cart
    return redirect('cart_detail')

View cart items

from django.shortcuts import render

def cart_detail(request):
    cart = request.session.get('cart', [])
    return render(request, 'cart.html', {
        'cart': cart
    })

Clear cart

def clear_cart(request):
    request.session['cart'] = []
    return redirect('cart_detail')

You could store a cart in a cookie for very tiny applications, but sessions are usually the better choice because they are easier to manage and less exposed to tampering.

Session storage backends in Django

Django supports several session backends. This is useful because not every application wants the same storage strategy.

1. Database-backed sessions

This is the default backend.

SESSION_ENGINE = 'django.contrib.sessions.backends.db'

It stores session data in the database. This is simple, reliable, and good for many projects.

2. Cached sessions

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

This stores session data in cache instead of the database. It can be faster, but cache persistence depends on your cache system.

3. Cached database sessions

SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'

This uses both cache and database, giving a balance of speed and durability.

4. File-based sessions

SESSION_ENGINE = 'django.contrib.sessions.backends.file'

This stores session data in files on disk. It is usually less common in production but can be useful in some environments.

5. Signed cookie sessions

SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'

This stores session data in the cookie itself, but it is signed so it cannot be altered without detection.

This backend is convenient because it does not need database storage, but it has trade-offs. The cookie can become large, and the data is still client-side, even if signed.

When to use signed cookie sessions

Signed cookie sessions can be useful if you want to avoid server-side session storage. But you should use them carefully.

They are best suited for small, non-sensitive session data when you accept the limitations of client-side storage. They are not ideal if you need:

  • large session data

  • server-side invalidation

  • strong control over temporary data

  • easy session inspection and cleanup

For many projects, the database backend remains the most straightforward choice.

How Django protects session data

One of Django’s strengths is that it is designed with security in mind. Sessions benefit from several protections:

  • the session key is random and difficult to guess

  • the session data is stored server-side by default

  • cookies can be marked secure and HTTP-only

  • Django integrates session and authentication handling cleanly

Still, security depends on configuration and developer behavior. A secure framework does not automatically make insecure code safe. For example, if you store secrets in cookies or fail to use HTTPS in production, you can still create risk.

Common mistakes with sessions and cookies

A lot of bugs in Django projects come from a few repeated misunderstandings.

1. Storing too much in cookies

Cookies have size limits, and browsers send them with every request. That means large cookies can slow down your site and cause issues.

2. Storing sensitive data in plain text cookies

This is risky. Even if the cookie is not easy to read casually, you should assume browser-side data is not private.

3. Forgetting to save modified session data

Sometimes developers change nested session data and wonder why it did not persist. For example:

def update_cart(request):
    cart = request.session.get('cart', [])
    cart.append('book-3')
    request.session['cart'] = cart

This works because the session key is reassigned. But if you mutate complex objects in place, Django may not always know the session changed unless you set request.session.modified = True.

4. Not using HTTPS in production

If cookies contain authentication-related information, sending them over plain HTTP is a bad idea.

5. Confusing authentication with session storage

Authentication is a process. Sessions are a mechanism. Django auth uses sessions to remember the logged-in user, but they are not the same thing.

6. Not clearing session on logout

If you log a user out, you should flush or clear the session properly.

Session and authentication in Django

Django’s authentication system works closely with sessions. When a user logs in using django.contrib.auth, Django stores their user ID in the session. That is how it remembers who they are on the next request.

A simplified login flow might look like this:

from django.contrib.auth import authenticate, login
from django.shortcuts import redirect, render

def login_view(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)
            return redirect('dashboard')
    return render(request, 'login.html')

The login() function stores the authenticated user information in the session. Later, request.user becomes available thanks to AuthenticationMiddleware.

This is one of the most elegant parts of Django because authentication feels natural once the session layer is working.

How to inspect sessions in the database

If you are using the default database session backend, Django stores sessions in the django_session table.

You can inspect them using the Django shell or database tools.

Example in Django shell

python manage.py shell

Then:

from django.contrib.sessions.models import Session
sessions = Session.objects.all()
for session in sessions:
    print(session.session_key)
    print(session.get_decoded())

This is useful for debugging, but remember that session data should still be treated carefully. It may contain user-related information that should not be exposed casually.

Example: tracking page visits using session

A very simple and practical example is counting how many pages a user has visited during the current session.

from django.shortcuts import render

def page_counter(request):
    visits = request.session.get('visits', 0)
    request.session['visits'] = visits + 1
    return render(request, 'counter.html', {
        'visits': request.session['visits']
    })

This is a small example, but it illustrates a powerful idea: session data can help you create stateful user experiences without building a database table for every tiny piece of temporary data.

Example: language preference with cookies

A language preference is often better stored in a cookie because it is a user preference that should persist between visits.

from django.shortcuts import redirect

def set_language(request, lang_code):
    response = redirect('home')
    response.set_cookie('language', lang_code, max_age=60 * 60 * 24 * 365)
    return response

Then in a view:

def home(request):
    language = request.COOKIES.get('language', 'en')
    return render(request, 'home.html', {'language': language})

This works well because the browser can remember the preference even after the user leaves and comes back later.

Can cookies and sessions be combined?

Absolutely. In fact, they already are combined in most Django applications.

A common pattern is:

  • cookie stores session ID

  • session stores user state

  • another cookie stores preference data like theme or language

This division of labor is healthy and practical. Not every piece of data belongs in the same place.

For example, a site might use:

  • session for logged-in state

  • cookie for theme preference

  • cookie for consent status

  • session for cart data

That structure makes the application easier to reason about.

Best practices for sessions and cookies in Django

A few principles make a big difference in real projects.

Keep session data small

Session data is not a dumping ground. Store only what you actually need.

Store sensitive data on the server

If something must be protected, the server side is usually the better place.

Use secure cookie settings

Especially in production, use HTTPS and set cookie security flags properly.

Clear sessions on logout

Do not leave stale authenticated sessions lying around.

Use signed or server-backed mechanisms for important values

If a value matters, do not depend on a simple readable cookie alone.

Avoid long-term storage in sessions

Sessions are temporary by nature. If data must persist permanently, use the database.

Be intentional about expiration

A session that never ends can be a security and usability issue.

A more complete example: preference + login + session cart

Let’s connect everything in a more realistic mini-application.

View to add an item to cart

from django.shortcuts import redirect

def add_book(request, book_id):
    cart = request.session.get('cart', [])
    if book_id not in cart:
        cart.append(book_id)
    request.session['cart'] = cart
    return redirect('cart')

View to set a theme cookie

from django.shortcuts import redirect

def set_dark_theme(request):
    response = redirect('home')
    response.set_cookie('theme', 'dark', max_age=60 * 60 * 24 * 30)
    return response

View to show dashboard

from django.shortcuts import render

def dashboard(request):
    username = request.session.get('username', 'Guest')
    cart = request.session.get('cart', [])
    theme = request.COOKIES.get('theme', 'light')

    return render(request, 'dashboard.html', {
        'username': username,
        'cart': cart,
        'theme': theme,
    })

This kind of structure reflects how real websites often work. Sessions and cookies are not just theoretical ideas; they are the quiet machinery that makes everyday features possible.

Debugging session problems

Sometimes a session feature appears not to work, and the cause is one of a few usual suspects.

Check middleware

Without SessionMiddleware, session support will fail.

Check browser cookies

If the browser is not storing the session cookie, your session will not survive between requests.

Check secure settings in development

If you set SESSION_COOKIE_SECURE = True while testing over HTTP, the browser will not send the cookie.

Check whether you reassign mutable session objects

If you modify a list or dictionary in place, Django may not detect the change unless you assign it back or set request.session.modified = True.

Check expiration settings

A session may be expiring earlier than expected.

Check the database

If you use database sessions, confirm that django_session rows are being created.

Human side of the topic

It is easy to talk about sessions and cookies like they are just technical details, but in real life they shape how people feel about your website. A good session setup can make a site feel smooth, personal, and dependable. The user logs in once and stays logged in. Their language preference sticks. Their cart does not vanish. Their experience feels thoughtful.

A bad setup does the opposite. The site forgets them too quickly, logs them out unexpectedly, loses state, or behaves inconsistently from page to page. These are the kinds of details users may not explicitly notice when they are done well, but they feel them immediately when they are broken.

That is why learning sessions and cookies is not just a backend exercise. It is part of learning how to build websites that respect the user’s time and trust.

Final thoughts

Django makes sessions and cookies straightforward to use, but the real value comes from understanding the difference between them. Cookies are small pieces of browser-stored data. Sessions are a server-side way to keep state across requests. In Django, the browser usually stores a session key in a cookie, while the actual session data lives in the session backend.

Once you understand that model, many features become easier to build:

  • login systems

  • shopping carts

  • language preferences

  • theme settings

  • one-time messages

  • temporary application state

The most important habits are simple: keep session data small, protect cookies properly, use HTTPS in production, clear sessions on logout, and choose the right storage place for the right kind of data. If you do that, sessions and cookies will stop feeling mysterious and start feeling like one of the most useful parts of Django.

#Django sessions #Django cookies #Django session framework #Django cookie handling #Django session tutorial #Django authentication sessions #Django session examples #Django cookies explained #Django backend sessions #Django security best practices

Subscribe to our newsletter

12k+

Subscribers

Weekly

Frequency

Free

Always