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:
User visits your site.
Django creates or loads a session.
The browser stores a
sessionidcookie.The server stores session data in the session backend.
On later requests, the browser sends
sessionidback.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=darkin a cookie is finestoring 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.