Skip to content

feat(identity): switch the internal pool domain to managed login - #173

Merged
Alexanderamiri merged 1 commit into
mainfrom
feat/cognito-managed-login-v2
Oct 29, 2026
Merged

Alexanderamiri merged 1 commit into
mainfrom
feat/cognito-managed-login-v2

Conversation

@Alexanderamiri

Copy link
Copy Markdown
Member

Why

The classic hosted UI ignores prompt=select_account. Managed login forwards it to Google, so heroes signed in to a personal Google account get the account chooser instead of a dead end (#157). Stacked on #170.

What

internal_managed_login_version default 1 → 2 (aws_cognito_user_pool_domain.internal). The doc table in cognito-google-setup.md is updated to match. The external pool stays on the classic UI.

How verified

  • terraform fmt -check -recursive terraform
  • The plan must show one in-place update of module.identity.aws_cognito_user_pool_domain.internal (managed_login_version: 1 -> 2), with no pool, domain or client replacement.

Rollout

Merge only after #170 is applied and its reconcile has run. Before merging, check that every internal client has a style (loop in docs/cognito-google-setup.md step 3). On a managed-login domain, a client without a style has no sign-in page. Live today, the only internal client, cupcake-cupcake, already has a style (created manually on 2026-10-24).

After apply:

  • Open https://cupcake.javazone.no/ and sign in. Expect the redirect to the IdP (GoogleSAML until the cutover), not a Cognito error page.
  • python3 scripts/auth-doctor.py --app-yaml <frosting>/app.yaml
  • Users may have to sign in again once (a new Cognito session).

Rollback: set the variable back to 1. This is also an in-place update.

🤖 Generated with Claude Code

Replaces #171, which GitHub closed when its base branch (#170) was deleted on merge.

Managed login forwards prompt=select_account to Google, so heroes get the
account chooser. Every app client has a branding style from app-auth-sync
(previous change), which managed login needs to show a sign-in page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Alexanderamiri
Alexanderamiri requested a review from a team as a code owner October 29, 2026 22:05
@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 0 to add, 1 to change, 0 to destroy.

Plan output
Acquiring state lock. This may take a few moments...
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # module.identity.aws_cognito_user_pool_domain.internal will be updated in-place
  ~ resource "aws_cognito_user_pool_domain" "internal" {
        id                              = "javabin-internal"
      ~ managed_login_version           = 1 -> 2
        # (8 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

LLM Review

Risk: 🟢 LOW

Plan makes one in-place update to a Cognito user pool domain (javabin-internal), bumping managed_login_version from 1 to 2, with no creates or destroys.

  • ✅ [routine] module.identity.aws_cognito_user_pool_domain.internal is updated in-place: managed_login_version changes from 1 to 2. This switches the domain to the newer Cognito managed login experience. No replacement or destruction is planned.
  • 💥 [destruction] Plan summary is 0 to add, 1 to change, 0 to destroy. The domain id (javabin-internal) is unchanged, so the domain itself is not recreated and the hosted login URL should stay the same.
  • 🔒 [security] No IAM, security group, or public-access changes are present in this plan. The managed login version change alters the login UI for users of the internal user pool, so it is worth a quick check that any custom branding or app client settings still render correctly after apply.
  • 💰 [cost] No new billable resources are created. Cognito pricing is based on monthly active users and is not affected by this version change.

@Alexanderamiri
Alexanderamiri merged commit d87a3f7 into main Oct 29, 2026
4 checks passed
@Alexanderamiri
Alexanderamiri deleted the feat/cognito-managed-login-v2 branch October 29, 2026 22:06
Alexanderamiri added a commit that referenced this pull request Oct 30, 2026
…175)

Replaces #172 (auto-closed when its base branch was deleted). Rebased
onto main: #170, #173 (managed login) and #174 (IdP probe client) are
merged and applied.

## Why

GoogleSAML has no account chooser. A browser signed in to a personal
Google account hits a dead-end `403 app_not_configured_for_user`
(#157). The Google provider on managed login can show
Google's account chooser, but only if `prompt=select_account` reaches
Google. **DRAFT: do not merge until the checklist below is done.**

## Probe result (2026-10-30, `platform-idp-probe`, client
`75ejr2cdi133ictvip1mc32nr3`, lists only `Google`)

Each request got a 302 straight to
`accounts.google.com/o/oauth2/v2/auth`. Results were the same on
repeated runs:

| `/oauth2/authorize` extra params | `prompt` in Google URL | `hd` in
Google URL |
|---|---|---|
| `prompt=select_account` | **absent** | absent |
| `prompt=select_account&identity_provider=Google` |
`prompt=select_account` | absent |
| `identity_provider=Google` | absent | absent |
| none | absent | absent |
| `prompt=select_account&identity_provider=Google&hd=java.no` |
`prompt=select_account` | absent |

Managed login forwards `prompt` only when the request names the IdP. On
the single-IdP auto-redirect it drops it. `hd` is never forwarded.
Domain enforcement relies on the Internal consent screen and
auth-claims, as designed. The generic OIDC IdP fallback is **not
needed**: the Google-type IdP does get `prompt`, the request just has to
include `identity_provider`. So this PR adds that fix instead of the
fallback.

## What

- `internal_identity_providers` default changes from `["GoogleSAML"]` to
`["Google"]`. One list feeds every internal app client (app-auth-sync)
and auth-claims' `FEDERATED_IDENTITY_PROVIDERS`.
- **ALB sign-in** (`terraform/modules/service-routing`): internal-pool
rules now send `identity_provider=<client's IdP>` next to
`prompt=select_account`. The IdP is read from the client's
`cognito-client-identity-provider` parameter, which app-auth-sync
writes, so it is never hardcoded and follows the cutover. Adds a
`identity_provider_parameter_name` output to `cognito-app-client`.
- **Apps:** `docs/app-yaml-reference.md` "Account chooser" now says to
send `identity_provider=$COGNITO_IDENTITY_PROVIDER` together with
`COGNITO_EXTRA_AUTH_PARAMS`. The generate-modules comment and
`docs/cognito-google-setup.md` step 3 record the probe result.

## How verified

- `python3 -m unittest discover -s scripts/tests`: OK. `python3 -m
unittest discover -s terraform/lambda-src/tests`: 219 OK.
- `terraform fmt -check -recursive terraform` and `terraform validate`
on `service-routing`.
- Expected platform plan: the app-auth-sync and auth-claims Lambda
environments are updated in place. No Cognito pool, domain or client is
replaced (Terraform manages no clients). After apply, the CI reconcile
switches every internal client to `Google` and rewrites
`cognito-client-identity-provider`, which restarts each auth app
(app-secret-rollout).
- The service-routing change reaches app repos on their next deploy.
Before the cutover it sends `identity_provider=GoogleSAML`, which
matches today's auto-redirect.

## Rollout: pre-cutover checklist

- [x] #170 applied, every internal client has a branding style
(app-auth-sync)
- [x] #173 applied: the internal domain is on managed login
- [ ] Internal-pool lockdown (#166) is live: `REQUIRED_EMAIL_DOMAIN`, no
self sign-up, no `COGNITO` on internal clients
- [x]
`/javabin/platform/google-oauth/internal/{client-id,client-secret}`
exist (the `Google` IdP is in the pool)
- [x] Probe passes when `identity_provider` is sent (table above)
- [ ] Apps that use ALB sign-in redeploy with this service-routing
version
- [ ] frosting and any other SPA/backend send `prompt=select_account`
**and** `identity_provider=$COGNITO_IDENTITY_PROVIDER`. `prompt` alone
is dropped
- [ ] Apps keyed by `sub` (check cupcake/frosting) can handle a new
`sub` per user. Google users are `Google_<sub>` with a new `sub`. The
platform keys on email

## Rollout: after merge

1. `python3 scripts/auth-doctor.py --app-yaml <app>/app.yaml` for each
auth app. Expect a redirect to `accounts.google.com` with
`prompt=select_account`. auth-doctor sends `identity_provider` like a
correct app, so also check that the app itself sends it.
2. Sign in once with a personal Gmail account active in the browser.
Google must show the account chooser.
3. A week later, delete the 4 `GoogleSAML_*` users, the `GoogleSAML`
provider and the Google Admin SAML app (`docs/cognito-google-setup.md`,
"Decommission").

Rollback: revert to `["GoogleSAML"]` (GoogleSAML still exists until
decommission).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant