AWS Cognito Hosted UI is convenient right up until you want a serious Content Security Policy.

Then you hit the wall: you don’t control the response headers for the managed login pages the way you would on your own app. That changes how you think about CSP completely.

This guide is the practical version: what you can and can’t do, where CSP actually applies, and copy-paste examples for the setups I see most often.

The short version

If you use AWS Cognito Hosted UI, the login pages are served by Cognito’s domain or your custom domain backed by Cognito. For those pages:

  • You generally cannot fully manage CSP headers like you would in Nginx, CloudFront, or your app server.
  • You should still set a strong CSP on your own application that redirects users to Cognito for sign-in.
  • If you need strict CSP on the actual authentication pages, the usual answer is:
    • build your own login UI against Cognito APIs, or
    • put a carefully designed proxy/CDN layer in front, if your architecture allows it.

If you were hoping for “paste this CSP into Cognito Hosted UI settings,” that setting does not really exist in the way people expect.

Where CSP matters in a Cognito flow

A Cognito auth flow usually has at least two browser surfaces:

  1. Your app
    • https://app.example.com
  2. Cognito Hosted UI
    • https://your-domain.auth.region.amazoncognito.com
    • or a custom domain like https://login.example.com

You need to treat them separately.

Your app CSP

This one is under your control. Set it aggressively.

Typical concerns:

  • redirecting to Cognito /login
  • handling the callback endpoint
  • loading your own JS bundle
  • maybe calling Cognito endpoints with XHR/fetch
  • maybe embedding nothing at all

Cognito Hosted UI CSP

This one is mostly AWS-controlled. You can customize branding and some content, but not the whole HTTP header stack.

That means:

  • don’t assume your site’s CSP applies there
  • don’t promise auditors “we enforce strict CSP on all auth pages” unless you’ve verified the exact deployment path
  • test the real response headers from the actual Hosted UI domain

What a real CSP header looks like

Here’s a real-world CSP header from a production-style site, taken from your reference data:

content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-MDZhYWRlZmYtZjVlZC00NGZlLWFmZjQtODdjZDg4NTcyMmMx' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.headertest.com https://collect.tallytics.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com; frame-src 'self' https://consentcdn.cookiebot.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'

That’s a decent example of what modern CSP looks like in the wild:

  • tight defaults
  • explicit analytics and consent tooling
  • strict-dynamic with a nonce
  • frame-ancestors 'none'
  • object-src 'none'

You can use the same style for your app that integrates with Cognito. Just don’t expect to drop this directly onto Hosted UI unless you control the layer emitting the headers.

Best-practice CSP for the app that uses Cognito Hosted UI

If your app just redirects users to Cognito, start here.

Strict baseline

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self';
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

This is the clean baseline. Then add only what the app really needs.

CSP when redirecting to Cognito Hosted UI

A browser redirect to Cognito does not require adding Cognito to script-src. Usually people over-allow here.

If your app only does a top-level navigation like this:

window.location.href = "https://login.example.com/login?client_id=abc123&response_type=code&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback";

you usually do not need Cognito in:

  • script-src
  • connect-src
  • img-src

Because it’s just navigation.

Your app CSP can remain tight.

CSP for SPA apps using Cognito endpoints directly

If your SPA talks to Cognito endpoints with fetch, token exchange helpers, or logout endpoints, then add the Cognito origin to connect-src.

Example

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self' https://login.example.com https://your-domain.auth.us-east-1.amazoncognito.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self' https://login.example.com https://your-domain.auth.us-east-1.amazoncognito.com;
  object-src 'none';

A few notes:

  • connect-src is for XHR, fetch, WebSocket, EventSource.
  • form-action matters if you submit forms cross-origin.
  • If you only redirect with window.location, form-action may not be needed.

CSP for apps embedding third-party analytics alongside Cognito

A lot of teams secure auth but then bolt on GTM and analytics with a policy that’s basically “allow half the internet.” Don’t do that.

If your app needs analytics, keep the allowlist narrow.

Example with GTM and Google Analytics

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.googletagmanager.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://www.google-analytics.com https://*.google-analytics.com https://*.googletagmanager.com;
  frame-src 'self';
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

If you’re using nonces and modern bootstrapping, you can go stricter.

Nonce-based version

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic' https://www.googletagmanager.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://*.google-analytics.com https://*.googletagmanager.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

That pattern is close to the real CSP header you provided. It’s a good shape for modern apps.

For more ready-made patterns, official docs should come first, but I also like using https://csp-examples.com as a scratchpad for policy ideas.

Can you set CSP on Cognito Hosted UI directly?

Usually, not in a fully supported “set arbitrary security headers” way.

That’s the core limitation.

You should verify the exact behavior in your environment by checking the real response headers from:

  • /login
  • /signup
  • /forgotPassword
  • callback-related pages if used

Official AWS documentation is the source to check for current Hosted UI and custom domain behavior: https://docs.aws.amazon.com/cognito/

What to do if compliance requires CSP on the actual login page

I’ve seen this come up in audits and security reviews. There are really only three honest answers.

1. Accept AWS-managed headers for Hosted UI

This is the lowest effort option.

Use Hosted UI as-is, and document:

  • Cognito manages that page
  • your app enforces CSP on all app-controlled routes
  • the identity provider page is third-party managed infrastructure

This is often acceptable if your threat model is realistic and your controls are documented.

2. Build your own login UI

If you need full control over CSP, this is the cleanest route.

You host the login page yourself and integrate with Cognito using supported auth flows. Then you can ship a proper CSP like this:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self' https://cognito-idp.us-east-1.amazonaws.com https://login.example.com;
  frame-ancestors 'none';
  base-uri 'none';
  form-action 'self';
  object-src 'none';

That gives you real control:

  • nonces
  • no inline scripts
  • no surprise third-party dependencies
  • predictable audit story

3. Put a controlled layer in front

Sometimes teams try to front Hosted UI with CloudFront or another edge layer to inject CSP headers.

Be careful.

This can work in some architectures, but it’s easy to break:

  • redirects
  • cookies
  • OIDC parameters
  • TLS/domain assumptions
  • vendor support boundaries

If you go this route, test every auth path and don’t assume AWS will support weird reverse-proxy behavior just because it seems to work today.

Nginx example for your own app

If your app sits behind Nginx and uses Cognito Hosted UI for login redirects:

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://login.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self' https://login.example.com; object-src 'none'" always;

CloudFront response headers policy example

For an app served through CloudFront:

{
  "ContentSecurityPolicy": {
    "Override": true,
    "ContentSecurityPolicy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://login.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self' https://login.example.com; object-src 'none'"
  }
}

Apply that to the app distribution, not to Cognito unless your architecture explicitly puts CloudFront in front of it and you’ve tested the whole auth flow.

Common mistakes

Adding Cognito to every directive

You usually don’t need:

script-src 'self' https://login.example.com;
img-src 'self' https://login.example.com;
style-src 'self' https://login.example.com;

That’s cargo-cult CSP. Add origins only where the browser actually uses them.

Forgetting form-action

If your app posts to another origin, form-action controls that. People miss it all the time.

Using unsafe-inline forever

Sometimes you need it temporarily for legacy CSS or JS. Fine. But treat it as debt, not a feature.

Assuming Hosted UI inherits your app headers

It doesn’t. Different origin, different response, different CSP.

Good default policy for most Cognito-integrated apps

If you want one sane starting point, use this:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self' https://login.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self' https://login.example.com;
  object-src 'none';

Replace https://login.example.com with your Cognito custom domain if your app makes direct requests there. If it only redirects, you can probably remove it from connect-src and maybe form-action too.

My recommendation

If you’re using Cognito Hosted UI, keep the model simple:

  • lock down your app with a real CSP
  • verify the actual headers on Cognito-hosted pages
  • don’t pretend you control what you don’t control
  • if strict CSP is mandatory on the login page itself, host that page yourself

That’s the tradeoff. Hosted UI buys convenience and managed auth UX. Full CSP control usually means owning the frontend.