Entra ID Will Block Login Script Injection: Harden Keycloak CSP

Guilliano Molaire Guilliano Molaire 12 min read

Microsoft has announced that Entra ID will enforce a stricter Content Security Policy (CSP) on its browser sign-in pages at login.microsoftonline.com starting in mid-October 2026, so that only scripts from Microsoft’s trusted domains run during authentication and browser extensions or tools that inject code into the page stop working. You do not need to change anything in Keycloak because of that announcement, but it is a good prompt to check your own login page, because Keycloak’s default CSP does not restrict scripts at all. To harden a Keycloak login page you set a script-restricting policy in Realm settings, Security defenses, Headers, test it first in the Content-Security-Policy-Report-Only header, and expect to adjust the theme, because the stock Keycloak login theme itself uses inline scripts.

This post covers what Microsoft announced, what Keycloak sends by default, why a strict policy breaks login themes (including the stock one), how to roll out a policy without locking users out, and a checklist for custom login JavaScript. It is written for engineers who run Keycloak 26.x themselves or through an identity management as a service provider, and it sticks to the Keycloak login page instead of covering CSP in general.

What did Microsoft announce for Entra ID sign-in pages?

Microsoft first announced the change in late 2025 under Message Center item MC1191924 (covered at the time by The Hacker News and Redmond Mag, December 2025) and has since reconfirmed it in MC1481309 (the admin notification channel for Microsoft 365 changes, mirrored by a community tracker). Entra ID will add a Content Security Policy header to sign-in pages on login.microsoftonline.com, allowing scripts only from Microsoft’s trusted CDN domains and inline scripts only from trusted Microsoft sources. The rollout is scheduled for mid to late October 2026, so enforcement had not started when this was written. Browser extensions, monitoring tools or customization tools that inject scripts into the sign-in experience will stop working, although users can still sign in, and organizations that use no such tools need to do nothing. Windows Report (“Microsoft Is About to Block Injected Scripts on Entra ID Sign-Ins”) and Petri (“Microsoft Entra ID sign-in security: script injection”) summarize the rollout, and the details are in Microsoft’s Message Center items in your own admin center.

The change applies to browser sign-in on login.microsoftonline.com, which means MSAL and API token flows that do not render that page are not affected, and it is not described as affecting Entra External ID tenants.

For a Keycloak operator the announcement matters in two ways. If your users sign in to Keycloak through Entra ID as an identity provider, nothing about your Keycloak configuration changes, but any browser extension that injects into the Microsoft page on your users’ machines may stop working. More broadly, it shows the direction large identity providers are going: the login page is treated as the most sensitive page on the web, and nothing should run there that the provider did not ship.

Why does script injection on a login page matter?

A login page is where the user types a password, so any script that runs on it can read what they type. If an attacker gets JavaScript onto the page through a cross-site scripting (XSS) flaw, a compromised third-party script or a malicious browser extension, that script can copy the username and password as they are entered, read the one-time code from a second-factor step, or change the form so it posts credentials to another server. Because the page is genuinely yours, with your certificate and domain, the user has no visual cue that anything is wrong, and multi-factor authentication does not help against a script that captures the one-time code and replays it immediately.

The OWASP Cross Site Scripting Prevention Cheat Sheet lists a Content Security Policy as a defense in depth measure: output encoding and input handling remain the primary fixes, and the CSP limits the damage when something slips through. That framing fits the login page well. Keycloak encodes values in its own templates, but a realm that has accumulated custom theme code, analytics snippets and chat widgets has a much larger surface than the stock pages.

How does Keycloak set security headers per realm?

Keycloak sets a group of browser security headers on responses, and each realm can override the values. In the Admin Console you find them under Realm settings > Security defenses > Headers. The defaults below come from BrowserSecurityHeaders.java at the 26.8.0 tag of the Keycloak source.

Header Default value in 26.8.0
Content-Security-Policy frame-src 'self'; frame-ancestors 'self'; object-src 'none';
Content-Security-Policy-Report-Only empty (not sent)
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
X-Robots-Tag none
Strict-Transport-Security max-age=31536000; includeSubDomains
Referrer-Policy no-referrer

Keycloak 26.8 has no X-XSS-Protection entry in the realm headers, since modern browsers have dropped their XSS filters and there is nothing left to configure there.

The important observation is in the first row. The default CSP only controls framing and plugins: it says who may embed Keycloak in a frame (frame-ancestors), what Keycloak itself may embed (frame-src) and that no plugin content is allowed (object-src 'none'). It contains no script-src or default-src directive, so by default a browser will run any script that ends up in your login page, from any origin, inline or external. Keycloak’s own source acknowledges this: a comment in DefaultSecurityHeadersProvider.java says the header handling “will be refactored as part of introducing a more strict CSP header”.

Two other details from that source file affect how you work with the headers. The full set applies to HTML responses, which includes the login pages, while JSON responses and redirects receive a reduced set that leaves out the CSP. And if you edit the CSP value, Keycloak parses it into directives and can still adjust frame-ancestors and frame-src for specific flows, so keep those two directives in whatever you write.

What do themes inject, and why does a strict CSP break them?

A Keycloak login theme is a set of FreeMarker templates plus resources, and it can add JavaScript in two common ways. The first is the scripts= property in the theme’s theme.properties, a space-separated list of files that the base template.ftl turns into <script src="${url.resourcesPath}/..."> tags. I confirmed that loop in the keycloak.v2 login template.ftl at 26.8.0. The second is editing the templates themselves, which is how most analytics tags, chat widgets and tracking pixels end up in a login page. Our theme customization guide walks through the structure, and the Keycloakify post covers building a theme as a React project.

A policy such as script-src 'self' allows only script files loaded from your Keycloak host and blocks everything else. That fits files listed in scripts=, but it blocks three things you will meet in real themes:

  • Third-party scripts loaded from another origin (analytics, chat, A/B testing, tag managers) unless you add that origin to the policy.
  • Inline scripts, meaning code written between <script> tags in a template.
  • Inline event handlers, such as onclick="..." attributes, which count as inline script too.

The stock theme itself contains the second and third kinds. The keycloak.v2 login template.ftl at 26.8.0 includes an inline import map, several inline <script type="module"> blocks (session polling, the dark-mode check and a link handler) and inline handlers such as the language selector’s onchange and the restart button’s onclick. Inline handlers and scripts are not limited to template.ftl. At 26.8.0 the page templates also carry inline onsubmit handlers (login.ftl, login-password.ftl, login-username.ftl, login-otp.ftl, login-update-password.ftl, login-recovery-authn-code-input.ftl), inline <script> blocks (login-otp, register, webauthn-authenticate, webauthn-register, webauthn-error, login-passkeys-conditional-authenticate, code, password-validation, login-recovery-authn-code-config) and inline style attributes (the passkeys template and login-oid4vp). So script-src 'self' without 'unsafe-inline' breaks the unmodified keycloak.v2 theme, and moving only your own JavaScript into files is not enough: you would override template.ftl and roughly a dozen page templates as well.

Does Keycloak support CSP nonces in login themes?

No. I searched the 26.8.0 login templates (keycloak.v2 and base) for nonce and found no matches. Keycloak reads the CSP from the realm setting, parses it into directives and rebuilds it for each response, but it does not insert a per-response value. A nonce (a random value that changes on every response, placed in the header as 'nonce-...' and repeated on each allowed <script nonce="..."> tag) needs both a fresh header value for each page load and templates that receive it, and neither exists. Be skeptical of any guide that suggests adding 'nonce-...' to the Keycloak CSP field, since the value would be constant and offers no protection.

Hashes are the other mechanism for allowing specific inline code: 'sha256-...' in script-src permits an inline block whose content hashes to that value. That works only when the content is byte-for-byte identical on every request, and the stock inline scripts interpolate per-request values (the session polling URL and the authentication session hash, for example), so their hashes change. For custom inline code you can compute a hash for a static snippet, but the more maintainable route is the one in the next section.

So the options on Keycloak 26.x are, in order of preference:

  1. Move custom JavaScript into files served from the theme and list them in scripts=. For a policy without 'unsafe-inline', also override the stock templates listed above, and accept the maintenance of keeping those overrides current across upgrades.
  2. Keep third-party scripts out of the login page, or allow their specific origins explicitly and accept that you are trusting those providers with your login form.
  3. Allow 'unsafe-inline' for scripts, with an explicit list of allowed origins, if you keep the stock inline code. This is the realistic first enforced policy for a stock theme, though it gives up protection against injected inline script.

How do you roll out a CSP without breaking login?

Use the Report-Only header first. Content-Security-Policy-Report-Only takes the same syntax as the enforcing header, but the browser only reports violations and does not block anything. Keycloak has a separate field for it in the same Headers screen, empty by default, so you can test a stricter policy while the current one stays in force.

  1. Inventory what runs on the login page. Open your login, registration, password reset and required-action pages with the browser developer tools and list every script source, inline block and handler. Look at the pages your realm actually uses: a realm with social login, WebAuthn or an OTP step has more than the basic form.
  2. Write the candidate policy. Two cautions first. form-action also governs where a form submission may redirect, and Chromium applies it to the redirect after a successful login, so a form-action 'self' policy blocks the return to an application on another origin; list your application origins there (shown below as https://app.example.com) and test the federated login paths specifically. And this example is only a target for a theme that has been reworked as described above: against the unmodified keycloak.v2 theme it breaks pages, which is why step 3 comes first. The example is default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-src 'self'; frame-ancestors 'self'; object-src 'none'; base-uri 'self'; form-action 'self' https://app.example.com. Add your own origins where you truly need them.

    A realistic first enforced policy for a stock theme is script-src 'self' 'unsafe-inline' plus the third-party origins you actually use, tested in Report-Only first. That still blocks scripts from origins you did not list, which covers most injected-script attacks, but not inline injection. Dropping 'unsafe-inline' requires the template overrides described earlier.

  3. Put it in the Report-Only field under Realm settings > Security defenses > Headers, and add a reporting endpoint (report-uri or report-to) that you control, or at least watch the browser console during testing, because report-only mode only helps if someone reads the reports.
  4. Fix the violations. Each report names the blocked directive and source. Move inline code to files, drop scripts you no longer need, and add origins deliberately.
  5. Move the policy to the enforcing field once reports are quiet across the browsers your users run, and keep the previous value written down so you can revert from the same screen.
  6. Keep Report-Only running with the next tightening step, since new theme changes and Keycloak upgrades can add inline code. The stock template has changed between releases, so recheck after upgrades.

Should you set CSP in Keycloak or at the reverse proxy?

You can set it in either place, but pick one. If Keycloak sends a Content-Security-Policy header and your proxy adds another, the browser enforces both, and a resource must satisfy every policy it receives, so the effective policy is the intersection of the two. That is a common cause of “the page works direct but breaks through the proxy” reports. Our reverse proxy guide covers the forwarding headers Keycloak needs, which is a separate concern from response headers.

Setting it in Keycloak keeps the policy per realm, which suits a multi-realm setup where each realm has a different theme, and it travels with the realm export. Setting it at the proxy suits a single policy across several applications, but it is easy to forget when a proxy is replaced. Whichever you choose, check the final response headers with curl -I against the public URL, because a proxy can also strip or duplicate headers.

A web application firewall (WAF) is a different layer. It inspects requests going to Keycloak, for example flagging script payloads in form fields, while a CSP controls what the browser will execute from the response. Our WAF post explains that Skycloak’s WAF includes OWASP Core Rule Set rules for cross-site scripting, and recommends running new rules in log-only mode before blocking, the same rollout logic as Report-Only. The two controls complement each other, since the WAF tries to stop a malicious payload arriving and the CSP limits what a payload can do if one is stored or reflected anyway.

What should you check before shipping custom login JavaScript?

  • Is the code in a file referenced from scripts= or imported by the theme, with no inline <script> block or onclick-style attribute?
  • Does every script come from your own Keycloak host, and is each external origin there for a reason someone can state?
  • Do you really need the script on the login page, as opposed to the application after sign-in? Analytics and chat widgets rarely need to see the credential form.
  • If a third-party script is unavoidable, is it pinned to a version you host yourself, so its content cannot change under you?
  • Have you tested the full set of flows (password, social login, OTP, WebAuthn, password reset, required actions) with the policy in Report-Only?
  • Does the page set no inline styles that your style-src will block? Styles are the next thing a strict policy tends to trip over.
  • Have you rechecked after a Keycloak upgrade, since the base templates can change? If a theme stops applying altogether, the theme troubleshooting guide is the place to start.

How does this differ between self-hosted and managed Keycloak?

On a self-hosted server you own the realm headers, the theme JAR and the proxy, so all of the above is in your hands, and so is keeping the theme in step with new Keycloak releases. On a managed service the realm headers are still a realm setting, but the theme is deployed through the provider’s mechanism. At Skycloak, custom themes can be uploaded at the cluster level (our Keycloakify post notes this is a feature of the Business plan and up), and the branding feature page describes the no-code options. Check the realm’s Headers screen on your own instance to see the values in effect. The production-ready checklist and the security hardening checklist are good companions for the rest of a realm review, and the post on Microsoft Entra ID through a Keycloak lens is useful if Entra is one of your identity providers.

Frequently asked questions

Will the Entra ID change break sign-in to my Keycloak realm?

Not through anything in Keycloak. The change is reported to apply to the Microsoft sign-in page, and browser or API token flows that do not render that page are described as unaffected. The group at risk is users with extensions or tools that inject scripts into Microsoft’s page, who would lose that injected behavior while still being able to sign in.

What is Keycloak’s default Content-Security-Policy?

In the 26.8.0 source it is frame-src 'self'; frame-ancestors 'self'; object-src 'none';. It limits framing and plugins and does not restrict scripts, so you add script directives yourself if you want that protection.

Can I use a CSP nonce in a Keycloak login theme?

Not on current 26.x. The login templates do not emit nonces, and the realm header is a static string, so it cannot carry a per-request value. Move scripts into files served from your own host, or use hashes for static inline snippets.

Can I test my policy before enforcing it?

You can test it in the Content-Security-Policy-Report-Only field in the realm’s Headers settings, which reports violations without blocking. Watch the reports across all your login flows before copying the policy into the enforcing field.

Does a CSP replace fixing XSS bugs?

No. It reduces what injected script can do, but encoding output and validating input remain the primary defense, and the OWASP guidance treats the CSP as a second layer.

Patched on the day, not on your next maintenance window

Keycloak 26.7.2 fixed CVE-2026-18963, an account takeover through password reset. Skycloak had it available for auto-upgrade and told customers the same day upstream shipped it. Self-hosted teams schedule that work themselves.

Guilliano Molaire
Written by
Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Start Free Trial Talk to Sales
© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman