Skip to content

fix(cli): fix OAuth provider scopes and attribute mappings in generate#14777

Draft
9pace wants to merge 9 commits intogen2-migrationfrom
fix/oauth-generate-scopes-attributes
Draft

fix(cli): fix OAuth provider scopes and attribute mappings in generate#14777
9pace wants to merge 9 commits intogen2-migrationfrom
fix/oauth-generate-scopes-attributes

Conversation

@9pace
Copy link
Copy Markdown
Contributor

@9pace 9pace commented Apr 13, 2026

Summary

  • Fix scope field name typo: authorized_scopesauthorize_scopes (Cognito's actual field name)
  • Remove incorrect VALID_SCOPES filter that mangled provider scopes (e.g. Facebook's public_profileprofile)
  • Route non-standard attribute mapping keys (e.g. usernamesub/id) into custom sub-object instead of dropping them

Before (generated resource.ts)

google: {
  clientId: secret('GOOGLE_CLIENT_ID'),
  clientSecret: secret('GOOGLE_CLIENT_SECRET'),
  attributeMapping: {
    email: 'email',
  },
},
facebook: {
  clientId: secret('FACEBOOK_CLIENT_ID'),
  clientSecret: secret('FACEBOOK_CLIENT_SECRET'),
  attributeMapping: {
    email: 'email',
  },
},

Scopes missing entirely. usernamesub/id mapping dropped.

After (generated resource.ts)

google: {
  clientId: secret('GOOGLE_CLIENT_ID'),
  clientSecret: secret('GOOGLE_CLIENT_SECRET'),
  scopes: ['openid', 'email', 'profile'],
  attributeMapping: {
    email: 'email',
    custom: {
      username: 'sub',
    },
  },
},
facebook: {
  clientId: secret('FACEBOOK_CLIENT_ID'),
  clientSecret: secret('FACEBOOK_CLIENT_SECRET'),
  scopes: ['email', 'public_profile'],
  attributeMapping: {
    email: 'email',
    custom: {
      username: 'id',
    },
  },
},

Caveats

  • Provider scopes are no longer validated — the old validation was against the wrong namespace (Cognito OAuth scopes vs provider scopes), but there is now no validation at all
  • OIDC/SAML providers flatten standard + custom attributes back into a single Record since their rendering path doesn't support the custom sub-object

Test plan

  • Verified end-to-end with a test app using Google and Facebook social login: Gen2 deployed with correct scopes and attribute mappings without manual post-generate edits to OAuth config
  • Run generate on existing migration test apps with social providers
  • Verify OIDC/SAML provider rendering is unchanged

Three bugs in auth.renderer.ts caused generate to produce broken OAuth
config for social identity providers (Google, Facebook):

1. Scope field name typo: deriveProviderSpecificScopes() looked for
   'authorized_scopes' but Cognito returns 'authorize_scopes'. Scopes
   were never collected. Added the correct field name to the lookup.

2. Scope mangling: provider scopes were filtered against VALID_SCOPES
   (Cognito OAuth scopes), which is the wrong namespace. Facebook's
   'public_profile' was mapped to 'profile'. Removed the filter —
   provider scopes are now passed through as-is.

3. Attribute mapping: filterAttributeMapping() dropped keys not in
   MAPPED_USER_ATTRIBUTE_NAME (e.g. 'username' -> 'sub' for Google,
   'username' -> 'id' for Facebook). Changed to route unknown keys
   into a 'custom' sub-object matching Gen2's AttributeMapping CDK
   interface.

Caveats:
- Provider scopes are no longer validated. The old validation was
  incorrect (wrong namespace), but there is now no validation at all —
  whatever the provider returns is passed through verbatim.
- OIDC/SAML providers flatten standard + custom back into a single
  Record since their rendering path doesn't support the custom
  sub-object. If a custom key collides with a mapped standard key,
  the custom value wins.

Verified end-to-end with a test app using Google and Facebook social
login. Gen2 deployed with correct scopes and attribute mappings
without manual post-generate edits to OAuth config.
@9pace 9pace force-pushed the fix/oauth-generate-scopes-attributes branch from 1ffface to 66e090b Compare April 13, 2026 21:08
9pace added 3 commits April 20, 2026 17:16
Two bugs in cfn-output-resolver.ts caused incorrect template resolution
during the refactor step:

1. Phase 1 GetAtt resolution passed already-resolved ARN values through
   buildArn(), producing nested ARNs (e.g. arn:.../userpool/arn:...).
   Added a guard to short-circuit when the output value is already an ARN.

2. Phase 2 fallback resolution used PhysicalResourceId for Custom::
   resources, but custom resource GetAtt attributes come from the backing
   Lambda's response Data — not the physical ID. These are now skipped
   so CloudFormation evaluates them at deploy time.
on oAuth resources during lock

Add a new operation to the lock step that
sets DeletionPolicy: Retain on
HostedUICustomResourceInputs and
HostedUIProvidersCustomResourceInputs in
the auth nested stack. This prevents
social auth custom resources (IDPs
and Domain) from being deleted during
or after migration.

The operation fetches the auth nested
stack template, patches the
DeletionPolicy for the relevant
resources, and updates the stack while
preserving existing parameter values.
Gen1 creates the Cognito domain and social IDPs via a Lambda custom
resource, so they aren't in the Gen1 CFN template and get left behind
when the UserPool is moved to Gen2. After the move, fetch them live from
Cognito and import them into the Gen2 stack via CreateChangeSet(IMPORT),
matching providers to Gen2 logical IDs by ProviderName. Imported
resources are written with DeletionPolicy: Retain so rollback can orphan
them safely.
@9pace 9pace force-pushed the fix/oauth-generate-scopes-attributes branch from 97a0f06 to 67571e1 Compare April 22, 2026 20:29
9pace added 5 commits April 29, 2026 15:45
Implement the Orphan + Import approach for handling OAuth/social login
(Google, Facebook) during gen2-migration refactor.

Instead of moving UserPoolDomain and UserPoolIdentityProvider through
the holding stack (which breaks due to Fn::GetAtt references to
AmplifySecretFetcherResource), orphan them from Gen2 and re-import
them as native CFN resources.

Forward: beforeMove sets DeletionPolicy Retain then orphans IDPs and
domain from Gen2. move() imports Gen1 physical IDPs and domain into
Gen2 under Gen2 logical IDs after the core StackRefactor.

Rollback: move() orphans imported IDPs and domain from Gen2 after the
core Gen2-to-Gen1 StackRefactor. afterMove() re-imports Gen2 original
IDPs and domain after restoring P2 from the holding stack.

Also fixes Ref resolution in cfn-output-resolver to fall back to
PhysicalResourceId for intra-stack references not exposed via stack
outputs. This resolves UserPoolClient SupportedIdentityProviders
references to IDP logical IDs during the holding stack move.

ADR-005 updated with Orphan + Import addendum.
…itution

Generate step now emits addPropertyDeletionOverride('SupportedLoginProviders')
on the IdentityPool, removing the Fn::GetAtt to AmplifySecretFetcherResource
entirely. This property enables direct federation which Amplify does not use —
Gen1 never sets it and social login works via UserPool CognitoIdentityProviders.

Removes the resolveTarget() PLACEHOLDER override, expectedTargetChanges(), and
identityPoolLogicalIds from AuthCognitoForwardRefactorer. Both forward and
rollback updateSource/updateTarget now produce empty changesets for auth.

ADR-005 Addendum updated to reflect this decision.
The resolveTarget override and expectedTargetChanges were removed in the
previous commit. Remove the corresponding test describe block.
… validation

- auth-cognito-rollback: OAuth rollback plan, targetLogicalId exclusions, orphan Retain check
- cfn-parameter-resolver: resolveNoEchoParameters coverage
- category-refactorer: UPDATE_ROLLBACK_COMPLETE accepted, expectedTargetChanges allowlist
- rollback-category-refactorer: formatting only
- Snapshot updates for scopes, attribute mappings, and domain override
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