What Are Content Security Policy (CSP) Headers in Keycloak?
The complete, in-depth guide to the browser-security headers Keycloak sends with every page, and how to configure them correctly.
Imagine a hotel that, alongside handing you your room key, also gives you a short printed card at check-in: “Only trust deliveries from our own kitchen and our approved vendors. Don’t let anyone slide anything under your door claiming to be from us unless it matches this list.” That card doesn’t stop every possible scam, but it gives you — and every automated system checking on your behalf — a clear, written standard to verify incoming things against. A web browser rendering Keycloak’s own login page benefits from an equivalent card, delivered not on paper but as an HTTP response header. That header, and several close relatives shipped alongside it, are the subject of this guide: Keycloak’s Content Security Policy (CSP) Headers, part of what Keycloak itself calls its Security Defenses.
This guide is a comprehensive, ground-up exploration of these headers: what a browser security header actually is and why it matters, exactly which headers Keycloak sends and configures by default, how the Content-Security-Policy header’s own syntax works, how these settings live inside a Realm’s configuration, and the real-world mistakes teams make when they loosen these defenses without understanding what they’re giving up. No prior web-security background is assumed — every term is explained in full the moment it first appears.
1Core Concepts
What Is an HTTP Response Header?
Every time a browser requests a web page, the server sends back not just the visible page content but also a set of behind-the-scenes instructions called HTTP response headers — small pieces of metadata the browser reads before deciding how to actually treat and display that page. Most headers are mundane, like stating the content’s file type. A security header is a specific category of these instructions whose entire purpose is telling the browser how to defend itself and the user while rendering this particular page.
What Is Content Security Policy Specifically?
The Content-Security-Policy header is a web standard that lets a server declare, in a compact written syntax, exactly which sources of content — scripts, styles, images, fonts, embedded frames — the browser should be willing to load and execute while displaying this page. Anything outside that declared list, even if somehow injected into the page’s markup, is refused by the browser itself, entirely independent of whatever Keycloak’s own server-side code does.
An earlier guide in this series covered “Policy” as a reusable authorization rule inside Keycloak’s Authorization Services, deciding who can do what. A Content Security Policy header is an entirely unrelated concept, despite the shared word — it’s a browser-security instruction about which content sources are trustworthy, with no connection to users, Roles, or permissions at all.
Why Keycloak Cares About This at All
Keycloak’s login pages are, by design, one of the most attractive possible targets on an entire network — they’re where usernames and passwords are actually typed. A successful attack that manages to inject even a small amount of unauthorized script into that login page could quietly capture credentials as they’re typed. CSP and its related headers are Keycloak’s built-in defense against exactly this class of attack, enabled by default rather than left to individual administrators to remember to configure.
2Architecture & Components
graph TD
Browser["Browser Requests
Keycloak Login Page"]
Browser --> KC["Keycloak Server"]
KC -->|"Response includes"| Headers["Security Defenses Headers"]
Headers --> CSP["Content-Security-Policy"]
Headers --> XFO["X-Frame-Options"]
Headers --> XCT["X-Content-Type-Options"]
Headers --> HSTS["Strict-Transport-Security"]
Headers --> RP["Referrer-Policy"]
CSP -->|"Enforced by"| BrowserEngine["Browser's Own Rendering Engine"]
XFO -->|"Enforced by"| BrowserEngine
Content-Security-Policy
Declares which sources of scripts, styles, frames, and other content the browser should trust while rendering the page — the main subject of this guide.
X-Frame-Options
Controls whether this page can be loaded inside a frame on another site, defending against a class of attack called clickjacking, covered fully in Chapter 6.
X-Content-Type-Options
Tells the browser not to second-guess a file’s declared content type, closing off a subtle class of content-confusion attack.
Strict-Transport-Security (HSTS)
Instructs the browser to only ever reach this server over HTTPS going forward, even if a future link somehow points to an insecure address.
Referrer-Policy
Controls how much information about the page a user is leaving is shared with the next site they navigate to.
Where administrators actually configure these: inside the Keycloak Admin Console, under a Realm’s own settings, in a section named Security Defenses, itself split into a Headers tab (covered throughout this guide) and a separate Brute Force Detection tab, which addresses a completely different concern — repeated failed login attempts — and isn’t covered further here.
3How the Content-Security-Policy Header’s Syntax Works
Directives and Sources
A Content-Security-Policy value is built from a series of directives, each naming a category of content (like script-src for scripts, or frame-src for embedded frames), followed by a space-separated list of sources that category is allowed to load from. A source can be a specific domain, a special keyword like 'self' (meaning “this same origin the page was loaded from”), or a small number of other special values covered as they come up.
| Directive | Controls |
|---|---|
default-src | The fallback source list applied to any content category not given its own specific directive |
script-src | Which sources JavaScript may be loaded and executed from |
style-src | Which sources CSS styling may be loaded from |
frame-src | Which sources may be embedded inside an iframe on this page |
object-src | Which sources plugin content such as Flash or Java applets may load from — commonly disabled entirely on modern pages |
Return to the hotel’s printed card from the opening. A real card wouldn’t just say “trust our kitchen” in general — it would specify separately which vendor delivers linens, which delivers food, and which handles maintenance requests, each with its own approved list. CSP directives work the same way: rather than one blanket “trusted sources” list, the policy separates scripts, styles, frames, and other content categories, each with sources appropriate to that specific category.
Keycloak’s Actual Default Value
Out of the box, Keycloak ships a Content-Security-Policy default roughly equivalent to frame-src 'self'; frame-ancestors 'self'; object-src 'none'; — permitting frames only from the same origin as Keycloak itself, permitting this page to be framed only by pages on that same origin (closely related to the clickjacking defense in Chapter 6), and disallowing plugin content entirely. This default is intentionally restrictive, reflecting the sensitivity of the pages it protects.
4Internal Working: How the Browser Actually Enforces This
Browser Requests the Page
A user’s browser navigates to a Keycloak-hosted login page, sending a standard HTTP request exactly as it would for any other website.
Keycloak Attaches Its Configured Headers
Alongside the actual page content, Keycloak’s server includes the Content-Security-Policy header and its sibling headers, using whatever values are currently configured for this Realm.
Browser Parses the Policy Before Rendering
Before executing any script or loading any embedded resource on the page, the browser reads and internally records the declared policy, establishing the rules it will check every subsequent load against.
Each Content Load Is Checked Against the Policy
As the page renders and potentially attempts to load a script, a style, or a frame, the browser checks that specific source against the matching directive, allowing it only if it matches an approved source.
Violations Are Blocked and Reported
Anything failing this check is simply refused by the browser — the resource never loads — and, depending on configuration, a violation report can be generated describing exactly what was blocked and why.
This is worth stating plainly: once Keycloak sends the header, it has no further role in enforcing it. The actual blocking decision happens entirely inside the requesting browser’s own rendering engine. This means the protection depends on the browser correctly supporting and respecting CSP — true of every modern browser, but worth knowing as the underlying mechanism rather than assuming Keycloak itself is somehow filtering content server-side.
5Deep Dive: Configuring CSP Inside a Realm
Where the Setting Lives
Inside a given Realm’s settings, the Security Defenses section’s Headers tab exposes a text field labeled Content-Security-Policy, pre-populated with Keycloak’s own restrictive default described in Chapter 3. An administrator can edit this value directly, adding or loosening directives as the organization’s actual needs require.
Production Example: Allowing a Trusted Analytics Script
An organization wanting to load a specific, trusted third-party analytics script on a customized login theme would need to add that script’s exact domain to the script-src directive, rather than loosening the policy broadly — a deliberate, narrow addition rather than disabling the protection altogether.
The Related Content-Security-Policy-Report-Only Field
Keycloak also exposes a separate Content-Security-Policy-Report-Only field, sent as its own header, which instructs the browser to evaluate a policy and report any violations without actually blocking anything. This is a genuinely useful tool for testing a stricter policy in a live environment before committing to actually enforcing it, since it surfaces exactly what would have broken without any real user-facing impact while testing.
Before making an existing, already-live Content-Security-Policy stricter, running the intended stricter version through the Report-Only field for a period first — reviewing the resulting violation reports — is a far safer path than editing the enforced policy directly and discovering what broke only after real users hit it.
6Deep Dive: X-Frame-Options and Clickjacking Protection
frame-ancestors directive and the older X-Frame-Options header both defend against the same underlying attack, worth understanding on its own terms.What Clickjacking Actually Is
Clickjacking is an attack where a malicious site embeds a legitimate page — Keycloak’s own login form, for instance — inside an invisible or disguised frame on top of its own content, tricking a user into clicking or typing into what they believe is the malicious site’s own interface, while actually interacting with the genuine, embedded Keycloak page underneath.
Clickjacking is like a scammer placing a genuine bank’s real deposit slip underneath a fake, distracting form on their own counter, positioned so a customer’s pen lands on the real slip’s signature line while believing they’re signing the scammer’s own paperwork. The underlying document is completely genuine — the deception is entirely in how it’s been hidden and repositioned.
How Keycloak Defends Against This
Both the CSP frame-ancestors directive (covered in Chapter 3) and the separate, older X-Frame-Options header instruct the browser to simply refuse to display this page inside a frame on any origin other than the ones explicitly permitted. Keycloak defaults X-Frame-Options to a restrictive value permitting framing only from the same origin, mirroring its CSP default and providing the same protection for browsers that may not fully support the newer frame-ancestors directive.
Production Example: Embedding a Login Page Inside a Trusted Internal Portal
An organization building an internal employee portal that legitimately needs to embed the Keycloak login form inside its own page would need to explicitly add that portal’s own origin to both the CSP frame-ancestors directive and the X-Frame-Options configuration — a deliberate, narrow exception rather than disabling frame protection broadly across the entire Realm.
7Deep Dive: X-Content-Type-Options
What MIME-Sniffing Is
Some older browser behavior, called MIME-sniffing, involves a browser inspecting a file’s actual content and guessing its real type, even when the server has explicitly declared a different type in its response headers — originally a convenience feature for tolerating minor server misconfigurations. This guessing can be abused: a file uploaded as an apparently harmless image, but crafted to also be valid as executable script, might be “sniffed” by the browser into running as script rather than being treated as the harmless image it was declared to be.
How the Header Defends Against This
Keycloak sends X-Content-Type-Options: nosniff, which instructs the browser to strictly respect the declared content type and never attempt this kind of guessing — closing off the specific class of attack described above with a single, simple, always-on instruction that carries essentially no downside for legitimate content.
Unlike CSP, which frequently needs deliberate, narrow adjustment to accommodate legitimate custom content, X-Content-Type-Options has essentially one correct value for virtually every deployment, and administrators rarely have a legitimate reason to change it away from Keycloak’s own default.
8Deep Dive: Strict-Transport-Security (HSTS)
The Gap HTTPS Alone Leaves Open
Even when a server always serves pages over HTTPS, a user’s very first request — before ever receiving any response — might still be sent in plain, unencrypted HTTP, either by directly typing an address without specifying HTTPS or by following an old link. An attacker positioned on that user’s network could intercept this very first plain request and quietly redirect the entire session through their own server before it ever reaches the real one.
How HSTS Closes This Gap
The Strict-Transport-Security header instructs the browser to remember, for a specified duration, that this particular server should only ever be reached over HTTPS going forward — automatically upgrading any future plain HTTP request to HTTPS internally, before it’s ever actually sent out over the network, entirely eliminating the vulnerable window described above for any repeat visit.
Production Example: Protecting a Public-Facing Login Page From Network-Level Attacks
An organization exposing its Keycloak login page to the public internet, where users might connect from untrusted networks like public Wi-Fi, benefits directly from HSTS ensuring that once a user has visited even once, every subsequent visit is automatically protected from the interception window described above, without requiring the user to take any action themselves.
Because HSTS instructs the browser to refuse plain HTTP entirely for the specified duration, enabling it on a deployment whose HTTPS configuration isn’t fully reliable can lock users out until the remembered duration expires. HSTS should be enabled only once HTTPS itself is confirmed stable and correctly configured across the entire deployment.
9Deep Dive: Referrer-Policy
What a Referrer Is
When a user clicks a link from one page to another, the browser can include a Referrer header on the resulting request, telling the destination site which page the user was just on. This is often harmless, but a Keycloak login page’s own address can sometimes contain sensitive details in its URL — a specific Realm name, an internal application identifier — that an administrator may not want leaking to whatever external site a user happens to navigate to next.
How the Header Controls This
The Referrer-Policy header lets Keycloak specify how much of its own address should be shared when a user navigates away — options range from sharing the full address, to sharing only the origin without the specific path, to sharing nothing at all. Keycloak’s own default takes a conservative, privacy-preserving stance appropriate for pages that may contain sensitive addressing details.
10How These Headers Fit Alongside the Rest of Keycloak’s Security Model
Security Defenses Headers
The subject of this guide — defends against attacks that exploit how a browser renders and trusts content on Keycloak’s own pages.
Brute Force Detection
A separate Realm setting addressing repeated failed login attempts against user accounts — an entirely different threat model from anything covered in this guide.
OpenID Connect Protections
Mechanisms like PKCE, covered elsewhere in this series, defend the token-exchange process itself, independent of how the login page’s HTML happens to render in a browser.
Authorization Services Policies
The unrelated “Policy” concept covered in an earlier guide, governing which authenticated users can perform which actions — a downstream concern from the browser-security headers covered here.
Recognizing that these are genuinely separate, complementary layers — rather than redundant overlapping settings — helps avoid the common misconception that strengthening one automatically compensates for weakening another.
11Data Flow & Lifecycle
Default State. A newly created Realm inherits Keycloak’s own restrictive built-in defaults for every header covered in this guide, requiring no administrator action to receive a reasonable baseline of protection.
Customization Need Identified. A legitimate business need arises — a custom theme requiring an external script, a portal needing to embed the login page — that the current, restrictive default doesn’t accommodate.
Report-Only Testing (Recommended). Before changing the enforced policy directly, the intended change is tested through the Content-Security-Policy-Report-Only field, surfacing any unexpected violations without real user impact.
Deliberate, Narrow Change Applied. The enforced header value is updated with the smallest change that accommodates the legitimate need, rather than broadly loosening the policy.
Ongoing Review. As the deployment’s themes, integrations, and embedding needs evolve over time, these header configurations deserve periodic review to confirm every currently permitted exception is still genuinely needed.
graph LR
A["Realm Created
with Restrictive Defaults"] --> B["Legitimate Customization
Need Identified"]
B --> C["Tested via
Report-Only Header"]
C --> D["Narrow, Deliberate
Change Applied"]
D --> E["Periodic Review of
Existing Exceptions"]
E --> B
12Advantages, Disadvantages & Trade-offs
Advantages
- Provides meaningful defense-in-depth against a real, common class of attack (script injection, clickjacking) with essentially zero performance cost
- Enabled by sensible, restrictive defaults out of the box, requiring no administrator action to receive a reasonable baseline
- The Report-Only mode allows safely testing stricter policies in a live environment before committing to enforcement
- Centralizes this entire category of protection at the Realm level, rather than requiring every custom theme or integration to reimplement it independently
Disadvantages & Trade-offs
- Enforcement depends entirely on the requesting browser correctly supporting and respecting these headers — Keycloak’s server has no further role once the header is sent
- A poorly tested, overly strict CSP can silently break legitimate custom themes or integrations, sometimes in ways only discovered by real users hitting a broken page
- These headers defend the browser’s rendering of Keycloak’s own pages specifically — they offer no protection against threats that don’t involve a browser rendering untrusted content, like a direct API-level attack
- HSTS misconfiguration carries a genuine risk of temporarily locking out users if enabled before HTTPS itself is fully reliable
13Security
Problem
To accommodate one specific external script needed by a custom theme, an administrator sets script-src to permit scripts from any source at all, rather than naming that one specific trusted domain.
Why It Fails
This broad change effectively disables the very protection CSP exists to provide against script injection — any successfully injected malicious script would now be permitted to run exactly as freely as the legitimate one it was meant to accommodate, defeating the purpose of having a Content-Security-Policy at all.
Correct Approach
Add only the specific trusted domain the legitimate script actually needs, keeping every other source restricted, exactly as described in the Chapter 5 example — the narrowest change that accommodates the genuine need, and nothing broader.
Custom Themes Are a Common Source of Header Friction
A custom Keycloak theme, covered elsewhere in this series’ broader content, that loads its own fonts, scripts, or embedded content from an external source will run directly into the default CSP unless that source is explicitly added — a frequent, legitimate reason administrators end up adjusting these headers, and exactly the scenario the Report-Only testing approach in Chapter 5 is meant to make safe.
Never Disable These Headers Entirely as a Troubleshooting Shortcut
When a custom theme or integration breaks against the default policy, disabling the relevant header entirely is a tempting quick fix, but it removes the underlying protection for the entire Realm rather than solving the actual narrow compatibility problem — the correct fix is always identifying and adding the specific source actually needed, not removing the safeguard altogether.
Headers Can Be Silently Stripped by an Intermediate Proxy
A production Keycloak deployment often sits behind a reverse proxy, load balancer, or content delivery network, and it’s entirely possible for one of these intermediate layers to strip, rewrite, or override a header on its way to the actual browser, without any change ever being made inside Keycloak’s own configuration. This means a header verified as correctly configured inside the Admin Console isn’t automatically the same header a real user’s browser actually receives — the only reliable way to know is to check the live response directly, exactly as described in Chapter 14’s guidance on periodic live header checks.
Because a missing or stripped security header doesn’t cause any visible error or broken functionality on its own — the page still loads and works normally — this class of misconfiguration is particularly easy to overlook during ordinary functional testing. It surfaces only through a deliberate check of the actual response headers, or through an automated security-scanning tool built specifically to look for it.
14Monitoring, Logging & Metrics
CSP Violation Reports
Configuring a reporting endpoint alongside the Content-Security-Policy-Report-Only header surfaces exactly what content the policy would have blocked, both during testing and in ongoing production monitoring.
Header Configuration Change History
Logging every edit to a Realm’s Security Defenses settings supports later review of exactly when and why a header’s enforced value changed, and by whom.
Periodic Live Header Checks
Regularly confirming, from an actual browser or automated scanning tool, that the headers actually being sent match what’s configured — catching drift caused by an intermediate proxy or load balancer silently stripping or rewriting headers.
HSTS Preload Status
For public-facing deployments, tracking whether the domain is registered with browser HSTS preload lists provides an additional layer of protection even before a user’s very first visit.
15Design Patterns & Anti-patterns
Good Patterns
- Start from Keycloak’s own restrictive defaults rather than a looser custom baseline, and add only the specific, narrow exceptions genuinely needed
- Test every intended change through Report-Only first before applying it to the enforced header, especially for any change touching a widely used login flow
- Confirm HTTPS is fully reliable before enabling HSTS, given the lockout risk described in Chapter 8
- Review existing exceptions periodically, removing any that a retired integration or theme no longer actually needs
Anti-patterns to Avoid
Common Mistakes
- Loosening a directive broadly (permitting any source) to fix one narrow compatibility issue, as covered in Chapter 13
- Disabling a header entirely as a troubleshooting shortcut, rather than diagnosing and fixing the actual narrow cause
- Enabling HSTS before confirming HTTPS itself is fully and reliably working across the deployment
- Never revisiting old exceptions added for integrations or themes that have since been retired
16Real-World & Industry Examples
Financial Services Login Portals
A bank’s customer-facing login page, built on Keycloak, typically keeps the default restrictive CSP entirely unmodified, given how directly attractive its login form is as an attack target — any customization need is generally routed through a separately hosted page that redirects into Keycloak, rather than loosening the login page’s own protections.
Enterprise Portals Embedding Login Inline
A large enterprise’s internal employee portal embedding Keycloak’s login form directly inline commonly adjusts both the CSP frame-ancestors directive and X-Frame-Options to explicitly permit exactly that one internal portal’s own origin, following the narrow-exception pattern from Chapter 6 rather than disabling frame protection broadly.
Public SaaS Platforms With Custom Branded Themes
A SaaS platform offering customers their own branded login experience frequently needs to accommodate custom fonts or a small analytics script per customer theme, typically maintaining a reviewed, curated list of specifically approved external sources added to the CSP, rather than a single broadly permissive policy shared across every customer.
Government and Regulated Deployments
Deployments subject to formal security compliance requirements frequently leave Keycloak’s restrictive defaults entirely untouched and instead route any needed customization through server-side theme changes that don’t require loosening any header, keeping the deployment’s security posture as close to Keycloak’s own hardened baseline as possible.
17Frequently Asked Questions
18Summary and Key Takeaways
Key Takeaways
- Content-Security-Policy (CSP) is an HTTP response header letting Keycloak declare exactly which content sources a browser should trust while rendering its pages, defending primarily against script-injection attacks.
- CSP is sent alongside sibling headers — X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, and Referrer-Policy — each addressing a distinct, complementary threat under Keycloak’s Realm-level Security Defenses settings.
- Enforcement happens entirely inside the requesting browser, not on Keycloak’s own server — Keycloak’s role ends once the header is sent.
- Use the Content-Security-Policy-Report-Only field to safely test a stricter or modified policy before applying it to the enforced header.
- When a legitimate need requires loosening a header, add the narrowest possible exception — a single specific trusted domain — rather than broadly permitting an entire content category.
- Enable HSTS only once HTTPS itself is confirmed fully reliable across the deployment, given its lockout risk if enabled prematurely.
- This “Policy” is entirely unrelated to the Authorization Services “Policy” concept covered elsewhere in this series — the shared word describes two genuinely different mechanisms.