From 8fa0d17b0a8ef7b3b90212a202bccdddf2a90a41 Mon Sep 17 00:00:00 2001 From: Marsel Shaikhin Date: Thu, 28 Aug 2025 10:30:00 +0200 Subject: [PATCH 1/4] fix: process `navigateUnauthenticatedTo` earlier --- src/runtime/middleware/sidebase-auth.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/runtime/middleware/sidebase-auth.ts b/src/runtime/middleware/sidebase-auth.ts index f5dd7d6b..9e998437 100644 --- a/src/runtime/middleware/sidebase-auth.ts +++ b/src/runtime/middleware/sidebase-auth.ts @@ -88,6 +88,11 @@ export default defineNuxtRouteMiddleware((to) => { } } + // Redirect path was provided + if (options.navigateUnauthenticatedTo) { + return navigateTo(options.navigateUnauthenticatedTo) + } + if (authConfig.provider.type === 'authjs') { const callbackUrl = determineCallbackUrlForRouteMiddleware(authConfig, to) @@ -100,11 +105,6 @@ export default defineNuxtRouteMiddleware((to) => { return signIn(undefined, signInOptions) as Promise } - // Redirect path was provided - if (options.navigateUnauthenticatedTo) { - return navigateTo(options.navigateUnauthenticatedTo) - } - const loginPage = authConfig.provider.pages.login if (typeof loginPage !== 'string') { console.warn(`${ERROR_PREFIX} provider.pages.login is misconfigured`) From de69ff958d22eb94c7e4bdf8e49fc1fb2c8d0f97 Mon Sep 17 00:00:00 2001 From: Marsel Shaikhin Date: Thu, 28 Aug 2025 15:56:57 +0200 Subject: [PATCH 2/4] feat(#1046): add automatic middleware --- src/build/autoAddMiddleware.ts | 35 +++++++++ src/module.ts | 10 ++- tests/autoAddMiddleware.spec.ts | 128 ++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/build/autoAddMiddleware.ts create mode 100644 tests/autoAddMiddleware.spec.ts diff --git a/src/build/autoAddMiddleware.ts b/src/build/autoAddMiddleware.ts new file mode 100644 index 00000000..419d4d63 --- /dev/null +++ b/src/build/autoAddMiddleware.ts @@ -0,0 +1,35 @@ +// Minimal interface to facilitate unit-testing +export interface NuxtPage { + meta?: Record + children?: NuxtPage[] +} + +/** + * Adds `middleware: ['sidebase-auth']` to pages which use `definePageMeta({ auth: true })` or `definePageMeta({ auth: {} })`. + * @see https://nuxt.com/docs/4.x/guide/directory-structure/app/middleware#setting-middleware-at-build-time + */ +export function autoAddMiddleware(pages: NuxtPage[], middlewareName: string) { + for (const page of pages) { + if (page.meta !== undefined && (page.meta.auth === true || typeof page.meta.auth === 'object')) { + const previousMiddleware: unknown = page.meta.middleware + let normalizedMiddleware: unknown[] + + if (previousMiddleware === undefined) { + normalizedMiddleware = [] + } else if (Array.isArray(previousMiddleware)) { + normalizedMiddleware = previousMiddleware + } else { + normalizedMiddleware = [previousMiddleware] + } + + if (!normalizedMiddleware.includes(middlewareName)) { + normalizedMiddleware.push(middlewareName) + page.meta.middleware = normalizedMiddleware + } + } + + if (page.children) { + autoAddMiddleware(page.children, middlewareName) + } + } +} diff --git a/src/module.ts b/src/module.ts index 5f707275..14533555 100644 --- a/src/module.ts +++ b/src/module.ts @@ -22,6 +22,7 @@ import type { RefreshHandler, SupportedAuthProviders } from './runtime/types' +import { autoAddMiddleware } from './build/autoAddMiddleware' const topLevelDefaults = { isEnabled: true, @@ -99,6 +100,7 @@ const defaultsByBackend: { } const PACKAGE_NAME = 'sidebase-auth' +const MIDDLEWARE_NAME = PACKAGE_NAME export default defineNuxtModule({ meta: { @@ -218,7 +220,6 @@ export default defineNuxtModule({ }) // 5.2. Create refresh handler - // const generatedRefreshHandlerPath = resolve('./runtime/refreshHandler.ts') const generatedRefreshHandlerPath = addTemplate({ filename: './refreshHandler.ts', async getContents() { @@ -241,10 +242,15 @@ export default defineNuxtModule({ // 6. Register middleware for autocomplete in definePageMeta addRouteMiddleware({ - name: 'sidebase-auth', + name: MIDDLEWARE_NAME, path: resolve('./runtime/middleware/sidebase-auth') }) + // 6.5. Automatically add the middleware when `definePageMeta({ auth: true })` usage is detected + if (!options.globalAppMiddleware || !options.globalAppMiddleware.isEnabled) { + nuxt.hook('pages:extend', (pages) => autoAddMiddleware(pages, MIDDLEWARE_NAME)) + } + // 7. Add plugin for initial load addPlugin(resolve('./runtime/plugin')) diff --git a/tests/autoAddMiddleware.spec.ts b/tests/autoAddMiddleware.spec.ts new file mode 100644 index 00000000..70405e98 --- /dev/null +++ b/tests/autoAddMiddleware.spec.ts @@ -0,0 +1,128 @@ +import { describe, it, expect } from 'vitest' +import { autoAddMiddleware, type NuxtPage } from '../src/build/autoAddMiddleware' + +const MIDDLEWARE_NAME = 'sidebase-auth' + +describe('setMiddleware', () => { + it('adds middleware if meta.auth is true', () => { + testMiddleware({ meta: { auth: true } }, [MIDDLEWARE_NAME]) + }) + + it('adds middleware if meta.auth is object', () => { + testMiddleware({ meta: { auth: {} } }, [MIDDLEWARE_NAME]) + testMiddleware({ meta: { auth: { navigateUnauthenticatedTo: '/' } } }, [MIDDLEWARE_NAME]) + }) + + it('ignores pages without meta.auth', () => { + testMiddleware({}, undefined) + testMiddleware({ meta: {} }, undefined) + testMiddleware({ meta: { foo: 'bar' } }, undefined) + testMiddleware({ meta: { auth2: 'foo' } }, undefined) + testMiddleware({ meta: { middleware: 'foo' } }, 'foo') + testMiddleware({ meta: { middleware: ['foo'] } }, ['foo']) + const middlewareFunction = () => {} + testMiddleware({ meta: { middleware: middlewareFunction } }, middlewareFunction) + }) + + it('does not add when meta.auth is false', () => { + testMiddleware({ meta: { auth: false } }, undefined) + }) + + it('does not add when middleware is already present', () => { + testMiddleware( + { meta: { auth: true, middleware: MIDDLEWARE_NAME } }, + MIDDLEWARE_NAME + ) + testMiddleware( + { meta: { auth: true, middleware: [MIDDLEWARE_NAME] } }, + [MIDDLEWARE_NAME] + ) + testMiddleware( + { meta: { auth: true, middleware: ['foo', MIDDLEWARE_NAME, 'bar'] } }, + ['foo', MIDDLEWARE_NAME, 'bar'] + ) + testMiddleware( + { meta: { middleware: MIDDLEWARE_NAME } }, + MIDDLEWARE_NAME + ) + testMiddleware( + { meta: { middleware: [MIDDLEWARE_NAME] } }, + [MIDDLEWARE_NAME] + ) + testMiddleware( + { meta: { middleware: ['foo', MIDDLEWARE_NAME, 'bar'] } }, + ['foo', MIDDLEWARE_NAME, 'bar'] + ) + }) + + it('adds to an existing array', () => { + testMiddleware( + { meta: { auth: true, middleware: [] } }, + [MIDDLEWARE_NAME] + ) + testMiddleware( + { meta: { auth: true, middleware: ['foo'] } }, + ['foo', MIDDLEWARE_NAME] + ) + }) + + it('wraps string middleware into array', () => { + testMiddleware( + { meta: { auth: true, middleware: 'foo' } }, + ['foo', MIDDLEWARE_NAME] + ) + }) + + it('overrides undefined', () => { + testMiddleware( + { meta: { auth: true, middleware: undefined } }, + [MIDDLEWARE_NAME] + ) + }) + + it('wraps other middleware options into array', () => { + const functionMiddleware = () => {} + testMiddleware( + { meta: { auth: true, middleware: functionMiddleware } }, + [functionMiddleware, MIDDLEWARE_NAME] + ) + }) + + it('handles multiple pages', () => { + const pages: NuxtPage[] = [ + { meta: { auth: true } }, + { meta: { auth: true, middleware: 'foo' } } + ] + + autoAddMiddleware(pages, MIDDLEWARE_NAME) + + expect(pages[0].meta?.middleware).toEqual([MIDDLEWARE_NAME]) + expect(pages[1].meta?.middleware).toEqual(['foo', MIDDLEWARE_NAME]) + }) + + it('handles nested children', () => { + const pages: NuxtPage[] = [ + { + meta: {}, + children: [ + { meta: { auth: true } } + ] + } + ] + + autoAddMiddleware(pages, MIDDLEWARE_NAME) + + expect(pages[0].meta?.middleware).toBeUndefined() + expect(pages[0].children?.[0].meta?.middleware).toEqual([MIDDLEWARE_NAME]) + }) +}) + +/** + * Helper: test a single-page scenario + */ +function testMiddleware(page: NuxtPage, expected: unknown) { + const pages: NuxtPage[] = [page] + autoAddMiddleware(pages, MIDDLEWARE_NAME) + expect(pages[0].meta?.middleware).toEqual(expected) +} + From e80c2a46ae5d29f4d35491db9d08dd462ee48ab5 Mon Sep 17 00:00:00 2001 From: Marsel Shaikhin Date: Thu, 28 Aug 2025 17:21:19 +0200 Subject: [PATCH 3/4] chore: run lint fix --- src/build/autoAddMiddleware.ts | 6 ++++-- src/module.ts | 2 +- tests/autoAddMiddleware.spec.ts | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/build/autoAddMiddleware.ts b/src/build/autoAddMiddleware.ts index 419d4d63..6ba0dce3 100644 --- a/src/build/autoAddMiddleware.ts +++ b/src/build/autoAddMiddleware.ts @@ -16,9 +16,11 @@ export function autoAddMiddleware(pages: NuxtPage[], middlewareName: string) { if (previousMiddleware === undefined) { normalizedMiddleware = [] - } else if (Array.isArray(previousMiddleware)) { + } + else if (Array.isArray(previousMiddleware)) { normalizedMiddleware = previousMiddleware - } else { + } + else { normalizedMiddleware = [previousMiddleware] } diff --git a/src/module.ts b/src/module.ts index 14533555..658173c3 100644 --- a/src/module.ts +++ b/src/module.ts @@ -248,7 +248,7 @@ export default defineNuxtModule({ // 6.5. Automatically add the middleware when `definePageMeta({ auth: true })` usage is detected if (!options.globalAppMiddleware || !options.globalAppMiddleware.isEnabled) { - nuxt.hook('pages:extend', (pages) => autoAddMiddleware(pages, MIDDLEWARE_NAME)) + nuxt.hook('pages:extend', pages => autoAddMiddleware(pages, MIDDLEWARE_NAME)) } // 7. Add plugin for initial load diff --git a/tests/autoAddMiddleware.spec.ts b/tests/autoAddMiddleware.spec.ts index 70405e98..1eb38dfa 100644 --- a/tests/autoAddMiddleware.spec.ts +++ b/tests/autoAddMiddleware.spec.ts @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' -import { autoAddMiddleware, type NuxtPage } from '../src/build/autoAddMiddleware' +import { describe, expect, it } from 'vitest' +import { autoAddMiddleware } from '../src/build/autoAddMiddleware' +import type { NuxtPage } from '../src/build/autoAddMiddleware' const MIDDLEWARE_NAME = 'sidebase-auth' @@ -125,4 +126,3 @@ function testMiddleware(page: NuxtPage, expected: unknown) { autoAddMiddleware(pages, MIDDLEWARE_NAME) expect(pages[0].meta?.middleware).toEqual(expected) } - From f7c6711d76343e291da1a069883e030391abdfd0 Mon Sep 17 00:00:00 2001 From: Marsel Shaikhin Date: Thu, 28 Aug 2025 18:52:36 +0200 Subject: [PATCH 4/4] docs: improve the middleware documentation --- .../application-side/protecting-pages.md | 84 ++++++++++++++----- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/docs/guide/application-side/protecting-pages.md b/docs/guide/application-side/protecting-pages.md index e8fe50e6..bffe2226 100644 --- a/docs/guide/application-side/protecting-pages.md +++ b/docs/guide/application-side/protecting-pages.md @@ -23,40 +23,90 @@ export default defineNuxtConfig({ }) ``` -If you like to further customize the global middleware, you can pass an object of configurations to `globalAppMiddleware`. See the API reference here. +If you'd like to further customize the global middleware, you can pass an object of configurations to `globalAppMiddleware`. See the API reference [here](./configuration#globalappmiddleware). -### Disabling the Global Middleware - -If the global middleware is disabled, you can manually add the middleware to individual pages. This is only available if the global middleware is disabled, as you will get an error along the lines of `Error: Unknown route middleware: 'auth'`. This is because the auth middleware is then added globally and not available to use as a local, page-specific middleware. +When you use the global middleware, but want to disable it on some pages, use the [`definePageMeta`](https://nuxt.com/docs/api/utils/define-page-meta) macro to set the authentication metadata for a single page. ```vue - ``` ## Local Middleware -To locally enable or disable the middleware on a single page, you can use the [`definePageMeta`](https://nuxt.com/docs/api/utils/define-page-meta) macro to set the authentication metadata for a single page. +When you are not using the global middleware, you can still protect your pages individually using local middleware. To enable or disable the middleware on a single page, use the `auth` property on `definePageMeta`. -```vue - ``` +:::info + +When you use the automatic adding, the middleware will always be added after the other middleware you defined. This means that the following + +```ts +definePageMeta({ + auth: true, + middleware: 'other-middleware' +}) +``` + +will be interpreted as: + +```ts +definePageMeta({ + auth: true, + middleware: ['other-middleware', 'sidebase-auth'] +}) +``` + +::: + +### Manually adding local middleware + +Another way of enabling the middleware is by manually specifying it: + +```vue [Add middleware manually] + +``` + +This gives you a total control of the order in which the different middlewares are executed: + +```vue + +``` + +:::warning + +Using local middleware is only available if the global middleware was disabled, as otherwise you will get an error along the lines of `Error: Unknown route middleware: 'auth'`. This is because the auth middleware is then added globally and not available to use as a local, page-specific middleware. + +::: + ### Middleware options `auth` can be either a boolean or an object of further middleware configurations. @@ -82,7 +132,7 @@ Whether to allow only unauthenticated users to access this page. Authenticated u If you want to let everyone see the page, set `auth: false` instead (see [Local Middleware](#local-middleware)). -:::warning +:::danger This option is required from `0.9.4` onwards to prevent ambiguity ([related issue](https://github.com/sidebase/nuxt-auth/issues/926)). Make sure you set it, otherwise [Guest Mode](#guest-mode) will be **enabled** by default — your guests would be able to see the page, but your authenticated users would be redirected away. ::: @@ -122,13 +172,9 @@ definePageMeta({ You may create your own application-side middleware in order to implement custom, more advanced authentication logic. -:::warning -Creating a custom middleware is an advanced, experimental option and may result in unexpected or undesired behavior if you are not familiar with advanced Nuxt 3 concepts. -::: - To implement your custom middleware: -- Create an application-side middleware that applies either globally or is named (see the Nuxt docs for more) -- Add logic based on [`useAuth`](/guide/application-side/session-access) to it +- Create an application-side middleware that applies either globally or is named (see the [Nuxt docs](https://nuxt.com/docs/4.x/api/utils/define-page-meta#defining-middleware) for more); +- Add logic based on [`useAuth`](/guide/application-side/session-access) to it. When adding the logic, you need to watch out when calling other `async` composable functions. This can lead to `context`-problems in Nuxt, see [the explanation for this here](https://github.com/nuxt/framework/issues/5740#issuecomment-1229197529). In order to avoid these problems, you will need to either: