Security Headers

Protect your application with HTTP security headers middleware.

Overview

SecurityHeaders middleware adds important security headers to all responses:

  • X-Content-Type-Options - Prevents MIME sniffing
  • X-Frame-Options - Protects against clickjacking
  • X-XSS-Protection - Legacy XSS protection
  • Content-Security-Policy - Controls resource loading
  • Strict-Transport-Security - Enforces HTTPS
  • Referrer-Policy - Controls referrer information
  • Permissions-Policy - Controls browser features
  • Cache-Control - Controls caching behavior

Quick Start

use actix_security::http::security::SecurityHeaders;

App::new()
    .wrap(SecurityHeaders::default())
    .service(/* ... */)

Default Configuration

SecurityHeaders::default()

Adds these headers:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Referrer-Policy: strict-origin-when-cross-origin

Strict Configuration

SecurityHeaders::strict()

Maximum security with all headers:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Content-Security-Policy: default-src 'self'
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Referrer-Policy: no-referrer
Permissions-Policy: geolocation=(), microphone=(), camera=()
Cache-Control: no-cache, no-store, must-revalidate

Custom Configuration

X-Frame-Options

Protect against clickjacking:

use actix_security::http::security::headers::FrameOptions;

// Block all framing (default)
SecurityHeaders::new().frame_options(FrameOptions::Deny)

// Allow same origin
SecurityHeaders::new().frame_options(FrameOptions::SameOrigin)

// Disable header
SecurityHeaders::new().frame_options(FrameOptions::Disabled)

Content-Security-Policy

Control resource loading:

// Basic policy
SecurityHeaders::new()
    .content_security_policy("default-src 'self'")

// More permissive
SecurityHeaders::new()
    .content_security_policy("default-src 'self'; img-src *; script-src 'self' cdn.example.com")

// Complex policy
SecurityHeaders::new()
    .content_security_policy(
        "default-src 'self'; \
         script-src 'self' 'unsafe-inline' cdn.example.com; \
         style-src 'self' 'unsafe-inline'; \
         img-src 'self' data: https:; \
         font-src 'self' fonts.gstatic.com; \
         connect-src 'self' api.example.com"
    )

Strict-Transport-Security (HSTS)

Enforce HTTPS:

// Enable with 1 year max-age
SecurityHeaders::new().hsts(true, 31536000)

// With subdomains
SecurityHeaders::new()
    .hsts(true, 31536000)
    .hsts_include_subdomains(true)

// With preload (for HSTS preload list)
SecurityHeaders::new()
    .hsts(true, 31536000)
    .hsts_include_subdomains(true)
    .hsts_preload(true)

Warning: Only enable HSTS preload if you're committed to HTTPS forever. It's difficult to reverse.

Referrer-Policy

Control referrer information:

use actix_security::http::security::headers::ReferrerPolicy;

// No referrer (maximum privacy)
SecurityHeaders::new().referrer_policy(ReferrerPolicy::NoReferrer)

// Same origin only
SecurityHeaders::new().referrer_policy(ReferrerPolicy::SameOrigin)

// Strict origin when cross-origin (default)
SecurityHeaders::new().referrer_policy(ReferrerPolicy::StrictOriginWhenCrossOrigin)

// No referrer when downgrade
SecurityHeaders::new().referrer_policy(ReferrerPolicy::NoReferrerWhenDowngrade)

Permissions-Policy

Control browser features:

// Disable geolocation, microphone, camera
SecurityHeaders::new()
    .permissions_policy("geolocation=(), microphone=(), camera=()")

// Allow geolocation for self only
SecurityHeaders::new()
    .permissions_policy("geolocation=(self), microphone=(), camera=()")

Cache-Control

Control caching:

// No caching (for sensitive data)
SecurityHeaders::new()
    .cache_control("no-cache, no-store, must-revalidate")

// Private caching
SecurityHeaders::new()
    .cache_control("private, max-age=3600")

Complete Example

use actix_web::{get, App, HttpServer, HttpResponse, Responder};
use actix_security::http::security::SecurityHeaders;
use actix_security::http::security::headers::{FrameOptions, ReferrerPolicy};

#[get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Hello!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(
                SecurityHeaders::new()
                    .frame_options(FrameOptions::SameOrigin)
                    .content_security_policy("default-src 'self'; img-src *")
                    .hsts(true, 31536000)
                    .hsts_include_subdomains(true)
                    .referrer_policy(ReferrerPolicy::StrictOriginWhenCrossOrigin)
                    .permissions_policy("geolocation=(), microphone=(), camera=()")
                    .cache_control("no-cache")
            )
            .service(index)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Middleware Order

Place SecurityHeaders after authentication middleware but before your routes:

App::new()
    .wrap(SecurityHeaders::default())  // Adds security headers
    .wrap(SecurityTransform::new()     // Handles auth
        .config_authenticator(/* ... */)
        .config_authorizer(/* ... */))
    .service(/* ... */)

Testing

use actix_web::{test, App, http::StatusCode};

#[actix_web::test]
async fn test_security_headers() {
    let app = test::init_service(
        App::new()
            .wrap(SecurityHeaders::default())
            .service(test_endpoint)
    ).await;

    let req = test::TestRequest::get().uri("/test").to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::OK);

    let headers = resp.headers();
    assert_eq!(headers.get("x-content-type-options").unwrap(), "nosniff");
    assert_eq!(headers.get("x-frame-options").unwrap(), "DENY");
}

Spring Security Comparison

Spring Security:

http.headers(headers -> headers
    .frameOptions(frame -> frame.deny())
    .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
    .httpStrictTransportSecurity(hsts -> hsts
        .maxAgeInSeconds(31536000)
        .includeSubDomains(true))
    .referrerPolicy(referrer -> referrer
        .policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
);

Actix Security:

SecurityHeaders::new()
    .frame_options(FrameOptions::Deny)
    .content_security_policy("default-src 'self'")
    .hsts(true, 31536000)
    .hsts_include_subdomains(true)
    .referrer_policy(ReferrerPolicy::StrictOriginWhenCrossOrigin)

Security Recommendations

HeaderRecommended ValueNotes
X-Content-Type-OptionsnosniffAlways enable
X-Frame-OptionsDENY or SAMEORIGINPrevent clickjacking
Content-Security-PolicyApp-specificStart strict, relax as needed
Strict-Transport-Securitymax-age=31536000Only for HTTPS sites
Referrer-Policystrict-origin-when-cross-originBalance privacy/functionality
Permissions-PolicyDisable unused featuresCamera, mic, geolocation