Ensure the user is available outside of a component?
john-goldsmith opened this issue · 4 comments
Checklist
- I have looked into the Readme, Examples, and FAQ and have not found a suitable solution or answer.
- I have looked into the API documentation and have not found a suitable solution or answer.
- I have searched the issues and have not found a suitable solution or answer.
- I have searched the Auth0 Community forums and have not found a suitable solution or answer.
- I agree to the terms within the Auth0 Code of Conduct.
Describe the problem you'd like to have solved
Hello,
Thanks for taking the time and effort to author this package.
My question: what is the best way to ensure the user is loaded and available when the plugin is used outside the context of a component?
The use case I'm trying to understand and solve for: I use the Segment telemetry service that needs to "identify" a user so that all subsequent events are tied to that user. The issue I'm facing is that the user
object exposed by the plugin is reactive, and thus, not guaranteed to be available without some sort of change detection mechanism (aka, watch
).
Issue #99 is the closest issue I've found related to this, but despite trying my best to parse through it, I'm not sure the use case is quite the same.
An abridged version:
// src/plugins/auth0.ts
import { createAuth0 } from '@auth0/auth0-vue'
export default createAuth0({...})
// src/composables/useSegment.ts
import { watch } from 'vue'
import auth0Plugin from '@/src/plugins/auth0.ts'
import { AnalyticsBrowser } from '@segment/analytics-next'
const segment = new AnalyticsBrowser()
export function useSegment() {
/**
* The following only ever needs to happen once,
* and the user object must be available. It seems
* problematic to put the watcher here because
* anytime useSegment is invoked, it will unnecessarily
* create a watcher.
*
* Is this fine as-is and expected to be watched
* outside of components?
*/
watch(auth0Plugin.user, () => {
if (auth0Plugin.user.value) {
segment.identify({ email: auth0Plugin.user.value?.email })
segment.load() // sends off buffered events
}
})
return {
track: segment.track
}
}
// src/components/HelloWorld.vue
import { useSegment } from '@/composables/useSegment'
<script setup lang="ts">
const segment = useSegment()
</script>
<template>
<button @click="segment.track({...})">Submit</button>
</template>
Is there some way to await the user? Is auth0-spa-js
somehow better suited for this use case? Is there a more "component-first" approach? I'm open to any ideas or alternatives. Thanks for any insight you can provide!
Describe the ideal solution
I'll admit that I'm not very familiar with the inner workings of Auth0, so this might not even make sense or be very Vue3-like. Perhaps the plugin could expose a way to await
getting the user. Pseudo-code:
// src/composables/whatever.ts
const { getUser, isAuthenticated, isLoading } = useAuth0()
if (isAuthenticated && !isLoading) {
const user = await getUser()
}
That begs a similar question, however, of reacting to changes in isAuthenticated
and isLoading
.
Alternatives and current workarounds
No response
Additional context
No response
Our SDK exposes a isLoading
property. The meaning of this property is explained here.
In general, this means: Do not interact with the SDK until isLoading
is false. So what I think you want is:
- Somewhere at the top level, ensure the part that needs the user information is not rendered unless
isLoading
is false. - Doing so, you can guarantee that whenever
useSegment
is executed, the SDK is done loading. This means the user is "available", so you can just read its value without a watcher. I say "available", as the user can still be null which indicates the user is not logged in. If you want to ensureuseSegment
is only ever executed when there is an actual user, ensure you do the same thing withisAuthenticated
(ensure the part of the DOM that needsuseSegment
is not rendered unless isLoading is false, and isAuithenticated s true).
The above should allow you to integrate it without a watcher.
Closing, as I think this has been answered. Can always reopen if needed.
Thanks for the reply. My understanding of isLoading
and isAuthenticated
is inline with your descriptions, so that part makes sense. I'm not quite understanding what you mean by the following, however:
...ensure the part that needs the user information is not rendered unless
isLoading
is false
The part that needs the user information is useSegment()
itself. There is no rendering, in the direct sense of the word -- useSegment
is a composable that gets used by practically every component. Are you suggesting some sort of wrapper component that prevents all subsequent components from rendering until the user is available?
<Segment>
<App>
<HelloWorld />
</App>
</Segment>
// @/src/components/Segment.vue
<script setup>
const { useAuth0 } from '@auth0/auth0-vue'
const { useSegment } from '@/composables/useSegment'
const auth0 = useAuth0()
const segment = useSegment()
if (auth0.isAuthenticated && !auth0.isLoading) {
segment.identify({ email: user.value.email })
}
</script>
<template>
<App v-if="auth0.isAuthenticated && !auth0.isLoading" />
</template>
I'm probably misunderstanding what you're driving at, but the above feels more like a route guard to me.
useSegment
is used in Segment
, which is a component. So somewhere, higher up the hierarchy, you want to avoid rendering this component until isLoading
is false.
So the structure should be something along the lines of this:
- Root
- App; uses
useAuth0
to pull inisLoading
(and isAuthenticated depending on the use case) and shows/hides Segment- Segment; uses useSegment, user will be accurate
- App; uses
Are you suggesting some sort of wrapper component that prevents all subsequent components from rendering until the user is available
So basically, that is what I mean yes.
If you need to access the user in a non-reactive way, you need to wait until isLoading is no-longer true. Once it is no longer true, you can retrieve isAuthenticated.value
and user.value
and it should be accurate (as in: isAuthenticated is false or true, and user could be null or the actual value). How you wait until isLoading is true is up to you, but as the composable is executed on render, you might want to avoid rendering it in the first place. Additionally, you can also use a watcher, but I agree that that is a bit less neat to use.
I'm probably misunderstanding what you're driving at, but the above feels more like a route guard to me.
I can see why, but that's because of the isAuthenticated
. You have two approaches:
- use only
isLoading
: this just means you are "guarding" certain parts of your application to only render when our SDK is done loading (meaning you are waiting untill we know the value ofisAuthenticated
anduser
, which can be false and null. Or true and an actual user). - use
isLoading
andisAuthenticated
: this probably feels like route guarding, but on a not-router-level. You arent protecting routes, you are hiding components for users that arent logged in, which is perfectly valid and common to do.
Which of the two you want/need is up to you. It looks like you might have enough with just using isLoading
tho, as you want to wait until the SDK knows the accurate value of user
, allowing for it to be both null
or an actual user, which you then guard against in useSegment
yourself to not call identify
when the user is null.