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:
- Your app
https://app.example.com
- 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-dynamicwith a nonceframe-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-srcconnect-srcimg-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-srcis for XHR, fetch, WebSocket, EventSource.form-actionmatters if you submit forms cross-origin.- If you only redirect with
window.location,form-actionmay 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.