AWS Cognito
Curiosity supports Single Sign-On against an Amazon Cognito User Pool. There are two flows, and a workspace can use either one or both:
- Redirect login — the workspace sends users to the User Pool's hosted UI and completes an OpenID Connect authorization-code exchange against the pool. This needs an app client with a client secret.
- Load balancer headers — an AWS Application Load Balancer authenticates users against Cognito before the request reaches the workspace, and the workspace signs them in from the headers the load balancer forwards. This needs no app client and is off by default. See Signing in behind an Application Load Balancer.
Prerequisites
For the redirect login flow you will need, from the AWS console:
- Domain — the User Pool's hosted UI domain, either the Amazon-provided
<prefix>.auth.<region>.amazoncognito.comor the custom domain configured for the pool - App Client ID
- Client Secret
The User Pool must expose an email attribute to the app client — the workspace identifies a user
by their email address and the login fails without one.
Configuration Steps
1. Create a User Pool app client
- In the AWS console, open Amazon Cognito and select your user pool.
- Under App integration, note the pool's Domain (or create one if the pool has none).
- Create an app client of the Confidential client type so that it is issued a client secret.
- Set the Allowed callback URL to:
{domain}/api/cognitosso/completed-login-attempt - Under OAuth 2.0 grant types, select Authorization code grant, and under OpenID Connect
scopes select
openid,profileandemail. - Note down the Client ID and the Client secret.
2. Enter Details in Curiosity
- In Curiosity, navigate to Manage → Access → Single Sign-On (
#/manage/access/sso). - Select AWS Cognito.
- Enter the Domain, the App Client ID and the Client Secret.
- Click Save.
The Domain is stored as a bare host: a pasted https:// prefix and any trailing slash are
removed on save. Clearing the Domain and saving disables AWS Cognito login and removes every
stored Cognito value — the app client ID, the client secret, and the load balancer settings below.
The client secret is write-only: the settings screen only shows whether the server holds one, and leaving the field empty keeps the stored value.
SSO is a licensed feature — like Okta, Auth0 and SAML, AWS Cognito needs the full SSO tier, and the card prompts for a license upgrade instead of opening the settings when the workspace's license does not include it.
Signing in behind an Application Load Balancer
An Application Load Balancer configured with OIDC/Cognito authentication runs the whole login before the request reaches the workspace, so there is no redirect for the workspace to handle — the request simply arrives already authenticated, carrying what the load balancer established in request headers. Turning this flow on lets the workspace sign those users in.
Only enable this behind a load balancer
The forwarded headers are what identify the caller, so this flow must only be enabled when the workspace cannot be reached except through that load balancer. Anything that can reach the server directly can present headers of its own. Both headers are still verified before anybody is signed in, but the network boundary is what makes the flow safe.
The flow is off unless an administrator turns it on, and it needs both the Domain and the Load balancer ARN to work fully.
1. Configure the load balancer
- On the listener rule that fronts the workspace, add an
authenticate-cognitoaction (orauthenticate-oidc) pointing at the same User Pool. - Set the action's scope to
openid profile email. The default isopenidalone, which produces claims with no email address — and then no user can be resolved by either header. - Note the load balancer's ARN, e.g.
arn:aws:elasticloadbalancing:eu-central-1:123456789012:loadbalancer/app/my-lb/50dc6c495c0c9188.
2. Enable the flow in Curiosity
- Navigate to Manage → Access → Single Sign-On (
#/manage/access/sso) and select AWS Cognito. - Enter the User Pool's Domain. Leave App Client ID and Client Secret empty if you do not also want the redirect login flow — this flow never calls the token endpoint, so it needs no app client.
- Turn Sign users in from load balancer headers on.
- Enter the Load balancer ARN from the previous step.
- Click Save.
The three settings
These are the only three settings this flow has; the header names are fixed by AWS, so there is nothing to choose there. Unlike every other SSO setting in the workspace, these three can also be set as environment variables:
| Setting on the Single Sign-On page | Environment variable | Meaning |
|---|---|---|
| Sign users in from load balancer headers | MSK_COGNITO_LOGIN_TRUST_PROXY_AUTH_HEADERS |
true turns the flow on. Anything else leaves it off. |
| Domain | MSK_COGNITO_LOGIN_DOMAIN |
The User Pool's hosted UI domain. Required — the flow stays off without it. |
| Load balancer ARN | MSK_COGNITO_LOGIN_LOAD_BALANCER_ARN |
The ARN of the load balancer whose signature is accepted. |
MSK_COGNITO_LOGIN_TRUST_PROXY_AUTH_HEADERS=true
MSK_COGNITO_LOGIN_DOMAIN=my-pool.auth.eu-central-1.amazoncognito.com
MSK_COGNITO_LOGIN_LOAD_BALANCER_ARN=arn:aws:elasticloadbalancing:eu-central-1:123456789012:loadbalancer/app/my-lb/50dc6c495c0c9188
What is saved on the Single Sign-On page takes precedence over the environment, and the environment supplies whatever the page has not set. A change saved on the page applies to the next request; configuration that arrives another way — a configuration sync import, or replication onto a replica — is picked up within 30 seconds.
The redirect flow's credentials are graph-only
Only the three keys above can be set from the environment. The Cognito App Client ID and
Client Secret — like every other provider's credentials — are read from the workspace
configuration stored in the graph, so exporting MSK_COGNITO_LOGIN_CLIENT_ID or
MSK_COGNITO_LOGIN_CLIENT_SECRET in the container's environment does not enable the redirect
login flow. Configure those on the Single Sign-On page or ship them in a configuration sync payload.
The two headers
The load balancer sends two headers that can carry an identity, and either is enough on its own, because neither is taken on trust:
| Header | How it is checked | Role |
|---|---|---|
x-amzn-oidc-data |
The ES256 signature is verified against the public key AWS publishes for the region, and the JWT header's signer must be the configured load balancer ARN |
Preferred — it saves a call to Cognito per proxy session, and works on a workspace with no outbound route to the pool domain |
x-amzn-oidc-accesstoken |
Exchanged at the pool's /oauth2/userInfo endpoint, which rejects a token it did not issue, that has expired, or that has been revoked |
Fallback, and the only route when no load balancer ARN is configured |
A header that cannot be verified is discarded, never downgraded to trusted.
The result is cached against a hash of the two headers, so the verification happens once per proxy session rather than once per request. Both Cognito flows resolve to the same user node, so a user who has signed in through the hosted UI and one who arrives through the load balancer are the same account.
Why the load balancer ARN is required
AWS publishes one set of ELB signing keys per region, shared by every customer in that region. A
valid signature on x-amzn-oidc-data is therefore equally produced by any load balancer in the
region — including one an attacker owns and points at an identity provider they control, letting
them mint claims for any email address they like. The signature proves that some load balancer in
the region signed the claims, not which one.
Following AWS's own guidance, the workspace matches the JWT header's signer field against the
configured ARN, and refuses the header outright until an ARN is configured rather than trusting a
signature that identifies nobody. The region used to fetch the public key is taken from the
configured ARN as well, so nothing a caller sends decides which host the workspace contacts.
With no ARN configured, the signed header is ignored and only the forwarded access token is used.
Network access
- For the signed header, the workspace needs outbound access to
https://public-keys.auth.elb.<region>.amazonaws.com(AWS GovCloud regions publish these keys from S3 instead). Keys are cached after the first fetch. - For the access-token route, the workspace needs outbound access to the User Pool domain.
A workspace with neither route cannot authenticate anyone through this flow.
Read-only replicas
A read-only replica cannot create or update user nodes, so it signs in only users the primary has already created. A user who has never signed in on the primary is rejected on the replica, and the log records that there is no user node for them.
Troubleshooting
- Invalid Domain or App Client ID: AWS displays an error immediately upon redirection to the hosted UI.
- Invalid Client Secret: Login succeeds on the hosted UI, but fails upon returning to Curiosity.
- Redirect URI Mismatch: Ensure the callback URL registered on the app client matches your
workspace URL exactly. The workspace builds it from
MSK_PUBLIC_ADDRESS, falling back to the hostname of the incoming request when that is unset. - Login fails with a missing email: The User Pool does not expose an
emailattribute to the app client. - The load balancer flow does nothing: The workspace logs every step of it at information level
under the prefix
Cognito proxy-header:— including a line when AWS OIDC headers arrive while the flow is switched off, and one when the flow is on but no such headers arrive at all. Header names are logged, never their values. - Nobody can be signed in from the load balancer: The
authenticate-cognitoaction's scope is too narrow. The load balancer fills the signed header with exactly what the identity provider's user-info endpoint returned, soopenidalone starves both headers of an email address — set the scope toopenid profile email. - The signed header is ignored: Either no Load balancer ARN is configured, or the
signerin the header does not match the one that is. The workspace then falls back to the access token.
Related
- Single Sign-On overview — redirect URI shape shared by all providers.
- Read-only replicas
- Configuration sync