Django Sessions: What request.session is, how to use it, and best practices

- Andrés Cruz - ES En español

Video thumbnail

HTTP (Hypertext Transfer Protocol) is the fundamental protocol of the web. By design, it is a stateless protocol: each request made by a browser to the server is processed completely independently, with no memory of previous interactions. Once the server responds, it forgets who made the request.

This behavior poses an immediate problem: how does a web application know that the user browsing the profile page is the same one who logged in a few minutes ago? That is where Django's session system comes into play.

In the previous article we saw how to handle static files in Django. Now let's dive into one of the most important concepts for any real-world web application: the session.

What is a session in Django and why is it necessary

A session is a mechanism that allows associating data with a user across multiple HTTP requests. In other words, it is how Django "remembers" who you are between one request and the next.

The process works like this: when a user visits the application for the first time, Django creates a new session and sends a cookie to the browser with a unique identifier called sessionid. That identifier acts as a bridge between the client and the server: on each subsequent request, the browser automatically sends the sessionid in the Cookie header, and Django uses it to retrieve the session data previously stored on the server.

It is important to understand that the cookie only contains that identifier; the actual data (username, shopping cart, preferences, etc.) is stored on the server. This is what distinguishes sessions from simple cookies and what makes them more secure.

Responsible for this entire process is the SessionMiddleware class, which intercepts each incoming request to read the sessionid and makes the session data available in request.session during view processing. Upon completion, it saves the changes before sending the response to the client.

How Django manages sessions internally

The internal flow of SessionMiddleware in each request-response cycle is as follows:

  • Reads the sessionid from the client's cookie (if it exists).
  • Retrieves the associated data from the configured session engine (database, file, cache, etc.).
  • Exposes it in the request.session object, accessible from any view.
  • When the view finishes, saves changes and updates the cookie if necessary before sending the response.

This flow is the invisible "glue" that maintains consistency across pages, forms, and users throughout all navigation. It is, in fact, the base mechanism upon which Django builds its authentication system: without sessions, there would be no authenticated users.

Basic session middleware configuration in settings.py

The SessionMiddleware comes enabled by default in most Django projects, but it is always good practice to verify that it is present in MIDDLEWARE and in the correct order. It is important that it appears before AuthenticationMiddleware, as Django's authentication system depends on sessions already being available:

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ...
]

After running python manage.py migrate, Django automatically creates the django_session table in the database, which is where session data will be stored by default.

The request.session object works exactly like a Python dictionary: you can store, read, and delete data using key-value pairs. Any JSON-serializable data type (numbers, strings, lists, dictionaries) can be stored in it.

Using request.session in a Django view

A classic example to understand sessions is a visit counter: we want to count how many times a single user (or more precisely, the same browser) has accessed a URL. The @never_cache decorator is important: it tells the browser and intermediate proxies not to cache the response, ensuring that the counter is always read and updated from the actual session.

# views.py (Variant 1: Using render and direct session access)

from django.shortcuts import render
from django.views.decorators.cache import never_cache

# NOTE: In this case, 'render' needs a file template (for example, 'visits.html')
# Assuming a file 'visits.html' exists with the content: <h1>Total visits: {{ n_visits }}</h1>

@never_cache
def sessionvisit_view_variant1(request):
    # 1. Get and update the counter
    current_visits = request.session.get('n_visits', 0)
    new_visits = current_visits + 1
    
    # 2. Save the new value in the session
    request.session['n_visits'] = new_visits
    
    # 3. Use the 'render' shortcut to load and respond. 
    # Here we pass the *new* value so it displays correctly.
    return render(request, 'visits.html', {'n_visits': new_visits})

Every time I refresh the page, the count increases. The second argument of request.session.get('n_visits', 0) acts as a default value: if the key does not yet exist in the session, it returns 0 instead of raising an error.

In the django_session table, I can find the session keys and data.

We can also inspect session attributes in the browser using developer tools (Set-Cookie header). Thus, the browser stores the sessionid in a cookie, and every time it sends a request to the server, it transmits it inside the Cookie header.

The session_data field you will see in the database is a base64 encoded string. We can decode it like this:

import base64
import json

session_str = "eyJuX3Zpc2l0cyI6MTR9:1rQVYV:tvm4LNPRtDXalmCcaR3cXeIMgjj3c8BqzwDca0GdK0U"

# Split the string to get the base64 portion
base64_data = session_str.split(":")[0]

# Pad the string if necessary
padded_base64_data = base64_data + '=' * (-len(base64_data) % 4)

# Decode the base64 string
decoded_data = base64.b64decode(padded_base64_data)

# Assuming the decoded data is JSON serialized
deserialized_data = json.loads(decoded_data)

print(deserialized_data)

"""
{'n_visits': 14}
"""

Sessions and cookies are key components in managing state and user data in web applications, but they serve different purposes and operate in different ways.

Key differences between sessions and cookies in Django

Storage location

  • Cookies: Data is stored in the user's browser (client side).
  • Sessions: Data is stored on the server. The browser only holds the sessionid, which is a reference to that data.

Security

  • Cookies: Living on the client, they are more vulnerable to tampering and data theft through XSS attacks. They should never contain sensitive plain text information.
  • Sessions: More secure by design, as the client only knows the sessionid and never the actual data. Still, protecting that identifier is critical (hence the importance of using HTTPS).

Size and capacity

  • Cookies: Limited to about 4 KB per cookie. They are not suitable for large volumes of data.
  • Sessions: Storage is limited only by the engine you use (database, file system, Redis, etc.), so they can handle much larger amounts of data.

Use cases

  • Cookies: Ideal for storing user preferences (dark/light theme, language) or non-sensitive tracking data that must persist across browsing sessions.
  • Sessions: The right choice for managing authentication, handling temporary shopping carts, or storing any sensitive data that should only last while the user browses.

How to save, read, and delete data in the Session

Working with request.session is as simple as handling a Python dictionary. Below are the three patterns you will use constantly.

1. Saving a value

To save information in a session, simply assign a value to a key. For example, we save the ID of a newly created comment to display it on the next page:

def add_comment(request):
   # ... comment creation logic ...
   request.session['comment_id'] = comment.id
   return redirect('index')

2. Safely querying a value

Since request.session is accessible in any view, you can query that data from anywhere in the application. However, if the key does not exist, Django will raise a KeyError. The safe way to read it is to first check if it exists, or use the .get() method with a default value:

def index(request):
   context = {}
   if 'comment_id' in request.session:
       # Convert to string to avoid errors when concatenating with text
       context['last_comment'] = "Latest comment ID: " + str(request.session['comment_id'])
   return render(request, 'index.html', context)

3. Destroying a specific value

If we want the latest comment message to appear only once and then disappear (the pattern known as a flash message), we do the following:

def index(request):
   if 'comment_id' in request.session:
       print(f"Showing ID: {request.session['comment_id']}")
       # Delete the key from the session
       del request.session['comment_id']
   
   return render(request, 'index.html')

You can also use request.session.pop('comment_id', None) as a more concise alternative: it deletes and returns the value in a single operation without raising an error if the key does not exist.

Limitations: What can we store?

You can store simple structures such as numbers, strings, or lists (arrays):

request.session['comments'] = [1,2,3,4,5]

However, you cannot store complex objects directly (such as an instance of a database model) because the session does not know how to serialize Django objects by default.

  • ❌ Incorrect: request.session['user_obj'] = User.objects.get(id=1)
  • ✅ Correct: Simply store the PK (ID) of the object, and later when you need the object, look it up in the database using that ID stored in the session: request.session['user_id'] = user.pk

Session storage engines: SESSION_ENGINE

By default, Django stores session data in the database using django.contrib.sessions.backends.db. You can change this behavior by adjusting SESSION_ENGINE in your settings.py:

  • django.contrib.sessions.backends.db — Database (default). Simple and reliable.
  • django.contrib.sessions.backends.cache — Cache (Redis, Memcached). Much faster, ideal for high traffic.
  • django.contrib.sessions.backends.cached_db — Combination: reads from cache and writes to database as a backup.
  • django.contrib.sessions.backends.file — File system. Useful in local development.

For production projects with high traffic, using Redis as a session backend (along with django-redis) is a widespread practice that significantly reduces database load and improves response times.

Best practices and security in Django sessions

Sessions manage sensitive user data, making the application of security best practices essential. I recommend:

  • Enable HTTPS and use SESSION_COOKIE_SECURE = True in production so that the session cookie is only transmitted over encrypted connections.
  • Enable SESSION_COOKIE_HTTPONLY = True (enabled by default in Django) to prevent client-side JavaScript from accessing the cookie, mitigating XSS attacks.
  • Do not store sensitive information (passwords, API tokens, card details) directly in the session.
  • Use SESSION_EXPIRE_AT_BROWSER_CLOSE = True if you want the session to automatically expire when the user closes the browser.
  • Define a reasonable expiration time with SESSION_COOKIE_AGE (in seconds). The default value is two weeks (1209600 seconds).
  • Clean up expired sessions regularly using Django's management command:
$ python manage.py clearsessions

This command deletes all sessions from the database whose expiration date has passed. It is recommended to schedule it as a periodic task (for example, with cron or django-celery-beat) to keep the django_session table clean and prevent it from growing indefinitely.

In high-traffic environments, it can also be more efficient to use cache-based sessions (django.contrib.sessions.backends.cache).

Frequently asked questions about Django sessions

  • Where is session data stored by default?
    • In the django_session table in the database. You can change the backend by configuring SESSION_ENGINE in settings.py.
  • How do I clear a session or log out a user?
    • Use request.session.flush(): it deletes all data from the current session and generates a new sessionid, completely invalidating the previous session.
  • How can I inspect the contents of request.session?
    • Simply print it to the console or within a view:
    • print(dict(request.session.items()))
  • Why does Django Admin require SessionMiddleware?
    • Because the administration panel uses Django's authentication system, which relies on sessions to keep the user logged in between requests. Without SessionMiddleware, the AuthenticationMiddleware cannot properly associate users with requests.
  • What is the default name of the session cookie (SESSION_COOKIE_NAME)?
    • The default name is sessionid. You can change it using the SESSION_COOKIE_NAME variable in your settings.py.
  • How do I use Redis for sessions in Django (django session redis)?
    • Install django-redis and set SESSION_ENGINE = 'django.contrib.sessions.backends.cache' along with your Redis configuration in CACHES. It is the most recommended option for high-traffic production environments.
  • What happens with anonymous user sessions?
    • Django also creates sessions for unauthenticated users. This allows, for example, keeping an active shopping cart before the user registers or logs in.
  • What is SESSION_SAVE_EVERY_REQUEST used for?
    • By default, Django only saves the session if it has been modified. If you set SESSION_SAVE_EVERY_REQUEST = True, the session will be saved on every request, updating the expiration date as well. Useful for keeping sessions active in applications with long periods between requests.

Conclusion

Sessions in Django are a powerful tool for maintaining user state in web applications without compromising security.

Understanding how they are created, stored, and managed—from the moment SessionMiddleware reads the sessionid to when it saves changes in request.session—allows building robust authentication systems and long-lasting customizations.

Mastering request.session was key to understanding the "brain" of Django and its way of connecting data across requests. The golden rule: store simple, serializable data, always protect the cookie with HTTPS, and keep the django_session table clean with the clearsessions command.

Next step, Basic usage of the Shell - Interactive Python Console in Django

Learn how to manage user state in Django using request.session. Explore the differences between sessions and cookies, storage engines (such as SESSION_ENGINE and Redis), SessionMiddleware configuration, and security best practices. Includes code examples.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.