Cannot Properly Connect After Auth0 Callback in Vue.js

I am implementing Auth0 in a Vue.js application and facing issues with user authentication after returning from the Auth0 callback. My goal is to retrieve user information after login because I need specific user details to dynamically define application routes.

export default createAuth0({
    domain: config.auth_domain,
    clientId: config.auth_client_id,
    authorizationParams: {
        audience: config.auth_audience,
        redirect_uri: window.location.origin,
        scope: 'openid profile email offline_access',
    },
    useRefreshTokens: true,
    useRefreshTokensFallback: true,
    cacheLocation: 'localstorage',
})

createApp(App)
    .use(pinia)
    .use(router)
    .use(auth0)
    .mount('#app')
router.beforeEach(async (to) => {
    const query = window.location.search
    const isAuthCallback = query.includes('code=') && query.includes('state=')

    if (isAuthCallback) {
        auth0.checkSession()
        auth0.user.value // retrieve data
        return
  • checkSession() throws a 403 error in some cases.

  • If I return from the route guard (router.beforeEach), it loops continuously until the connection is established, which is not the expected behavior.

  • How can I properly wait for the session to be fully established after the callback without causing an infinite loop in the route guard?

First, let’s modify the Auth0 configuration to handle the authentication state more reliably:

const auth0 = createAuth0({
    domain: config.auth_domain,
    clientId: config.auth_client_id,
    authorizationParams: {
        audience: config.auth_audience,
        redirect_uri: window.location.origin,
        scope: 'openid profile email offline_access',
    },
    useRefreshTokens: true,
    useRefreshTokensFallback: true,
    cacheLocation: 'localstorage',
    // Add this to ensure Auth0 is ready before mounting
    skipRedirectCallback: true
})

Then, modify your route guard to properly handle the callback

router.beforeEach(async (to) => {
    // Check if it's a callback URL
    const isCallback = to.fullPath.includes('code=') && to.fullPath.includes('state=')
    
    try {
        // If it's the callback URL, handle the redirect
        if (isCallback) {
            // Explicitly handle the redirect
            await auth0.handleRedirectCallback()
            // After successful callback, redirect to home or intended route
            return { path: '/' } // or your desired route
        }

        // Check authentication state
        const isAuthenticated = await auth0.isAuthenticated()
        
        // If route requires auth and user isn't authenticated
        if (to.meta.requiresAuth && !isAuthenticated) {
            // Redirect to login
            await auth0.loginWithRedirect({
                appState: { targetUrl: to.fullPath }
            })
            return false
        }
    } catch (error) {
        console.error('Auth error:', error)
        return { path: '/error' } // Redirect to error page
    }
})

To handle user data loading, create a dedicated auth store using Pinia:

import { defineStore } from 'pinia'

export const useAuthStore = defineStore('auth', {
    state: () => ({
        user: null,
        isLoading: true,
        error: null
    }),
    
    actions: {
        async initializeAuth() {
            try {
                // Check if user is authenticated
                const isAuthenticated = await auth0.isAuthenticated()
                
                if (isAuthenticated) {
                    // Get the user info
                    const user = await auth0.getUser()
                    this.user = user
                }
            } catch (error) {
                this.error = error
            } finally {
                this.isLoading = false
            }
        }
    }
})

Finally, in your main App.vue, initialize the auth state:

<script setup>
import { onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth'

const authStore = useAuthStore()

onMounted(async () => {
    await authStore.initializeAuth()
})
</script>

Key improvements in this solution:

  1. skipRedirectCallback: true prevents Auth0 from automatically handling the callback, giving you more control over the process.
  2. handleRedirectCallback() is used explicitly in the route guard, which helps prevent the infinite loop issue you were experiencing.
  3. Using a Pinia store for auth state management provides a centralized place to handle user data and authentication state.
  4. The solution properly handles error cases and loading states.

To solve your specific issues:

  • The 403 error: This often occurs when the session isn’t properly initialized. By using skipRedirectCallback and explicitly handling the callback, we reduce the chance of this happening.
  • The infinite loop: This was likely happening because the route guard wasn’t properly resolving the authentication state. The new implementation ensures we only redirect when necessary and properly handles the callback state.
  • Waiting for the session: The Pinia store provides a clean way to track the loading state and ensure the application only renders when authentication is properly initialized.
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.