Description
I am attempting to implement redirect after signin following this pattern https://reacttraining.com/react-router/web/example/auth-workflow. I start the auth flow from a page that is auth protected and want to end at the same location after users successfully authenticate. In order to do so, I am passing the pathname of the page as customState to Auth.federatedSignIn({customState: location.pathname}); // i.e. location.pathname = '/protected'.
When Cognito redirects back to my sign in page. The auth flow fails with the following error.
OAuth - Error handling auth response. Error: Invalid state in OAuth flow
at OAuth.../node_modules/@aws-amplify/auth/lib-esm/OAuth/OAuth.js.OAuth._validateState (OAuth.js:244)
at OAuth.<anonymous> (OAuth.js:216)
at step (OAuth.js:55)
at Object.next (OAuth.js:36)
at OAuth.js:30
at new Promise (<anonymous>)
at ../node_modules/@aws-amplify/auth/lib-esm/OAuth/OAuth.js.__awaiter (OAuth.js:26)
at OAuth.../node_modules/@aws-amplify/auth/lib-esm/OAuth/OAuth.js.OAuth.handleAuthResponse (OAuth.js:190)
at AuthClass.<anonymous> (Auth.js:1683)
at step (Auth.js:44)
The problem appears to be related to how Amplify and the Cognito Auth service handle url encoding of the state parameter. When I perform the above, Amplify url encodes the customState value and stores that url encoded value in it's local storage. The redirect URL to Cognito contains the URL encoded state parameter:
state=cfjcnuSJgihzcAJP5uFZVuggAVcGDU34-%2Fprotected.
After authenticating, Cognito redirects back to by redirect_uri passing the state parameter unencoded.
state=cfjcnuSJgihzcAJP5uFZVuggAVcGDU34-/protected
The state validation logic then fails and stops the auth flow.
To Reproduce
"aws-amplify": "^2.2.1"Auth.federatedSignIn({customState:'/path/to/redirect/after/auth'});Expected behavior
I expect that I can pass any string as custom state (the documentation is very minimal, but does not call out any restrictions), then get the same value back via listening to the auth channel for customOAuthState messages. Via something like:
Hub.listen('auth', ({payload: {event, data}}) => {
switch (event) {
case 'customOAuthState':
redirectToPath(data);
}
};
What a great explanation!
This looks like a bug with how customState is expected to be a string and serialization isn't symmetrical between step 3. and step 4.
Same (stale/closed) issue: https://github.com/aws-amplify/amplify-js/issues/4550
Edit:
And a potential solution: https://github.com/aws-amplify/amplify-js/pull/3588#issuecomment-510725081
Great research @bramsvs! This looks like a straightforward fix affecting these two parts:
Does anybody have a solution for this?
Does someone from the Amplify team mind taking ownership of this and fixing the custom state problem once and for all? Or should I submit a PR myself? Happy to...
Here are a couple other related issues:
https://github.com/aws-amplify/amplify-js/issues/4550
https://github.com/aws-amplify/amplify-js/issues/3783
For me, it's the / which causes this issue. My workaround is replacing / with _.
Serialization step: Auth.federatedSignIn({ customState: callbackPath.replace(/\//g, "_") });
Deserialization step: const callbackPath = data.replace(/_/g, "/");
The issue is due to inconsistent encoding of the state in URL when cognito redirects to the app after login.
The state is in decoded format when user returns from cognito hosted UI after login: http://localhost:3000/?code=90c6b7c0-5b66-4429-a2c4-dfacbbfb26a5&state=4zmiIxppiNpIUlHPxbzSXpkh4UfeIWuM-http://localhost:3000?test
The state is encoded when user is already logged in and cognito redirects the user to the app without showing hosted UI: http://localhost:3000/?code=9320bae2-4220-4987-8818-179054d724f7&state=HbCjUEzvBR41URgtgj9OlTv1vAec7S1z-http%3A%2F%2Flocalhost%3A3000%3Ftest
As pointed out by @ericclemmons, a straightforward fix would be to not encode in setState:
With this, except for first login, subsequent logins will still throw Invalid state in OAuth flow error.
Possible solution:
Ensure customState is not affected by encodeURI/decodeURI by cognito - using atob(), base32 or md5 encoding.
Note: base32 and base64 uses = as padding, which will affect parsing of URL query params.
Closing as this issue is within our unstable version and looking to get released soon
Most helpful comment
Does someone from the Amplify team mind taking ownership of this and fixing the custom state problem once and for all? Or should I submit a PR myself? Happy to...
Here are a couple other related issues:
https://github.com/aws-amplify/amplify-js/issues/4550
https://github.com/aws-amplify/amplify-js/issues/3783