Skip to content

Commit 1d6c320

Browse files
committed
feat: make authSessionOpener configurable
1 parent 76548ca commit 1d6c320

File tree

3 files changed

+59
-30
lines changed

3 files changed

+59
-30
lines changed

packages/auth/__tests__/providers/cognito/signInWithRedirect.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('signInWithRedirect', () => {
165165
const [oauthUrl, redirectSignIn, preferPrivateSession] =
166166
mockOpenAuthSession.mock.calls[0];
167167
expect(oauthUrl).toStrictEqual(
168-
'https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=Google&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256',
168+
'https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=Google&scope=phone+email+openid+profile+aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256',
169169
);
170170
expect(redirectSignIn).toEqual(
171171
mockAuthConfigWithOAuth.Auth.Cognito.loginWith.oauth.redirectSignIn,
@@ -178,7 +178,7 @@ describe('signInWithRedirect', () => {
178178
await signInWithRedirect();
179179
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
180180
expect(oauthUrl).toStrictEqual(
181-
`https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedDefaultProvider}&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256`,
181+
`https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedDefaultProvider}&scope=phone+email+openid+profile+aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256`,
182182
);
183183
});
184184

@@ -187,7 +187,7 @@ describe('signInWithRedirect', () => {
187187
await signInWithRedirect({ provider: { custom: expectedCustomProvider } });
188188
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
189189
expect(oauthUrl).toStrictEqual(
190-
`https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedCustomProvider}&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256`,
190+
`https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedCustomProvider}&scope=phone+email+openid+profile+aws.cognito.signin.user.admin&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256`,
191191
);
192192
});
193193

@@ -207,7 +207,7 @@ describe('signInWithRedirect', () => {
207207
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
208208
const cognitoPrompt = prompt.toLowerCase();
209209
expect(oauthUrl).toStrictEqual(
210-
`https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedCustomProvider}&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&prompt=${cognitoPrompt}&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256`,
210+
`https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=${expectedCustomProvider}&scope=phone+email+openid+profile+aws.cognito.signin.user.admin&prompt=${cognitoPrompt}&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256`,
211211
);
212212
mockOpenAuthSession.mockClear();
213213
}
@@ -233,6 +233,19 @@ describe('signInWithRedirect', () => {
233233
mockAssertUserNotAuthenticated.mockClear();
234234
});
235235

236+
it('allows to override openAuthSession if specified', async () => {
237+
const mockAuthSessionOpener = jest.fn();
238+
await signInWithRedirect({
239+
provider: 'Google',
240+
options: {
241+
authSessionOpener: mockAuthSessionOpener,
242+
},
243+
});
244+
245+
expect(mockOpenAuthSession).not.toHaveBeenCalled();
246+
expect(mockAuthSessionOpener).toHaveBeenCalled();
247+
});
248+
236249
describe('specifications on Web', () => {
237250
describe('side effect', () => {
238251
it('attaches oauth listener to the Amplify singleton', async () => {
@@ -368,7 +381,7 @@ describe('signInWithRedirect', () => {
368381
mockOpenAuthSession.mock.calls[0];
369382

370383
expect(oauthUrl).toStrictEqual(
371-
'https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=Google&scope=phone%20email%20openid%20profile%20aws.cognito.signin.user.admin&login_hint=someone%40gmail.com&lang=en&nonce=88388838883&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256',
384+
'https://oauth.domain.com/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code&client_id=userPoolClientId&identity_provider=Google&scope=phone+email+openid+profile+aws.cognito.signin.user.admin&login_hint=someone%40gmail.com&lang=en&nonce=88388838883&state=oauth_state&code_challenge=code_challenge&code_challenge_method=S256',
372385
);
373386
expect(redirectSignIn).toEqual(
374387
mockAuthConfigWithOAuth.Auth.Cognito.loginWith.oauth.redirectSignIn,

packages/auth/src/providers/cognito/apis/signInWithRedirect.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212

1313
import '../utils/oauth/enableOAuthListener';
1414
import { cognitoHostedUIIdentityProviderMap } from '../types/models';
15-
import { getAuthUserAgentValue, openAuthSession } from '../../../utils';
15+
import {
16+
openAuthSession as _openAuthSession,
17+
getAuthUserAgentValue,
18+
} from '../../../utils';
1619
import { assertUserNotAuthenticated } from '../utils/signInHelpers';
1720
import { SignInWithRedirectInput } from '../types';
1821
import {
@@ -25,6 +28,7 @@ import {
2528
} from '../utils/oauth';
2629
import { createOAuthError } from '../utils/oauth/createOAuthError';
2730
import { listenForOAuthFlowCancellation } from '../utils/oauth/cancelOAuthFlow';
31+
import { OpenAuthSession } from '../../../utils/types';
2832

2933
/**
3034
* Signs in a user with OAuth. Redirects the application to an Identity Provider.
@@ -66,6 +70,7 @@ export async function signInWithRedirect(
6670
nonce: input?.options?.nonce,
6771
prompt: input?.options?.prompt,
6872
},
73+
authSessionOpener: input?.options?.authSessionOpener,
6974
});
7075
}
7176

@@ -76,17 +81,20 @@ const oauthSignIn = async ({
7681
customState,
7782
preferPrivateSession,
7883
options,
84+
authSessionOpener,
7985
}: {
8086
oauthConfig: OAuthConfig;
8187
provider: string;
8288
clientId: string;
8389
customState?: string;
8490
preferPrivateSession?: boolean;
8591
options?: SignInWithRedirectInput['options'];
92+
authSessionOpener?: OpenAuthSession;
8693
}) => {
8794
const { domain, redirectSignIn, responseType, scopes } = oauthConfig;
8895
const { loginHint, lang, nonce, prompt } = options ?? {};
8996
const randomState = generateState();
97+
const openAuthSession = authSessionOpener || _openAuthSession;
9098

9199
/* encodeURIComponent is not URL safe, use urlSafeEncode instead. Cognito
92100
single-encodes/decodes url on first sign in and double-encodes/decodes url
@@ -105,28 +113,25 @@ const oauthSignIn = async ({
105113
oAuthStore.storeOAuthState(state);
106114
oAuthStore.storePKCE(value);
107115

108-
const queryString = Object.entries({
109-
redirect_uri: redirectUri,
110-
response_type: responseType,
111-
client_id: clientId,
112-
identity_provider: provider,
113-
scope: scopes.join(' '),
114-
// eslint-disable-next-line camelcase
115-
...(loginHint && { login_hint: loginHint }),
116-
...(lang && { lang }),
117-
...(nonce && { nonce }),
118-
...(prompt && { prompt: prompt.toLowerCase() }), // Cognito expects lowercase prompt values
119-
state,
120-
...(responseType === 'code' && {
121-
code_challenge: toCodeChallenge(),
122-
code_challenge_method: method,
123-
}),
124-
})
125-
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
126-
.join('&');
127-
128-
// TODO(v6): use URL object instead
129-
const oAuthUrl = `https://${domain}/oauth2/authorize?${queryString}`;
116+
const params = new URLSearchParams([
117+
['redirect_uri', redirectUri],
118+
['response_type', responseType],
119+
['client_id', clientId],
120+
['identity_provider', provider],
121+
['scope', scopes.join(' ')],
122+
]);
123+
124+
loginHint && params.append('login_hint', loginHint);
125+
lang && params.append('lang', lang);
126+
nonce && params.append('nonce', nonce);
127+
prompt && params.append('prompt', prompt.toLowerCase());
128+
params.append('state', state);
129+
if (responseType === 'code') {
130+
params.append('code_challenge', toCodeChallenge());
131+
params.append('code_challenge_method', method);
132+
}
133+
const oAuthUrl = new URL('/oauth2/authorize', `https://${domain}/`);
134+
oAuthUrl.search = params.toString();
130135

131136
// this will only take effect in the following scenarios:
132137
// 1. the user cancels the OAuth flow on web via back button, and
@@ -135,8 +140,11 @@ const oauthSignIn = async ({
135140

136141
// the following is effective only in react-native as openAuthSession resolves only in react-native
137142
const { type, error, url } =
138-
(await openAuthSession(oAuthUrl, redirectSignIn, preferPrivateSession)) ??
139-
{};
143+
(await openAuthSession(
144+
oAuthUrl.href,
145+
redirectSignIn,
146+
preferPrivateSession,
147+
)) ?? {};
140148

141149
try {
142150
if (type === 'error') {

packages/auth/src/types/inputs.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { OpenAuthSession } from '../utils/types';
5+
46
import {
57
AuthDevice,
68
AuthUserAttribute,
@@ -70,6 +72,12 @@ export interface AuthSignInWithRedirectInput {
7072
provider?: AuthProvider | { custom: string };
7173
customState?: string;
7274
options?: {
75+
/**
76+
* on various mobile frameworks which allow js usage for app development (e.g. cordova)
77+
* in-app or webview redirects are discouraged or not allowed by the OS.
78+
* this gives an option to adjust the behaviour to the framework
79+
*/
80+
authSessionOpener?: OpenAuthSession;
7381
/**
7482
* On iOS devices, setting this to true requests that the browser not share cookies or other browsing data between
7583
* the authentication session and the user’s normal browser session. This will bypass the permissions dialog that

0 commit comments

Comments
 (0)