Django Secure Cookies
Introduction
Cookies are small pieces of data stored on a user's browser by websites they visit. In web applications, they're essential for maintaining state between HTTP requests, storing session information, and remembering user preferences. However, improperly implemented cookies can become an attack vector for malicious actors.
Django, a high-level Python web framework, provides several built-in features for implementing secure cookies. In this tutorial, we'll explore how Django handles cookies securely and how you can leverage these features in your applications.
Understanding Cookies in Web Security
Before diving into Django's implementation, let's understand why secure cookies matter:
- Authentication: Cookies often store session tokens that identify logged-in users
- Privacy: They may contain personal information or preferences
- Cross-site vulnerabilities: Insecure cookies can enable CSRF (Cross-Site Request Forgery) attacks
- Data theft: Without proper protections, cookies can be stolen or tampered with
Django's Secure Cookie Implementation
Django handles cookies securely through several key settings and features:
1. Session Cookies
Django uses cookies to store session data by default. The framework applies several security measures automatically:
# In settings.py - These are default settings you can customize
SESSION_COOKIE_SECURE = True # Ensures cookies are only sent over HTTPS
SESSION_COOKIE_HTTPONLY = True # Prevents JavaScript access to cookies
SESSION_COOKIE_SAMESITE = 'Lax' # Controls cross-site cookie behavior
These settings should be added to your settings.py
file. Let's explore what each one does:
- SESSION_COOKIE_SECURE: When set to
True
, cookies will only be sent over HTTPS connections, preventing them from being intercepted over insecure networks - SESSION_COOKIE_HTTPONLY: This setting prevents JavaScript from accessing the cookie, helping protect against XSS (Cross-Site Scripting) attacks
- SESSION_COOKIE_SAMESITE: Controls how cookies are sent in cross-site requests, with options like 'Strict', 'Lax', or 'None'
2. CSRF Protection
Django's built-in CSRF (Cross-Site Request Forgery) protection relies on secure cookies. When you include the CSRF token in your forms, Django creates a cookie that helps verify request authenticity:
# settings.py
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = 'Lax'
In your templates, you'll include the CSRF token like this:
<form method="post">
{% csrf_token %}
<!-- form fields -->
<button type="submit">Submit</button>
</form>
3. Setting and Reading Cookies in Django Views
While Django handles session and CSRF cookies automatically, sometimes you need to set custom cookies for your application logic:
from django.http import HttpResponse
def set_cookie_example(request):
response = HttpResponse("Cookie has been set!")
# Set a secure cookie
response.set_cookie(
'user_preference',
'dark_mode',
secure=True, # HTTPS only
httponly=True, # No JavaScript access
samesite='Lax', # SameSite policy
max_age=604800 # 1 week in seconds
)
return response
To read cookies in a view:
def read_cookie_example(request):
user_pref = request.COOKIES.get('user_preference', 'light_mode')
return HttpResponse(f"Your preference is: {user_pref}")
Best Practices for Secure Cookies in Django
1. Use HTTPS Everywhere
For secure cookies to work properly, your site must use HTTPS. In production:
# settings.py for production
SECURE_SSL_REDIRECT = True # Redirects all HTTP traffic to HTTPS
SECURE_HSTS_SECONDS = 31536000 # 1 year, enables HSTS
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
2. Set Appropriate Cookie Expiration
For sensitive operations, use shorter-lived cookies:
# Example setting a short-lived cookie
response.set_cookie(
'temporary_action',
'value',
max_age=300, # 5 minutes
secure=True,
httponly=True
)
3. Use Django's Built-in Session Framework
Rather than managing cookies directly, use Django's session framework when possible:
# Store data in the session
def save_to_session(request):
request.session['user_theme'] = 'dark'
request.session['last_action'] = 'profile_update'
return HttpResponse("Session data saved")
# Read data from the session
def read_from_session(request):
theme = request.session.get('user_theme', 'light')
return HttpResponse(f"Your theme is: {theme}")
Real-World Example: Remember Me Functionality
Let's implement a "Remember Me" checkbox for a login form:
from django.contrib.auth import login
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.contrib.auth.forms import AuthenticationForm
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
user = form.get_user()
remember_me = request.POST.get('remember_me') == 'on'
# Log the user in
login(request, user)
# Set session expiration based on remember me checkbox
if remember_me:
# Session will last for 2 weeks (14 days)
request.session.set_expiry(1209600)
else:
# Session expires when browser closes
request.session.set_expiry(0)
return redirect('home')
else:
form = AuthenticationForm()
return render(request, 'login.html', {'form': form})
And in your template:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="form-check">
<input type="checkbox" name="remember_me" id="remember_me">
<label for="remember_me">Remember Me</label>
</div>
<button type="submit">Login</button>
</form>
Testing Cookie Security
It's important to verify that your cookies are secure. Django's test client can help:
from django.test import TestCase, Client
class CookieSecurityTest(TestCase):
def test_session_cookie_attributes(self):
client = Client()
response = client.get('/')
# Check if the session cookie exists
self.assertIn('sessionid', response.cookies)
# Verify cookie attributes
session_cookie = response.cookies['sessionid']
self.assertTrue(session_cookie['secure'])
self.assertTrue(session_cookie['httponly'])
Additionally, you can use browser developer tools to inspect cookies and their attributes in a live environment.
Common Cookie-Related Vulnerabilities
1. Session Hijacking
If your cookies aren't secure, they can be stolen through network eavesdropping. Always use secure=True
and deploy on HTTPS.
2. Session Fixation
This occurs when an attacker sets a user's session ID, then waits for the user to authenticate. Django mitigates this by regenerating session IDs during login:
# This is done automatically by Django's login() function
# But you can manually rotate session keys too:
from django.contrib.sessions.backends.base import UpdateError
def rotate_session_key(request):
while True:
try:
request.session.create()
break
except UpdateError:
# A new session key was generated but not saved
continue
3. CSRF Attacks
Always use Django's CSRF protection for any view that modifies server state:
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def update_profile(request):
# Process form data
pass
Summary
Secure cookies are essential for maintaining a safe Django application. By leveraging Django's built-in security features for cookies, you can protect your users from common web vulnerabilities.
Key takeaways:
- Always set
SESSION_COOKIE_SECURE
,SESSION_COOKIE_HTTPONLY
, andCSRF_COOKIE_SECURE
toTrue
- Use HTTPS for all traffic in production
- Leverage Django's session framework rather than handling cookies directly
- Set appropriate cookie expiration times based on sensitivity
- Test your cookie security configurations
Additional Resources
- Django Documentation on Security
- OWASP Session Management Cheat Sheet
- Web Security Academy: Cookie Security
Exercises
- Configure your Django application to use secure cookies with all recommended settings in
settings.py
- Create a view that sets a custom secure cookie and another view that reads it
- Implement a "Remember Me" functionality in your authentication system
- Write a test case to verify that your cookies have the correct security attributes
- Use browser developer tools to inspect cookies on your website and verify their security attributes
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)