feat(cognito): hosted login page, PKCE and refresh-token grant

The pool's own users can now sign in through /oauth2/authorize the way apps
expect Cognito to work: without identity_provider (or with COGNITO) the
browser lands on a hosted login page, and a correct password redirects to the
registered callback with a one-time code.

- GET/POST /cognito-idp/login (and /login on custom domains), with a CSRF
  cookie and the same password rules and Pre/Post Authentication triggers as
  USER_PASSWORD_AUTH; wrong passwords, unknown and unconfirmed users get the
  same answer until the password is right
- a one-hour browser session, so the next authorize skips the form unless it
  sends prompt=login, and /logout with LogoutURLs validation
- PKCE S256 on both hosted and federated codes, and nonce in the ID token
- refresh_token grant on /oauth2/token
- codes now fire Pre Token Generation with TokenGeneration_HostedAuth instead
  of skipping the trigger
This commit is contained in:
Zakaria
2026-10-24 11:09:37 -04:00
parent e7fb8bb8d6
commit f4bbb6fab4
16 changed files with 1040 additions and 32 deletions
+30 -10
View File
@@ -75,9 +75,9 @@ AWS-shaped `/oauth2/authorize` and `/oauth2/idpresponse` endpoints to the config
OIDC provider, exchanges the returned code, provisions or reconciles the federated user,
and issues a Cognito authorization code for the registered callback.
Only `ProviderType=OIDC` is supported by this flow. Floci does not provide a built-in hosted
UI and does not implement social providers such as Google, Facebook, Login with Amazon or
SignInWithApple.
Only `ProviderType=OIDC` is supported by this flow. Floci does not implement social providers
such as Google, Facebook, Login with Amazon or SignInWithApple. The pool's own users sign in on
the hosted login page instead (see Well-Known And OAuth Endpoints below).
Two deliberate divergences from AWS, both consequences of not calling out to a third
party:
@@ -280,8 +280,8 @@ up, so an oversized request naming something that does not exist reports the req
problem rather than `ResourceNotFoundException`, and an update violating both reports them
in one message with the asset list first.
Branding is presentation for the hosted UI, which Floci does not serve, so it is stored
and returned rather than rendered. Two divergences follow from that:
Branding is presentation for the hosted UI. Floci's hosted login page does not apply it, so it
is stored and returned rather than rendered. Two divergences follow from that:
- **`Settings` is stored opaquely.** AWS validates it against a deep schema, rejecting
unknown properties with `Invalid settings provided. Validation errors: [{property:
@@ -304,17 +304,37 @@ and returned rather than rendered. Two divergences follow from that:
| `GET /{userPoolId}/.well-known/jwks.json` | JSON Web Key Set for JWT validation |
| `GET /cognito-idp/oauth2/authorize` | OIDC authorization-code start endpoint |
| `GET /cognito-idp/oauth2/idpresponse` | OIDC provider callback endpoint |
| `POST /cognito-idp/oauth2/token` | OAuth authorization-code and client-credentials token endpoint |
| `GET, POST /cognito-idp/login` | Hosted login page for the pool's own users |
| `GET /cognito-idp/logout` | Ends the hosted login session |
| `POST /cognito-idp/oauth2/token` | OAuth authorization-code, refresh-token and client-credentials token endpoint |
The OAuth endpoints support browser-style OIDC authorization-code sign-in as well as the
emulator-friendly client-credentials flow:
- `GET /cognito-idp/oauth2/authorize` validates the app client and callback, then redirects
to the configured provider with an opaque state and nonce.
- `GET /cognito-idp/oauth2/authorize` validates the app client and callback. With
`identity_provider` naming an OIDC provider it redirects there with an opaque state and nonce.
Without `identity_provider`, or with `identity_provider=COGNITO`, the pool's own users sign in
on the hosted login page; the client must list `COGNITO` in `SupportedIdentityProviders` (the
default when none are given).
- `GET /cognito-idp/login` shows a user name (or email) and password form. A correct password
redirects to the registered callback with a one-time authorization code and `state`, and starts
a one-hour browser session (the `cognito` cookie), so the next authorize request returns a code
without the form unless it sends `prompt=login`. Wrong passwords, unknown users and
unconfirmed users get the same answer until the password is right. Users with a temporary
password, MFA or a pending reset are told so; those steps are not served on the page yet.
- `GET /cognito-idp/logout?client_id=...&logout_uri=...` ends the session and redirects to
`logout_uri`, which must be one of the client's `LogoutURLs`. With `redirect_uri` and
`response_type=code` instead, it ends the session and shows the login page again.
- PKCE: send `code_challenge` with `code_challenge_method=S256` to authorize, and the matching
`code_verifier` when redeeming the code. `nonce` is copied into the ID token.
- Codes from the hosted login and from federated sign-in fire the Pre Token Generation trigger
with `triggerSource=TokenGeneration_HostedAuth`, and the hosted login fires Pre and Post
Authentication.
- `GET /cognito-idp/oauth2/idpresponse` consumes the provider state, exchanges the provider
code and redirects to the registered callback with a one-time Cognito authorization code.
- `POST /cognito-idp/oauth2/token` redeems that authorization code once, or issues a machine
token for `grant_type=client_credentials`.
- `POST /cognito-idp/oauth2/token` redeems that authorization code once, exchanges a
`refresh_token` for new access and ID tokens, or issues a machine token for
`grant_type=client_credentials`.
`POST /cognito-idp/oauth2/token` is intentionally emulator-friendly rather than full Cognito parity: