From 712d7c9eab896a929df69b240abf1c4511324f88 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:03:41 -0800 Subject: [PATCH] 15.1 docs: `forbidden`, `unauthorized`, and `authInterrupts` (#73039) **Do not merge until https://github.com/vercel/next.js/pull/72785 lands in canary.** Closes: - https://linear.app/vercel/issue/DOC-3820/[unstable]-forbidden-and-forbiddenjs - https://linear.app/vercel/issue/DOC-3849/[unstable]-unauthorized-and-unauthorizedjs - https://linear.app/vercel/issue/DOC-3860/[experimental]-authinterrupts-config-option --- .../03-file-conventions/forbidden.mdx | 50 ++++ .../03-file-conventions/not-found.mdx | 10 +- .../03-file-conventions/unauthorized.mdx | 114 +++++++++ .../04-functions/forbidden.mdx | 172 +++++++++++++ .../04-functions/unauthorized.mdx | 230 ++++++++++++++++++ .../01-next-config-js/authInterrupts.mdx | 33 +++ 6 files changed, 606 insertions(+), 3 deletions(-) create mode 100644 docs/01-app/03-api-reference/03-file-conventions/forbidden.mdx create mode 100644 docs/01-app/03-api-reference/03-file-conventions/unauthorized.mdx create mode 100644 docs/01-app/03-api-reference/04-functions/forbidden.mdx create mode 100644 docs/01-app/03-api-reference/04-functions/unauthorized.mdx create mode 100644 docs/01-app/03-api-reference/05-config/01-next-config-js/authInterrupts.mdx diff --git a/docs/01-app/03-api-reference/03-file-conventions/forbidden.mdx b/docs/01-app/03-api-reference/03-file-conventions/forbidden.mdx new file mode 100644 index 0000000000000..de4be46954652 --- /dev/null +++ b/docs/01-app/03-api-reference/03-file-conventions/forbidden.mdx @@ -0,0 +1,50 @@ +--- +title: forbidden.js +description: API reference for the forbidden.js special file. +related: + links: + - app/api-reference/functions/forbidden +version: canary +--- + +The **forbidden** file is used to render UI when the [`forbidden`](/docs/app/api-reference/functions/forbidden) function is invoked during authentication. Along with allowing you to customize the UI, Next.js will return a `403` status code. + +```tsx filename="app/forbidden.tsx" switcher +import Link from 'next/link' + +export default function Forbidden() { + return ( +
+

Forbidden

+

You are not authorized to access this resource.

+ Return Home +
+ ) +} +``` + +```jsx filename="app/forbidden.jsx" switcher +import Link from 'next/link' + +export default function Forbidden() { + return ( +
+

Forbidden

+

You are not authorized to access this resource.

+ Return Home +
+ ) +} +``` + +## Reference + +### Props + +`forbidden.js` components do not accept any props. + +## Version History + +| Version | Changes | +| --------- | -------------------------- | +| `v15.1.0` | `forbidden.js` introduced. | diff --git a/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx b/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx index dd23e63c4f06d..a5b2aae9db1a6 100644 --- a/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx +++ b/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx @@ -33,13 +33,17 @@ export default function NotFound() { } ``` -> **Good to know**: In addition to catching expected `notFound()` errors, the root `app/not-found.js` file also handles any unmatched URLs for your whole application. This means users that visit a URL that is not handled by your app will be shown the UI exported by the `app/not-found.js` file. +## Reference -## Props +### Props `not-found.js` components do not accept any props. -## Data Fetching +> **Good to know**: In addition to catching expected `notFound()` errors, the root `app/not-found.js` file also handles any unmatched URLs for your whole application. This means users that visit a URL that is not handled by your app will be shown the UI exported by the `app/not-found.js` file. + +## Examples + +### Data Fetching By default, `not-found` is a Server Component. You can mark it as `async` to fetch and display data: diff --git a/docs/01-app/03-api-reference/03-file-conventions/unauthorized.mdx b/docs/01-app/03-api-reference/03-file-conventions/unauthorized.mdx new file mode 100644 index 0000000000000..dda9f82ea463a --- /dev/null +++ b/docs/01-app/03-api-reference/03-file-conventions/unauthorized.mdx @@ -0,0 +1,114 @@ +--- +title: unauthorized.js +description: API reference for the unauthorized.js special file. +related: + links: + - app/api-reference/functions/unauthorized +version: canary +--- + +The **unauthorized** file is used to render UI when the [`unauthorized`](/docs/app/api-reference/functions/unauthorized) function is invoked during authentication. Along with allowing you to customize the UI, Next.js will return a `401` status code. + +```tsx filename="app/unauthorized.tsx" switcher +import Login from '@/app/components/Login' + +export default function Unauthorized() { + return ( +
+

401 - Unauthorized

+

Please log in to access this page.

+ +
+ ) +} +``` + +```jsx filename="app/unauthorized.js" switcher +import Login from '@/app/components/Login' + +export default function Unauthorized() { + return ( +
+

401 - Unauthorized

+

Please log in to access this page.

+ +
+ ) +} +``` + +## Reference + +### Props + +`unauthorized.js` components do not accept any props. + +## Examples + +### Displaying login UI to unauthenticated users + +You can use [`unauthorized`](/docs/app/api-reference/functions/unauthorized) function to render the `unauthorized.js` file with a login UI. + +```tsx filename="app/dashboard/page.tsx" switcher +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/server' + +export default async function DashboardPage() { + const session = await verifySession() + + if (!session) { + unauthorized() + } + + return
Dashboard
+} +``` + +```jsx filename="app/dashboard/page.js" switcher +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/server' + +export default async function DashboardPage() { + const session = await verifySession() + + if (!session) { + unauthorized() + } + + return
Dashboard
+} +``` + +```tsx filename="app/unauthorized.tsx" switcher +import Login from '@/app/components/Login' + +export default function UnauthorizedPage() { + return ( +
+

401 - Unauthorized

+

Please log in to access this page.

+ +
+ ) +} +``` + +```jsx filename="app/unauthorized.js" switcher +import Login from '@/app/components/Login' + +export default function UnauthorizedPage() { + return ( +
+

401 - Unauthorized

+

Please log in to access this page.

+ +
+ ) +} +``` + +## Version History + +| Version | Changes | +| --------- | ----------------------------- | +| `v15.1.0` | `unauthorized.js` introduced. | diff --git a/docs/01-app/03-api-reference/04-functions/forbidden.mdx b/docs/01-app/03-api-reference/04-functions/forbidden.mdx new file mode 100644 index 0000000000000..1bb8fc118fc43 --- /dev/null +++ b/docs/01-app/03-api-reference/04-functions/forbidden.mdx @@ -0,0 +1,172 @@ +--- +title: forbidden +description: API Reference for the forbidden function. +version: canary +related: + links: + - app/api-reference/file-conventions/forbidden +--- + +The `forbidden` function throws an error that renders a Next.js 403 error page. It is useful for handling authorization errors in your application. You can customize the UI using the [`forbidden.js` file](/docs/app/api-reference/file-conventions/forbidden). + +To start using `forbidden`, enable the experimental [`authInterrupts`](/docs/app/api-reference/config/next-config-js/authInterrupts) configuration option in your `next.config.js` file: + +```ts filename="next.config.ts" switcher +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + experimental: { + authInterrupts: true, + }, +} + +export default nextConfig +``` + +```js filename="next.config.js" switcher +module.exports = { + experimental: { + authInterrupts: true, + }, +} +``` + +`forbidden` can be used in [Server Components](/docs/app/building-your-application/rendering/server-components), [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations), and [Route Handlers](/docs/app/building-your-application/routing/route-handlers). + +```tsx filename="app/auth/route.tsx" switcher +import { verifySession } from '@/app/lib/dal' +import { forbidden } from 'next/navigation' + +export default async function AdminPage() { + const session = await verifySession() + + // Check if the user has the 'admin' role + if (session.role !== 'admin') { + forbidden() + } + + // Render the admin page for authorized users + return <> +} +``` + +```jsx filename="app/auth/route.js" switcher +import { verifySession } from '@/app/lib/dal' +import { forbidden } from 'next/navigation' + +export default async function AdminPage() { + const session = await verifySession() + + // Check if the user has the 'admin' role + if (session.role !== 'admin') { + forbidden() + } + + // Render the admin page for authorized users + return <> +} +``` + +## Good to know + +- The `forbidden` function cannot be called in the [root layout](/docs/app/building-your-application/routing/layouts-and-templates#root-layout-required). + +## Examples + +### Role-based route protection + +You can use the `forbidden` function to restrict access to certain routes based on user roles. This ensures that users who are authenticated but lack the required permissions cannot access the route. + +```tsx filename="app/admin/page.tsx" switcher +import { verifySession } from '@/app/lib/dal' +import { forbidden } from 'next/navigation' + +export default async function AdminPage() { + const session = await verifySession() + + // Check if the user has the 'admin' role + if (session.role !== 'admin') { + forbidden() + } + + // Render the admin page for authorized users + return ( +
+

Admin Dashboard

+

Welcome, {session.user.name}!

+
+ ) +} +``` + +```jsx filename="app/admin/page.js" switcher +import { verifySession } from '@/app/lib/dal' +import { forbidden } from 'next/navigation' + +export default async function AdminPage() { + const session = await verifySession() + + // Check if the user has the 'admin' role + if (session.role !== 'admin') { + forbidden() + } + + // Render the admin page for authorized users + return ( +
+

Admin Dashboard

+

Welcome, {session.user.name}!

+
+ ) +} +``` + +### Mutations with Server Actions + +When implementing mutations in Server Actions, you can use `forbidden` to only allow users with a specific role to update sensitive data. + +```tsx filename="app/auth/route.tsx" switcher +'use server' + +import { verifySession } from '@/app/lib/dal' +import { forbidden } from 'next/navigation' +import db from '@/app/lib/db' + +export async function updateRole(formData: FormData) { + const session = await verifySession() + + // Ensure only admins can update roles + if (session.role !== 'admin') { + forbidden() + } + + // Perform the role update for authorized users + // ... +} +``` + +```jsx filename="app/auth/route.js" switcher +'use server' + +import { verifySession } from '@/app/lib/dal' +import { forbidden } from 'next/navigation' +import db from '@/app/lib/db' + +export async function updateRole(formData) { + const session = await verifySession() + + // Ensure only admins can update roles + if (session.role !== 'admin') { + forbidden() + } + + // Perform the role update for authorized users + // ... +} +``` + +## Version History + +| Version | Changes | +| --------- | ----------------------- | +| `v15.1.0` | `forbidden` introduced. | diff --git a/docs/01-app/03-api-reference/04-functions/unauthorized.mdx b/docs/01-app/03-api-reference/04-functions/unauthorized.mdx new file mode 100644 index 0000000000000..5422f5458abc7 --- /dev/null +++ b/docs/01-app/03-api-reference/04-functions/unauthorized.mdx @@ -0,0 +1,230 @@ +--- +title: unauthorized +description: API Reference for the unauthorized function. +version: canary +related: + links: + - app/api-reference/file-conventions/unauthorized +--- + +The `unauthorized` function throws an error that renders a Next.js 401 error page. It is useful for handling authorization errors in your application. You can customize the UI using the [`unauthorized.js` file](/docs/app/api-reference/file-conventions/unauthorized). + +To start using `unauthorized`, enable the experimental [`authInterrupts`](/docs/app/api-reference/config/next-config-js/authInterrupts) configuration option in your `next.config.js` file: + +```ts filename="next.config.ts" switcher +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + experimental: { + authInterrupts: true, + }, +} + +export default nextConfig +``` + +```js filename="next.config.js" switcher +module.exports = { + experimental: { + authInterrupts: true, + }, +} +``` + +`unauthorized` can be invoked in [Server Components](/docs/app/building-your-application/rendering/server-components), [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations), and [Route Handlers](/docs/app/building-your-application/routing/route-handlers). + +```tsx filename="app/dashboard/page.tsx" switcher +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' + +export default async function DashboardPage() { + const session = await verifySession() + + if (!session) { + unauthorized() + } + + // Render the dashboard for authenticated users + return ( +
+

Welcome to the Dashboard

+

Hi, {session.user.name}.

+
+ ) +} +``` + +```jsx filename="app/dashboard/page.js" switcher +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' + +export default async function DashboardPage() { + const session = await verifySession() + + if (!session) { + unauthorized() + } + + // Render the dashboard for authenticated users + return ( +
+

Welcome to the Dashboard

+

Hi, {session.user.name}.

+
+ ) +} +``` + +## Examples + +### Displaying login UI to unauthenticated users + +You can use `unauthorized` function to display the `unauthorized.js` file with a login UI. + +```tsx filename="app/dashboard/page.tsx" switcher +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' + +export default async function DashboardPage() { + const session = await verifySession() + + if (!session) { + unauthorized() + } + + return
Dashboard
+} +``` + +```jsx filename="app/dashboard/page.js" switcher +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' + +export default async function DashboardPage() { + const session = await verifySession() + + if (!session) { + unauthorized() + } + + return
Dashboard
+} +``` + +```tsx filename="app/unauthorized.tsx" switcher +import Login from '@/app/components/Login' + +export default function UnauthorizedPage() { + return ( +
+

401 - Unauthorized

+

Please log in to access this page.

+ +
+ ) +} +``` + +```jsx filename="app/unauthorized.js" switcher +import Login from '@/app/components/Login' + +export default function UnauthorizedPage() { + return ( +
+

401 - Unauthorized

+

Please log in to access this page.

+ +
+ ) +} +``` + +### Mutations with Server Actions + +You can invoke `unauthorized` in Server Actions to ensure only authenticated users can perform specific mutations. + +```ts filename="app/actions/update-profile.ts" switcher +'use server' + +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' +import db from '@/app/lib/db' + +export async function updateProfile(data: FormData) { + const session = await verifySession() + + // If the user is not authenticated, return a 401 + if (!session) { + unauthorized() + } + + // Proceed with mutation + // ... +} +``` + +```js filename="app/actions/update-profile.js" switcher +'use server' + +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' +import db from '@/app/lib/db' + +export async function updateProfile(data) { + const session = await verifySession() + + // If the user is not authenticated, return a 401 + if (!session) { + unauthorized() + } + + // Proceed with mutation + // ... +} +``` + +### Fetching data with Route Handlers + +You can use `unauthorized` in Route Handlers to ensure only authenticated users can access the endpoint. + +```tsx filename="app/api/profile/route.ts" switcher +import { NextRequest, NextResponse } from 'next/server' +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' + +export async function GET(req: NextRequest): Promise { + // Verify the user's session + const session = await verifySession() + + // If no session exists, return a 401 and render unauthorized.tsx + if (!session) { + unauthorized() + } + + // Fetch data + // ... +} +``` + +```jsx filename="app/api/profile/route.js" switcher +import { verifySession } from '@/app/lib/dal' +import { unauthorized } from 'next/navigation' + +export async function GET() { + const session = await verifySession() + + // If the user is not authenticated, return a 401 and render unauthorized.tsx + if (!session) { + unauthorized() + } + + // Fetch data + // ... +} +``` + +## Version History + +| Version | Changes | +| --------- | -------------------------- | +| `v15.1.0` | `unauthorized` introduced. | diff --git a/docs/01-app/03-api-reference/05-config/01-next-config-js/authInterrupts.mdx b/docs/01-app/03-api-reference/05-config/01-next-config-js/authInterrupts.mdx new file mode 100644 index 0000000000000..6509d0ce55017 --- /dev/null +++ b/docs/01-app/03-api-reference/05-config/01-next-config-js/authInterrupts.mdx @@ -0,0 +1,33 @@ +--- +title: authInterrupts +description: Learn how to enable the experimental `authInterrupts` configuration option to use `forbidden` and `unauthorized`. +version: canary +related: + links: + - app/api-reference/functions/forbidden + - app/api-reference/functions/unauthorized + - app/api-reference/file-conventions/forbidden + - app/api-reference/file-conventions/unauthorized +--- + +The `authInterrupts` configuration option allows you to use [`forbidden`](/docs/app/api-reference/functions/forbidden) and [`unauthorized`](/docs/app/api-reference/functions/unauthorized) APIs in your application. While these functions are experimental, you must enable the `authInterrupts` option in your `next.config.js` file to use them: + +```ts filename="next.config.ts" switcher +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = { + experimental: { + authInterrupts: true, + }, +} + +export default nextConfig +``` + +```js filename="next.config.js" switcher +module.exports = { + experimental: { + authInterrupts: true, + }, +} +```