From 731c609c5a30c6ee65136591973def04cf612dc4 Mon Sep 17 00:00:00 2001 From: Marsel Shaikhin Date: Fri, 27 Jun 2025 19:22:15 +0200 Subject: [PATCH] fix(#621): store the data promise to await in route middleware --- playground-local/nuxt.config.ts | 4 ++-- src/runtime/composables/commonAuthState.ts | 14 +++++++++++++ src/runtime/composables/local/useAuth.ts | 8 +++++++- src/runtime/middleware/sidebase-auth.ts | 23 ++++++++++++++++++++-- src/runtime/types.ts | 1 + 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/playground-local/nuxt.config.ts b/playground-local/nuxt.config.ts index 0d51e9c4..d1530145 100644 --- a/playground-local/nuxt.config.ts +++ b/playground-local/nuxt.config.ts @@ -12,7 +12,7 @@ export default defineNuxtConfig({ signUp: { path: '/signup', method: 'post' } }, pages: { - login: '/' + login: '/login' }, token: { signInResponseTokenPointer: '/token/accessToken' @@ -37,7 +37,7 @@ export default defineNuxtConfig({ // Whether to refresh the session every time the browser window is refocused. enableOnWindowFocus: true, // Whether to refresh the session every `X` milliseconds. Set this to `false` to turn it off. The session will only be refreshed if a session already exists. - enablePeriodically: 5000, + enablePeriodically: 30000, // Custom refresh handler - uncomment to use // handler: './config/AuthRefreshHandler' }, diff --git a/src/runtime/composables/commonAuthState.ts b/src/runtime/composables/commonAuthState.ts index 5e42b310..e7ce8439 100644 --- a/src/runtime/composables/commonAuthState.ts +++ b/src/runtime/composables/commonAuthState.ts @@ -5,6 +5,19 @@ import { useState } from '#imports' export function makeCommonAuthState() { const data = useState('auth:data', () => undefined) + // Store non-hydratable promise in useState, promise would be skipped by JSON.stringify + const dataPromise = useState('auth:dataPromise', () => { + const holder = { + promise: undefined as Promise | undefined, + } + + Object.defineProperty(holder, 'promise', { + enumerable: false + }) + + return holder + }) + const hasInitialSession = computed(() => !!data.value) // If session exists, initialize as already synced @@ -30,6 +43,7 @@ export function makeCommonAuthState() { return { data, + dataPromise, loading, lastRefreshedAt, status, diff --git a/src/runtime/composables/local/useAuth.ts b/src/runtime/composables/local/useAuth.ts index 80b95864..76db1125 100644 --- a/src/runtime/composables/local/useAuth.ts +++ b/src/runtime/composables/local/useAuth.ts @@ -54,6 +54,7 @@ export function useAuth(): UseAuthReturn { const { data, + dataPromise, status, lastRefreshedAt, loading, @@ -184,7 +185,10 @@ export function useAuth(): UseAuthReturn { loading.value = true try { - const result = await _fetch(nuxt, path, { method, headers }) + const promise = _fetch(nuxt, path, { method, headers }) + dataPromise.value.promise = promise + + const result = await promise const { dataResponsePointer: sessionDataResponsePointer } = config.session data.value = jsonPointerGet(result, sessionDataResponsePointer) } @@ -197,7 +201,9 @@ export function useAuth(): UseAuthReturn { data.value = null rawToken.value = null } + loading.value = false + dataPromise.value.promise = undefined lastRefreshedAt.value = new Date() const { required = false, callbackUrl, onUnauthenticated, external } = getSessionOptions ?? {} diff --git a/src/runtime/middleware/sidebase-auth.ts b/src/runtime/middleware/sidebase-auth.ts index f5dd7d6b..b7deb555 100644 --- a/src/runtime/middleware/sidebase-auth.ts +++ b/src/runtime/middleware/sidebase-auth.ts @@ -2,7 +2,7 @@ import { isExternalUrl } from '../utils/url' import { isProduction } from '../helpers' import { ERROR_PREFIX } from '../utils/logger' import { determineCallbackUrlForRouteMiddleware } from '../utils/callbackUrl' -import { defineNuxtRouteMiddleware, navigateTo, useAuth, useRuntimeConfig } from '#imports' +import { defineNuxtRouteMiddleware, navigateTo, useAuth, useAuthState, useRuntimeConfig } from '#imports' type MiddlewareMeta = boolean | { /** @@ -37,7 +37,7 @@ declare module 'vue-router' { } } -export default defineNuxtRouteMiddleware((to) => { +export default defineNuxtRouteMiddleware(async (to) => { // Normalize options. If `undefined` was returned, we need to skip middleware const options = normalizeUserOptions(to.meta.auth) if (!options) { @@ -63,6 +63,25 @@ export default defineNuxtRouteMiddleware((to) => { return } + // Handle the possible loading state by awaiting the data promise + // https://github.com/sidebase/nuxt-auth/issues/621 + if (status.value === 'loading') { + const { dataPromise } = useAuthState() + + if (dataPromise.value.promise) { + try { + const data = await dataPromise.value.promise + if (data) { + // Data was successfully fetched - user is logged in + return + } + } + catch { + // an error occurred, proving that we need to continue with the redirect + } + } + } + // We do not want to block the login page when the local provider is used if (authConfig.provider.type === 'local') { const loginRoute: string | undefined = authConfig.provider.pages.login diff --git a/src/runtime/types.ts b/src/runtime/types.ts index e5f21f85..cf6ff8af 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -554,6 +554,7 @@ export interface CommonUseAuthReturn { export interface CommonUseAuthStateReturn { data: WrappedSessionData + dataPromise: Ref<{ promise?: Promise | undefined }> loading: Ref lastRefreshedAt: Ref status: ComputedRef