Skip to content

Commit c2697f7

Browse files
committed
feat: make authSessionOpener configurable
1 parent 88113b6 commit c2697f7

File tree

4 files changed

+58
-29
lines changed

4 files changed

+58
-29
lines changed

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ describe('signInWithRedirect', () => {
157157
const [oauthUrl, redirectSignIn, preferPrivateSession] =
158158
mockOpenAuthSession.mock.calls[0];
159159
expect(oauthUrl).toStrictEqual(
160-
'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',
160+
'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',
161161
);
162162
expect(redirectSignIn).toEqual(
163163
mockAuthConfigWithOAuth.Auth.Cognito.loginWith.oauth.redirectSignIn,
@@ -170,7 +170,7 @@ describe('signInWithRedirect', () => {
170170
await signInWithRedirect();
171171
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
172172
expect(oauthUrl).toStrictEqual(
173-
`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`,
173+
`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`,
174174
);
175175
});
176176

@@ -179,7 +179,7 @@ describe('signInWithRedirect', () => {
179179
await signInWithRedirect({ provider: { custom: expectedCustomProvider } });
180180
const [oauthUrl] = mockOpenAuthSession.mock.calls[0];
181181
expect(oauthUrl).toStrictEqual(
182-
`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`,
182+
`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`,
183183
);
184184
});
185185

@@ -189,6 +189,19 @@ describe('signInWithRedirect', () => {
189189
expect(mockUrlSafeEncode).toHaveBeenCalledWith(expectedCustomState);
190190
});
191191

192+
it('allows to override openAuthSession if specified', async () => {
193+
const mockAuthSessionOpener = jest.fn();
194+
await signInWithRedirect({
195+
provider: 'Google',
196+
options: {
197+
authSessionOpener: mockAuthSessionOpener,
198+
},
199+
});
200+
201+
expect(mockOpenAuthSession).not.toHaveBeenCalled();
202+
expect(mockAuthSessionOpener).toHaveBeenCalled();
203+
});
204+
192205
describe('specifications on Web', () => {
193206
describe('side effect', () => {
194207
it('attaches oauth listener to the Amplify singleton', async () => {
@@ -324,7 +337,7 @@ describe('signInWithRedirect', () => {
324337
mockOpenAuthSession.mock.calls[0];
325338

326339
expect(oauthUrl).toStrictEqual(
327-
'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',
340+
'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',
328341
);
329342
expect(redirectSignIn).toEqual(
330343
mockAuthConfigWithOAuth.Auth.Cognito.loginWith.oauth.redirectSignIn,

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

Lines changed: 31 additions & 23 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.
@@ -62,6 +66,7 @@ export async function signInWithRedirect(
6266
lang: input?.options?.lang,
6367
nonce: input?.options?.nonce,
6468
},
69+
authSessionOpener: input?.options?.authSessionOpener,
6570
});
6671
}
6772

@@ -72,17 +77,20 @@ const oauthSignIn = async ({
7277
customState,
7378
preferPrivateSession,
7479
options,
80+
authSessionOpener,
7581
}: {
7682
oauthConfig: OAuthConfig;
7783
provider: string;
7884
clientId: string;
7985
customState?: string;
8086
preferPrivateSession?: boolean;
8187
options?: SignInWithRedirectInput['options'];
88+
authSessionOpener?: OpenAuthSession;
8289
}) => {
8390
const { domain, redirectSignIn, responseType, scopes } = oauthConfig;
8491
const { loginHint, lang, nonce } = options ?? {};
8592
const randomState = generateState();
93+
const openAuthSession = authSessionOpener || _openAuthSession;
8694

8795
/* encodeURIComponent is not URL safe, use urlSafeEncode instead. Cognito
8896
single-encodes/decodes url on first sign in and double-encodes/decodes url
@@ -101,27 +109,24 @@ const oauthSignIn = async ({
101109
oAuthStore.storeOAuthState(state);
102110
oAuthStore.storePKCE(value);
103111

104-
const queryString = Object.entries({
105-
redirect_uri: redirectUri,
106-
response_type: responseType,
107-
client_id: clientId,
108-
identity_provider: provider,
109-
scope: scopes.join(' '),
110-
// eslint-disable-next-line camelcase
111-
...(loginHint && { login_hint: loginHint }),
112-
...(lang && { lang }),
113-
...(nonce && { nonce }),
114-
state,
115-
...(responseType === 'code' && {
116-
code_challenge: toCodeChallenge(),
117-
code_challenge_method: method,
118-
}),
119-
})
120-
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
121-
.join('&');
112+
const params = new URLSearchParams([
113+
['redirect_uri', redirectUri],
114+
['response_type', responseType],
115+
['client_id', clientId],
116+
['identity_provider', provider],
117+
['scope', scopes.join(' ')],
118+
]);
122119

123-
// TODO(v6): use URL object instead
124-
const oAuthUrl = `https://${domain}/oauth2/authorize?${queryString}`;
120+
loginHint && params.append('login_hint', loginHint);
121+
lang && params.append('lang', lang);
122+
nonce && params.append('nonce', nonce);
123+
params.append('state', state);
124+
if (responseType === 'code') {
125+
params.append('code_challenge', toCodeChallenge());
126+
params.append('code_challenge_method', method);
127+
}
128+
const oAuthUrl = new URL('/oauth2/authorize', `https://${domain}/`);
129+
oAuthUrl.search = params.toString();
125130

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

131136
// the following is effective only in react-native as openAuthSession resolves only in react-native
132137
const { type, error, url } =
133-
(await openAuthSession(oAuthUrl, redirectSignIn, preferPrivateSession)) ??
134-
{};
138+
(await openAuthSession(
139+
oAuthUrl.href,
140+
redirectSignIn,
141+
preferPrivateSession,
142+
)) ?? {};
135143

136144
try {
137145
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,
@@ -58,6 +60,12 @@ export interface AuthSignInWithRedirectInput {
5860
provider?: AuthProvider | { custom: string };
5961
customState?: string;
6062
options?: {
63+
/**
64+
* on various mobile frameworks which allow js usage for app development (e.g. cordova)
65+
* in-app or webview redirects are discouraged or not allowed by the OS.
66+
* this gives an option to adjust the behaviour to the framework
67+
*/
68+
authSessionOpener?: OpenAuthSession;
6169
/**
6270
* On iOS devices, setting this to true requests that the browser not share cookies or other browsing data between
6371
* the authentication session and the user’s normal browser session. This will bypass the permissions dialog that

packages/aws-amplify/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@
451451
"name": "[Auth] signInWithRedirect (Cognito)",
452452
"path": "./dist/esm/auth/index.mjs",
453453
"import": "{ signInWithRedirect }",
454-
"limit": "19.37 kB"
454+
"limit": "19.44 kB"
455455
},
456456
{
457457
"name": "[Auth] fetchUserAttributes (Cognito)",
@@ -469,7 +469,7 @@
469469
"name": "[Auth] OAuth Auth Flow (Cognito)",
470470
"path": "./dist/esm/auth/index.mjs",
471471
"import": "{ signInWithRedirect, signOut, fetchAuthSession }",
472-
"limit": "19.93 kB"
472+
"limit": "20.01 kB"
473473
},
474474
{
475475
"name": "[Auth] Associate WebAuthN Credential (Cognito)",

0 commit comments

Comments
 (0)