Skip to main content

Django Clickjacking Protection

Introduction

Clickjacking is a malicious technique where an attacker tricks users into clicking something different from what they perceive, potentially causing them to reveal confidential information or take actions without their knowledge. In this attack, a transparent or opaque layer is placed over a legitimate page, making users think they're clicking on the visible page when they're actually clicking on a hidden element controlled by the attacker.

Django provides built-in protection against clickjacking attacks through its middleware system. This tutorial will guide you through understanding clickjacking threats and implementing proper protection mechanisms in your Django applications.

What is Clickjacking?

Clickjacking (also known as a "UI redress attack") occurs when an attacker uses multiple transparent or opaque layers to trick a user into clicking on a button or link on a different page than they think they are clicking on.

For example, imagine you see what appears to be a "Download Free E-book" button on a website, but when you click it, you're actually clicking an invisible "Delete My Account" button on a different website that has been loaded in a transparent iframe.

Django's Built-in Protection

Django provides protection against clickjacking through the X-Frame-Options middleware, which is enabled by default in all new Django projects. This middleware prevents your pages from being loaded within an iframe on another site.

How the X-Frame-Options Middleware Works

The middleware sets the X-Frame-Options HTTP header, which tells browsers whether they should allow your site to be framed (loaded in an iframe) by other sites.

By default, Django sets this header to SAMEORIGIN, which allows your pages to be framed only by other pages on the same domain.

Configuring Clickjacking Protection

Django's clickjacking protection is controlled by two settings in your project's settings.py file:

python
# Default setting - enables the middleware
MIDDLEWARE = [
# ...other middleware...
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# ...other middleware...
]

# Default value is 'SAMEORIGIN'
X_FRAME_OPTIONS = 'SAMEORIGIN'

X-Frame-Options Values

The X_FRAME_OPTIONS setting can have three possible values:

  1. 'DENY' - No site can frame your pages, not even your own site
  2. 'SAMEORIGIN' - Only pages from the same origin can frame your pages
  3. 'ALLOW-FROM origin' - Specific site(s) can frame your pages (note: this is deprecated in modern browsers)

Examples of Configuring Clickjacking Protection

Example 1: Maximum Protection

If you want to prevent your site from being framed by any other site:

python
# In settings.py
X_FRAME_OPTIONS = 'DENY'

This is the most secure option and prevents any site, including your own, from framing your pages.

Example 2: Default Protection

The default Django setting allows framing from the same origin:

python
# In settings.py
X_FRAME_OPTIONS = 'SAMEORIGIN'

This allows your pages to be framed by other pages on your own site.

Per-View Protection

Sometimes you may need different clickjacking protection for specific views. Django allows you to override the global setting on a per-view basis.

Using the @xframe_options_exempt Decorator

If you want to exempt a view from the X-Frame-Options protection:

python
from django.views.decorators.clickjacking import xframe_options_exempt

@xframe_options_exempt
def my_view(request):
# This view will not have the X-Frame-Options header
return render(request, 'template.html')

Using the @xframe_options_deny Decorator

To enforce the strictest protection on a specific view:

python
from django.views.decorators.clickjacking import xframe_options_deny

@xframe_options_deny
def sensitive_view(request):
# This view will have X-Frame-Options: DENY
return render(request, 'sensitive_template.html')

Using the @xframe_options_sameorigin Decorator

To ensure a view can only be framed by the same site:

python
from django.views.decorators.clickjacking import xframe_options_sameorigin

@xframe_options_sameorigin
def my_view(request):
# This view will have X-Frame-Options: SAMEORIGIN
return render(request, 'template.html')

Real-World Applications

Example: Protecting an Administrative Dashboard

Administrative dashboards typically contain sensitive functionality that should never be framed by external sites:

python
from django.views.decorators.clickjacking import xframe_options_deny

@login_required
@xframe_options_deny
def admin_dashboard(request):
user_stats = UserStats.objects.get_for_user(request.user)
return render(request, 'dashboard.html', {'stats': user_stats})

Example: Allowing Embedding for a Widget

If you've created a widget that should be embeddable on other sites, you might want to exempt it from clickjacking protection:

python
from django.views.decorators.clickjacking import xframe_options_exempt

@xframe_options_exempt
def embeddable_widget(request):
context = {'widget_data': get_widget_data()}
return render(request, 'widget.html', context)

However, be careful with this approach - only use xframe_options_exempt for views that:

  1. Don't contain sensitive information
  2. Don't perform any actions that could be exploited if clicked unknowingly
  3. Are specifically designed to be embedded in other sites

Content Security Policy (CSP)

For more advanced protection, consider implementing Content Security Policy in addition to X-Frame-Options. Django supports this through third-party packages like django-csp.

A basic implementation with django-csp would look like:

python
# In settings.py
MIDDLEWARE = [
# ...other middleware...
'csp.middleware.CSPMiddleware',
# ...other middleware...
]

CSP_DEFAULT_SRC = ("'self'",)
CSP_FRAME_ANCESTORS = ("'self'",) # Similar to X-Frame-Options: SAMEORIGIN

The CSP_FRAME_ANCESTORS directive provides more flexible control over which sites can frame your content.

Testing Your Clickjacking Protection

To test if your clickjacking protection is working correctly:

  1. Create a simple HTML file on your computer:
html
<!DOCTYPE html>
<html>
<head>
<title>Clickjacking Test</title>
</head>
<body>
<h1>Clickjacking Test</h1>
<iframe src="https://your-django-site.com/protected-page/" width="800" height="600"></iframe>
</body>
</html>
  1. Open this file in your browser
  2. If your protection is working correctly, the iframe should either:
    • Be blank (if X_FRAME_OPTIONS = 'DENY')
    • Show an error (depending on the browser)
    • Not load your page

Summary

Clickjacking protection is an essential security feature for all web applications. Django provides easy-to-use tools to protect your site:

  • Django includes XFrameOptionsMiddleware by default
  • The default setting (SAMEORIGIN) provides good protection for most sites
  • You can customize protection globally via the X_FRAME_OPTIONS setting
  • Per-view customization is possible with decorators
  • For advanced protection, consider implementing Content Security Policy

By understanding and properly implementing clickjacking protection, you'll significantly enhance the security of your Django applications and protect your users from potential attacks.

Additional Resources

  1. Django Official Documentation on Clickjacking Protection
  2. OWASP Clickjacking Defense Guide
  3. Content Security Policy (MDN Web Docs)
  4. django-csp package

Exercises

  1. Create a Django view that displays sensitive user information and ensure it has the strictest clickjacking protection.
  2. Create a different view that's designed to be embedded on other websites.
  3. Implement Content Security Policy in your Django project using django-csp.
  4. Create a test HTML page with an iframe to verify your clickjacking protection is working as expected.
  5. Update your Django project to use X_FRAME_OPTIONS = 'DENY' and test how it affects your application.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)