I am using Amplify auth to handle authentication. However, after I do the Auth.signIn() and Auth.confirmSignIn() (for the user to enter the 6-digit code),
the value returned from the hook useAuthenticator for authStatus is 'unauthenticated' and for route is still 'signIn'...
I "fixed" this by just calling window.location.reload() in the promise resolve after Auth.confirmSignIn(), but it just feels wrong... and there should be a better way to notify Amplify that we have successfully signed in.
The question:
How do I refresh the auth state so that the useAuthenticator picks up on my successful (custom) sign in?
Code, if needed - below
Main parts of the SignIn component:
import { Amplify, Auth } from "aws-amplify";
import { CognitoUser } from "amazon-cognito-identity-js";
// other imports, component definition and local state...
const onSubmit = () => {
Auth.signIn({
username: state.username,
password: state.password,
})
.then((response: CognitoUser) => {
// if needed, do something custom with the response...
// if we have a `customChallenge`
if (resp.challengeName) {
// navigate to ConfirmSignIn component which renders the form to enter authentication code (from SMS or the Authenticator app)
}
}
};
// component render() with username and password inputs and submit button...
Main parts of the ConfirmSignIn component:
import { useAuthenticator } from "@aws-amplify/ui-react";
import { CognitoUser } from "amazon-cognito-identity-js";
// other imports, component definition and local state...
const onSubmit = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
await Auth.confirmSignIn(user, code, AmplifyChallengeName.SMS)
.then(async csi_data => {
// calling this doesn't work... doesn't refresh the `useAuthenticator` return values
const cognitoUser: CognitoUser = await Auth.currentAuthenticatedUser({ bypassCache: true });
const currentSession = await Auth.currentSession();
// calling this doesn't work either...
cognitoUser.refreshSession(currentSession.getRefreshToken(), (err, session) => {
const { idToken, refreshToken, accessToken } = session;
});
// calling this works... but feels wrong
// window.location.reload();
});
}
// component render() with code input and submit button...
and here's the Login component which has the <Authenticator>:
import { Authenticator, AuthenticatorProps, useAuthenticator } from "@aws-amplify/ui-react";
import AuthenticatorWrapper from "./AuthenticatorWrapper";
// other imports
const customizedAuthRoutes = ["signIn", "confirmResetPassword"]; //
// component definition...
const renderCustomizedRoute = useCallback(() => {
switch (route) {
case "signIn":
return <SignIn />;
case "confirmResetPassword":
default:
return (
<ConfirmResetPassword />
);
}
}, [route]);
return (
<AuthenticatorWrapper>
{customizedAuthRoutes.includes(route) ? (
renderCustomizedRoute()
) : (
<Authenticator
services={services}
className="Login"
hideSignUp
components={components}
formFields={formFields}
>
{children}
</Authenticator>
)}
</AuthenticatorWrapper>
);
Authenticator Wrapper:
export default function AuthenticatorWrapper({ children }: PropsWithChildren) {
const { authStatus } = useAuthenticator(context => [context.authStatus]);
if (authStatus === "unauthenticated") {
return (
<div className="LogoContentCopyrightLayout">
<Logo className="LoginLogo" />
{children}
<Copyright />
</div>
);
}
return <>{children}</>;
}
In your
SignIncomponent, you are using theAuth.signIn()method from AWS Amplify to manually sign in the user.Then, in your
ConfirmSignIncomponent, you are using theAuth.confirmSignIn()method to manually confirm the sign in.So you're using the
Authmethods to manually handle the sign in process.However, in your AuthenticatorWrapper, you are using the
useAuthenticatorhook from AWS Amplify UI to get the authentication status.In other words, you are using the
useAuthenticatorhook and the AWS Amplify Auth methods together.The
useAuthenticatorhook is intended to be used with theAuthenticatorcomponent to manage state and UI, while the AWS Amplify Auth methods likesignInandconfirmSignInare typically used separately to handle authentication manually.In your situation, you are mixing these two approaches. You are manually handling the sign-in process using the Auth methods, but you are relying on the
useAuthenticatorhook to update your application's state.The problem is that the
useAuthenticatorhook is not aware of the changes to the user's authentication status when you manually sign in the user, which is why theauthStatusremains'unauthenticated'.A better approach would be to rely entirely on the
Authenticatorcomponent anduseAuthenticatorhook, or to handle the authentication process manually and manage the application state yourself. The first approach is simpler and requires less code, while the second approach offers more flexibility and control.Here is an example of how you can modify your sign-in process to use the
Authenticatorcomponent anduseAuthenticatorhook:SignIn Component:
ConfirmSignIn Component:
Login Component:
Here, the
Authenticatorcomponent automatically handles the sign-in process, and theonAuthStateChangeprop is used to update the application's state when the user's authentication status changes.Meaning: This code will render the
MySignIncomponent when theauthStateis'signIn', and theMyConfirmSignIncomponent when theauthStateis'confirmSignIn'.The
onAuthStateChangeprop of theAuthenticatorcomponent will automatically update theauthStateanduserwhen the authentication status changes.This way, you will not need to use
Auth.signIn()orAuth.confirmSignIn()methods manually. TheAuthenticatorcomponent will handle all the authentication flow for you.If you prefer to continue handling the authentication process manually, you will need to manage the application state yourself. This could involve creating a context or a Redux store to manage the user's authentication status, and updating this state whenever the user signs in or out.