Skip to content

Commit 5bb5478

Browse files
authored
fix(#1042,#1052): handle value returned by navigateToAuthPage (#1057)
* fix(#1042,#1052): handle value returned by navigateToAuthPage * docs: add an explanation to the value * chore: fix lint
1 parent 0088492 commit 5bb5478

File tree

4 files changed

+68
-20
lines changed

4 files changed

+68
-20
lines changed

docs/guide/application-side/protecting-pages.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,15 @@ export default defineNuxtRouteMiddleware((to) => {
197197
* We cannot directly call and/or return `signIn` here as `signIn` uses async composables under the hood, leading to "nuxt instance undefined errors", see https://github.com/nuxt/framework/issues/5740#issuecomment-1229197529
198198
*
199199
* So to avoid calling it, we return it immediately.
200+
*
201+
* Important: you need to explicitly handle the value returned by the `signIn`,
202+
* for example by changing it to `false` (to abort further navigation) or `undefined` (to process other middleware).
203+
* See https://github.com/sidebase/nuxt-auth/issues/1042
200204
*/
201-
return signIn(undefined, { callbackUrl: to.path }) as ReturnType<typeof navigateTo>
205+
return signIn(
206+
undefined,
207+
{ callbackUrl: to.path }
208+
).then(() => /* abort further route navigation */ false)
202209
})
203210
```
204211

src/runtime/composables/authjs/useAuth.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ interface SignInResult {
3131
status: number
3232
ok: boolean
3333
url: any
34+
/**
35+
* Result returned by `navigateToAuthPage`, which needs to be passed back to vue-router by the middleware.
36+
* @see https://github.com/sidebase/nuxt-auth/pull/1057
37+
*/
38+
navigationResult: boolean | string | void | undefined
3439
}
3540

3641
export interface SignInFunc {
@@ -92,15 +97,16 @@ export function useAuth(): UseAuthReturn {
9297
const configuredProviders = await getProviders()
9398
if (!configuredProviders) {
9499
const errorUrl = resolveApiUrlPath('error', runtimeConfig)
95-
await navigateToAuthPageWN(nuxt, errorUrl, true)
100+
const navigationResult = await navigateToAuthPageWN(nuxt, errorUrl, true)
96101

97102
return {
98103
// Future AuthJS compat here and in other places
99104
// https://authjs.dev/reference/core/errors#invalidprovider
100105
error: 'InvalidProvider',
101106
ok: false,
102107
status: 500,
103-
url: errorUrl
108+
url: errorUrl,
109+
navigationResult,
104110
}
105111
}
106112

@@ -122,14 +128,15 @@ export function useAuth(): UseAuthReturn {
122128

123129
const selectedProvider = provider && configuredProviders[provider]
124130
if (!selectedProvider) {
125-
await navigateToAuthPageWN(nuxt, hrefSignInAllProviderPage, true)
131+
const navigationResult = await navigateToAuthPageWN(nuxt, hrefSignInAllProviderPage, true)
126132

127133
return {
128134
// https://authjs.dev/reference/core/errors#invalidprovider
129135
error: 'InvalidProvider',
130136
ok: false,
131137
status: 400,
132-
url: hrefSignInAllProviderPage
138+
url: hrefSignInAllProviderPage,
139+
navigationResult,
133140
}
134141
}
135142

@@ -165,7 +172,7 @@ export function useAuth(): UseAuthReturn {
165172

166173
if (redirect || !isSupportingReturn) {
167174
const href = data.url ?? callbackUrl
168-
await navigateToAuthPageWN(nuxt, href)
175+
const navigationResult = await navigateToAuthPageWN(nuxt, href)
169176

170177
// We use `http://_` as a base to allow relative URLs in `callbackUrl`. We only need the `error` query param
171178
const error = new URL(href, 'http://_').searchParams.get('error')
@@ -174,7 +181,8 @@ export function useAuth(): UseAuthReturn {
174181
error,
175182
ok: true,
176183
status: 302,
177-
url: href
184+
url: href,
185+
navigationResult,
178186
}
179187
}
180188

@@ -186,7 +194,8 @@ export function useAuth(): UseAuthReturn {
186194
error,
187195
status: 200,
188196
ok: true,
189-
url: error ? null : data.url
197+
url: error ? null : data.url,
198+
navigationResult: undefined,
190199
}
191200
}
192201

src/runtime/composables/authjs/utils/navigateToAuthPage.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { hasProtocol, isScriptProtocol } from 'ufo'
2-
import { abortNavigation, callWithNuxt, useRouter } from '#app'
2+
import { callWithNuxt, useRouter } from '#app'
33
import type { NuxtApp } from '#app'
44

55
export function navigateToAuthPageWN(nuxt: NuxtApp, href: string, isInternalRouting?: boolean) {
@@ -16,16 +16,19 @@ const URL_QUOTE_RE = /"/g
1616
* manually set `window.location.href` on the client **and then fake return a Promise that does not immediately resolve to block navigation (although it will not actually be fully awaited, but just be awaited long enough for the naviation to complete)**.
1717
* 2. Additionally on the server-side, we cannot use `navigateTo(signInUrl)` as this uses `vue-router` internally which does not know the "external" sign-in page of next-auth and thus will log a warning which we want to avoid.
1818
*
19-
* Adapted from https://github.com/nuxt/nuxt/blob/16d213bbdcc69c0cc72afb355755ff877654a374/packages/nuxt/src/app/composables/router.ts#L119-L217
19+
* Adapted from https://github.com/nuxt/nuxt/blob/dc69e26c5b9adebab3bf4e39417288718b8ddf07/packages/nuxt/src/app/composables/router.ts#L130-L247
2020
*
21-
* @param nuxt Nuxt app context
21+
* @param nuxtApp Nuxt app context
2222
* @param href HREF / URL to navigate to
2323
*/
24-
function navigateToAuthPage(nuxt: NuxtApp, href: string, isInternalRouting = false) {
24+
function navigateToAuthPage(nuxtApp: NuxtApp, href: string, isInternalRouting = false) {
2525
const router = useRouter()
2626

27+
// https://github.com/nuxt/nuxt/blob/dc69e26c5b9adebab3bf4e39417288718b8ddf07/packages/nuxt/src/app/composables/router.ts#L84-L93
28+
const inMiddleware = Boolean(nuxtApp._processingMiddleware)
29+
2730
if (import.meta.server) {
28-
if (nuxt.ssrContext) {
31+
if (nuxtApp.ssrContext) {
2932
const isExternalHost = hasProtocol(href, { acceptRelative: true })
3033
if (isExternalHost) {
3134
const { protocol } = new URL(href, 'http://localhost')
@@ -38,17 +41,31 @@ function navigateToAuthPage(nuxt: NuxtApp, href: string, isInternalRouting = fal
3841
// We also skip resolution for internal routing to avoid triggering `No match found` warning from Vue Router
3942
const location = isExternalHost || isInternalRouting ? href : router.resolve(href).fullPath || '/'
4043

41-
// TODO: consider deprecating in favour of `app:rendered` and removing
42-
return nuxt.callHook('app:redirected').then(() => {
44+
async function redirect(response: false | undefined) {
45+
// TODO: consider deprecating in favour of `app:rendered` and removing
46+
await nuxtApp.callHook('app:redirected')
4347
const encodedLoc = location.replace(URL_QUOTE_RE, '%22')
4448
const encodedHeader = encodeURL(location, isExternalHost)
45-
nuxt.ssrContext!._renderResponse = {
49+
50+
nuxtApp.ssrContext!._renderResponse = {
4651
statusCode: 302,
4752
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
4853
headers: { location: encodedHeader },
4954
}
50-
abortNavigation()
51-
})
55+
return response
56+
}
57+
58+
// We wait to perform the redirect last in case any other middleware will intercept the redirect
59+
// and redirect somewhere else instead.
60+
if (!isExternalHost && inMiddleware) {
61+
// For an unknown reason, `final.fullPath` received here is not percent-encoded, leading to the check always failing.
62+
// To preserve compatibility with NuxtAuth < 1.0, we simply return `undefined`.
63+
// TODO: Find the reason or report the issue to Nuxt if `navigateTo` has the same problem (`router.resolve` handles the `%2F` in callback URL correctly)
64+
// router.afterEach(final => final.fullPath === location ? redirect(false) : undefined)
65+
// return href
66+
return redirect(undefined)
67+
}
68+
return redirect(!inMiddleware ? undefined : /* abort further route navigation */ false)
5269
}
5370
}
5471

src/runtime/middleware/sidebase-auth.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,23 @@ export default defineNuxtRouteMiddleware((to) => {
101101
callbackUrl
102102
}
103103

104-
// @ts-expect-error This is valid for a backend-type of `authjs`, where sign-in accepts a provider as a first argument
105-
return signIn(undefined, signInOptions) as Promise<void>
104+
return signIn(
105+
// @ts-expect-error This is valid for a backend-type of `authjs`, where sign-in accepts a provider as a first argument
106+
undefined,
107+
signInOptions
108+
).then((signInResult) => {
109+
// `signIn` function automatically navigates to the correct page,
110+
// we need to tell `vue-router` what page we navigated to by returning the value.
111+
if (signInResult) {
112+
return signInResult.navigationResult
113+
}
114+
115+
// When no result was provided, allow other middleware to run by default.
116+
// When `false` is used, other middleware will be skipped.
117+
// See: https://router.vuejs.org/guide/advanced/navigation-guards.html#Global-Before-Guards
118+
// See: https://github.com/nuxt/nuxt/blob/dc69e26c5b9adebab3bf4e39417288718b8ddf07/packages/nuxt/src/pages/runtime/plugins/router.ts#L241-L250
119+
return true
120+
})
106121
}
107122

108123
const loginPage = authConfig.provider.pages.login

0 commit comments

Comments
 (0)