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
- Check the browser console for “Refused to load” errors.
- Whitelisting: Add missing domains in the right directive (e.g.,
connect-src
vs.script-src
). - Certificate permissions: Ensure Nginx can read SSL cert files if using HTTPS.
- 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.
Subscribe to my newsletter
Read articles from Saurav Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Saurav Sharma
Saurav Sharma
I am a Self Taught Backend developer With 3 Years of Experience. Currently, I am working at a tech Startup based in The Bahamas. Here are my skills so far - 💪Expert at - 🔹Python 🔹Django 🔹Django REST framework 🔹Celery ( for distributed tasks ) 🔹ORM ( Know how to write fast queries & design models ) 🔹Django 3rd party packages along with postgresQL and mysql as Databases. 🔹Cache using Redis & Memcache 🔹Numpy + OpenCV for Image Processing 🔹ElasticSearch + HayStack 🔹Linux ( Debian ) 😎 Working Knowledge - Html, CSS, JavaScript, Ajax, Jquery, Git ( GitHub & BitBucket ), Basic React & React Native, Linux ( Arch ), MongoDB, VPS 🤠 Currently Learning - 🔹More Deep Dive into Django 🔹Docker 🔹Making APIs more Robust 🔹NeoVim 🔹System Design ☺️ Will Be Learn in upcoming months - 🔹GraphQL 🔹 Rust language Other than above, there is not a single technology ever exists that i can't master if needed.