Skip to main content

Implementing Strict Content Security Policy ( CSP ) in Vue.js & Django with Nginx

Increase the security of your Vue.js app with CSP

Updated 4 min readMarkdown
Implementing Strict Content Security Policy ( CSP ) in Vue.js & Django with Nginx

Introduction

Content Security Policy (CSP) is a security standard that helps prevent cross-site scripting (XSS), clickjacking, and other code-injection attacks by whitelisting trusted sources of content. When correctly configured, CSP ensures that only approved scripts, styles, images, and connections can be loaded by the browser.

Why CSP Matters

Without a strict CSP, an attacker could inject malicious scripts into your application, steal user credentials, or redirect users to phishing pages:

flowchart LR
  subgraph Attacker
    A1[Craft malicious payload] --> A2[Host exploit page or social-engineer user]
  end

  subgraph Server
    B1[Receives request with payload]
    B2[Writes to DB or reflects it back]
    B1 --> B2
  end

  subgraph VictimBrowser
    C1[Fetch page from Server]
    C1 --> C2[Parses HTML including attacker’s `<script>`]
    C2 --> C3[Executes malicious JS]
    C3 --> C4[Steals cookies or redirects to phishing site]
  end

  A2 --> B1
  B2 --> C1

How CSP Works

When the browser receives HTML along with a CSP header, it enforces each directive on every resource request:

flowchart TD
  1[Start: Browser requests page] --> 2[Receive HTML + CSP header]
  2 --> 3[Parse HTML]
  3 --> 4{Resource Tag?}
  4 -->|Yes| 5[Identify Resource Type]
  5 --> 6[Lookup CSP directive for that type]
  6 --> 7{URL in whitelist?}
  7 -->|Yes| 8[Allow load]
  7 -->|No| 9[Block load & log warning]
  4 -->|No| 10[Continue rendering]
  8 --> 10
  9 --> 10
  10 --> 11[Page finishes rendering]
  11 --> 12[End]

Browser Enforcement Flow

This diagram shows how the browser enforces CSP on resources like scripts, styles, images, and XHR/fetch requests:

flowchart LR
  subgraph Server
    A[Serve index.html] -->|Include CSP header| B((HTTP response))
  end

  subgraph Browser
    B --> C{Read CSP header}
    C --> D[Enforce rules on scripts/styles/images/connect]
    D -->|Allowed| E[Load resource]
    D -->|Blocked| F[Drop resource + warn]
  end

  subgraph Page
    E --> G[App JS runs]
    F --> G
  end

Configuring CSP in Nginx for Vue.js

In your Nginx server block for the Vue.js static build, add a CSP header:

server {
    listen 80;
    server_name yourdomain.com;

    root /var/www/vue-dist;
    index index.html;

    add_header Content-Security-Policy "
      default-src 'self';
      script-src 'self' https://cdn.jsdelivr.net;
      style-src 'self' 'unsafe-inline';
      img-src 'self' data:;
      font-src 'self' https://fonts.gstatic.com;
      connect-src 'self' https://api.yourdomain.com;
    " always;

    location / {
        try_files $uri $uri/ /index.html;
    }
}
  • default-src: Fallback for all resource types.

  • script-src: Whitelist your own bundle and trusted CDNs.

  • style-src: Allow inline styles for runtime libraries like Floating-UI.

  • img-src: Permit data URIs for base64 images.

  • font-src: Load web fonts from trusted providers.

  • connect-src: Ensure the SPA can communicate with the Django API.

Configuring CSP in Django

Use the django-csp middleware or a custom header:

Using django-csp

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

CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC  = ("'self'", "https://cdn.jsdelivr.net")
CSP_STYLE_SRC   = ("'self'", "'unsafe-inline'")
CSP_IMG_SRC     = ("'self'", "data:")
CSP_CONNECT_SRC = ("'self'", "https://api.yourdomain.com")

Custom Middleware

# middleware.py
from django.utils.deprecation import MiddlewareMixin

class ContentSecurityPolicyMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        policy = (
            "default-src 'self'; "
            "script-src 'self' https://cdn.jsdelivr.net; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data:; "
            "connect-src 'self' https://api.yourdomain.com;"
        )
        response['Content-Security-Policy'] = policy
        return response

Note: If your Django project only serves API endpoints (no HTML responses or templates), you typically don't need to configure CSP on the Django side. Simply enforce CSP at your Nginx or frontend layer.

Inline Script Handling

When an attacker tries to inject inline <script> tags via URL parameters or form inputs, CSP will block execution:

flowchart LR
  A[Victim visits /page?msg=PAYLOAD] --> B[Browser requests HTML + CSP header]
  B --> C[Browser parses HTML]
  C --> D[Finds inline `<script>` from PAYLOAD]
  D --> E{script-src allows inline?}
  E -->|No| F[Block execution + throw console error]
  E -->|Yes| G[Would execute script]
  F --> H[User safe]

Testing & Troubleshooting

  1. Check the browser console for “Refused to load” errors.

  2. Whitelisting: Add missing domains in the right directive (e.g., connect-src vs. script-src).

  3. Certificate permissions: Ensure Nginx can read SSL cert files if using HTTPS.

  4. Disable Rocket Loader (Cloudflare) if you must avoid 'unsafe-inline' for scripts.

Conclusion

Implementing a strict Content Security Policy via Nginx and Django dramatically reduces the risk of XSS and resource injection attacks. By carefully whitelisting only trusted sources and monitoring violations, you harden both your Vue.js frontend and Django backend against a wide range of threats.


ox

Tired of hand-writing server config? ox deploys your repo to your own server with Caddy and systemd set up for you. No Docker, no config maze. Try deploywithox.com

About the author

  • Hi, I’m a self-taught backend developer with 3+ years of experience, currently working at a tech startup based in The Bahamas. I mostly work with Python and Django, building APIs, designing database models, and improving performance when needed. I enjoy learning new tools and technologies as projects require.

No comments yet. Be the first to comment.