aws-amplify / amplify-ui

Amplify UI is a collection of accessible, themeable, performant React (and more!) components that can connect directly to the cloud.
https://ui.docs.amplify.aws
Apache License 2.0
889 stars 282 forks source link

FR(Authenticator): Support rendering Authenticator before calling Amplify.configure #5042

Open dannymas-amazon opened 7 months ago

dannymas-amazon commented 7 months ago

Before creating a new issue, please confirm:

On which framework/platform are you having an issue?

React

Which UI component?

Authenticator

How is your app built?

Vite

What browsers are you seeing the problem on?

Chrome, Firefox, Microsoft Edge, Safari

Which region are you seeing the problem in?

us-east-1

Please describe your bug.

At the entry point of my app, I am performing a fetch call to get configuration values, then manually performing Amplify.configure. After the fetch call I am creating the root and loading the Authenticator component. This is creating a race condition which can be solved by awaiting the fetch call before creating the root or by adding the createRoot in the then after the fetch call.

However, without the fix, the authenticator loads before amplify is configured. Then when you attempt to login, behind the scenes the authenticator checks if a user is logged in (Which there is) and it returns the error There is already a signed in user. and doesnt reload the component. This cause the site to be stuck at the authenticator and there is no way to get around it. Reloading will load the whole app recreating the problem.

I have two potential suggestions for handling this use case.

  1. Reloading the authenticator component once the first time amplify.configure is called. This needs to be thought through because there could be potential security concerns around this.
  2. When handling that specific error, reload the component. That error is indicative that there we are succesfully connecting to cognito and that someone is currently signed in according to the session and auth data, which should just bypass the authenticator anyway.

What's the expected behaviour?

I would expect when I click sign in, that if there is already a user signed in, it would load the application

Help us reproduce the bug!

In order to reproduce, create a race condition between when you configure amplify and when you load the authenticator. You can use the example code below. You can also just do create a short timeout before doing your normal amplify configure which should reproduce the same results.

Code Snippet

In main.jsx

fetch(`${basePath}/api/amplify-config`).then(async (response) => {
    const amplifyConfig = await response.json();
    Amplify.configure({
        Auth: {
            Cognito: {
                userPoolClientId: amplifyConfig.appClientId,
                userPoolId: amplifyConfig.userPoolId,
                identityPoolId: amplifyConfig.identityPoolId
            }
        },
        Storage: {
            S3: {
                region: amplifyConfig.region,
                bucket: amplifyConfig.uploadBucket
            }
        },
        API: {
            REST: {
                api: {
                    endpoint: basePath,
                    region: amplifyConfig.region,
                },
                headers: async () => {
                    return {
                        Authorization: `Bearer ${(await fetchAuthSession())
                            .tokens?.idToken?.toString()}`,
                    }
                }
            },
        },
    }, {
        API: {
            REST: {
                headers: async (options) => {
                    return {
                        Authorization: `Bearer ${(await fetchAuthSession())
                            .tokens?.idToken?.toString()}`,
                    }
                }
            }
        }
    });
})

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <Authenticator.Provider>
            <App/>
        </Authenticator.Provider>
    </React.StrictMode>
)

In App.jsx

export default function App() {
    const { authStatus, signOut, user } = useAuthenticator((context) => [context.user, context.authStatus]);

    useEffect(() => {
    }, [authStatus])

    return (
        <>
            {authStatus !== "authenticated" || user === undefined ? <Authenticator/> :
                <Suspense fallback={<Loader/>}>
                    <Router user={user} signOut={() => signOut()}/>
                </Suspense>}
        </>
    )
}

Console log output

No response

Additional information and screenshots

Screenshot 2024-03-05 at 1 53 54 PM

calebpollman commented 7 months ago

@dannymas-amazon Thanks for opening this ✅