amplify-ui icon indicating copy to clipboard operation
amplify-ui copied to clipboard

Vue: why is the <authenticator> component now required for useAuthenticator?

Open jtleniger opened this issue 1 year ago • 10 comments

I'm attempting to upgrade from @aws-amplify/ui-vue 3.1.29 to 4.2.1.

Previously, my Vue application used useAuthenticator in various places to control the UI based on authentication status.

If a user wanted to sign in, I redirected to an authentication route which contained the authenticator component.

Upon upgrading, it appears this strategy doesn't work.

I see in the docs (not sure if this was there before):

You must render the UI component before using the useAuthenticator composable. useAuthenticator was designed to retrieve UI specific state such as route and user and should not be used without the UI component.

What am I supposed to do if I don't want to wrap my entire application in the authenticator component? From what I understand, this will automatically redirect any signed out users to the sign in view.

jtleniger avatar Feb 22 '24 19:02 jtleniger

This is a personal show stopper for me.

Thankfully moving logic from components to composables in vue is usually pretty easy and it should be a quick fix.

robokozo avatar Feb 23 '24 04:02 robokozo

@robokozo @jtleniger Can you provide more details / code samples of what specifically isn't working for you? I don't think this behavior should have changed between 3 and 4. We have a e2e test that tests this scenario of using useAuthenticator without rendering the authenticator, so I'm trying to understand if there's a gap in our testing: https://github.com/aws-amplify/amplify-ui/blob/main/examples/vue/src/pages/ui/components/authenticator/auth-status/index.vue

Would also note that the docs seem to be out of date here, we'll get those updated.

reesscot avatar Feb 23 '24 16:02 reesscot

I'm not at my personal computer atm, but the basic idea was something like.. imagine 2 routes

  • App.vue
<script setup>
</script>
<template>
    <Link to="/sign-in">
    <Link to="/other">
    <router-view></router-view>
</template>
  • SignIn.vue
<script setup>
     const auth = useAuthenticator()
</script>
<template>
    <authenticator></authenticator>
    <h1>Hello {{ auth.user?.username }}!</h1>
</template>

  • Other.vue
<script setup>
    const auth = useAuthenticator()
</script>
<template>
    <h1>Hello {{ auth.user?.username }}!</h1>
</template>

When navigating from sign-in to other, Other.vue maintains the user information and displays the username If I refresh from the other route, i do not get the auth information.

I took a look at the <authenticator> component in the repo, and it seems to initialize the state machine for useAuth. I imagine this is related?

https://github.com/aws-amplify/amplify-ui/blob/77551bc86b01e3150232b158f0aa683bef0cf12b/packages/vue/src/components/authenticator.vue#L71-L92

robokozo avatar Feb 23 '24 16:02 robokozo

I've created a minimum example reproducing the behavior we're talking about: https://github.com/jtleniger/amplify-ui-5028

We have a e2e test that tests this scenario of using useAuthenticator without rendering the authenticator

Interesting! The docs seem to agree with the behavior, stating this shouldn't work.

jtleniger avatar Feb 23 '24 16:02 jtleniger

Something to add, I'm not necessarily suggesting the behavior should be reverted to what it was in version 3.

I'm just wondering with this change, what is the intended way to achieve what the example repo is trying to do?

jtleniger avatar Feb 23 '24 17:02 jtleniger

I ran back to test my code on my personal computer and I can't recreate my particular problem anymore. (Sorry if I raised any alarms.. I must have been really tired last night ^_^)

robokozo avatar Feb 23 '24 17:02 robokozo

@jtleniger can you try moving the useAuthenticator inside the route guard?

      beforeEnter: () => {
        const auth = useAuthenticator();
        if (auth.authStatus !== 'authenticated') {
          return { name: 'signin'}
        }
      },

robokozo avatar Feb 23 '24 17:02 robokozo

I can try that but the issue isn't related to the route guard.

The issue is that while signed in, on a route which does not render the authenticator component (home or protected in the example repo), if the page is refreshed, useAuthenticator does not have the correct state.

jtleniger avatar Feb 23 '24 17:02 jtleniger

Hi @jtleniger, Moving the useAuthenticator into the route guard does seem fix the issue with authStatus not updating correctly inside the components, but it still has an issue in your route guard because the initial authStatus is "configuring", and thus you will still redirect. After moving useAuthenticator into the auth guard, the authStatus is will be "configuring" and then updated next to "authenticated" if rendering a component, but the redirect has already happened.

However, in the case of your router logic you may find it simpler to just check directly whether the user is authenticated via the JS getCurrentUser API:

import { getCurrentUser } from 'aws-amplify/auth'

const router = createRouter({
    {
      path: '/protected',
      name: 'protected',
      beforeEnter: async () => {
        try {
          await getCurrentUser();
        } catch (e) {
          return { name: 'signin'}
        }
      },
      component: ProtectedView
    },
    //...

Showing authenticated state after page refresh: CleanShot 2024-02-23 at 12 45 38

Let me know if this works for you!

reesscot avatar Feb 23 '24 19:02 reesscot

Oh interesting.

So it's possible to break the the auth status by calling useAuthenticator at the top level of the router file.

I'm inferring here, but is it due to the fact that app.use(router), thus useAuthenticator is called before Amplify.configure?

Not sure if this should be considered a bug; perhaps a note in the docs or maybe useAuthenticator should throw if it is called before configuration? Assuming that's the issue.

Feel free to close this, thanks for your help!

jtleniger avatar Feb 23 '24 20:02 jtleniger

I think the main issue here is that useAuthenticator is a composable that's mean to be used in a component, but not intended to be used in router code. Its initial value is always going to be 'configuring', so it's better to rely on the lower level getCurrentUser JS API here. Going to close this out, since it seems like your question was answered, but please feel free to open a new issue if you're still having issues!

reesscot avatar Feb 26 '24 23:02 reesscot