Skip to content

Security Headers for Authentication — Complete Implementation Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Security Headers for Authentication. We cover key concepts, practical examples, and best practices to help you master this topic.

Security headers are HTTP response headers that instruct the browser to enforce security policies, directly protecting authentication tokens and session data from common web attacks including XSS, clickjacking, MIME sniffing, and protocol downgrade attacks.

What You'll Learn

By the end of this lesson, you will configure all critical security headers, understand how each header protects authentication, implement Content-Security-Policy for XSS prevention, and set secure cookie attributes for session tokens.

Why It Matters

Security headers provide browser-level protection that complements server-side security. A properly configured CSP prevents XSS attacks that could steal auth tokens. HSTS prevents protocol downgrade attacks. These headers are the first line of defense for authentication security.

Real-World Use

A user accesses a banking application over a coffee shop WiFi. Without HSTS, an attacker could downgrade the connection to HTTP and intercept the session cookie. With HSTS, the browser knows to only use HTTPS. If an XSS vulnerability exists, CSP prevents the attacker from exfiltrating the session cookie.

Security Headers Architecture

flowchart LR
    subgraph "Browser"
        A[CSP Blocks XSS]
        B[HSTS Enforces HTTPS]
        C[X-Frame-Options Blocks Clickjacking]
        D[SameSite Protects CSRF]
        E[HttpOnly Protects Cookie from JS]
    end
    subgraph "Server"
        F[Set Headers on Every Response]
        G[Set Cookie Attributes]
    end
    F --> A
    F --> B
    F --> C
    G --> D
    G --> E
    style F fill:#f90,color:#fff

Security Headers Implementation (Express)

const express = require("express");
const helmet = require("helmet");
const app = express();

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'strict-dynamic'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.example.com"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      frameAncestors: ["'none'"],
      formAction: ["'self'"],
      baseUri: ["'self'"],
      reportUri: "/csp-report",
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
  frameguard: { action: "deny" },
  noSniff: true,
  referrerPolicy: { policy: "strict-origin-when-cross-origin" },
}));

app.use((req, res, next) => {
  res.setHeader("Permissions-Policy",
    "geolocation=(), microphone=(), camera=(), payment=()"
  );
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
  next();
});

app.get("/api/login", (req, res) => {
  res.cookie("session", "token-value", {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
    domain: "example.com",
    path: "/",
    maxAge: 24 * 60 * 60 * 1000,
  });
  res.json({ message: "Cookie set with secure attributes" });
});

Manual Security Headers (Python FastAPI)

from fastapi import FastAPI, Response
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        response.headers["Content-Security-Policy"] = (
            "default-src 'self'; "
            "script-src 'self'; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data:; "
            "font-src 'self'; "
            "form-action 'self'; "
            "frame-ancestors 'none'; "
            "base-uri 'self'; "
        )
        response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
        response.headers["Permissions-Policy"] = (
            "geolocation=(), microphone=(), camera=()"
        )
        return response

app.add_middleware(SecurityHeadersMiddleware)

@app.get("/health")
async def health():
    return {"status": "ok"}

Security Headers Reference

Header Purpose Recommended Value
Content-Security-Policy Prevents XSS and data injection Restrictive policy by origin
Strict-Transport-Security Enforces HTTPS connections max-age=31536000; includeSubDomains
X-Frame-Options Prevents clickjacking DENY
X-Content-Type-Options Prevents MIME sniffing nosniff
Referrer-Policy Controls referrer header strict-origin-when-cross-origin
Permissions-Policy Restricts browser APIs geolocation=(), microphone=()
Cross-Origin-Opener-Policy Isolates cross-origin Windows same-origin
Cross-Origin-Embedder-Policy Prevents loading untrusted resources require-corp
def set_secure_cookie(response, name, value, max_age=86400):
    response.set_cookie(
        key=name,
        value=value,
        max_age=max_age,
        secure=True,         # HTTPS only
        httponly=True,       # JavaScript cannot access
        samesite="strict",   # CSRF protection
        domain="example.com",
        path="/",
    )

# For refresh tokens
def set_refresh_token_cookie(response, token):
    response.set_cookie(
        key="refresh_token",
        value=token,
        max_age=604800,      # 7 days
        secure=True,
        httponly=True,
        samesite="strict",
        path="/api/auth",    # Only sent to auth endpoints
    )

# Clear cookie on logout
def clear_auth_cookies(response):
    response.delete_cookie("access_token", path="/")
    response.delete_cookie("refresh_token", path="/api/auth")

