Security Headers (CSP, CORS)
When to Use
Configuring HTTP security headers to prevent clickjacking, MIME sniffing, XSS, and control cross-origin requests.
Steps
- Content Security Policy (CSP) via contrib module
composer require drupal/csp drush en csp -y
Configure at /admin/config/system/csp:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline'; # Avoid unsafe-inline if possible
img-src 'self' data: https:;
- Security Kit (SecKit) module for comprehensive headers
composer require drupal/seckit drush en seckit -y
Enables: - X-Frame-Options (clickjacking protection) - X-Content-Type-Options (MIME sniffing protection) - X-XSS-Protection (XSS filter) - Referrer-Policy - Feature-Policy/Permissions-Policy
-
CORS configuration in services.yml
# sites/default/services.yml cors.config: enabled: true allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] allowedOrigins: ['https://trusted-domain.com'] allowedOriginsPatterns: ['/^https?:\/\/(.+\.)?example\.com$/'] exposedHeaders: ['Content-Length', 'X-Drupal-Cache'] maxAge: 3600 supportsCredentials: true -
Custom headers in .htaccess (Apache)
# Force HTTPS <IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" Header always set X-Frame-Options "SAMEORIGIN" Header always set X-Content-Type-Options "nosniff" Header always set Referrer-Policy "strict-origin-when-cross-origin" </IfModule> -
CSP in code (if not using contrib)
// In controller or middleware $response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self' https://cdn.example.com" );
Decision Points
| At this step... | If... | Then... |
|---|---|---|
| CSP implementation | Many inline scripts/styles | Use csp module with reporting, gradually tighten |
| CSP implementation | Clean codebase | Start strict: default-src 'self' |
| CORS | Frontend on different domain | Enable CORS, whitelist specific origins |
| CORS | Same-origin only | Leave CORS disabled (default) |
| Deployment | Production | Set HSTS with long max-age |
| Deployment | Development | Skip HSTS or use short max-age |
Common Mistakes
- Using
'unsafe-inline'in CSP -- Defeats XSS protection; use nonces or hashes - Allowing all origins in CORS (
*) -- CSRF vulnerability if credentials allowed - Not testing CSP in report-only mode -- Broken site when enforced
- Setting HSTS on HTTP -- Header ignored; requires HTTPS
- Forgetting CSP for iframes -- Embedded content can bypass restrictions
- Not configuring CSP for aggregated JS/CSS -- Drupal's aggregation may break strict CSP
See Also
- Reference: https://www.drupal.org/project/csp
- Reference: https://www.drupal.org/project/seckit
- Reference: https://www.drupal.org/node/2715637 (CORS documentation)
- Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP