diff --git a/packages/auth/src/Auth.ts b/packages/auth/src/Auth.ts index 8fc1b9c7ca8..f5bfdeee26b 100644 --- a/packages/auth/src/Auth.ts +++ b/packages/auth/src/Auth.ts @@ -43,6 +43,7 @@ import { Parser, JS, UniversalStorage, + urlSafeDecode, } from '@aws-amplify/core'; import { CookieStorage, @@ -1974,7 +1975,7 @@ export class AuthClass { dispatchAuthEvent( 'customOAuthState', - customState, + urlSafeDecode(customState), `State for user ${currentUser.getUsername()}` ); } diff --git a/packages/auth/src/OAuth/OAuth.ts b/packages/auth/src/OAuth/OAuth.ts index 7b61bd0417e..31428f06746 100644 --- a/packages/auth/src/OAuth/OAuth.ts +++ b/packages/auth/src/OAuth/OAuth.ts @@ -21,7 +21,7 @@ import { CognitoHostedUIIdentityProvider, } from '../types/Auth'; -import { ConsoleLogger as Logger, Hub } from '@aws-amplify/core'; +import { ConsoleLogger as Logger, Hub, urlSafeEncode } from '@aws-amplify/core'; import sha256 from 'crypto-js/sha256'; import Base64 from 'crypto-js/enc-base64'; @@ -78,11 +78,18 @@ export default class OAuth { customState?: string ) { const generatedState = this._generateState(32); + + /* encodeURIComponent is not URL safe, use urlSafeEncode instead. Cognito + single-encodes/decodes url on first sign in and double-encodes/decodes url + when user already signed in. Using encodeURIComponent, Base32, Base64 add + characters % or = which on further encoding becomes unsafe. '=' create issue + for parsing query params. + Refer: https://github.com/aws-amplify/amplify-js/issues/5218 */ const state = customState - ? `${generatedState}-${customState}` + ? `${generatedState}-${urlSafeEncode(customState)}` : generatedState; - oAuthStorage.setState(encodeURIComponent(state)); + oAuthStorage.setState(state); const pkce_key = this._generateRandom(128); oAuthStorage.setPKCE(pkce_key); diff --git a/packages/core/__tests__/StringUtils-test.ts b/packages/core/__tests__/StringUtils-test.ts new file mode 100644 index 00000000000..aecf7838f2f --- /dev/null +++ b/packages/core/__tests__/StringUtils-test.ts @@ -0,0 +1,27 @@ +import { urlSafeEncode, urlSafeDecode } from '../src/Util'; +import { TextDecoder, TextEncoder } from 'util'; +global.TextEncoder = TextEncoder; +global.TextDecoder = TextDecoder; + +const complexCustomState = 'https://amplify-app:300/?empty=&list=1,a,%,@'; + +describe('StringUtils', () => { + test('urlSafe encoding', () => { + const urlSafeState = urlSafeEncode(complexCustomState); + expect(encodeURIComponent(urlSafeState)).toEqual(urlSafeState); + }); + test('urlSafe decoding', () => { + const urlSafeState = urlSafeEncode(complexCustomState); + const encodedState = encodeURIComponent(urlSafeState); + + expect(decodeURIComponent(encodedState)).toEqual(encodedState); + }); + test('URI encode/decode with url safe state', () => { + const urlSafeState = urlSafeEncode(complexCustomState); + const encodedState = encodeURIComponent(urlSafeState); + + expect(urlSafeDecode(decodeURIComponent(encodedState))).toEqual( + complexCustomState + ); + }); +}); diff --git a/packages/core/src/Util/StringUtils.ts b/packages/core/src/Util/StringUtils.ts new file mode 100644 index 00000000000..53e19c0f3a1 --- /dev/null +++ b/packages/core/src/Util/StringUtils.ts @@ -0,0 +1,18 @@ +export function urlSafeEncode(str: string) { + return str + .split('') + .map(char => + char + .charCodeAt(0) + .toString(16) + .padStart(2, '0') + ) + .join(''); +} + +export function urlSafeDecode(hex: string) { + return hex + .match(/.{2}/g) + .map(char => String.fromCharCode(parseInt(char, 16))) + .join(''); +} diff --git a/packages/core/src/Util/index.ts b/packages/core/src/Util/index.ts index e22e2984509..eb130b3dfb4 100644 --- a/packages/core/src/Util/index.ts +++ b/packages/core/src/Util/index.ts @@ -2,3 +2,4 @@ export * from './Retry'; export { default as Mutex } from './Mutex'; export { default as Reachability } from './Reachability'; export * from './DateUtils'; +export * from './StringUtils';