Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/fluffy-donkeys-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@aws-amplify/auth': patch
---

fix(auth): allow prompt=none silent SSO to resume federated sessions

`signInWithRedirect` always appended `identity_provider=COGNITO` to the `/oauth2/authorize` request when no `provider` or `idpIdentifier` was supplied. Cognito treats `identity_provider` as a provider selector, so pinning it to `COGNITO` while requesting a silent sign in with `options.prompt: 'NONE'` restricted the attempt to native Cognito sessions. Users whose live hosted UI session originated from a federated IdP (for example Google or a SAML provider) were rejected with `error=login_required` instead of having their session resumed.

`identity_provider` is now omitted only when `prompt` is `'NONE'` and neither `provider` nor `idpIdentifier` is specified, which lets Cognito resume whichever session is already active. All other behavior is unchanged: an explicit `provider` still sends `identity_provider`, an `idpIdentifier` still sends `idp_identifier`, and the interactive no-argument call still defaults to `identity_provider=COGNITO`.

Fixes https://github.com/aws-amplify/amplify-js/issues/14897
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,43 @@ describe('signInWithRedirect', () => {
);
});

it('omits identity_provider when prompt is NONE and no provider is specified', async () => {
await signInWithRedirect({ options: { prompt: 'NONE' } });
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
expect(oauthUrl).not.toContain('identity_provider');
expect(oauthUrl).toStrictEqual(
`https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&scope=phone+email+openid+profile+aws.cognito.signin.user.admin&prompt=none&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256`,
);
});

it('keeps identity_provider when prompt is NONE and a provider is specified', async () => {
await signInWithRedirect({
provider: 'Google',
options: { prompt: 'NONE' },
});
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
expect(oauthUrl).toContain('identity_provider=Google');
});

it('keeps idp_identifier only when prompt is NONE and an idpIdentifier is specified', async () => {
await signInWithRedirect({
provider: { idpIdentifier: 'example.com' },
options: { prompt: 'NONE' },
});
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
expect(oauthUrl).toContain('idp_identifier=example.com');
expect(oauthUrl).not.toContain('identity_provider');
});

it('keeps the default identity_provider for prompt values other than NONE', async () => {
for (const prompt of promptTypes.filter(value => value !== 'NONE')) {
await signInWithRedirect({ options: { prompt } });
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
expect(oauthUrl).toContain('identity_provider=COGNITO');
mockOpenAuthSession.mockClear();
}
});

it('uses custom state if specified', async () => {
const expectedCustomState = 'verify_me';
await signInWithRedirect({ customState: expectedCustomState });
Expand Down
13 changes: 10 additions & 3 deletions packages/auth/src/providers/cognito/apis/signInWithRedirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export async function signInWithRedirect(
await assertUserNotAuthenticated();
}

let provider = 'COGNITO'; // Default
let provider: string | undefined = 'COGNITO'; // Default
let idpIdentifier: string | undefined;

if (typeof input?.provider === 'string') {
Expand All @@ -59,6 +59,13 @@ export async function signInWithRedirect(
provider = input.provider.custom;
} else if (input?.provider?.idpIdentifier) {
({ idpIdentifier } = input.provider);
} else if (input?.options?.prompt === 'NONE') {
// `identity_provider` acts as a provider selector, so pinning it to the
// default `COGNITO` would restrict a silent `prompt=none` attempt to
// native Cognito sessions and fail with `login_required` for users whose
// existing session came from a federated IdP. Omitting it lets Cognito
// resume whichever session is already active.
provider = undefined;
}

return oauthSignIn({
Expand Down Expand Up @@ -89,7 +96,7 @@ const oauthSignIn = async ({
authSessionOpener,
}: {
oauthConfig: OAuthConfig;
provider: string;
provider?: string;
idpIdentifier?: string;
clientId: string;
customState?: string;
Expand Down Expand Up @@ -127,7 +134,7 @@ const oauthSignIn = async ({
// Add either identity_provider or idp_identifier, but not both
if (idpIdentifier) {
params.append('idp_identifier', idpIdentifier);
} else {
} else if (provider) {
params.append('identity_provider', provider);
}

Expand Down
Loading