From cc953b3d2d245dee5700ead1fdcc59c1cb10653a Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Mon, 12 Oct 2026 20:15:01 -0400 Subject: [PATCH] docs(react): point the Next.js README section at @seamless-auth/nextjs --- .changeset/react-readme-nextjs-package.md | 5 ++ AGENTS.md | 5 +- packages/react/README.md | 56 +++++++++++++++++++---- 3 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 .changeset/react-readme-nextjs-package.md diff --git a/.changeset/react-readme-nextjs-package.md b/.changeset/react-readme-nextjs-package.md new file mode 100644 index 0000000..8f2e9be --- /dev/null +++ b/.changeset/react-readme-nextjs-package.md @@ -0,0 +1,5 @@ +--- +'@seamless-auth/react': patch +--- + +Point the README's Next.js section at `@seamless-auth/nextjs`, which serves the `/auth` routes from a route handler and resolves the session with `getSeamlessSession`. It no longer says a Next.js server package is still planned. diff --git a/AGENTS.md b/AGENTS.md index a5dda90..5d10b2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -320,8 +320,9 @@ Next.js and server rendering (#78): `refreshSession({ background: true })`. Never document forwarding browser cookies to the adapter's `/auth/users/me` from a server: an expired access cookie makes the adapter rotate the refresh token in a response the browser - never sees. Server reads go through `getSeamlessUser` in `@seamless-auth/core` -- the server half (a route-handler adapter and session helpers) belongs in a + never sees. Next.js server reads go through `getSeamlessSession` in + `@seamless-auth/nextjs` (other servers use `getSeamlessUser` in `@seamless-auth/core`) +- the server half (the route-handler adapter and session helpers) is the `@seamless-auth/nextjs` package in `seamless-auth-server`, not here Tests live in each package's `tests/` directory and run as two Jest projects diff --git a/packages/react/README.md b/packages/react/README.md index 73c59dc..27c84be 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -119,15 +119,48 @@ The package works in the Next.js App Router. Every module ships with a `'use cli `AuthProvider` can be rendered straight from a server layout, and nothing touches `window` or storage while rendering on the server. +The server half is [`@seamless-auth/nextjs`](https://www.npmjs.com/package/@seamless-auth/nextjs). +It serves the `/auth` routes from a route handler on the application's own origin and reads the +session in server components, so a Next.js app needs no separate backend for auth: + +```bash +npm install @seamless-auth/nextjs @seamless-auth/react +``` + +```ts +// app/auth/[...seamless]/route.ts +import { createSeamlessAuthHandler } from '@seamless-auth/nextjs'; + +export const { GET, POST, PUT, PATCH, DELETE } = createSeamlessAuthHandler({ + authServerUrl: process.env.AUTH_SERVER_URL!, + audience: process.env.AUTH_SERVER_URL!, + cookieSecret: process.env.COOKIE_SIGNING_KEY!, + serviceSecret: process.env.API_SERVICE_TOKEN!, + jwksKid: process.env.JWKS_KID, +}); +``` + ```tsx // app/layout.tsx +import { cookies, headers } from 'next/headers'; +import { getSeamlessSession } from '@seamless-auth/nextjs'; import { AuthProvider } from '@seamless-auth/react'; -export default function RootLayout({ children }: { children: React.ReactNode }) { +export const dynamic = 'force-dynamic'; + +export default async function RootLayout({ children }: { children: React.ReactNode }) { + const session = await getSeamlessSession(await cookies(), { + authServerUrl: process.env.AUTH_SERVER_URL!, + cookieSecret: process.env.COOKIE_SIGNING_KEY!, + serviceSecret: process.env.API_SERVICE_TOKEN!, + jwksKid: process.env.JWKS_KID, + userAgent: (await headers()).get('user-agent') ?? undefined, + }); + return ( - + {children} @@ -136,6 +169,11 @@ export default function RootLayout({ children }: { children: React.ReactNode }) } ``` +`apiHost=""` sends every auth request to `/auth` on the page's own origin, which is the route +handler above. The full guide, including route protection in `proxy.ts`, is at +[docs.seamlessauth.com/build/nextjs](https://docs.seamlessauth.com/build/nextjs/), and +`seamless init my-app --nextjs` scaffolds a working starter. + Things to know: - **Use custom UI, not `AuthRoutes`.** The bundled screens route with react-router, which a Next.js @@ -167,13 +205,13 @@ Only `user` is required. The provider still revalidates in the background, witho server could not see (for example an expired access cookie with a live refresh cookie) still signs in. `initialSession` is read once, when the provider creates its session. -Resolve the user on the server with `getSeamlessUser()` from `@seamless-auth/core`, which verifies -the access cookie locally and asks the auth API directly. It needs your adapter's cookie secret, and -the session cookies have to reach the Next.js server, so serve the adapter from the same site. Do not forward the browser's cookies to -your adapter's `/auth/users/me` from the server: when the access cookie has expired the adapter -rotates the refresh token in a response the browser never receives, and the browser's next refresh -is then treated as a replay and revokes the session. A dedicated server package for Next.js is tracked in -[seamless-auth-server#170](https://github.com/fells-code/seamless-auth-server/issues/170). +`getSeamlessSession` from `@seamless-auth/nextjs` resolves the session on the server: it verifies +the adapter's access cookie locally and asks the auth API directly. It never refreshes, so it returns +`null` for an expired access cookie and the provider renews the session in the browser. The session cookies have to reach the Next.js server, which they do when the route handler +serves `/auth` from the same origin. Do not forward the browser's cookies to `/auth/users/me` from +the server: when the access cookie has expired the adapter rotates the refresh token in a response +the browser never receives, and the browser's next refresh is then treated as a replay and revokes +the session. `refreshSession({ background: true })` is public, for revalidating a session your UI already shows (on window focus, for example) without flashing a loading state.