Common Mistakes

  1. Setting CSP with unsafe-inline and unsafe-eval everywhere defeats CSP's XSS protection.
  2. Forgetting to includeSubDomains in HSTS leaves subdomains vulnerable to downgrade.
  3. Setting X-Frame-Options to SAMEORIGIN when DENY is appropriate for auth pages.
  4. Not setting the path attribute on cookies, allowing tokens to be sent to unintended endpoints.
  5. Using overly permissive CSP report-uri that leaks sensitive data in violation reports.
  6. Not testing security headers with tools like securityheaders.com or Mozilla Observatory.

Practice Questions

  1. How does Content-Security-Policy prevent authentication token theft?

CSP restricts which sources scripts can load and where data can be sent. Even if an XSS vulnerability exists, CSP blocks the attacker's script from loading external resources or exfiltrating tokens to attacker-controlled servers.

  1. Why is HSTS important for authentication?

HSTS tells the browser to always use HTTPS for the domain. This prevents man-in-the-middle attacks that downgrade HTTPS to HTTP, which would expose session cookies and auth tokens in plain text.

  1. How do cookie attributes work together to protect auth tokens?

HttpOnly prevents JavaScript access (XSS protection). Secure ensures HTTPS-only transmission. SameSite prevents cross-origin cookie sending (CSRF protection). Domain and Path restrict the cookie's scope.

  1. Challenge: Configure a complete security headers suite for a production application with CSP that allows necessary third-party scripts (analytics, CDN), HSTS with preload, all cross-origin isolation headers, and secure cookie configuration for both access and refresh tokens.

FAQ

Can security headers break my application?

Yes. CSP can block legitimate scripts. HSTS can make development with HTTP impossible. Test headers in a staging environment first. Use CSP report-only mode to detect violations before enforcing.

What is CSP report-only mode?

CSP report-only (Content-Security-Policy-Report-Only) logs violations without blocking them. Use it to discover policy issues before switching to enforcement mode. Configure a report-uri to collect violation reports.

How do I test my security headers?

Use online tools: securityheaders.com, Mozilla Observatory, CSP Evaluator. Use curl: curl -I https://yourapp.com. Add tests in your CI/CD pipeline that verify headers on every deployment.

What happens if I set conflicting security headers?

Headers can interact. For example, CSP frame-ancestors 'none' and X-Frame-Options: DENY both prevent framing. Browsers respect the most restrictive policy. Avoid conflicting configurations.

Mini Project: Security Header Auditor

Build a CLI tool that checks a website's security headers, scores them against best practices, and provides remediation recommendations.

import requests
import sys

class SecurityHeaderAuditor:
    HEADERS = {
        "strict-transport-security": {"weight": 20, "good": "max-age=31536000"},
        "content-security-policy": {"weight": 25, "good": "default-src 'self'"},
        "x-frame-options": {"weight": 10, "good": "DENY"},
        "x-content-type-options": {"weight": 10, "good": "nosniff"},
        "referrer-policy": {"weight": 10, "good": "strict-origin-when-cross-origin"},
        "permissions-policy": {"weight": 10, "good": "geolocation=()"},
        "x-xss-protection": {"weight": 5, "good": "1; mode=block"},
    }

    def audit(self, url):
        try:
            resp = requests.get(url, timeout=10, allow_redirects=True)
        except requests.RequestException as e:
            print(f"Cannot reach {url}: {e}")
            return

        score = 0
        max_score = sum(h["weight"] for h in self.HEADERS.values())

        print(f"Security Header Audit: {resp.url}")
        print(f"Status: {resp.status_code}")
        print("=" * 60)

        for header, config in self.HEADERS.items():
            value = resp.headers.get(header)
            if value:
                score += config["weight"]
                print(f"[OK] {header}: {value[:60]}")
            else:
                print(f"[!!] {header}: MISSING (recommended: {config['good']})")

        print(f"\nScore: {score}/{max_score} ({score * 100 // max_score}%)")
        if score < max_score * 0.7:
            print("Grade: F - Critical headers missing")
        elif score < max_score * 0.9:
            print("Grade: C - Improve header configuration")
        else:
            print("Grade: A - Good security posture")

if __name__ == "__main__":
    auditor = SecurityHeaderAuditor()
    auditor.audit(sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000")

What's Next

Build the authentication project to tie together all authentication patterns into a complete system.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro