diff --git a/README.md b/README.md index 8aa1ddcb..86a4d2a1 100644 --- a/README.md +++ b/README.md @@ -8,75 +8,17 @@ [![Join our Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/9MUHR8WT9B) -> Nuxt user authentication and sessions via [NextAuth.js](https://github.com/nextauthjs/next-auth). `nuxt-auth` wraps [NextAuth.js](https://github.com/nextauthjs/next-auth) to offer the reliability & convenience of a 12k star library to the nuxt 3 ecosystem with a native developer experience (DX). +> `nuxt-auth` is a feature-packed, open-source authentication module for Nuxt 3 applications. -## Quick Start - -1. Install the package: - ```sh - npm i -D @sidebase/nuxt-auth - ``` -2. Add the package to your `nuxt.config.ts`: - ```ts - export default defineNuxtConfig({ - modules: ['@sidebase/nuxt-auth'], - auth: { - enableGlobalAppMiddleware: true - } - }) - ``` -3. Create the authentication handler (`NuxtAuthHandler`) and add at least one [authentication provider](https://next-auth.js.org/providers/): - ```ts - // file: ~/server/api/auth/[...].ts - import { NuxtAuthHandler } from '#auth' - import GithubProvider from 'next-auth/providers/github' - - export default NuxtAuthHandler({ - providers: [ - // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point - GithubProvider.default({ clientId: 'your-client-id-here', clientSecret: 'your-client-secret-here' }) - ] - }) - ``` - - `[..].ts` is a catch-all route, see the [nuxt server docs](https://v3.nuxtjs.org/guide/directory-structure/server#catch-all-route) -4. Done! You can now use all user-related functionality, for example: - - application-side session access: - ```ts - // file: ~/pages/some-page.vue - const { status, data, signIn, signOut } = useSession() - - status.value // Session status: `unauthenticated`, `loading`, `authenticated` - data.value // Session data, e.g., expiration, user.email, ... +`nuxt-auth` wraps [NextAuth.js](https://github.com/nextauthjs/next-auth) to offer the reliability & convenience of a 12k star library to the nuxt 3 ecosystem with a native developer experience (DX). - await signIn() // Sign-in the user - await signOut() // Sign-out the user - ``` - - application-side local page protection (i.e., when `enableGlobalAppMiddleware: true` not set): - ```ts - // file: ~/pages/some-page.vue - - - - ``` - - server-side (e.g., from `~/server/api/session.get.ts`): - ```ts - import { getServerSession } from '#auth' +## Quick Start - export default eventHandler(async (event) => { - const session = await getServerSession(event) - if (!session) { - return { status: 'unauthenticated!' } - } - return { status: 'authenticated!', text: 'im protected by an in-endpoint check', session } - }) - ``` +```sh +npm i -D @sidebase/nuxt-auth +``` -There's more supported methods in the `useSession` composable, you can create [universal-application-](#middleware) and [server-api-middleware](#server-side-middleware) that make use of the authentication status and more. All of this is [documented below](#documentation). +Then visit the [Quick Start documentation](https://sidebase.io/nuxt-auth/quick-start) to setup the module. ## Features @@ -107,8 +49,6 @@ There's more supported methods in the `useSession` composable, you can create [u - `GET /csrf`, - `GET /providers` -`nuxt-auth` is actively maintained. The goal of this library is to reach feature-parity with `NextAuth.js`, see the current [status](#project-roadmap) below. - ## Demo Page Visit the [`nuxt-auth` demo page here](https://nuxt-auth-example.sidebase.io/): @@ -116,620 +56,27 @@ Visit the [`nuxt-auth` demo page here](https://nuxt-auth-example.sidebase.io/): You can find the [demo source-code here](https://github.com/sidebase/nuxt-auth-example). -## Documentation - -The `nuxt-auth` module takes care of authentication and sessions: -- authentication: The process of ensuring that somebody is who they claims to be, e.g., via a username and password or by trusting an external authority (e.g., oauth via google, amazon, ...) -- sessions: Persist the information that you have been authenticated for some duration across different requests. Additional data can be attached to a session, e.g., a `username`. (Note: If you need only sessions but no authentication, you can check-out [nuxt-session](https://github.com/sidebase/nuxt-session)) - -In addition, you can use `nuxt-auth` to build authorization on top of the supported authentication + session mechanisms: As soon as you know "whos who", you can use this information to let somebody with the right email adress (for example) into a specific area. Right now, this is not in-scope of `nuxt-auth` itself. - -If you want to have a more interactive introduction, check-out the [demo page](#demo-page) or the [module playground](#module-playground). - -Below we describe: -1. [Configuration](#configuration) - - [`nuxt.config.ts`](#nuxtconfigts) - - [origin](#origin) - - [basePath](#basepath) - - [NuxtAuthHandler](#nuxtauthhandler) - - [Example with two providers](#example-with-two-providers) - - [Example with a custom Strapi JWT provider](#example-with-a-custom-strapi-jwt-provider) -2. [Application-side usage](#application-side-usage) - - [Session access and manipulation](#session-access-and-manipulation) - - [Redirects](#redirects) - - [Custom sign-in page](#custom-sign-in-page) - - [Create the custom sign-in page](#create-the-custom-sign-in-page) - - [Configure `nuxt-auth` to redirect to the custom sign-in page](#configure-nuxt-auth-to-redirect-to-the-custom-sign-in-page) - - [Middleware](#middleware) - - [Global middleware](#global-middleware) - - [Disabling the global middleware locally](#disabling-the-global-middleware-locally) - - [Local middleware](#local-middleware) -3. [Server-side usage](#server-side-usage) - - [Server-side endpoint protection](#server-side-endpoint-protection) - - [Server-side middleware](#server-side-middleware) - - [Getting the JWT token](#getting-the-jwt-token) - - [Application-side JWT token access](#application-side-jwt-token-access) -4. [REST API](#rest-api) -5. [Security](#security) - - [Disclosure](#disclosure) -6. [Glossary](#glossary) -7. [Prior Work and Module Concept](#prior-work-and-module-concept) - - [Project Roadmap](#project-roadmap) -8. [Module Playground](#module-playground) -9. [Development](#development) - -### Configuration - -There's two places to configure `nuxt-auth`: -- [`auth`-key in `nuxt.config.ts`](#nuxtconfigts): Configure the module itself, e.g., where the auth-endpoints are, what origin the app is deployed to, ... -- [NuxtAuthHandler](#nuxtauthhandler): Configure the authentication behavior, e.g., what authentication providers to use - -For development, using the [Quick Start](#quick-start)-configuration can already bring you quite far.. - -For a production deployment, you will have to at least set the: -- `origin` inside the `nuxt.config.ts` config (equivalent to `NEXTAUTH_URL` environment variable), -- `secret` inside the `NuxtAuthHandler` config (equivalent to `NEXTAUTH_SECRET` environment variable) - -#### `nuxt.config.ts` - -Use the `auth`-key inside your `nuxt.config.ts` to configure the module itself. Below is a full configuration with the default-values set: -```ts -export default defineNuxtConfig({ - modules: ['@sidebase/nuxt-auth'], - auth: { - // The module is enabled. Change this to disable the module - isEnabled: true, - - // The origin is set to the development origin. Change this when deploying to production - origin: 'http://localhost:3000', - - // The base path to the authentication endpoints. Change this if you want to add your auth-endpoints at a non-default location - basePath: '/api/auth', - - // Whether to periodically refresh the session. Change this to `true` for a refresh every seconds or set this to a number like `5000` for a refresh every 5000 milliseconds (aka: 5 seconds) - enableSessionRefreshPeriodically: false, - - // Whether to refresh the session whenever a window focus event happens, i.e, when your user refocuses the window. Set this to `false` to turn this off - enableSessionRefreshOnWindowFocus: true, - - // Whether to add a global authentication middleware that will protect all pages without exclusion - enableGlobalAppMiddleware: false - } -}) -``` - -The `origin` and the `basePath` together are equivalent to the `NEXTAUTH_URL` environment variable of NextAuth.js - -##### origin - -**You must set the `origin` in production, this includes when you run `npm run build`!** This is so that `nuxt-auth` can ensure that callbacks for authentication are correct. The `origin` consists out of (up to) 3 parts: -- scheme: `http` or `https` -- host: e.g., `localhost`, `example.org`, `www.sidebase.io` -- port: e.g., `:3000`, `:4444`; leave empty to implicitly set `:80` (this is an internet convention, don't ask) - -For [the demo-app](https://nuxt-auth-example.sidebase.io) we set the `origin` to `https://nuxt-auth-example.sidebase.io`. If for some reason required, you can explicitly set the `origin` to `http://localhost:3000` to stop `nuxt-auth` from aborting `npm run build` when the origin is unset. - -##### basePath - -This is what tells the module where you added the authentication endpoints. Per default the `basePath` is set to `/api/auth`, so that means that the module expects that all requests to `/api/auth/*` will be handled by the `NuxtAuthHandler`. - -To statify this, you need to create a [catch-all server-route](https://v3.nuxtjs.org/guide/directory-structure/server#catch-all-route) at that location by creating a file `~/server/api/auth/[...].ts` that exports the `NuxtAuthHandler`, see more on this in the [Quick Start](#quick-start) or in the [configuration section below](#serverapiauthts). - -If you want to have the authentication at another location, you can overwrite the `basePath`, e.g., when setting: -- `basePath: "/api/_auth"` -> add the authentication catch-all endpoints into `~/server/api/_auth/[...].ts` -- `basePath: "/_auth"` -> add the authentication catch-all endpoints into `~/server/routes/_auth/[...].ts` (see [Nuxt server-routes docs on this](https://v3.nuxtjs.org/guide/directory-structure/server/#server-routes)) - -#### NuxtAuthHandler - -Use the `NuxtAuthHandler({ ... })` to configure how the authentication itself behaves: -```ts -// file: ~/server/api/auth/[...].ts -import { NuxtAuthHandler } from '#auth' - -export default NuxtAuthHandler({ - // your authentication configuration here! -}) -``` - -The `NuxtAuthHandler` accepts [all options that NextAuth.js accepts for its API initialization](https://next-auth.js.org/configuration/options#options). Use this place to configure authentication providers (oauth-google, credential flow, ...), your `secret` (equivalent to `NEXTAUTH_SECRET` in NextAuth.js), add callbacks for authentication events, configure a custom logger and more. Read the linked `NextAuth.js` configuration to figure out how this works and what you can do. - -##### Example with two providers - -Here's what a full config can look like, we allow authentication via: -- a Github Oauth flow -- a username + password flow (called `CredentialsProvider`) - -```ts -// file: ~/server/api/auth/[...].ts -import CredentialsProvider from 'next-auth/providers/credentials' -import GithubProvider from 'next-auth/providers/github' -import { NuxtAuthHandler } from '#auth' - -export default NuxtAuthHandler({ - providers: [ - // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point - GithubProvider.default({ - clientId: 'your-client-id', - clientSecret: 'your-client-secret' - }), - // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point - CredentialsProvider.default({ - // The name to display on the sign-in form (e.g. 'Sign-in with...') - name: 'Credentials', - // The credentials is used to generate a suitable form on the sign-in page. - // You can specify whatever fields you are expecting to be submitted. - // e.g. domain, username, password, 2FA token, etc. - // You can pass any HTML attribute to the tag through the object. - credentials: { - username: { label: 'Username', type: 'text', placeholder: '(hint: jsmith)' }, - password: { label: 'Password', type: 'password', placeholder: '(hint: hunter2)' } - }, - authorize (credentials: any) { - // You need to provide your own logic here that takes the credentials - // submitted and returns either a object representing a user or value - // that is false/null if the credentials are invalid. - // NOTE: THE BELOW LOGIC IS NOT SAFE OR PROPER FOR AUTHENTICATION! - - const user = { id: '1', name: 'J Smith', username: 'jsmith', password: 'hunter2' } - - if (credentials?.username === user.username && credentials?.password === user.password) { - // Any object returned will be saved in `user` property of the JWT - return user - } else { - // eslint-disable-next-line no-console - console.error('Warning: Malicious login attempt registered, bad credentials provided') - // If you return null then an error will be displayed advising the user to check their details. - return null - // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter - } - } - }) - ] -}) -``` - -Note: There's more possible options for the `nextAuth.options` object, see [here](https://next-auth.js.org/configuration/options#options) for all available options. - -Note: The above credential-provider example is taken over in part from the [NextAuth.js credentials example](https://next-auth.js.org/configuration/providers/credentials). It is _not_ considered safe for production usage and you would need to adapt it further, e.g., by calling another service that provides authentication, such as [strapi](#example-with-a-custom-strapi-jwt-provider) - -##### Example with a custom Strapi JWT provider - -This section gives an example of how the `NuxtAuthHandler` can be configured to use Strapi JWTs for authentication via the `CredentialsProvider` provider. - -You have to configure the following places to make `nuxt-auth` work with Strapi: -- `STRAPI_BASE_URL` in `.env`: Add the Strapi environment variable to your .env file -- [`runtimeConfig.STRAPI_BASE_URL`-key in `nuxt.config.ts`](#nuxtconfigts): Add the Strapi base url env variable to the runtime config -- [`auth`-key in `nuxt.config.ts`](#nuxtconfigts): Configure the module itself, e.g., where the auth-endpoints are, what origin the app is deployed to, ... -- [NuxtAuthHandler](#nuxtauthhandler): Configure the authentication behavior, e.g., what authentication providers to use - -For a production deployment, you will have to at least set the: -- `STRAPI_BASE_URL` Strapi base URL for all API endpoints by default http://localhost:1337 - -1. Create a `.env` file with the following lines: -```env -// Strapi v4 url, out of the box - ORIGIN=http://localhost:3000 - NUXT_SECRET=a-not-so-good-secret - STRAPI_BASE_URL=http://localhost:1337/api -``` - -2. Set the following options in your `nuxt.config.ts`: -```ts -export default defineNuxtConfig({ - runtimeConfig: { - // The private keys which are only available server-side - NUXT_SECRET: process.env.NUXT_SECRET, - // Default http://localhost:1337/api - STRAPI_BASE_URL: process.env.STRAPI_BASE_URL, - }, - auth: { - origin: process.env.ORIGIN, - }, -}); -``` - -3. Create the catch-all `NuxtAuthHandler` and add the this custom Strapi credentials provider: -```ts -// file: ~/server/api/auth/[...].ts -import CredentialsProvider from "next-auth/providers/credentials"; -import { NuxtAuthHandler } from "#auth"; -const config = useRuntimeConfig() - -export default NuxtAuthHandler({ - secret: config.NUXT_SECRET, - providers: [ - // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point - CredentialsProvider.default({ - name: "Credentials", - credentials: {}, // Object is required but can be left empty. - async authorize(credentials: any) { - const response = await $fetch( - `${config.STRAPI_BASE_URL}/auth/local/`, - { - method: "POST", - body: JSON.stringify({ - identifier: credentials.username, - password: credentials.password, - }), - } - ); - - if (response.user) { - const u = { - id: response.id, - name: response.user.username, - email: response.user.email, - }; - return u; - } else { - return null - } - }, - }), - ] -}); -``` - -### Application-side usage - -This module allows you user-data access, signing in, signing out and more [via `useSession`](#session-access-and-manipulation). It also allows you to defined [middleware that protects pages](#middleware). - -Application-side usage refers to any code like pages, components or composables that are part of the universal server- and client-side rendering of Nuxt, see more in the [glossary](#glossary). - -#### Session access and manipulation - -The `useSession` composable is your main gateway to accessing and manipulating session-state and data. Here's the main methdos you can use: - -```ts -const { - status, - data, - lastRefreshedAt, - getCsrfToken, - getProviders, - getSession, - signIn, - signOut, -} = useSession() - -// Session status, either `unauthenticated`, `loading`, `authenticated`, see https://next-auth.js.org/getting-started/client#signout -status.value - -// Session data, either `undefined` (= authentication not attempted), `null` (= user unauthenticated), `loading` (= session loading in progress), see https://next-auth.js.org/getting-started/client#signout -data.value - -// Time at which the session was last refreshed, either `undefined` is no refresh was attempted or a `Date`-object of the time the refresh happened -lastRefreshedAt.value - -// Get / Reload the current session from the server, pass `{ required: true }` to force a login if no session exists, see https://next-auth.js.org/getting-started/client#getsession -await getSession() - -// Get the current CSRF token, usually you do not need this function, see https://next-auth.js.org/getting-started/client#signout -await getCsrfToken() - -// Get the supported providers, e.g., to build your own login page, see https://next-auth.js.org/getting-started/client#getproviders -await getProviders() - -// Trigger a sign-in, see https://next-auth.js.org/getting-started/client#signin -await signIn() - -// Trigger a sign-in with a redirect afterwards, see https://next-auth.js.org/getting-started/client#signin -await signIn(undefined, { callbackUrl: '/protected' }) - -// Trigger a sign-in via a specific authentication provider with a redirect afterwards, see https://next-auth.js.org/getting-started/client#signin -await signIn('github', { callbackUrl: '/protected' }) - -// Trigger a sign-in with username and password already passed, e.g., from your own custom-made sign-in form -await singIn('credentials', { username: 'jsmith', password: 'hunter2' }) - -// Trigger a sign-out, see https://next-auth.js.org/getting-started/client#signout -await signOut() - -// Trigger a sign-out and send the user to the sign-out page afterwards -await signOut({ calbackUrl: '/signout' }) -``` - -Session `data.value` has the following interface: -```ts -interface DefaultSession { - user?: { - name?: string | null; - email?: string | null; - image?: string | null; - }; - expires: ISODateString; -} -``` - -Note that this is only set when the use is logged-in and when the provider used to login the user provides the fields. - -##### Redirects - -You can also pass the `callbackUrl` option to both the `signIn` and the `signOut` method. This allows you to redirect a user to a certain pages, after they've completed the action. This can be useful when a user attempts to open a page (`/protected`) but has to go through external authentication (e.g., via their google account) first. - -You can use it like: -```ts -await signIn({ callbackUrl: '/protected' }) -``` - -to redirect the user to the protected page they wanted to access _after_ they've been authenticated. - -You can do the same for signing out the user: -```ts -await signOut({ callbackUrl: '/protected' }) -``` - -E.g., to redirect the user away from the already loaded, protected, page after signout (else, you will have to handle the redirect yourself). - -You may also pass specify a callback for the `getSession` utility: -```ts -await getSession({ callbackUrl: '/protected' }) -``` - -##### Custom sign-in page - -To create a custom sign-in page you will need to: -1. Create the custom sign-in page: Creating the actual page your user will enter their credentials on OR select their oauth provider (e.g., google, azure, ...) -2. Configure `nuxt-auth` to redirect to the custom sign-in page: If a sign-in is triggered or a session check fails, `nuxt-auth` has to forward you to your custom sign-in page, instead of the `nuxt-auth` builtin sign-in page - -###### Create the custom sign-in page - -To create your custom sign-in page you can use `signIn` to directly start a provider-flow once the user selected it, e.g., by clicking on a button on your custom sign-in page. Here is a very simple sign-in page that either directly starts a github-oauth sign-in flow or directly signs in the user via the credentials flow: -```vue - - - - -``` - -Note: -- In the above example `username` and `password` are hard-coded. In your own custom page, these two fields should probably come from inputs on your page. -- We set `required: false` in `useSession`, as the user is _not_ expected to have an active session when they are on the custom sign-in page - -If you want to create a custom sign-in page that dynamically offers sign-in options based on your configured providers, you can call `getProviders()` first and then iterate over the supported providers to generate your sign-in page. - -###### Configure `nuxt-auth` to redirect to the custom sign-in page +## Development -Redirects to the sign-in page happen automatically, e.g., when a `getSession()` call fails to get a session or when `signIn()` is called. By default this redirect sends the user to `/api/auth/signin`. To use a custom sign-in page we will have to configure `nuxt-auth` to send the user to the custom sign-in page instead. - -We can apply this configuration in the `NuxtAuthHandler`: -```ts -// file: ~/server/api/auth/[...].ts -import { NuxtAuthHandler } from '#auth' - -export default NuxtAuthHandler({ - pages: { - // Change the default behavior to use `/login` as the path for the sign-in page - signIn: '/login' - }, - providers: [ - // ... your provider configuration - ] -}) -``` - -We can also configure the default-location for other pages in the `pages` configuration, see [the NextAuth.js pages docs for more](https://next-auth.js.org/configuration/pages). - -#### Middleware - -The `nuxt-auth` module comes with an included [middleware](https://v3.nuxtjs.org/guide/directory-structure/middleware#middleware-directory) to authenticate users in your application. The middleware can either be enabled [globally to protect all pages](#global-middleware) or [locally inside specific pages](#local-middleware). - -The middleware will check whether a user is authenticated. If the user is not they are forced to login, but will be redirected to the page they originally wanted to visit after logging in. If the user is authenticated, they will be forwarded to their desired page. - -##### Global middleware - -To create a global authentication middleware that ensures that your user is authenticated no matter which page they visit, you configure `nuxt-auth` as follows: -```ts -export default defineNuxtConfig({ - modules: ['@sidebase/nuxt-auth'], - auth: { - enableGlobalAppMiddleware: true - } -}) -``` - -That's it! Every page of your application will now need authentication for the user to visit it. - -###### Disabling the global middleware locally - -To disable the global middleware on a specific page only, you can use the [`definePageMeta` macro](https://nuxt.com/docs/api/utils/define-page-meta#definepagemeta) to turn `auth` off: -```vue - - - - -``` - -##### Local middleware - -To protect specific pages with a middleware, you can use the [`definePageMeta` macro](https://nuxt.com/docs/api/utils/define-page-meta#definepagemeta) to turn `auth` on: -```vue - - - - -``` - -Note: You cannot use local protection when you turned on the global middleware by setting `enableGlobalAppMiddleware: true` in the `nuxt-auth` configuration. 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. - -#### Server-side usage - -On the server side you can get access to the current session like this: -```ts -import { getServerSession } from '#auth' - -export default eventHandler(async (event) => { - const session = await getServerSession(event) -}) -``` - -This is inspired by [the `getServerSession`](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#securing-api-routes) of NextAuth.js. It also avoids an external, internet call to the `GET /api/auth/sessions` endpoint, instead directly calling a pure JS-method. - -##### Server-side endpoint protection - -To protect an endpoint with, check the session after fetching it: -```ts -// file: ~/server/api/protected.get.ts -import { getServerSession } from '#auth' - -export default eventHandler(async (event) => { - const session = await getServerSession(event) - if (!session) { - return { status: 'unauthenticated!' } - } - return { status: 'authenticated!' } -}) - -``` - -##### Server-side middleware - -You can also use this in a [Nuxt server middleware](https://v3.nuxtjs.org/guide/directory-structure/server#server-middleware) to protect multiple pages at once and keep the authentication logic out of your endpoints: -```ts -// file: ~/server/middleware/auth.ts -import { getServerSession } from '#auth' - -export default eventHandler(async (event) => { - const session = await getServerSession(event) - if (!session) { - throw createError({ statusMessage: 'Unauthenticated', statusCode: 403 }) - } -}) -``` - -##### Getting the JWT Token - -Getting the (decoded) JWT token of the current user can be helpful, e.g., to use it to access an external api that requires this token for authentication or authorization. - -You can get the JWT token that was passed along with the request using `getToken`: -```ts -// file: ~/server/api/token.get.ts -import { getToken } from '#auth' - -export default eventHandler(async (event) => { - const token = await getToken({ event }) - - return token || 'no token present' -}) -``` - -The function behaves identical to the [`getToken` function from NextAuth.js](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken) with one change: you have to pass in the h3-`event` instead of `req`. This is due to how cookies can be accessed on h3: not via `req.cookies` but rather via `useCookies(event)`. - -You do not need to pass in any further parameters like `secret`, `secureCookie`, ... They are automatically inferred to the values you configured if not set and reading the token will work out of the box. You _may_ pass these options, e.g., to get the raw, encoded JWT token you can pass `raw: true`. - -###### Application-side JWT token access - -To access the JWT token application-side, e.g., in a `.vue` page, you can create an endpoint like this: -```ts -// file: ~/server/api/token.get.ts -import { getToken } from '#auth' - -export default eventHandler(event => getToken({ event })) -``` - -Then from your application-side code you can fetch it like this: -```vue -// file: app.vue - - - -``` - -Note that you have to pass the cookie-header manually. You also have to pass it using [`useRequestHeaders`](https://v3.nuxtjs.org/api/composables/use-request-headers/) so that the cookies are also correctly passed when this page is rendered server-side during the [universal-rendering process](https://v3.nuxtjs.org/guide/concepts/rendering#universal-rendering). - -#### REST API - -All endpoints that NextAuth.js supports are also supported by `nuxt-auth`: -- `GET /signin`, -- `POST /signin/:provider`, -- `GET/POST /callback/:provider`, -- `GET /signout`, -- `POST /signout`, -- `GET /session`, -- `GET /csrf`, -- `GET /providers` - -You can directly interact with them if you wish to, it's probably a better idea to use `useSession` where possible though. [See the full rest API documentation of NextAuth.js here](https://next-auth.js.org/getting-started/rest-api). - -#### Security - -This section mostly contains a list of possible security problems. Note that the below flaws exist with many libraries and frameworks we use in our day-to-day when building and working with APIs. Even your vanilla Nuxt app already posesses some of these shortcoming. Missing in the below list are estimates of how likely it is that one of the list-items may occur and what impact it will have on your app. This is because that heavily depends on: -- your app: Are you building a fun project? A proof of concept? The next fort-nox money management app? -- your environment: Building a freely available app for fun? Have authentication in front of your app and trust all users that successfully authenticated? Superb! Don't trust anyone? Then please be extra-careful when using this library and when building you backend in general - -Without further ado, here's some attack cases you can consider and take action against. Neither the attack vectors, the problems or the mitigations are exhaustive: -1. sending arbitrary data: Denial-of-Service by server-ressource exhaustion (bandwidth, cpu, memory), arbitrary code execution (if you parse the data), ... -2. creation arbitrarily many sessions: Denial-of-Service by server-ressource exhaustion (bandwidth, cpu, memory) -3. guessing correct session ids: session data can leak -4. stealing session id(s) of client(s): session data can leak - -Read up how to mitigate these and more issues if you see fit. Checkout the [`nuxt-security`](https://github.com/Baroshem/nuxt-security) module that may help with some of these. - -##### Disclosure - -A last reminder: This library was not written by crypto- or security-experts. Please proceed at your own risk, inspect the code if you want to and open issues / pull requests where you see room for improvement. If you want to file a security-concern privately, please send an email to `support@sidestream.tech` with the subject saying "SECURITY nuxt-auth" and we'll look into your request ASAP. - -#### Glossary - -There are some terms we use in this documentation that may not immeadiatly be known to every reader. Here is an explanation for some of them: -- `application` / `application-side` / `universal-application`: This references all Nuxt code of your app that is [universally rendered](https://v3.nuxtjs.org/guide/concepts/rendering#universal-rendering). In short this means that that code is rendered on the server-side and on the client-side, so all JS in it is executed twice. This is an important distinction, as some things may behave different on the server-side than on the client-side. We use `application...` to denote something that will be universally rendered -- `server` / `server-side`: This references all Nuxt code of your app that will run **only** on your server. For example, all code inside the `~/server` directory should only ever run on the server - -#### Prior Work and Module Concept - -The idea of this library is to re-use all the open-source implementation that already exist in the JS ecosystem instead of rolling our own. The idea was born when researching through the ecosystem of framework-specific authentication libraries to figure out what the best implementation approach for a state-of-the-art Nuxt 3 authentication library would be. - -During research it became clear that implementing everything from scratch will be: -- a lot of work that has already been open-sourced by others, -- error prone as authentication has a lot of intricacies that need to be resolved in order to get it right, -- hard to maintain as authentication providers come and go, -- hard to build initial trust for as authentication is important and cannot go wrong, - -In order to avoid these problems without taking forever (leaving Nuxt without an authentication library in the meantime), we decided to investigate if we can wrap [`NextAuth.js`](https://github.com/nextauthjs/next-auth), the most popular authentication library in the Next.js ecosystem by far and a trusted, well maintained one at that! - -In our investigation we found prior attempts to make NextAuth.js framework agnostic. These have more or less come to fruition, so far mostly resulting in some PoCs and example apps. Looking at these was quite helpful to get started. In particular, big pushes in the right direction came from: -- [NextAuth.js app examples](https://github.com/nextauthjs/next-auth/tree/main/apps) -- [Various comments, proposals, ... of this thread](https://github.com/nextauthjs/next-auth/discussions/3942), special thanks to @brillout for starting the discussion, @balazsorban44 for NextAuth.js and encouraging the discussion, @wobsoriano for adding PoCs for multiple languages - -The main part of the work was to piece everything together, resolve some outstanding issues with existing PoCs, add new things where nothing existed yet, e.g., for the `useSession` composable by going through the NextAuth.js client code and translating it to a Nuxt 3 approach. - -The module had another big iteration in collaboration with @JoaoPedroAS51 to make `useSession` a sync operation and trigger the session lifecycle from a plugin rather than the `useSession` composable itself. - -##### Project Roadmap +- Run `npm run dev:prepare` to generate type stubs. +- Use `npm run dev` to start [the module playground](./playground) in development mode. +- Run `npm run lint` to run eslint +- Run `npm run types` to run typescheck via tsc +- Run `npm publish --access public` to publish (bump version before) -This project is under active development: A lot of stuff already works and as NextAuth.js handles the authentication under the hood, the module should already be ready for most use-cases. Still, some functionality is missing, e.g., we've focused on oauth-providers in the first implementation, so the credential- and email-flow are untested. + +[npm-version-src]: https://img.shields.io/npm/v/@sidebase/nuxt-auth/latest.svg +[npm-version-href]: https://npmjs.com/package/@sidebase/nuxt-auth -Roughly, the priorities of `nuxt-auth` are: -1. Reach feature parity: There's some options, configuration and behavior from NextAuth.js that we do not support yet. We first want to reach feature parity on this. If any missing feature is of particular relevance to you: [Open an issue](https://github.com/sidebase/nuxt-auth/issues/new) -2. Add some of our own: There's ideas we have to support extended user management, e.g., discuss whether we want to better support the `local` / `credentials` flow than NextAuth.js does out of the box (they don't do it for good reasons, so, there really is an honest discussion to be had), adding more UI focused components that automatically and easily wrap your app in a nice auth page, ... +[npm-downloads-src]: https://img.shields.io/npm/dt/@sidebase/nuxt-auth.svg +[npm-downloads-href]: https://npmjs.com/package/@sidebase/nuxt-auth -We also want to listen to all suggestions, feature requests, bug reports, ... from you. So if you have any ideas, please open an issue or reach out to us on Twitter or via E-Mail. +[license-src]: https://img.shields.io/npm/l/@sidebase/nuxt-auth.svg +[license-href]: https://npmjs.com/package/@sidebase/nuxt-auth -#### Module Playground +### Module Playground -This module also has it's own playground, you can also use that to get familiar with it and play around a bit: +This module also has it's own playground: ```sh > git clone https://github.com/sidebase/nuxt-auth @@ -745,23 +92,3 @@ This module also has it's own playground, you can also use that to get familiar # -> open http://localhost:3000 ``` - -Note: The playground has considerably less polishing than the example page. - -#### Development - -- Run `npm run dev:prepare` to generate type stubs. -- Use `npm run dev` to start [the module playground](./playground) in development mode. -- Run `npm run lint` to run eslint -- Run `npm run types` to run typescheck via tsc -- Run `npm publish --access public` to publish (bump version before) - - -[npm-version-src]: https://img.shields.io/npm/v/@sidebase/nuxt-auth/latest.svg -[npm-version-href]: https://npmjs.com/package/@sidebase/nuxt-auth - -[npm-downloads-src]: https://img.shields.io/npm/dt/@sidebase/nuxt-auth.svg -[npm-downloads-href]: https://npmjs.com/package/@sidebase/nuxt-auth - -[license-src]: https://img.shields.io/npm/l/@sidebase/nuxt-auth.svg -[license-href]: https://npmjs.com/package/@sidebase/nuxt-auth diff --git a/docs/content/1.getting-started/1.index.md b/docs/content/1.getting-started/1.index.md new file mode 100644 index 00000000..03e4d93c --- /dev/null +++ b/docs/content/1.getting-started/1.index.md @@ -0,0 +1,69 @@ +--- +description: "Introduction to `nuxt-auth` and its features as an authentication module for your Vue / Nuxt 3 application." +--- + +# Introduction + +`nuxt-auth` is an open source Nuxt module that provides authentication for Nuxt 3 applications. + +`nuxt-auth` supports three main types of authentication out of the box: +::list{type="success"} +- OAuth: Authentication via third-party OAuth providers like Google, Azure, Github, Discord, ... +- Credentials: Authentication via a username and password that the user supplies and custom logic that you implement yourself +- Emails: Authentication via "Magic URL" emails, like Slack or Notion +:: + +`nuxt-auth` is able to provide the above and more (like database adapters, callback hooks, ...) by wrapping [NextAuth.js](https://github.com/nextauthjs/next-auth) under the hood. This gives the reliability & convenience of a >12.000 github star library to the Nuxt 3 ecosystem with a native nuxt developer experience (DX). Wrapping `NextAuth.js` has the second advantage + +`nuxt-auth` also provides Nuxt 3 specific features like a convenient application-side composable to login, logout, access user-authentication data or an authentication middleware and plugin that take care of managing the user authentication lifecycle by fetching authentication data on initial load, refreshing the user authentication on re-focusing the tab and more. + +::callout +#summary +Show me the code! + +#content +Visit the [quick start](/nuxt-auth/getting-started/quick-start) page to see code examples. + +Checkout the example `nuxt-auth` app: https://nuxt-auth-example.sidebase.io/ + +Here's the source-code https://github.com/sidebase/nuxt-auth-example of the example app. +:: + +## Authentication providers + +::list{type="success"} +- OAuth (e.g., Github, Google, Twitter, Azure, ...) +- Custom OAuth (write it yourself) +- Credentials (password + username) +- Email Magic URLs +:: + +## Application Side Session Management + +::list{type="success"} +- composable `const { signIn, signOut, status, data, lastRefreshedAt, ... } = useSession()` +- Auto-refresh the session periodically +- Auto-refresh the session on tab-refocus +- Efficient session fetching: One time on page load, afterwards for specific actions (e.g., on navigation) +- Full typescript support for all methods and properties +:: + +## Application Protection + +::list{type="success"} +- Application-side middleware protection either for the full application or specific pages +- Server-side middleware and endpoint protection +:: + +## REST API + +::list{type="success"} +- `GET /signin`, +- `POST /signin/:provider`, +- `GET/POST /callback/:provider`, +- `GET /signout`, +- `POST /signout`, +- `GET /session`, +- `GET /csrf`, +- `GET /providers` +:: diff --git a/docs/content/1.getting-started/2.installation.md b/docs/content/1.getting-started/2.installation.md new file mode 100644 index 00000000..81def721 --- /dev/null +++ b/docs/content/1.getting-started/2.installation.md @@ -0,0 +1,22 @@ +--- +description: "How to install nuxt-auth." +--- + +# Installation + +You can install `nuxt-auth` via `npm`, `yarn` or `pnpm`: +::code-group +```bash [npm] +npm i -D @sidebase/nuxt-auth +``` +```bash [yarn] +yarn add --dev @sidebase/nuxt-auth +``` +```bash [pnpm] +pnpm i -D @sidebase/nuxt-auth +``` +:: + +## Requirements + +`nuxt-auth` only needs Nuxt 3 to run. In the future Nuxt 2 or Nuxt Bridge may be supported. diff --git a/docs/content/1.welcome/1.index.md b/docs/content/1.getting-started/3.quick-start.md similarity index 60% rename from docs/content/1.welcome/1.index.md rename to docs/content/1.getting-started/3.quick-start.md index 25110c1f..bb19d0ea 100644 --- a/docs/content/1.welcome/1.index.md +++ b/docs/content/1.getting-started/3.quick-start.md @@ -1,16 +1,6 @@ -# Getting started +# Quick Start -Install the package: - ::code-group - ```bash [NPM] - npm i -D @sidebase/nuxt-auth - ``` - ```bash [Yarn] - yarn add @sidebase/nuxt-auth --dev - ``` - :: - -Add the package to your `nuxt.config.ts`: +After [installing the package](/nuxt-auth/getting-started/installation), add the package to your `nuxt.config.ts`: ```ts export default defineNuxtConfig({ @@ -18,7 +8,7 @@ export default defineNuxtConfig({ }) ``` -Create the authentication handler (`NuxtAuthHandler`) and add at least one [authentication provider](https://next-auth.js.org/providers/): +Then create the authentication handler (`NuxtAuthHandler`) that will expose the API endpoints for handling all authentication-related requests and add at least one [authentication provider](https://next-auth.js.org/providers/): ```ts // file: ~/server/api/auth/[...].ts @@ -36,7 +26,7 @@ export default NuxtAuthHandler({ }) ``` -Done! You can now use all user-related functionality, for example: +That's it! You can now use all user-related functionality, for example: ::code-group ```ts [Application side] @@ -63,6 +53,4 @@ export default eventHandler(async (event) => { ``` :: -::alert{type="info"} -There's more supported methods in the `useSession` composable, you can create [universal-application-](https://v3.nuxtjs.org/guide/directory-structure/middleware) and [server-api-middleware](https://v3.nuxtjs.org/guide/directory-structure/server#server-middleware) that make use of the authentication status and more. All of this is [documented here](/nuxt-auth/usage). -:: +To learn how to protect pages read [about the application-side usage](/nuxt-auth/application-side), to learn how to protect server-routes and API endpoints read [about the server-side usage](/nuxt-auth/server-side). diff --git a/docs/content/1.getting-started/4.getting-help.md b/docs/content/1.getting-started/4.getting-help.md new file mode 100644 index 00000000..aa03b4ec --- /dev/null +++ b/docs/content/1.getting-started/4.getting-help.md @@ -0,0 +1,15 @@ +--- +description: "How to get help when using `nuxt-auth` in your Vue / Nuxt 3 application." +--- + +# Getting Help + +At some point, you may find that there's an issue you need some help with. + +But don't worry! We're a friendly community of developers and we'd love to help. Concretely this means to: +- Checkout the docs (page that you are currently viewing), +- Search open issues and discussions: https://github.com/sidebase/nuxt-auth/issues +- Hop on Discord to ask us directly: https://discord.gg/VzABbVsqAc, +- Open an issue to file a bug, ask for an enhancement or get an answer to a question: https://github.com/sidebase/nuxt-auth/issues/new/choose + +We aim to follow the getting-help standards of the nuxt-project as described here and ask you to do the same when opening an issue or pinging us for help: https://nuxt.com/docs/community/getting-help#getting-help. diff --git a/docs/content/1.welcome/_dir.yml b/docs/content/1.getting-started/_dir.yml similarity index 58% rename from docs/content/1.welcome/_dir.yml rename to docs/content/1.getting-started/_dir.yml index 7a74e191..76238cf5 100644 --- a/docs/content/1.welcome/_dir.yml +++ b/docs/content/1.getting-started/_dir.yml @@ -1,2 +1,2 @@ -title: Welcome +title: Getting Started icon: heroicons-outline:sparkles diff --git a/docs/content/1.index.md b/docs/content/1.index.md index 908e473b..cff0b9ca 100644 --- a/docs/content/1.index.md +++ b/docs/content/1.index.md @@ -1,3 +1,3 @@ --- -redirect: /nuxt-auth/welcome +redirect: /getting-started --- diff --git a/docs/content/1.welcome/2.features.md b/docs/content/1.welcome/2.features.md deleted file mode 100644 index 0acf09ad..00000000 --- a/docs/content/1.welcome/2.features.md +++ /dev/null @@ -1,20 +0,0 @@ -# Features - -::list{type="success"} -- Authentication providers -- OAuth (e.g., Github, Google, Twitter, Azure, ...) -- Custom OAuth (write it yourself) -- Credentials (password + username) -- Isomorphic / Universal Auth Composable -- useSession composable to: signIn, signOut, getCsrfToken, getProviders, getSession -- full typescript support for all methods and property -- Persistent sessions across requests -- Application-side middleware protection -- Server-side middleware and endpoint protection -- REST API -:: - -::list{type="warning"} -- Email Magic URLs -:: - diff --git a/docs/content/1.welcome/3.demo-page.md b/docs/content/1.welcome/3.demo-page.md deleted file mode 100644 index 63155331..00000000 --- a/docs/content/1.welcome/3.demo-page.md +++ /dev/null @@ -1,12 +0,0 @@ -# Demo Page - -::alert{type="info"} -Visit the nuxt-auth [demo page here](https://nuxt-auth-example.sidebase.io/): -:: - -::alert{type="success"} -You can find the [demo source-code here](https://github.com/sidebase/nuxt-auth-example). -:: - -![nuxt-auth demo page](/nuxt-auth/nuxt-auth-example-preview.png) - diff --git a/docs/content/2.configuration/0.index.md b/docs/content/2.configuration/0.index.md deleted file mode 100644 index e1f3f77d..00000000 --- a/docs/content/2.configuration/0.index.md +++ /dev/null @@ -1,36 +0,0 @@ -# Glossary - -The nuxt-auth module takes care of authentication and sessions. -There are some terms we use in this documentation that may not immediately be known to every reader. Here is an explanation for some of them: - -::callout -#summary -authentication - -#content -The process of ensuring that somebody is who they claims to be, e.g., via a username and password or by trusting an external authority (e.g., oauth via google, amazon, ...) -:: - -::callout{type="success"} -#summary -sessions - -#content -Persist the information that you have been authenticated for some duration across different requests. Additional data can be attached to a session, e.g., a username. (Note: If you need only sessions but no authentication, you can check-out nuxt-session) -:: - -::callout{type="warning"} -#summary -application / application-side / universal-application - -#content -This references all Nuxt code of your app that is universally rendered. In short this means that that code is rendered on the server-side and on the client-side, so all JS in it is executed twice. This is an important distinction, as some things may behave different on the server-side than on the client-side. We use application... to denote something that will be universally rendered -:: - -::callout{type="danger"} -#summary -server / server-side - -#content -This references all Nuxt code of your app that will run only on your server. For example, all code inside the ~/server directory should only ever run on the server -:: diff --git a/docs/content/2.configuration/1.index.md b/docs/content/2.configuration/1.index.md new file mode 100644 index 00000000..bf9f8672 --- /dev/null +++ b/docs/content/2.configuration/1.index.md @@ -0,0 +1,13 @@ +--- +description: "Overview of the configuration options of `nuxt-auth` for Vue / Nuxt 3." +--- + +# Overview + +There's two places to configure `nuxt-auth`: +- [auth key in `nuxt.config.ts`](/nuxt-auth/configuration/nuxt-config): Configure the module itself, e.g., whether global page protection is enabled +- [NuxtAuthHandler](/nuxt-auth/configuration/nuxt-auth-handler): Configure the authentication behavior, e.g., what authentication providers to use + +For development, using the [Quick Start configuration](/nuxt-auth/getting-started/quick-start) will already bring you quite far. For a production deployment, you will have to set at least the: +- `origin` inside the `nuxt.config.ts` config, +- `secret` inside the `NuxtAuthHandler` config diff --git a/docs/content/2.configuration/1.nuxt-config.md b/docs/content/2.configuration/2.nuxt-config.md similarity index 51% rename from docs/content/2.configuration/1.nuxt-config.md rename to docs/content/2.configuration/2.nuxt-config.md index 24945a33..960ea1ac 100644 --- a/docs/content/2.configuration/1.nuxt-config.md +++ b/docs/content/2.configuration/2.nuxt-config.md @@ -1,12 +1,11 @@ --- -title: "Nuxt Config" description: "Learn how to configure nuxt-auth inside of the nuxt-config.ts" toc: true --- -# nuxt.config.ts +# Module (nuxt.config.ts) -Use the `auth`-key inside your `nuxt.config.ts` to configure the module itself. Right now this is limited to the following options: +Use the `auth`-key inside the `nuxt.config.ts` to configure the `nuxt-auth` module itself. Here are the available configuration options and their default values: ```ts export default defineNuxtConfig({ modules: ['@sidebase/nuxt-auth'], @@ -15,15 +14,24 @@ export default defineNuxtConfig({ isEnabled: true, // The origin is set to the development origin. Change this when deploying to production - origin: 'http://localhost:300', + origin: 'http://localhost:3000', // The base path to the authentication endpoints. Change this if you want to add your auth-endpoints at a non-default location - basePath: '/api/auth' + basePath: '/api/auth', + + // Whether to periodically refresh the session. Change this to `true` for a refresh every seconds or set this to a number like `5000` for a refresh every 5000 milliseconds (aka: 5 seconds) + enableSessionRefreshPeriodically: false, + + // Whether to refresh the session whenever a window focus event happens, i.e, when your user refocuses the window. Set this to `false` to turn this off + enableSessionRefreshOnWindowFocus: true, + + // Whether to add a global authentication middleware that will protect all pages without exclusion + enableGlobalAppMiddleware: false } }) ``` -The `origin` and the `basePath` together are equivalent to the `NEXTAUTH_URL` environment variable of NextAuth.js +The `origin` and the `basePath` together are equivalent to the [`NEXTAUTH_URL` environment variable of NextAuth.js](https://next-auth.js.org/configuration/options#nextauth_url) ## origin @@ -32,28 +40,22 @@ The `origin` and the `basePath` together are equivalent to the `NEXTAUTH_URL` en - host: e.g., `localhost`, `example.org`, `www.sidebase.io` - port: e.g., `:3000`, `:4444`; leave empty to implicitly set `:80` (this is an internet convention, don't ask) -For [the demo-app](https://nuxt-auth-example.sidebase.io) we set the `origin` to `https://nuxt-auth-example.sidebase.io`. If for some reason required, you can explicitly set the `origin` to `http://localhost:3000` to stop `nuxt-auth` from aborting `npm run build` when the origin is unset. +For the demo-app at https://nuxt-auth-example.sidebase.io we set the `origin` to `https://nuxt-auth-example.sidebase.io`. If for some reason required, you can explicitly set the `origin` to `http://localhost:3000` to stop `nuxt-auth` from aborting `npm run build` when the origin is unset. ## basePath This is what tells the module where you added the authentication endpoints. Per default the `basePath` is set to `/api/auth`, so that means that the module expects that all requests to `/api/auth/*` will be handled by the `NuxtAuthHandler`. -To statify this, you need to create a [catch-all server-route](https://v3.nuxtjs.org/guide/directory-structure/server#catch-all-route) at that location by creating a file `~/server/api/auth/[...].ts` that exports the `NuxtAuthHandler`, see more on this in the [Quick Start](#quick-start) or in the [configuration section below](#serverapiauthts). +To statify this, you need to create a [catch-all server-route](https://nuxt.com/docs/guide/directory-structure/pages/#catch-all-route) at that location by creating a file `~/server/api/auth/[...].ts` that exports the `NuxtAuthHandler`, see more on this in the [Quick Start](/nuxt-auth/getting-started/quick-start) or in the [`NuxtAuthHandler` documentation](/nuxt-auth/configuration/nuxt-auth-handler) If you want to have the authentication at another location, you can overwrite the `basePath`, e.g., when setting: - `basePath: "/api/_auth"` -> add the authentication catch-all endpoints into `~/server/api/_auth/[...].ts` -- `basePath: "/_auth"` -> add the authentication catch-all endpoints into `~/server/routes/_auth/[...].ts` (see [Nuxt server-routes docs on this](https://v3.nuxtjs.org/guide/directory-structure/server/#server-routes)) +- `basePath: "/_auth"` -> add the authentication catch-all endpoints into `~/server/routes/_auth/[...].ts` -## NuxtAuthHandler +See [Nuxt server-routes docs on catch-all routes for a further explanation.](https://nuxt.com/docs/guide/directory-structure/server#server-routes) -Use the `NuxtAuthHandler({ ... })` to configure how the authentication itself behaves: -```ts -// file: ~/server/api/auth/[...].ts -import { NuxtAuthHandler } from '#auth' +## enableGlobalAppMiddleware -export default NuxtAuthHandler({ - // your authentication configuration here! -}) -``` +This is a middleware that comes included with `nuxt-auth`. When you enable it, it will protect _all_ pages, so even your index page (`/`) will not be accessible without a login anymore. -The `NuxtAuthHandler` accepts [all options that NextAuth.js accepts for its API initialization](https://next-auth.js.org/configuration/options#options). Use this place to configure authentication providers (oauth-google, credential flow, ...), your `secret` (equivalent to `NEXTAUTH_SECRET` in NextAuth.js), add callbacks for authentication events, configure a custom logger and more. Read the linked `NextAuth.js` configuration to figure out how this works and what you can do. +Read more on this topic [in the page protection docs](/nuxt-auth/application-side/protecting-pages#global-middleware). diff --git a/docs/content/2.configuration/2.nuxt-auth-handler.md b/docs/content/2.configuration/3.nuxt-auth-handler.md similarity index 60% rename from docs/content/2.configuration/2.nuxt-auth-handler.md rename to docs/content/2.configuration/3.nuxt-auth-handler.md index 1ed71854..b6763b70 100644 --- a/docs/content/2.configuration/2.nuxt-auth-handler.md +++ b/docs/content/2.configuration/3.nuxt-auth-handler.md @@ -1,45 +1,56 @@ --- -title: "NuxtAuthHandler" -description: "Learn how to configure nuxt-auth with the NuxtAuthHandler" -toc: false +description: "Learn how to configure the NuxtAuthHandler that handles all authentication requests on the server-side" --- # NuxtAuthHandler -After setting up nuxt-auth inside your `nuxt-config.ts` you can begin defining providers and other options inside your NuxtAuthHandler. +After setting up [nuxt-auth inside your `nuxt-config.ts`](/nuxt-auth/configuration/nuxt-config) you can begin defining providers and other options inside your NuxtAuthHandler. -## Creating a new NuxtAuthHandler +## Creating the `NuxtAuthHandler` -In order to create your own `NuxtAuthHanlder`, create the file `~/server/api/auth/[...].ts`. This file will automatically intercept all -requests going to `https://localhost/api/auth` and inject the authentication api. If you wish you can also use a custom api path, however this change will need to be +In order to create your own `NuxtAuthHandler`, create the file `~/server/api/auth/[...].ts`. This file will automatically set up the authentication API that responds to all +requests going to `https://localhost/api/auth/*`. If you wish you can also use a custom file location and api path, however this change will need to be reflected in the [basePath](/nuxt-auth/configuration/nuxt-config#basepath), which is configured in the [nuxt-config.ts](/nuxt-auth/configuration/nuxt-config). -## Configuring your NuxtAuthHandler +## Configuring the `NuxtAuthHandler` -Use the `NuxtAuthHandler({ ... })` to configure how the authentication itself behaves: +After creating the file, add the `NuxtAuthHandler` to it. Use the `NuxtAuthHandler({ ... })` to configure how the authentication itself behaves, what [callbacks should be called](https://next-auth.js.org/configuration/callbacks), what [database adapters should be used](https://next-auth.js.org/adapters/overview) and more: ::code-group -```ts [Simple NuxtAuthHandler] +```ts [Empty NuxtAuthHandler] +// file: ~/server/api/auth/[...].ts import { NuxtAuthHandler } from '#auth' - + export default NuxtAuthHandler({ - // your authentication configuration here! + // your authentication configuration here! }) ``` -```ts [Full NuxtAuthHandler] -import CredentialsProvider from 'next-auth/providers/credentials' +```ts [NuxtAuthHandler with Github Provider] +// file: ~/server/api/auth/[...].ts import GithubProvider from 'next-auth/providers/github' import { NuxtAuthHandler } from '#auth' export default NuxtAuthHandler({ - // A secret string you define, to ensure correct encryption - secret: 'your-secret-here', + // A secret string you define, to ensure correct encryption + secret: 'your-secret-here', providers: [ // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point GithubProvider.default({ clientId: 'your-client-id', clientSecret: 'your-client-secret' - }), + }) + ] +}) +``` +```ts [NuxtAuthHandler with Credentials Provider] +// file: ~/server/api/auth/[...].ts +import CredentialsProvider from 'next-auth/providers/credentials' +import { NuxtAuthHandler } from '#auth' + +export default NuxtAuthHandler({ + // A secret string you define, to ensure correct encryption + secret: 'your-secret-here', + providers: [ // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point CredentialsProvider.default({ // The name to display on the sign in form (e.g. 'Sign in with...') @@ -79,4 +90,6 @@ export default NuxtAuthHandler({ ``` :: -The `NuxtAuthHandler` accepts [all options that NextAuth.js accepts for its API initialization](https://next-auth.js.org/configuration/options#options). Use this place to configure authentication providers (oauth-google, credential flow, ...), your `secret` (equivalent to `NEXTAUTH_SECRET` in NextAuth.js), add callbacks for authentication events, configure a custom logger and more. Read the linked `NextAuth.js` configuration to figure out how this works and what you can do. +Checkout the [provider examples](/nuxt-auth/provider-examples) to see different examples of provider configurations, e.g., for Strapi or Directus. + +The `NuxtAuthHandler` accepts [all options that NextAuth.js accepts for its API initialization](https://next-auth.js.org/configuration/options#options). Use this place to configure authentication providers (oauth-Google, credential flow, ...), your `secret` (equivalent to `NEXTAUTH_SECRET` in NextAuth.js), add callbacks for authentication events, configure a custom logger and more. Read the [`NextAuth.js` docs to see all possible options](https://next-auth.js.org/configuration/options#options). diff --git a/docs/content/3.application-side/1.index.md b/docs/content/3.application-side/1.index.md new file mode 100644 index 00000000..38a32a85 --- /dev/null +++ b/docs/content/3.application-side/1.index.md @@ -0,0 +1,14 @@ +--- +description: "Application-side usage of `nuxt-auth` for Vue / Nuxt 3 apps." +--- + +# Overview + +On the application-side this module offers: +::list{type="success"} +- [`useSession` composable for session access and management](/nuxt-auth/application-side/session-access-and-management) +- [Creation of custom sign-in pages](/nuxt-auth/application-side/custom-sign-in-page) +- [Middleware to protect your application on the application side](/nuxt-auth/application-side/protecting-pages) +:: + +Application-side usage refers to any code like pages, components or composables that are part of the universal server- and client-side rendering of Nuxt, see more in the [glossary](/nuxt-auth/further-reading/glossary). diff --git a/docs/content/3.application-side/2.session-access-and-management.md b/docs/content/3.application-side/2.session-access-and-management.md new file mode 100644 index 00000000..48e9cef3 --- /dev/null +++ b/docs/content/3.application-side/2.session-access-and-management.md @@ -0,0 +1,89 @@ +# Session Access and Management + +The `useSession` composable is your main gateway to accessing and manipulating session-state and data. Here's the main methdos you can use: + +```ts +const { + status, + data, + lastRefreshedAt, + getCsrfToken, + getProviders, + getSession, + signIn, + signOut, +} = useSession() + +// Session status, either `unauthenticated`, `loading`, `authenticated`, see https://next-auth.js.org/getting-started/client#signout +status.value + +// Session data, either `undefined` (= authentication not attempted), `null` (= user unauthenticated), `loading` (= session loading in progress), see https://next-auth.js.org/getting-started/client#signout +data.value + +// Time at which the session was last refreshed, either `undefined` if no refresh was attempted or a `Date`-object of the time the refresh happened +lastRefreshedAt.value + +// Get / Reload the current session from the server, pass `{ required: true }` to force a login if no session exists, see https://next-auth.js.org/getting-started/client#getsession +await getSession() + +// Get the current CSRF token, usually you do not need this function, see https://next-auth.js.org/getting-started/client#signout +await getCsrfToken() + +// Get the supported providers, e.g., to build your own login page, see https://next-auth.js.org/getting-started/client#getproviders +await getProviders() + +// Trigger a sign-in, see https://next-auth.js.org/getting-started/client#signin +await signIn() + +// Trigger a sign-in with a redirect afterwards, see https://next-auth.js.org/getting-started/client#signin +await signIn(undefined, { callbackUrl: '/protected' }) + +// Trigger a sign-in via a specific authentication provider with a redirect afterwards, see https://next-auth.js.org/getting-started/client#signin +await signIn('github', { callbackUrl: '/protected' }) + +// Trigger a sign-in with username and password already passed, e.g., from your own custom-made sign-in form +await singIn('credentials', { username: 'jsmith', password: 'hunter2' }) + +// Trigger a sign-out, see https://next-auth.js.org/getting-started/client#signout +await signOut() + +// Trigger a sign-out and send the user to the sign-out page afterwards +await signOut({ calbackUrl: '/signout' }) +``` + +Session `data.value` has the following interface: +```ts +interface DefaultSession { + user?: { + name?: string | null; + email?: string | null; + image?: string | null; + }; + expires: ISODateString; +} +``` + +Note that this is only set when the use is logged-in and when the provider used to login the user provides the fields. + +## Redirects + +You can also pass the `callbackUrl` option to both the `signIn`, the `signOut` and the `getSession` method. This allows you to redirect a user to a certain pages, after they've completed the action. This can be useful when a user attempts to open a page (`/protected`) but has to go through external authentication (e.g., via their google account) first. + +You can use it like: +```ts +await signIn({ callbackUrl: '/protected' }) +``` + +to redirect the user to the protected page they wanted to access _after_ they've been authenticated. + +You can do the same for signing out the user: +```ts +await signOut({ callbackUrl: '/protected' }) +``` + +E.g., to redirect the user away from the already loaded, protected, page after signout (else, you will have to handle the redirect yourself). + +You may also pass specify a callback for the `getSession` utility: +```ts +await getSession({ callbackUrl: '/protected' }) +``` diff --git a/docs/content/3.application-side/3.custom-sign-in-page.md b/docs/content/3.application-side/3.custom-sign-in-page.md new file mode 100644 index 00000000..c5ca101a --- /dev/null +++ b/docs/content/3.application-side/3.custom-sign-in-page.md @@ -0,0 +1,65 @@ +# Custom sign-in Page + +To create a custom sign-in page you will need to: +1. Create the custom sign-in page: Creating the actual page your user will enter their credentials on OR select their oauth provider (e.g., google, azure, ...) +2. Configure `nuxt-auth` to redirect to the custom sign-in page: If a sign-in is triggered or a session check fails, `nuxt-auth` has to forward you to your custom sign-in page, instead of the `nuxt-auth` builtin sign-in page +3. Optional: Disable the `nuxt-auth` global protection middleware for the custom page if you have it enabled + +## Create the Custom sign-in Page + +To create your custom sign-in page you can use `signIn` to directly start a provider-flow once the user selected it, e.g., by clicking on a button on your custom sign-in page. Here is a very simple sign-in page that either directly starts a github-oauth sign-in flow or directly signs in the user via the credentials flow: +```vue + + + + +``` + +Note: +- In the above example `username` and `password` are hard-coded. In your own custom page, these two fields should probably come from inputs on your page. +- We disable the global `nuxt-auth` middleware for this page by using `definePageMeta({ auth: false })`, this is only necessary if you have set `enableGlobalAppMiddleware: true` in [the `nuxt-auth` module configuration](/nuxt-auth/configuration/nuxt-config) + +If you want to create a custom sign-in page that dynamically offers sign-in options based on your configured providers, you can call `getProviders()` first and then iterate over the supported providers to generate your sign-in page. + +## Configure `nuxt-auth` to redirect to the Custom sign-in Page + +Redirects to the sign-in page happen automatically, e.g., when a `getSession()` call fails to get a session or when `signIn()` is called. By default this redirect sends the user to `/api/auth/signin`. To use a custom sign-in page we will have to configure `nuxt-auth` to send the user to the custom sign-in page instead. + +We can apply this configuration in the `NuxtAuthHandler`: +```ts +// file: ~/server/api/auth/[...].ts +import { NuxtAuthHandler } from '#auth' + +export default NuxtAuthHandler({ + pages: { + // Change the default behavior to use `/login` as the path for the sign-in page + signIn: '/login' + }, + providers: [ + // ... your provider configuration + ] +}) +``` + +We can also configure the default-location for other pages in the `pages` configuration, see [the NextAuth.js pages docs for more](https://next-auth.js.org/configuration/pages). + +## Disable the global page protection middleware + +As already outlined in the first step, you will need to add: +```ts +definePageMeta({ auth: false }) +``` +to your login page if you have enabled the [global page protection middleware](/nuxt-auth/application-side/protecting-pages). This is so that your users can access the login pages without being forced to login. Not disabling the global middleware would result in an endless loop of redirects. + +If you have not set `enableGlobalAppMiddleware` or have set it to `false` this step does not apply to your application. diff --git a/docs/content/3.application-side/4.protecting-pages.md b/docs/content/3.application-side/4.protecting-pages.md new file mode 100644 index 00000000..615f80de --- /dev/null +++ b/docs/content/3.application-side/4.protecting-pages.md @@ -0,0 +1,77 @@ +# Protecting Pages + +`nuxt-auth` offers two different approaches to protect pages: +1. Global protection: Protects all pages with manual exceptions +2. Local protection: Protects specific pages + +Briefly summarized, you can enable global protection (1) in your `nuxt.config.ts`: +```ts +export default defineNuxtConfig({ + modules: ['@sidebase/nuxt-auth'], + auth: { + enableGlobalAppMiddleware: true + } +}) +``` + +Now *all pages* will require sign-in. Learn how to add excepted pages [below](/nuxt-auth/application-side/protecting-pages#disabling-the-global-middleware-locally) + +To enable page-local protection (2), add the following `definePageMeta` directive to a page: +```vue + + + + +``` + +You cannot mix approach (1) and (2). So, if the global middleware is enabled, you cannot additionally add another protection middleware to a specific page. + +## Global middleware + +To create a global authentication middleware that ensures that your user is authenticated no matter which page they visit, you configure `nuxt-auth` as follows: +```ts +export default defineNuxtConfig({ + modules: ['@sidebase/nuxt-auth'], + auth: { + enableGlobalAppMiddleware: true + } +}) +``` + +That's it! Every page of your application will now need authentication for the user to visit it. + +### Disabling the global middleware locally + +To disable the global middleware on a specific page only, you can use the [`definePageMeta` macro](https://nuxt.com/docs/api/utils/define-page-meta#definepagemeta) to turn `auth` off: +```vue + + + + +``` + +Note: This only works on `pages/`. It notably does not work inside the `app.vue`. + +## Local middleware + +To protect specific pages with a middleware, you can use the [`definePageMeta` macro](https://nuxt.com/docs/api/utils/define-page-meta#definepagemeta) to turn `auth` on: +```vue + + + + +``` + +Note: You cannot use local protection when you turned on the global middleware by setting `enableGlobalAppMiddleware: true` in the `nuxt-auth` configuration. 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. diff --git a/docs/content/3.application-side/_dir.yml b/docs/content/3.application-side/_dir.yml new file mode 100644 index 00000000..201d148b --- /dev/null +++ b/docs/content/3.application-side/_dir.yml @@ -0,0 +1,2 @@ +title: Application-Side +icon: heroicons-outline:computer-desktop diff --git a/docs/content/3.providers/_dir.yml b/docs/content/3.providers/_dir.yml deleted file mode 100644 index 7448d0ab..00000000 --- a/docs/content/3.providers/_dir.yml +++ /dev/null @@ -1,2 +0,0 @@ -title: Providers -icon: heroicons-outline:user-group diff --git a/docs/content/3.providers/index.md b/docs/content/3.providers/index.md deleted file mode 100644 index 48005819..00000000 --- a/docs/content/3.providers/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Overview ---- - -# Providers - -We are currently working on adding more examples of providers using `nuxt-auth`. In the meantime you can view the official provider documentation by `NextAuth` [here](https://next-auth.js.org/providers/). diff --git a/docs/content/4.server-side/1.index.md b/docs/content/4.server-side/1.index.md new file mode 100644 index 00000000..af7f5fb6 --- /dev/null +++ b/docs/content/4.server-side/1.index.md @@ -0,0 +1,14 @@ +--- +description: "Server-side usage of `nuxt-auth` for Vue / Nuxt 3 apps." +--- + +# Overview + +On the server-side this module offers: +::list{type="success"} +- [Session Access and Route Protection](/nuxt-auth/server-side/session-access-and-route-protection) +- [JWT Access](/nuxt-auth/server-side/jwt-access) +- [REST API](/nuxt-auth/server-side/rest-api) +:: + +Server-side usage refers to any code that only runs on the server, e.g., in your `~/server/` directory, read the [glossary](/nuxt-auth/further-reading/glossary) for more. diff --git a/docs/content/4.server-side/2.session-access-and-route-protection.md b/docs/content/4.server-side/2.session-access-and-route-protection.md new file mode 100644 index 00000000..704abfcb --- /dev/null +++ b/docs/content/4.server-side/2.session-access-and-route-protection.md @@ -0,0 +1,44 @@ +# Session Access and Route Protection + +On the server side you can get access to the current session like this: +```ts +import { getServerSession } from '#auth' + +export default eventHandler(async (event) => { + const session = await getServerSession(event) +}) +``` + +This is inspired by [the `getServerSession`](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#securing-api-routes) of NextAuth.js. It also avoids an external, internet call to the `GET /api/auth/sessions` endpoint, instead directly calling a pure JS-method. + +## Endpoint Protection + +To protect an endpoint, check the session after fetching it: +```ts +// file: ~/server/api/protected.get.ts +import { getServerSession } from '#auth' + +export default eventHandler(async (event) => { + const session = await getServerSession(event) + if (!session) { + return { status: 'unauthenticated!' } + } + return { status: 'authenticated!' } +}) + +``` + +## Server Middleware + +You can also use this in a [Nuxt server middleware](https://nuxt.com/docs/guide/directory-structure/server#server-middleware) to protect multiple pages at once and keep the authentication logic out of your endpoints: +```ts +// file: ~/server/middleware/auth.ts +import { getServerSession } from '#auth' + +export default eventHandler(async (event) => { + const session = await getServerSession(event) + if (!session) { + throw createError({ statusMessage: 'Unauthenticated', statusCode: 403 }) + } +}) +``` diff --git a/docs/content/4.usage/1.server-side.md b/docs/content/4.server-side/3.jwt-access.md similarity index 57% rename from docs/content/4.usage/1.server-side.md rename to docs/content/4.server-side/3.jwt-access.md index 1b8997ca..6a29e4d5 100644 --- a/docs/content/4.usage/1.server-side.md +++ b/docs/content/4.server-side/3.jwt-access.md @@ -1,53 +1,4 @@ ---- -title: "Server side" ---- - -# Server-side usage - -On the server side you can get access to the current session like this: -```ts -import { getServerSession } from '#auth' - -export default eventHandler(async (event) => { - const session = await getServerSession(event) -}) -``` - -This is inspired by [the getServerSession](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#securing-api-routes) of NextAuth.js. It also avoids an external, internet call to the `GET /api/auth/sessions` endpoint, instead directly calling a pure JS-method. - -## Server-side endpoint protection - -To protect an endpoint with, check the session after fetching it: -```ts -// file: ~/server/api/protected.get.ts -import { getServerSession } from '#auth' - -export default eventHandler(async (event) => { - const session = await getServerSession(event) - if (!session) { - return { status: 'unauthenticated!' } - } - return { status: 'authenticated!' } -}) - -``` - -## Server-side middleware - -You can also use this in a [Nuxt server middleware](https://v3.nuxtjs.org/guide/directory-structure/server#server-middleware) to protect multiple pages at once and keep the authentication logic out of your endpoints: -```ts -// file: ~/server/middleware/auth.ts -import { getServerSession } from '#auth' - -export default eventHandler(async (event) => { - const session = await getServerSession(event) - if (!session) { - throw createError({ statusMessage: 'Unauthenticated', statusCode: 403 }) - } -}) -``` - -## Getting the JWT Token +# JWT Access Getting the (decoded) JWT token of the current user can be helpful, e.g., to use it to access an external api that requires this token for authentication or authorization. @@ -67,7 +18,7 @@ The function behaves identical to the [`getToken` function from NextAuth.js](htt You do not need to pass in any further parameters like `secret`, `secureCookie`, ... They are automatically inferred to the values you configured if not set and reading the token will work out of the box. You _may_ pass these options, e.g., to get the raw, encoded JWT token you can pass `raw: true`. -### Application-side JWT token access +## Application-side JWT token access To access the JWT token application-side, e.g., in a `.vue` page, you can create an endpoint like this: ```ts diff --git a/docs/content/4.server-side/4.rest-api.md b/docs/content/4.server-side/4.rest-api.md new file mode 100644 index 00000000..70524605 --- /dev/null +++ b/docs/content/4.server-side/4.rest-api.md @@ -0,0 +1,17 @@ +# REST API + +All endpoints that NextAuth.js supports are also supported by `nuxt-auth`: + +| Endpoint | Request | +|--------------------------------|:-------------| +| `${basePath}/signin` | `GET` | +| `${basePath}/signin/:provider` | `POST` | +| `${basePath}/callback/:provider` | `GET` `POST` | +| `${basePath}/signout` | `GET` `POST` | +| `${basePath}/session` | `GET` | +| `${basePath}/csrf` | `GET` | +| `${basePath}/providers` | `GET` | + +The `basePath` is `/api/auth` per default and [can be configured in the `nuxt.config.ts`](/nuxt-auth/configuration/nuxt-config). + +You can directly interact with these API endpoints if you wish to, it's probably a better idea to use `useSession` where possible though. [See the full rest API documentation of NextAuth.js here](https://next-auth.js.org/getting-started/rest-api). diff --git a/docs/content/4.usage/_dir.yml b/docs/content/4.server-side/_dir.yml similarity index 68% rename from docs/content/4.usage/_dir.yml rename to docs/content/4.server-side/_dir.yml index 7cd238ef..06a72e86 100644 --- a/docs/content/4.usage/_dir.yml +++ b/docs/content/4.server-side/_dir.yml @@ -1,2 +1,2 @@ -title: Usage +title: Server-Side icon: heroicons-outline:computer-desktop diff --git a/docs/content/4.usage/0.index.md b/docs/content/4.usage/0.index.md deleted file mode 100644 index 4547a678..00000000 --- a/docs/content/4.usage/0.index.md +++ /dev/null @@ -1,211 +0,0 @@ ---- -title: "Application side" ---- - -# Application-side usage - -This module allows you user-data access, signing in, signing out and more [via `useSession`](#session-access-and-manipulation). It also allows you to defined [middleware that protects pages](#middleware). - -Application-side usage refers to any code like pages, components or composables that are part of the universal server- and client-side rendering of Nuxt, see more in the [glossary](/nuxt-auth/configuration). - -## Session access and manipulation - -The `useSession` composable is your main gateway to accessing and manipulating session-state and data. Here's the main methdos you can use: - -```ts -const { - status, - data, - getCsrfToken, - getProviders, - getSession, - signIn, - signOut, -} = await useSession({ - // Whether a session is required. If it is, a redirect to the signin page will happen if no active session exists - required: true -}) - -// Session status, either `unauthenticated`, `loading`, `authenticated`, see https://next-auth.js.org/getting-started/client#signout -status.value - -// Session data, either `undefined` (= authentication not attempted), `null` (= user unauthenticated), `loading` (= session loading in progress), see https://next-auth.js.org/getting-started/client#signout -data.value - -// Get / Reload the current session from the server, pass `{ required: true }` to force a login if no session exists, see https://next-auth.js.org/getting-started/client#getsession -await getSession() - -// Get the current CSRF token, usually you do not need this function, see https://next-auth.js.org/getting-started/client#signout -await getCsrfToken() - -// Get the supported providers, e.g., to build your own login page, see https://next-auth.js.org/getting-started/client#getproviders -await getProviders() - -// Trigger a sign in, see https://next-auth.js.org/getting-started/client#signin -await signIn() - -// Trigger a sign in with a redirect afterwards, see https://next-auth.js.org/getting-started/client#signin -await signIn(undefined, { callbackUrl: '/protected' }) - -// Trigger a sign in via a specific authentication provider with a redirect afterwards, see https://next-auth.js.org/getting-started/client#signin -await signIn('github', { callbackUrl: '/protected' }) - -// Trigger a sign in with username and password already passed, e.g., from your own custom-made sign-in form -await singIn('credentials', { username: 'jsmith', password: 'hunter2' }) - -// Trigger a sign out, see https://next-auth.js.org/getting-started/client#signout -await signOut() - -// Trigger a sign out and send the user to the sign out page afterwards -await signOut({ calbackUrl: '/signout' }) -``` - -Session `data` has the following interface: -```ts -interface DefaultSession { - user?: { - name?: string | null; - email?: string | null; - image?: string | null; - }; - expires: ISODateString; -} -``` - -Note that this is only set when the use is logged-in and when the provider used to login the user provides the fields. - -### Redirects - -You can also pass the `callbackUrl` option to both the `signIn` and the `signOut` method. This allows you to redirect a user to a certain pages, after they've completed the action. This can be useful when a user attempts to open a page (`/protected`) but has to go through external authentication (e.g., via their google account) first. - -You can use it like: -```ts -await signIn({ callbackUrl: '/protected' }) -``` - -to redirect the user to the protected page they wanted to access _after_ they've been authenticated. - -You can do the same for signing out the user: -```ts -await signOut({ callbackUrl: '/protected' }) -``` - -E.g., here to redirect the user away from the already loaded, protected, page after signout (else, you will have to handle the redirect yourself). - -### Custom sign in page - -To create your custom sign-in page you can use `signIn` to directly start a provider-flow once the user selected it, e.g., by clicking on a button on your custom sign-in page. Here is a very simple sign-in page that either directly starts a github-oauth sign in flow or directly signs in the user via the credentials flow: -```vue - - - -``` - -Note: In the above example `username` and `password` are hard-coded. In your own custom page, these two fields should probably come from inputs on your page. - -If you want to create a custom sign-in page that dynamically offers sign-in options based on your configured providers, you can call `getProviders()` first and then iterate over the supported providers to generate your sign in page. - -## Middleware - -You can use this library to define application middleware. This library supports all of [Nuxt's supported middleware approaches](https://v3.nuxtjs.org/guide/directory-structure/middleware#middleware-directory). In all methods shown below we make use of the `callbackUrl` parameter to give the best user experience: If the user is not authenticated, they are forced to login, but will be redirected to the same page they wanted to visit after they successfully logged in. Without a `callbackUrl` parameter, the user would be directed to the index page `/`. - -### Global middleware - -Create a global authentication middleware that ensures that your user is authenticated no matter which page they visit. Create a file in the `middleware` directory that has a `.global.ts` suffix. - -It should look like this: - -```ts -// file: ~/middleware/auth.global.ts -export default defineNuxtRouteMiddleware(async (to) => { - await useSession({ callbackUrl: to.path }) -}) -``` - -That's it! This middleware will fetch a session and if no active session exists for the current user redirect to the login screen. If you want to write custom redirect logic, you could alter the above code to only apply to specific routes. - -Here is a global middleware that protects only the routes that start with `/secret/`: -```ts -// file: ~/middleware/auth.global.ts -export default defineNuxtRouteMiddleware(async (to) => { - if (to.path.startsWith('/secret/')) { - await useSession({ callbackUrl: to.path }) - } -}) -``` - -Example of a middleware that redirects to a custom login page: -```ts -// file: ~/middleware/auth.global.ts -export default defineNuxtRouteMiddleware(async (to) => { - // 1. Always allow access to the login page - if (to.path === '/login') { - return - } - - // 2. Otherwise: Check status and redirect to login page - const { status } = await useSession({ required: false }) - if (status.value !== 'authenticated') { - navigateTo('/login') - } -}) -``` - -### Named middleware - -Named middleware behave similar to [global middleware](#global-middleware) but are not automatically applied to any pages. - -To use them, first create a middleware: -```ts -// file: ~/middleware/auth.ts -export default defineNuxtRouteMiddleware(async (to) => { - await useSession({ callbackUrl: to.path }) -}) -``` - -Then inside your pages use the middleware with `definePageMeta`: -```vue - - - - -``` - -Note: `definePageMeta` can only be used inside the `pages/` directory. - -Nuxt now calls the `auth.ts` middleware on every visit to this page. - -### Inline middleware - -To define a named middleware, you need to use `definePageMeta` as described [in the nuxt docs](https://v3.nuxtjs.org/api/utils/define-page-meta/). Then you can just call `useSession` as in the other middleware. Here's an example that would protect just the page itself: -```vue - - - - -``` - -Note: `definePageMeta` can only be used inside the `pages/` directory diff --git a/docs/content/4.usage/2.REST-API.md b/docs/content/4.usage/2.REST-API.md deleted file mode 100644 index 9313b4ec..00000000 --- a/docs/content/4.usage/2.REST-API.md +++ /dev/null @@ -1,15 +0,0 @@ -# REST API - -All endpoints that NextAuth.js supports are also supported by `nuxt-auth`: - -| Endpoint | Request | -|--------------------------------|:-------------| -| `:basePath/signin` | `GET` | -| `:basePath/signin/:provider` | `POST` | -| `:basePath/callback/:provider` | `GET` `POST` | -| `:basePath/signout` | `GET` `POST` | -| `:basePath/session` | `GET` | -| `:basePath/csrf` | `GET` | -| `:basePath/providers` | `GET` | - -You can directly interact with them if you wish to, it's probably a better idea to use `useSession` where possible though. [See the full rest API documentation of NextAuth.js here](https://next-auth.js.org/getting-started/rest-api). diff --git a/docs/content/5.examples/1.nuxt-auth-example.md b/docs/content/5.examples/1.nuxt-auth-example.md deleted file mode 100644 index ec609954..00000000 --- a/docs/content/5.examples/1.nuxt-auth-example.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Minimal" ---- -# Minimal nuxt-auth example - -Minimal example of using `nuxt-auth`. See the repo [here](https://github.com/sidebase/nuxt-auth-example). - -::sandbox{repo="sidebase/nuxt-auth-example" branch="main" dir="/" file="app.vue"} -:: diff --git a/docs/content/5.recipes/1.index.md b/docs/content/5.recipes/1.index.md new file mode 100644 index 00000000..63a35ba4 --- /dev/null +++ b/docs/content/5.recipes/1.index.md @@ -0,0 +1,9 @@ +--- +description: "`nuxt-auth` recipes on how to use Strapi, Directus, expand user session data and more for Vue / Nuxt 3 apps." +--- + +# Overview + +The following pages contain some recipes for commonly asked patterns, questions and implementations. The recipes are mostly provided by the community and can serve as guidelines to implement something similar in your Nuxt 3 application. The recipes are not all tested through by the sidebase team. If you have any concerns, questions or improvement proposals or want to contribute a recipe yourself, we'd be very happy if you open an issue on our repository: https://github.com/sidebase/nuxt-auth/issues/new/choose + +Thanks to everybody who contributed so far ❤️ diff --git a/docs/content/5.recipes/2.strapi.md b/docs/content/5.recipes/2.strapi.md new file mode 100644 index 00000000..7eb11ad5 --- /dev/null +++ b/docs/content/5.recipes/2.strapi.md @@ -0,0 +1,79 @@ +# Strapi + +This section gives an example of how the `NuxtAuthHandler` can be configured to use Strapi JWTs for authentication via the `CredentialsProvider` provider. + +You have to configure the following places to make `nuxt-auth` work with Strapi: +- `STRAPI_BASE_URL` in `.env`: Add the Strapi environment variable to your .env file +- [`runtimeConfig.STRAPI_BASE_URL`-key in `nuxt.config.ts`](/nuxt-auth/configuration/nuxt-config): Add the Strapi base url env variable to the runtime config +- [`auth`-key in `nuxt.config.ts`](/nuxt-auth/configuration/nuxt-config): Configure the module itself, e.g., where the auth-endpoints are, what origin the app is deployed to, ... +- [NuxtAuthHandler](/nuxt-auth/configuration/nuxt-auth-handler): Configure the authentication behavior, e.g., what authentication providers to use + +For a production deployment, you will have to at least set the: +- `STRAPI_BASE_URL` Strapi base URL for all API endpoints by default http://localhost:1337 + +1. Create a `.env` file with the following lines: +```env +// Strapi v4 url, out of the box +ORIGIN=http://localhost:3000 +NUXT_SECRET=a-not-so-good-secret +STRAPI_BASE_URL=http://localhost:1337/api +``` + +2. Set the following options in your `nuxt.config.ts`: +```ts +export default defineNuxtConfig({ + runtimeConfig: { + // The private keys which are only available server-side + NUXT_SECRET: process.env.NUXT_SECRET, + // Default http://localhost:1337/api + STRAPI_BASE_URL: process.env.STRAPI_BASE_URL, + }, + auth: { + origin: process.env.ORIGIN, + }, +}); +``` + +3. Create the catch-all `NuxtAuthHandler` and add the this custom Strapi credentials provider: +```ts +// file: ~/server/api/auth/[...].ts +import CredentialsProvider from "next-auth/providers/credentials"; +import { NuxtAuthHandler } from "#auth"; +const config = useRuntimeConfig() + +export default NuxtAuthHandler({ + secret: config.NUXT_SECRET, + providers: [ + // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point + CredentialsProvider.default({ + name: "Credentials", + credentials: {}, // Object is required but can be left empty. + async authorize(credentials: any) { + const response = await $fetch( + `${config.STRAPI_BASE_URL}/auth/local/`, + { + method: "POST", + body: JSON.stringify({ + identifier: credentials.username, + password: credentials.password, + }), + } + ); + + if (response.user) { + const u = { + id: response.id, + name: response.user.username, + email: response.user.email, + }; + return u; + } else { + return null + } + }, + }), + ] +}); +``` + +Checkout this blog-post for further notes and explanation: https://darweb.nl/foundry/article/nuxt3-sidebase-strapi-user-auth diff --git a/docs/content/5.recipes/3.directus.md b/docs/content/5.recipes/3.directus.md new file mode 100644 index 00000000..9557ead1 --- /dev/null +++ b/docs/content/5.recipes/3.directus.md @@ -0,0 +1,201 @@ +# Directus + +This section gives an example of how the `NuxtAuthHandler` can be configured to use Directus JWTs for authentication via the `CredentialsProvider` provider and how to implement a token refresh for the Directus JWT. + +The below is a code-example that needs to be adapted to your specific configuration: +```ts +import CredentialsProvider from "next-auth/providers/credentials"; +import { NuxtAuthHandler } from "#auth"; + +/** + * Takes a token, and returns a new token with updated + * `accessToken` and `accessTokenExpires`. If an error occurs, + * returns the old token and an error property + */ +async function refreshAccessToken(refreshToken: { + accessToken: string; + accessTokenExpires: string; + refreshToken: string; +}) { + try { + console.warn("trying to post to refresh token"); + + const refreshedTokens = await $fetch<{ + data: { + access_token: string; + expires: number; + refresh_token: string; + }; + } | null>("https://domain.directus.app/auth/refresh", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: { + refresh_token: refreshToken.refreshToken, + mode: "json", + }, + }); + + if (!refreshedTokens || !refreshedTokens.data) { + console.warn("No refreshed tokens"); + throw refreshedTokens; + } + + console.warn("Refreshed tokens successfully"); + return { + ...refreshToken, + accessToken: refreshedTokens.data.access_token, + accessTokenExpires: Date.now() + refreshedTokens.data.expires, + refreshToken: refreshedTokens.data.refresh_token, + }; + } catch (error) { + console.warn("Error refreshing token", error); + return { + ...refreshToken, + error: "RefreshAccessTokenError", + }; + } +} + +export default NuxtAuthHandler({ + // secret needed to run nuxt-auth in production mode (used to encrypt data) + secret: process.env.NUXT_SECRET, + + providers: [ + // @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point + CredentialsProvider.default({ + // The name to display on the sign in form (e.g. 'Sign in with...') + name: "Credentials", + // The credentials is used to generate a suitable form on the sign in page. + // You can specify whatever fields you are expecting to be submitted. + // e.g. domain, username, password, 2FA token, etc. + // You can pass any HTML attribute to the tag through the object. + credentials: { + email: { label: "Email", type: "text" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials: any) { + // You need to provide your own logic here that takes the credentials + // submitted and returns either a object representing a user or value + // that is false/null if the credentials are invalid. + // NOTE: THE BELOW LOGIC IS NOT SAFE OR PROPER FOR AUTHENTICATION! + + try { + const payload = { + email: credentials.email, + password: credentials.password, + }; + + const userTokens = await $fetch<{ + data: { access_token: string; expires: number; refresh_token: string }; + } | null>("https://domain.directus.app/auth/login", { + method: "POST", + body: payload, + headers: { + "Content-Type": "application/json", + "Accept-Language": "en-US", + }, + }); + + const userDetails = await $fetch<{ + data: { + id: string; + email: string; + first_name: string; + last_name: string; + role: string; + phone?: string; + cvr?: string; + company_name?: string; + }; + } | null>("https://domain.directus.app/users/me", { + method: "GET", + headers: { + "Content-Type": "application/json", + "Accept-Language": "en-US", + Authorization: `Bearer ${userTokens?.data?.access_token}`, + }, + }); + + if (!userTokens || !userTokens.data || !userDetails || !userDetails.data) { + throw createError({ + statusCode: 500, + statusMessage: "Next auth failed", + }); + } + + const user = { + id: userDetails.data.id, + email: userDetails.data.email, + firstName: userDetails.data.first_name, + lastName: userDetails.data.last_name, + role: userDetails.data.role, + phone: userDetails.data.phone, + cvr: userDetails.data.cvr, + companyName: userDetails.data.company_name, + accessToken: userTokens.data.access_token, + accessTokenExpires: Date.now() + userTokens.data.expires, + refreshToken: userTokens.data.refresh_token, + }; + + const allowedRoles = [ + "53ed3a6a-b236-49aa-be72-f26e6e4857a0", + "d9b59a92-e85d-43e2-8062-7a1242a8fce6", + ]; + + // Only allow admins and sales + if (!allowedRoles.includes(user.role)) { + throw createError({ + statusCode: 403, + statusMessage: "Not allowed", + }); + } + + return user; + } catch (error) { + console.warn("Error logging in", error); + + return null; + } + }, + }), + ], + + session: { + strategy: "jwt", + }, + + callbacks: { + async jwt({ token, user, account }) { + if (account && user) { + console.warn("JWT callback", { token, user, account }); + return { + ...token, + ...user, + }; + } + + // Handle token refresh before it expires of 15 minutes + if (token.accessTokenExpires && Date.now() > token.accessTokenExpires) { + console.warn("Token is expired. Getting a new"); + return refreshAccessToken(token); + } + + return token; + }, + async session({ session, token }) { + session.user = { + ...session.user, + ...token, + }; + + return session; + }, + }, +}); +``` + +This was contributes by [@madsh93 from Github](https://github.com/madsh93) here: +- Github Comment: https://github.com/sidebase/nuxt-auth/issues/64#issuecomment-1330308402 +- Gist: https://gist.github.com/madsh93/b573b3d8f070e62eaebc5c53ae34e2cc diff --git a/docs/content/5.recipes/4.custom-session-data.md b/docs/content/5.recipes/4.custom-session-data.md new file mode 100644 index 00000000..ea347309 --- /dev/null +++ b/docs/content/5.recipes/4.custom-session-data.md @@ -0,0 +1,57 @@ +# Custom Session Data + +This guide explains how to add custom data to the user session. + +To expand / change / customize the session data you need to to follow the following steps: +1. Add callbacks to the `NuxtAuthProvider` to alter the session data +2. Update the TypeScript interfaces of `nuxt-auth` to get accurate typing in your application + +In the following example we'll add a new field to the session: `accessToken`. This `accessToken` will the `access_token` we receive from an imaginary OAuth provider. + +## Adding Callback to Alter Session Data + +First, you must add jwt and session callbacks to [the `NuxtAuthHandler`](/nuxt-auth/configuration/nuxt-auth-handler): +```ts +// file: ~/server/api/auth/[...].ts +export default NuxtAuthHandler({ + callbacks: { + // Callback when the JWT is created / updated, see https://next-auth.js.org/configuration/callbacks#jwt-callback + async jwt({ token, account, profile }) { + // Persist the OAuth access_token and or the user id to the token right after signin + if (account) { + token.accessToken = account.access_token + token.id = profile.id + } + return token + }, + + // Callback whenever session is checked, see https://next-auth.js.org/configuration/callbacks#session-callback + async session({ session, token, user }) { + // Send properties to the client, like an access_token and user id from a provider. + session.accessToken = token.accessToken + session.user.id = token.id + + return session + } + }, + ..., // other config + providers: [...], // Your provider config +}) +``` + +## Updating TypeScript Interfaces for Accurate Typing + +```ts +// file: ~/next-auth.d.ts +import { DefaultSession } from "next-auth" + +declare module "next-auth" { + interface Session { + // New access token field we added to the session + accessToken: string + user: DefaultSession["user"] + } +} +``` + +Thanks to [@JoaoPedroAS51 from Github](https://github.com/JoaoPedroAS51) for providing this information here: https://github.com/sidebase/nuxt-auth/issues/61#issuecomment-1330747199 diff --git a/docs/content/5.recipes/5.nuxt-auth-example.md b/docs/content/5.recipes/5.nuxt-auth-example.md new file mode 100644 index 00000000..66493e1d --- /dev/null +++ b/docs/content/5.recipes/5.nuxt-auth-example.md @@ -0,0 +1,6 @@ +# Nuxt Auth Example Code + +Minimal example of using `nuxt-auth` for http://nuxt-auth-example.sidebase.io/. See the repo [here](https://github.com/sidebase/nuxt-auth-example). + +::sandbox{repo="sidebase/nuxt-auth-example" branch="main" dir="/" file="app.vue"} +:: diff --git a/docs/content/5.recipes/_dir.yml b/docs/content/5.recipes/_dir.yml new file mode 100644 index 00000000..70c9f704 --- /dev/null +++ b/docs/content/5.recipes/_dir.yml @@ -0,0 +1,2 @@ +title: Recipes +icon: heroicons-outline:queue-list diff --git a/docs/content/6.ressources/1.external-blogs-guides-examples.md b/docs/content/6.ressources/1.external-blogs-guides-examples.md new file mode 100644 index 00000000..7e300b31 --- /dev/null +++ b/docs/content/6.ressources/1.external-blogs-guides-examples.md @@ -0,0 +1,9 @@ +# External Blogs, Guides and Examples + +This is a collection of ressources that we'll expand over time: +- [Example of Nuxt 3, Strapi and `nuxt-auth`](https://darweb.nl/foundry/article/nuxt3-sidebase-strapi-user-auth) +- [Example NuxtAuthHandler configuration for Directus](https://gist.github.com/madsh93/b573b3d8f070e62eaebc5c53ae34e2cc) +- [How to expand the user data object](https://github.com/sidebase/nuxt-auth/issues/61#issuecomment-1330946022) +- [Docus NuxtAuthHandler that shows how to manually fetch an external JWT token, how to expand the user data and how to track session expiry manually](https://gist.github.com/madsh93/b573b3d8f070e62eaebc5c53ae34e2cc) + +Note: If you're looking to make something work in `nuxt-auth` the nice thing is that you can use most `NextAuth.js` ressources to help you along the way, as the `NuxtAuthHandler` behaves identical to the [`NextAuth` handler](https://next-auth.js.org/configuration/initialization). diff --git a/docs/content/6.ressources/2.glossary.md b/docs/content/6.ressources/2.glossary.md new file mode 100644 index 00000000..4e82048f --- /dev/null +++ b/docs/content/6.ressources/2.glossary.md @@ -0,0 +1,9 @@ +# Glossary + +There are some terms we use in this documentation that may not be known to every reader. Here is an explanation for some of them: +- `application` / `application-side` / `universal-application`: This references all Nuxt code of your app that is [universally rendered](https://nuxt.com/docs/guide/concepts/rendering#universal-rendering). In short this means that that code is rendered on the server-side and on the client-side, so all JS in it is executed twice. This is an important distinction, as some things may behave different on the server-side than on the client-side. We use `application...` to denote something that will be universally rendered +- `server` / `server-side`: This references all Nuxt code of your app that will run **only** on your server. For example, all code inside the `~/server` directory should only ever run on the server +- `sessions`: A set of information that persists for a longer duration, e.g., the username and email that persists while your user is logged in +- `authentication`: Verifying that someone is who they claims to be, e.g., by asking them for a username and password or by asking Google to verify it (OAuth) and then trust their result + +[Let us know if you're missing something from this list](https://github.com/sidebase/nuxt-auth/issues/new/choose) diff --git a/docs/content/1.welcome/4.Security.md b/docs/content/6.ressources/3.security.md similarity index 53% rename from docs/content/1.welcome/4.Security.md rename to docs/content/6.ressources/3.security.md index 5293cf96..eeea23b1 100644 --- a/docs/content/1.welcome/4.Security.md +++ b/docs/content/6.ressources/3.security.md @@ -1,16 +1,8 @@ # Security -This section mostly contains a list of possible security problems. Note that the below flaws exist with many libraries and frameworks we use in our day-to-day when building and working with APIs. Even your vanilla Nuxt app already possesses some of these shortcoming. Missing in the below list are estimates of how likely it is that one of the list-items may occur and what impact it will have on your app. This is because that heavily depends on: - -::alert{type="warning"} -**your app**: Are you building a fun project? A proof of concept? The next fort-nox money management app? -:: - -::alert{type="warning"} -**your environment**: Building a freely available app for fun? Have authentication in front of your app and trust all users that successfully authenticated? Superb! Don't trust anyone? Then please be extra-careful when using this library and when building you backend in general -:: - ---- +This section mostly contains a list of possible security problems. Note that the below flaws exist with many libraries and frameworks we use in our day-to-day when building and working with APIs. Even your vanilla Nuxt app already posesses some of these shortcoming. Missing in the below list are estimates of how likely it is that one of the list-items may occur and what impact it will have on your app. This is because that heavily depends on: +- your app: Are you building a fun project? A proof of concept? The next fort-nox money management app? +- your environment: Building a freely available app for fun? Have authentication in front of your app and trust all users that successfully authenticated? Superb! Don't trust anyone? Then please be extra-careful when using this library and when building you backend in general Without further ado, here's some attack cases you can consider and take action against. Neither the attack vectors, the problems or the mitigations are exhaustive: 1. sending arbitrary data: Denial-of-Service by server-ressource exhaustion (bandwidth, cpu, memory), arbitrary code execution (if you parse the data), ... @@ -18,8 +10,8 @@ Without further ado, here's some attack cases you can consider and take action a 3. guessing correct session ids: session data can leak 4. stealing session id(s) of client(s): session data can leak -Read up how to mitigate these and more issues if you see fit. Checkout the [nuxt-security](https://github.com/Baroshem/nuxt-security) module that may help with some of these. +Read up how to mitigate these and more issues if you see fit. Checkout the [`nuxt-security`](https://github.com/Baroshem/nuxt-security) module that may help with some of these. ## Disclosure -A last reminder: This library was not written by crypto- or security-experts. Please proceed at your own risk, inspect the code if you want to and open issues / pull requests where you see room for improvement. If you want to file a security-concern privately, please send an email to `support@sidestream.tech` with the subject saying "SECURITY nuxt-auth" and we'll look into your request ASAP. +A last reminder: This library was not written by crypto- or security-experts. Please proceed at your own risk, inspect the code if you want to and open issues / pull requests where you see room for improvement. If you want to file a security-concern privately, please send an email to `sidebase@sidestream.tech` with the subject saying "SECURITY nuxt-auth" and we'll look into your request ASAP. diff --git a/docs/content/6.ressources/4.prior-work.md b/docs/content/6.ressources/4.prior-work.md new file mode 100644 index 00000000..87998dc7 --- /dev/null +++ b/docs/content/6.ressources/4.prior-work.md @@ -0,0 +1,19 @@ +# Prior Work and Module Concept + +The idea of this library is to re-use all the open-source implementation that already exist in the JS ecosystem instead of rolling our own. The idea was born when researching through the ecosystem of framework-specific authentication libraries to figure out what the best implementation approach for a state-of-the-art Nuxt 3 authentication library would be. + +During research it became clear that implementing everything from scratch will be: +- a lot of work that has already been open-sourced by others, +- error prone as authentication has a lot of intricacies that need to be resolved in order to get it right, +- hard to maintain as authentication providers come and go, +- hard to build initial trust for as authentication is important and cannot go wrong, + +In order to avoid these problems without taking forever (leaving Nuxt without an authentication library in the meantime), we decided to investigate if we can wrap [`NextAuth.js`](https://github.com/nextauthjs/next-auth), the most popular authentication library in the Next.js ecosystem by far and a trusted, well maintained one at that! + +In our investigation we found prior attempts to make NextAuth.js framework agnostic. These have more or less come to fruition, so far mostly resulting in some PoCs and example apps. Looking at these was quite helpful to get started. In particular, big pushes in the right direction came from: +- [NextAuth.js app examples](https://github.com/nextauthjs/next-auth/tree/main/apps) +- [Various comments, proposals, ... of this thread](https://github.com/nextauthjs/next-auth/discussions/3942), special thanks to @brillout for starting the discussion, @balazsorban44 for NextAuth.js and encouraging the discussion, @wobsoriano for adding PoCs for multiple languages + +The main part of the work was to piece everything together, resolve some outstanding issues with existing PoCs, add new things where nothing existed yet, e.g., for the `useSession` composable by going through the NextAuth.js client code and translating it to a Nuxt 3 approach. + +The module had another big iteration in collaboration with @JoaoPedroAS51 to make `useSession` a sync operation and trigger the session lifecycle from a plugin rather than the `useSession` composable itself. diff --git a/docs/content/5.examples/_dir.yml b/docs/content/6.ressources/_dir.yml similarity index 61% rename from docs/content/5.examples/_dir.yml rename to docs/content/6.ressources/_dir.yml index 2d7645de..9bf0dbd7 100644 --- a/docs/content/5.examples/_dir.yml +++ b/docs/content/6.ressources/_dir.yml @@ -1,2 +1,2 @@ -title: Examples +title: Ressources icon: heroicons-outline:play diff --git a/playground/pages/index.vue b/playground/pages/index.vue index 1fecc954..bfab446a 100644 --- a/playground/pages/index.vue +++ b/playground/pages/index.vue @@ -27,7 +27,8 @@