{"id":37545947,"url":"https://github.com/wristband-dev/nextjs-auth","last_synced_at":"2026-01-16T08:50:51.937Z","repository":{"id":249548465,"uuid":"809111048","full_name":"wristband-dev/nextjs-auth","owner":"wristband-dev","description":"SDK for integrating your NextJS application with Wristband. Handles user authentication, session management, and token management.","archived":false,"fork":false,"pushed_at":"2025-12-12T20:44:46.000Z","size":601,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-14T11:02:02.133Z","etag":null,"topics":["authentication","b2b","cookie","jwt","multi-tenant","multitenancy","nextjs","react","saas","session","sso","token"],"latest_commit_sha":null,"homepage":"https://wristband.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wristband-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-06-01T18:03:46.000Z","updated_at":"2025-12-12T20:41:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"e640074d-65c3-4f51-b685-8bd109870d3e","html_url":"https://github.com/wristband-dev/nextjs-auth","commit_stats":null,"previous_names":["wristband-dev/nextjs-auth"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/wristband-dev/nextjs-auth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wristband-dev%2Fnextjs-auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wristband-dev%2Fnextjs-auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wristband-dev%2Fnextjs-auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wristband-dev%2Fnextjs-auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wristband-dev","download_url":"https://codeload.github.com/wristband-dev/nextjs-auth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wristband-dev%2Fnextjs-auth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","b2b","cookie","jwt","multi-tenant","multitenancy","nextjs","react","saas","session","sso","token"],"created_at":"2026-01-16T08:50:51.808Z","updated_at":"2026-01-16T08:50:51.894Z","avatar_url":"https://github.com/wristband-dev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://wristband.dev\"\u003e\n    \u003cpicture\u003e\n      \u003cimg src=\"https://assets.wristband.dev/images/email_branding_logo_v1.png\" alt=\"Github\" width=\"297\" height=\"64\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n  \u003cp align=\"center\"\u003e\n    Enterprise-ready auth that is secure by default, truly multi-tenant, and ungated for small businesses.\n  \u003c/p\u003e\n  \u003cp align=\"center\"\u003e\n    \u003cb\u003e\n      \u003ca href=\"https://wristband.dev\"\u003eWebsite\u003c/a\u003e •\n      \u003ca href=\"https://docs.wristband.dev/\"\u003eDocumentation\u003c/a\u003e\n    \u003c/b\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n\u003cbr/\u003e\n\n---\n\n\u003cbr/\u003e\n\n# Wristband Multi-Tenant Authentication SDK for Next.js\n\n[![npm package](https://img.shields.io/badge/npm%20i-nextjs--auth-brightgreen)](https://www.npmjs.com/package/@wristband/nextjs-auth)\n[![version number](https://img.shields.io/github/v/release/wristband-dev/nextjs-auth?color=green\u0026label=version)](https://github.com/wristband-dev/nextjs-auth/releases)\n[![License](https://img.shields.io/github/license/wristband-dev/nextjs-auth)](https://github.com/wristband-dev/nextjs-auth/blob/main/LICENSE.md)\n[![Actions Status](https://github.com/wristband-dev/nextjs-auth/workflows/Test/badge.svg)](https://github.com/wristband-dev/nextjs-auth/actions)\n\nEnterprise-ready authentication for multi-tenant [Next.js applications](https://nextjs.org/) using OAuth 2.1 and OpenID Connect standards. It works for both the Next.js App Router and Pages Router.\n\n\u003cbr\u003e\n\n## Overview\n\nThis SDK provides complete authentication integration with Wristband, including:\n\n- **Login flow** - Redirect to Wristband and handle OAuth callbacks\n- **Session management** - Encrypted cookie-based sessions with optional CSRF token protection\n- **Token handling** - Automatic access token refresh and validation\n- **Logout flow** - Token revocation and session cleanup\n- **Multi-tenancy** - Support for tenant subdomains and custom domains\n\nLearn more about Wristband's authentication patterns:\n\n- [Backend Server Integration Pattern](https://docs.wristband.dev/docs/backend-server-integration)\n- [Login Workflow In Depth](https://docs.wristband.dev/docs/login-workflow)\n\n\u003e **💡 Learn by Example**\n\u003e\n\u003e Want to see the SDK in action? Check out our [Next.js demo applications](#wristband-multi-tenant-nextjs-demo-apps). The demos showcase real-world authentication patterns and best practices.\n\n\u003cbr\u003e\n\n---\n\n\u003cbr\u003e\n\n## Table of Contents\n\n- [Migrating From Older SDK Versions](#migrating-from-older-sdk-versions)\n- [Prerequisites](#prerequisites)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [1) Initialize the Auth SDK](#1-initialize-the-auth-sdk)\n  - [2) Set Up Session Management](#2-set-up-session-management)\n  - [3) Add Auth Endpoints](#3-add-auth-endpoints)\n    - [Login Endpoint](#login-endpoint)\n    - [Callback Endpoint](#callback-endpoint)\n    - [Logout Endpoint](#logout-endpoint)\n    - [Session Endpoint](#session-endpoint)\n    - [Token Endpoint (Optional)](#token-endpoint-optional)\n  - [4) Protect Your Pages, Actions, and APIs](#4-protect-your-pages-actions-and-apis)\n    - [Set Up Authentication Middleware](#set-up-authentication-middleware)\n    - [Protect Server Actions (App Router Only)](#protect-server-actions-app-router-only)\n    - [Manual Session Access (Optional)](#manual-session-access-optional)\n  - [5) Pass Your Access Token to Downstream APIs](#5-pass-your-access-token-to-downstream-apis)\n- [Wristband Auth Configuration Options](#wristband-auth-configuration-options)\n  - [Auth Config Options](#auth-config-options)\n  - [createWristbandAuth()](#createwristbandauth)\n- [Auth API](#auth-api)\n  - [login()](#login)\n  - [callback()](#callback)\n  - [createCallbackResponse() (App Router)](#createcallbackresponse-app-router)\n  - [logout()](#logout)\n  - [refreshTokenIfExpired()](#refreshtokenifexpired)\n- [Session Management](#session-management)\n  - [Session Configuration](#session-configuration)\n  - [The Session Object](#the-session-object)\n  - [Session Helper Functions](#session-helper-functions)\n    - [getSessionFromRequest()](#getsessionfromrequest)\n    - [getPagesRouterSession()](#getpagesroutersession)\n    - [getReadOnlySessionFromCookies()](#getreadonlysessionfromcookies)\n    - [getMutableSessionFromCookies()](#getmutablesessionfromcookies)\n    - [saveSessionWithCookies()](#savesessionwithcookies)\n    - [destroySessionWithCookies()](#destroysessionwithcookies)\n  - [Session Access Patterns](#session-access-patterns)\n  - [Session API](#session-api)\n    - [session.fromCallback()](#sessionfromcallbackcallbackdata-customfields)\n    - [session.save()](#sessionsave)\n    - [session.saveToResponse()](#sessionsavetoresponseresponse)\n    - [session.destroy()](#sessiondestroy)\n    - [session.destroyToResponse()](#sessiondestroytoresponseresponse)\n    - [session.getSessionResponse()](#sessiongetsessionresponse)\n    - [session.getTokenResponse()](#sessiongettokenresponse)\n  - [CSRF Protection](#csrf-protection)\n- [Authentication Middleware](#authentication-middleware)\n  - [createMiddlewareAuth()](#createmiddlewareauth)\n    - [SESSION Strategy](#session-strategy)\n    - [JWT Strategy](#jwt-strategy)\n    - [Middleware Chaining](#middleware-chaining) \n  - [createServerActionAuth()](#createserveractionauth)\n- [Related Wristband SDKs](#related-wristband-sdks)\n- [Wristband Multi-Tenant Next.js Demo Apps](#wristband-multi-tenant-nextjs-demo-apps)\n- [Questions](#questions)\n\n\u003cbr\u003e\n\n## Migrating From Older SDK Versions\n\nOn an older version of our SDK? Check out our migration guide:\n\n- [Instructions for migrating to Version 4.x](migration/v4/README.md)\n- [Instructions for migrating to Version 3.x](migration/v3/README.md)\n- [Instructions for migrating to Version 2.x](migration/v2/README.md)\n\n\u003cbr\u003e\n\n## Prerequisites\n\n\u003e **⚡ Try Our Next.js Quickstart!**\n\u003e\n\u003e For the fastest way to get started with Next.js authentication, follow our [Quick Start Guide](https://docs.wristband.dev/docs/auth-quick-start). It walks you through setting up a working Next.js app with Wristband authentication in minutes. Refer back to this README for comprehensive documentation and advanced usage patterns.\n\nBefore installing, ensure you have:\n\n- [Node.js](https://nodejs.org/en) \u003e= 20.0.0\n- [Next.js](https://nextjs.org/) \u003e= 14.0.0\n- Your preferred package manager (npm \u003e= 9.6.0, yarn, pnpm, etc.)\n\n\u003cbr\u003e\n\n## Installation\n\n```bash\n# With npm\nnpm install @wristband/nextjs-auth\n\n# Or with yarn\nyarn add @wristband/nextjs-auth\n\n# Or with pnpm\npnpm add @wristband/nextjs-auth\n```\n\n\u003cbr\u003e\n\n## Usage\n\n### 1) Initialize the Auth SDK\n\nFirst, create an instance of `WristbandAuth` in your Next.js directory structure in any location of your choice (i.e. `src/wristband.ts`). Then, you can export this instance and use it across your project. When creating an instance, you provide all necessary configurations for your application to correlate with how you've set it up in the Wristband Dashboard.\n\n```typescript\n// src/wristband.ts\nimport { createWristbandAuth } from '@wristband/nextjs-auth';\n\n/**\n * Wristband authentication instance for handling login, callback, and logout flows.\n */ \nexport const wristbandAuth = createWristbandAuth({\n  clientId: \"replace-me-with-your-client-id\",\n  clientSecret: \"replace-me-with-your-client-secret\",\n  wristbandApplicationVanityDomain: \"replace-me-with-your-vanity-domain\",\n});\n```\n\n\u003e **💡 Disabling Secure Cookies in Local Development**\n\u003e\n\u003e By default, `WristbandAuth` creates secure cookies (for tracking login state), meaning they are only sent over HTTPS connections. Most browsers make an exception for localhost and allow secure cookies to be sent over HTTP (e.g., http://localhost). However, some browsers, such as Safari, enforce stricter rules and never send secure cookies over HTTP, even for localhost. If you need to disable the secure cookies for local development, set `dangerouslyDisableSecureCookies: true`. However, be sure to **re-enable secure cookies before deploying to production**.\n\n\u003cbr\u003e\n\n### 2) Set Up Session Management\n\nWristband provides encrypted cookie-based session management built directly into this SDK, powered by [@wristband/typescript-session](https://github.com/wristband-dev/typescript-session). Add basic session configuration to enable the auth endpoints (Login, Callback, etc.) in the next steps.\n\n#### App Router\n\n```typescript\n// src/wristband.ts (continued - add to existing file)\nimport { NextRequest } from 'next/server';\nimport { getSessionFromRequest, SessionOptions } from '@wristband/nextjs-auth';\n\n// ...\n\n/**\n * Session configuration for authentication.\n * \n * IMPORTANT: Use a strong 32+ character secret in production and set secure: true\n */\nconst sessionOptions: SessionOptions = {\n  secrets: 'dummyval-b5c1-463a-812c-0d8db87c0ec5',  // 32+ character secret\n  maxAge: 3600, // 1 hour in seconds\n  secure: process.env.NODE_ENV === 'production',  // Must be true in Production\n};\n\n/**\n * Retrieves the session from a NextRequest.\n * \n * Use in:\n * - App Router API Route Handlers\n * - Middleware/proxy functions\n */\nexport function getRequestSession(request: NextRequest) {\n  return getSessionFromRequest(request, sessionOptions);\n}\n```\n\n#### Pages Router\n\n```typescript\n// src/wristband.ts (continued - add to existing file)\nimport * as http from 'http';\nimport { getPagesRouterSession, SessionOptions } from '@wristband/nextjs-auth';\n\n// ...\n\n/**\n * Session configuration for authentication.\n * \n * IMPORTANT: Use a strong 32+ character secret in production and set secure: true\n */\nconst sessionOptions: SessionOptions = {\n  secrets: 'dummyval-b5c1-463a-812c-0d8db87c0ec5',  // 32+ character secret\n  maxAge: 3600, // 1 hour in seconds\n  secure: process.env.NODE_ENV === 'production',  // Must be true in Production\n};\n\n/**\n * Retrieves session from Pages Router API routes and SSR functions.\n * \n * Use in:\n * - Pages Router API Route Handlers\n * - getServerSideProps()\n */\nexport function getSession(req: http.IncomingMessage, res: http.ServerResponse) {\n  return getPagesRouterSession(req, res, sessionOptions);\n}\n```\n\n\u003cbr\u003e\n\n### 3) Add Auth Endpoints\n\nThere are **four core API endpoints** your Next.js server should expose to facilitate authentication workflows in Wristband:\n\n- Login Endpoint\n- Callback Endpoint\n- Logout Endpoint\n- Session Endpoint\n\nYou'll need to add these endpoints to your Next.js API routes. There's also one additional endpoint you can implement depending on your authentication needs:\n\n- Token Endpoint (optional)\n\n\u003cbr\u003e\n\n#### Login Endpoint\n\nThe goal of the Login Endpoint is to initiate an auth request by redirecting to the [Wristband Authorization Endpoint](https://docs.wristband.dev/reference/authorizev1). It will store any state tied to the auth request in a Login State Cookie, which will later be used by the Callback Endpoint. The frontend of your application should redirect to this endpoint when users need to log in to your application.\n\n##### App Router\n\n```typescript\n// src/app/api/auth/login/route.ts\nimport type { NextRequest } from 'next/server';\nimport { wristbandAuth } from '../../../../wristband';\n\n// Login Endpoint at \"/api/auth/login\" (route can be wherever you prefer)\nexport async function GET(req: NextRequest) {\n  return await wristbandAuth.appRouter.login(req);\n}\n```\n\n##### Pages Router\n\n```typescript\n// src/pages/api/auth/login.ts\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { wristbandAuth } from '../../../wristband';\n\n// Login Endpoint at \"/api/auth/login\" (route can be wherever you prefer)\nexport default async function loginEndpoint(req: NextApiRequest, res: NextApiResponse) {\n  if (req.method !== 'GET') {\n    res.status(405).end();\n    return;\n  }\n\n  const authorizeUrl = await wristbandAuth.pagesRouter.login(req, res);\n  res.redirect(authorizeUrl);\n}\n```\n\n\u003cbr\u003e\n\n#### Callback Endpoint\n\nThe goal of the Callback Endpoint is to receive incoming calls from Wristband after the user has authenticated and ensure that the Login State cookie contains all auth request state in order to complete the Login Workflow. From there, it will call the [Wristband Token Endpoint](https://docs.wristband.dev/reference/tokenv1) to fetch necessary JWTs, call the [Wristband Userinfo Endpoint](https://docs.wristband.dev/reference/userinfov1) to get the user's data, and create a session for the application containing the JWTs and user data.\n\n##### App Router\n\n```typescript\n// src/app/api/auth/callback/route.ts\nimport { NextRequest } from 'next/server';\nimport { getRequestSession, wristbandAuth } from '../../../../wristband';\n\n// Callback Endpoint at \"/api/auth/callback\" (route can be wherever you prefer)\nexport async function GET(req: NextRequest) {\n  const callbackResult = await wristbandAuth.appRouter.callback(req);\n  const { callbackData, redirectUrl, type } = callbackResult;\n\n  if (type === 'redirect_required') {\n    return await wristbandAuth.appRouter.createCallbackResponse(req, redirectUrl);\n  }\n  \n  // Set authentication data into the session\n  const session = await getRequestSession(req);\n  session.fromCallback(callbackData);\n\n  // Create the response that will send the user back to your application.\n  const appUrl = callbackData.returnUrl || `\u003cyour_app_home_url\u003e`;\n  const callbackResponse = await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);\n\n  // Save session headers to the response; then redirect to your app.\n  return await session.saveToResponse(callbackResponse);\n}\n```\n\n##### Pages Router\n\n```typescript\n// src/pages/api/auth/callback.ts\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { getSession, wristbandAuth } from '../../../wristband';\n\n// Callback Endpoint at \"/api/auth/callback\" (route can be wherever you prefer)\nexport default async function callbackEndpoint(req: NextApiRequest, res: NextApiResponse) {\n  if (req.method !== 'GET') {\n    res.status(405).end();\n    return;\n  }\n\n  const callbackResult = await wristbandAuth.pagesRouter.callback(req, res);\n  const { callbackData, redirectUrl, type } = callbackResult;\n\n  if (type === 'redirect_required') {\n    res.redirect(redirectUrl);\n    return;\n  }\n\n  // Save authentication data in the session\n  const session = await getSession(req, res);\n  session.fromCallback(callbackData);\n  await session.save();\n\n  // Send the user back to the application.\n  res.redirect(callbackData.returnUrl || `\u003cyour_app_home_url\u003e`);\n}\n```\n\n\u003cbr\u003e\n\n#### Logout Endpoint\n\nThe goal of the Logout Endpoint is to destroy the application's session that was established during the Callback Endpoint execution. If refresh tokens were requested during the Login Workflow, then a call to the [Wristband Revoke Token Endpoint](https://docs.wristband.dev/reference/revokev1) will occur. It then will redirect to the [Wristband Logout Endpoint](https://docs.wristband.dev/reference/logoutv1) in order to destroy the user's authentication session within the Wristband platform. From there, Wristband will send the user to the Tenant-Level Login Page (unless configured otherwise).\n\n##### App Router\n\n```typescript\n// src/app/api/auth/logout/route.ts\nimport type { NextRequest } from 'next/server';\nimport { getRequestSession, wristbandAuth } from '../../../../wristband';\n\n// Logout Endpoint at \"/api/auth/logout\" (route can be wherever you prefer)\nexport async function GET(req: NextRequest) {\n  const session = await getRequestSession(req);\n\n  // Create the logout redirect response\n  const logoutResponse = await wristbandAuth.appRouter.logout(req, {\n    refreshToken: session.refreshToken,\n    tenantCustomDomain: session.tenantCustomDomain,\n    tenantName: session.tenantName,\n  });\n\n  // Always destroy session before redirecting.\n  return await session.destroyToResponse(logoutResponse);\n});\n```\n\n##### Pages Router\n\n```typescript\n// src/pages/api/auth/logout.ts\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { getSession, wristbandAuth } from '../../../wristband';\n\n// Logout Endpoint at \"/api/auth/logout\" (route can be wherever you prefer)\nexport default async function logoutEndpoint(req: NextApiRequest, res: NextApiResponse) {\n  if (req.method !== 'GET') {\n    res.status(405).end();\n    return;\n  }\n\n  // Create the logout redirect URL\n  const session = await getSession(req, res);\n  const logoutUrl = await wristbandAuth.pagesRouter.logout(req, res, {\n    refreshToken: session.refreshToken,\n    tenantCustomDomain: session.tenantCustomDomain,\n    tenantName: session.tenantName,\n  });\n\n  // Always destroy session before redirecting.\n  session.destroy();\n  res.redirect(logoutUrl);\n});\n```\n\n\u003cbr\u003e\n\n#### Session Endpoint\n\n\u003e [!NOTE]\n\u003e This endpoint is required for Wristband frontend SDKs to function. For more details, see the [Wristband Session Management documentation](https://docs.wristband.dev/docs/session-management-backend-server).\n\nWristband frontend SDKs require a Session Endpoint in your backend to verify authentication status and retrieve session metadata. Create a protected session endpoint that uses `session.getSessionResponse()` to return the session response format expected by Wristband's frontend SDKs. The response type will always have a `userId` and a `tenantId` in it. You can include any additional data for your frontend by customizing the `metadata` parameter (optional), which requires JSON-serializable values. **The response must not be cached**.\n\n\u003e **⚠️ Important:**\n\u003e This endpoint must be protected with authentication middleware, which is shown in [Section 4](#4-protect-your-pages-actions-and-apis).\n\n##### App Router\n\n```typescript\n// src/app/api/auth/session/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { getRequestSession } from '../../../../wristband';\n\n// Session Endpoint at \"/api/auth/session\" (route can be wherever you prefer)\nexport async function GET(req: NextRequest) {\n  const session = await getRequestSession(req);\n  const sessionResponse = session.getSessionResponse({ foo: 'bar' });\n  return NextResponse.json(sessionResponse, {\n    headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache' },\n  });\n});\n```\n\n##### Page Router\n\n```typescript\n// src/pages/api/auth/session.ts\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { getSession } from '../../../wristband';\n\n// Session Endpoint at \"/api/auth/session\" (route can be wherever you prefer)\nexport default async function sessionEndpoint(req: NextApiRequest, res: NextApiResponse) {\n  if (req.method !== 'GET') {\n    res.status(405).end();\n    return;\n  }\n\n  const session = await getSession(req, res);\n  const sessionResponse = session.getSessionResponse({ foo: 'bar' });\n  res.setHeader('Cache-Control', 'no-store');\n  res.setHeader('Pragma', 'no-cache');\n  res.status(200).json(sessionResponse);\n}\n```\n\n##### Response Type\n\nThe Session Endpoint returns the `SessionResponse` type to your frontend:\n\n```json\n{\n  \"tenantId\": \"tenant_abc123\",\n  \"userId\": \"user_xyz789\",\n  \"metadata\": {\n    \"foo\": \"bar\",\n    // Any other optional data you provide...\n  }\n}\n```\n\n\u003cbr\u003e\n\n#### Token Endpoint (Optional)\n\n\u003e [!NOTE]\n\u003e This endpoint is required when your frontend needs to make authenticated API requests directly to Wristband or other protected services. For more details, see the [Wristband documentation on using access tokens from the frontend](https://docs.wristband.dev/docs/authenticating-api-requests-with-bearer-tokens#using-access-tokens-from-the-frontend).\n\u003e\n\u003e If your application doesn't need frontend access to tokens (e.g., all API calls go through your backend), you can skip this endpoint.\n\nSome applications require the frontend to make direct API calls to Wristband or other protected services using the user's access token. The Token Endpoint provides a secure way for your frontend to retrieve the current access token and its expiration time without exposing it in the session cookie or in browser storage.\n\nCreate a protected token endpoint that uses `session.getTokenResponse()` to return the token data expected by Wristband's frontend SDKs. **The response must not be cached**.\n\n\u003e **⚠️ Important:**\n\u003e This endpoint must be protected with authentication middleware, which is shown in [Section 4](#4-protect-your-pages-actions-and-apis).\n\n##### App Router\n\n```typescript\n// src/app/api/auth/token/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { getRequestSession } from '../../../../wristband';\n\n// Token Endpoint at \"/api/auth/token\" (route can be wherever you prefer)\nexport async function GET(req: NextRequest) {\n  const session = await getRequestSession(req);\n  const tokenResponse = session.getTokenResponse();\n  return NextResponse.json(tokenResponse, {\n    headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache' },\n  });\n});\n```\n\n##### Page Router\n\n```typescript\n// src/pages/api/auth/token.ts\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { getSession } from '../../../wristband';\n\n// Token Endpoint at \"/api/auth/token\" (route can be wherever you prefer)\nexport default async function tokenEndpoint(req: NextApiRequest, res: NextApiResponse) {\n  if (req.method !== 'GET') {\n    res.status(405).end();\n    return;\n  }\n\n  const session = await getSession(req, res);\n  const tokenResponse = session.getTokenResponse();\n  res.setHeader('Cache-Control', 'no-store');\n  res.setHeader('Pragma', 'no-cache');\n  res.status(200).json(tokenResponse);\n}\n```\n\n##### Response Type\n\nThe Token Endpoint returns the `TokenResponse` type to your frontend:\n\n```json\n{\n  \"accessToken\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...\",\n  \"expiresAt\": 1735689600000\n}\n```\n\nYour frontend can then use the `accessToken` in the Authorization header when making API requests:\n\n```typescript\nconst tokenResponse = await fetch('/api/auth/token');\nconst { accessToken } = await tokenResponse.json();\n\n// Use token to call Wristband API\nconst userResponse = await fetch('https://\u003cyour-wristband-app-vanity-domain\u003e/api/v1/users/123', {\n  headers: { 'Authorization': `Bearer ${accessToken}` }\n});\n```\n\n\u003cbr\u003e\n\n### 4) Protect Your Pages, Actions, and APIs\n\nOnce your auth endpoints are set up, protect your application routes with authentication middleware and add session helpers for accessing session data in different contexts.\n\n#### Set Up Authentication Middleware\n\nIn Next.js, middleware (or proxy in Next.js 16+) is the ideal place to centralize authentication checks and token refresh for most scenarios. The authentication middleware works with both App Router and Pages Router, but has important differences in what it protects:\n\n**What middleware protects:**\n- ✅ **API Routes** (App and Pages Router) - All routes matching `protectedApis` patterns\n- ✅ **Session \u0026 Token Endpoints** - `/api/auth/session` and `/api/auth/token` are automatically protected when using `'SESSION'` strategy (customizable via `sessionConfig.sessionEndpoint` and `sessionConfig.tokenEndpoint` config)\n- ✅ **Pages** (Pages Router) - Server-rendered pages matching `protectedPages` patterns\n- ✅ **Server Components that are pages** (App Router) - Page components matching `protectedPages` patterns\n\n**What middleware does NOT protect:**\n- ❌ **Server Actions** (App Router) - Must use `createServerActionAuth()` for manual checks\n- ❌ **Server Components that are not pages** (App Router) - Server Components render during the RSC phase before middleware runs. Thus, only Server Components that are pages (route segments) are protected because they trigger a full page request that passes through middleware first.\n- ❌ **Client Components** (App Router) - Client Components render in the browser after the initial page load. Authentication is enforced on the initial page request via middleware, but subsequent client-side auth state management should use Wristband's [@wristband/react-client-auth](https://github.com/wristband-dev/react-client-auth) frontend SDK.\n\nAdd middleware configuration to your `wristband.ts` file:\n\n```typescript\n// src/wristband.ts (continued - add to existing file)\n\n// ...\n\n/**\n * Authentication middleware that protects routes in Next.js middleware.\n * \n * Automatically handles:\n * - Session validation for protected routes\n * - Token refresh when access tokens expire\n * - 401 responses for unauthenticated API requests\n * - Login redirects for unauthenticated page requests\n */\nexport const requireMiddlewareAuth = wristbandAuth.createMiddlewareAuth({\n  authStrategies: ['SESSION'],\n  sessionConfig: { sessionOptions },\n  protectedApis: ['/api/v1(.*)'],  // Regex patterns for protected API routes\n  protectedPages: ['/', '/dashboard', '/settings(.*)'],  // Regex patterns for protected pages\n});\n```\n\nNow create the middleware or proxy file (depending on your version of Next.js) at the root of your `src` directory (or project root if not using `src`).\n\n**Next.js 16+**:\n\n```typescript\n// src/proxy.ts\nimport { NextRequest } from 'next/server';\nimport { requireMiddlewareAuth } from './wristband';\n\nexport async function proxy(req: NextRequest) {\n  return await requireMiddlewareAuth(req);\n}\n\nexport const config = {\n  /*\n   * Match all paths except for:\n   * 1. /_next (Next.js internals)\n   * 2. /fonts (inside /public)\n   * 3. /examples (inside /public)\n   * 4. all root files inside /public (e.g. /favicon.ico)\n   */\n  matcher: ['/((?!_next|fonts|examples|[\\\\w-]+\\\\.\\\\w+).*)'],\n};\n```\n\n**Next.js 15 and earlier**:\n\n```typescript\n// src/middleware.ts\nimport { NextRequest } from 'next/server';\nimport { requireMiddlewareAuth } from './wristband';\n\nexport async function middleware(req: NextRequest) {\n  return await requireMiddlewareAuth(req);\n}\n\nexport const config = {\n  /*\n   * Match all paths except for:\n   * 1. /_next (Next.js internals)\n   * 2. /fonts (inside /public)\n   * 3. /examples (inside /public)\n   * 4. all root files inside /public (e.g. /favicon.ico)\n   */\n  matcher: ['/((?!_next|fonts|examples|[\\\\w-]+\\\\.\\\\w+).*)'],\n};\n```\n\nThe middleware automatically:\n\n- ✅ **Validates authentication** - Checks each auth strategy in order until one succeeds\n- ✅ **Refreshes expired tokens** - When using `'SESSION'` strategy AND when `refreshToken` and `expiresAt` are present in session\n- ✅ **Extends session expiration** - Rolling session window on each authenticated request (`'SESSION'` strategy only)\n- ✅ **Returns 401 for API Routes** - Unauthenticated requests to protected API routes\n- ✅ **Redirects pages to login** - Unauthenticated requests to protected pages (customizable via `onPageUnauthenticated`)\n- ✅ **Auto-protects auth endpoints** - Session and Token Endpoints protected by default (`'SESSION'` strategy only)\n- ✅ **Auto-bypasses Server Actions** - Server Action routes skip middleware protection (must manually check auth)\n\n\u003cbr\u003e\n\n#### Protect Server Actions (App Router Only)\n\nServer Actions are **not protected by middleware** because they execute as POST requests to internal Next.js endpoints that bypass the middleware/proxy layer. Add the Server Action auth helper to your Wristband file:\n\n```typescript\n// src/wristband.ts (continued - add to existing file)\n\n// ...\n\n/**\n * Authentication helper for Server Actions.\n * \n * Server Actions bypass Next.js middleware, so they must perform their own auth checks.\n * This helper validates the session and automatically refreshes expired tokens.\n */\nexport const requireServerActionAuth = wristbandAuth.appRouter.createServerActionAuth({\n  sessionOptions,\n});\n```\n\nHere's an example of how to use it in your Server Actions:\n\n```typescript\n// src/app/actions/my-action.ts\n'use server';\n\nimport { cookies } from 'next/headers';\nimport { requireServerActionAuth } from './wristband';\n\nexport async function updateUserProfile(formData: FormData) {\n  // The helper function will return you the current session if authentication succeeds.\n  const cookieStore = await cookies();\n  const { authenticated, reason, session } = await requireServerActionAuth(cookieStore);\n  \n  // Check authentication result\n  if (!authenticated) {\n    return { error: 'Unauthorized', reason };\n  }\n\n  // Access the authenticated session\n  const { userId } = session;\n\n  // ...your business logic here...\n\n  return { success: true };\n}\n```\n\n\u003cbr\u003e\n\n#### Manual Session Access (Optional)\n\nIn most cases, middleware and `createServerActionAuth()` handle all authentication needs. For advanced use cases where you need direct session access for custom logic, conditional rendering, or fine-grained session mutations, you can manually retrieve session data in the following contexts:\n\n- **API Route Handlers** (App Router \u0026 Pages Router)\n- **Server Components** (App Router) - Read-only access\n- **Server Actions** (App Router) - For advanced session mutations beyond `createServerActionAuth()`\n- **`getServerSideProps()`** (Pages Router)\n\n##### App Router: Server Components (Read-Only)\n\nBe aware that Server Components cannot modify sessions (read-only) because they render during the RSC (React Server Components) phase where response headers and cookies cannot be set. If you need to read session data in a Server Component, add this helper:\n\n```typescript\n// src/wristband.ts (continued - add to existing file)\nimport { getReadOnlySessionFromCookies, NextJsCookieStore } from '@wristband/nextjs-auth';\n\n// ...\n\n/**\n * Retrieves read-only session for Server Components.\n */\nexport function getServerComponentSession(cookieStore: NextJsCookieStore) {\n  return getReadOnlySessionFromCookies(cookieStore, sessionOptions);\n}\n```\n\nHere's an example of how to use it in your Server Components:\n\n```typescript\n// src/app/dashboard/page.tsx\nimport { cookies } from 'next/headers';\nimport { getServerComponentSession } from '../../wristband';\n\nexport default async function DashboardPage() {\n  const cookieStore = await cookies();\n  const session = await getServerComponentSession(cookieStore);\n  const { isAuthenticated, userId } = session;\n\n  if (!isAuthenticated) {\n    return \u003cdiv\u003ePlease log in.\u003c/div\u003e;\n  }\n\n  return \u003cdiv\u003eWelcome, {userId}\u003c/div\u003e;\n}\n```\n\n##### App Router: Server Actions (Advanced)\n\nFor advanced use cases where you need direct session manipulation without using `createServerActionAuth()`, add these helpers:\n\n```typescript\n// src/wristband.ts (continued - add to existing file)\nimport {\n  getMutableSessionFromCookies,\n  saveSessionWithCookies,\n  destroySessionWithCookies,\n  MutableSession,\n} from '@wristband/nextjs-auth';\n\n// ...\n\n/**\n * Retrieves mutable session for Server Actions.\n * Call saveServerActionSession() after modifying to persist changes.\n */\nexport async function getServerActionSession(cookies: NextJsCookieStore) {\n  return await getMutableSessionFromCookies(cookies, sessionOptions);\n}\n\n/**\n * Saves modified session data back to cookies (Server Actions only).\n */\nexport async function saveServerActionSession(cookies: NextJsCookieStore, session: MutableSession) {\n  await saveSessionWithCookies(cookies, session);\n}\n\n/**\n * Destroys session and clears cookies (Server Actions only).\n */\nexport function destroyServerActionSession(cookies: NextJsCookieStore, session: MutableSession) {\n  destroySessionWithCookies(cookies, session);\n}\n```\n\nHere's an example of how to use it in your Server Actions:\n\n```typescript\n// src/app/actions/my-action.ts\n'use server';\n\nimport { cookies } from 'next/headers';\nimport {\n  destroyServerActionSession,\n  getServerActionSession, \n  saveServerActionSession\n} from '../../wristband';\n\nexport async function customAction() {\n  // Get session (without performing auth check)\n  const cookieStore = await cookies();\n  const session = await getServerActionSession(cookieStore);\n\n  // Manually peform auth check\n  if (!session.isAuthenticated) {\n    // Destroy session\n    destroyServerActionSession(cookieStore, session);\n    return { error: 'Unauthorized' };\n  }\n\n  // Modify session and save changes\n  session.customField = 'value';\n  await saveServerActionSession(cookieStore, session);\n\n  return { success: true };\n}\n```\n\n##### App Router: API Routes\n\nAPI routes for the App Router can use the `getRequestSession()` helper already defined in [Section 2](#2-set-up-session-management):\n\n```typescript\n// src/app/api/orders/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { getRequestSession } from '../../../../wristband';\n\nexport async function GET(req: NextRequest) {\n  const session = await getRequestSession(req);\n\n  if (!session.isAuthenticated) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n\n  return NextResponse.json({ orders: [], userId: session.userId });\n}\n```\n\n##### Pages Router: API Routes and getServerSideProps()\n\nAPI routes for the Pages Router can use the `getSession()` helper already defined in [Section 2](#2-set-up-session-management):\n\n**API Route:**\n\n```typescript\n// src/pages/api/profile.ts\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { getSession } from '../../wristband';\n\nexport default async function apiRouteHandler(req: NextApiRequest, res: NextApiResponse) {\n  const session = await getSession(req, res);\n\n  if (!session.isAuthenticated) {\n    return res.status(401).json({ error: 'Unauthorized' });\n  }\n\n  return res.json({ userId: session.userId });\n}\n```\n\n**SSR:**\n\n```typescript\n// pages/dashboard.tsx\nimport type { GetServerSideProps } from 'next';\nimport { getSession } from '../wristband';\n\nexport const getServerSideProps: GetServerSideProps = async (context) =\u003e {\n  const session = await getSession(context.req, context.res);\n\n  if (!session.isAuthenticated) {\n    return {\n      redirect: { destination: '/api/auth/login', permanent: false },\n    };\n  }\n\n  return {\n    props: { userId: session.userId },\n  };\n};\n\nexport default function Dashboard({ userId }: { userId: string }) {\n  return \u003cdiv\u003eWelcome, {userId}\u003c/div\u003e;\n}\n```\n\n\u003cbr\u003e\n\n### 5) Pass Your Access Token to Downstream APIs\n\n\u003e [!NOTE]\n\u003e This is only applicable if you wish to call Wristband's APIs directly or protect your application's other downstream backend APIs.\n\nIf you intend to utilize Wristband APIs within your application or secure any backend APIs or downstream services using the access token provided by Wristband, you must include this token in the `Authorization` HTTP request header.\n\n```bash\nAuthorization: Bearer \u003caccess_token_value\u003e\n```\n\nFor example, if you were using attempting to fetch user data from Wristband in an API route, you would pass the access token from your application session into the `Authorization` header as follows:\n\n```typescript\nconst session = await getRequestSession(req);\nconst { accessToken, userId } = session;\n\nconst userResponse = await fetch(`https://yourapp-yourcompany.us.wristband.dev/api/v1/users/${userId}`, {\n  method: 'GET',\n  headers: { Authorization: `Bearer ${accessToken}` },\n});\n\nif (userResponse.status === 401) {\n  redirect('/api/auth/login');\n  return null;\n}\n\nconst user = await userResponse.json();\n\nconsole.log(user); // Output -\u003e { id: 123, ... }\n```\n\n#### Using Access Tokens from the Frontend\n\nFor scenarios where your frontend needs to make direct API calls with the user's access token, use the [Token Endpoint](#token-endpoint-optional) to securely retrieve the current access token.\n\n\u003cbr\u003e\n\n## Wristband Auth Configuration Options\n\nThe `createWristbandAuth()` function is used to instatiate the Wristband SDK.  It takes an `AuthConfig` type as an argument.\n\n### Auth Config Options\n\n`AuthConfig` contains the full set of options for integrating Wristband auth, including required, optional, and auto-configured values.\n\n| AuthConfig Field | Type | Required | Auto-Configurable | Description |\n| ---------------- | ---- | -------- | ----------------- | ----------- |\n| autoConfigureEnabled | boolean | No | _N/A_ | Flag that tells the SDK to automatically set some of the SDK configuration values by calling to Wristband's SDK Auto-Configuration Endpoint. Any manually provided configurations will take precedence over the configs returned from the endpoint. Auto-configure is enabled by default. When disabled, if manual configurations are not provided, then an error will be thrown. |\n| clientId | string | Yes | No | The ID of the Wristband client. |\n| clientSecret | string | Yes | No | The client's secret. |\n| customApplicationLoginPageUrl | string | No | Yes | Custom Application-Level Login Page URL (i.e. Tenant Discovery Page URL). This value only needs to be provided if you are self-hosting the application login page. By default, the SDK will use your Wristband-hosted Application-Level Login page URL. If this value is provided, the SDK will redirect to this URL in certain cases where it cannot resolve a proper Tenant-Level Login URL. |\n| dangerouslyDisableSecureCookies | boolean | No | No | USE WITH CAUTION: If set to `true`, the \"Secure\" attribute will not be included in any cookie settings. This should only be done when testing in local development environments that don't have HTTPS enabed.  If not provided, this value defaults to `false`. |\n| isApplicationCustomDomainActive | boolean | No | Yes | Indicates whether your Wristband application is configured with an application-level custom domain that is active. This tells the SDK which URL format to use when constructing the Wristband Authorize Endpoint URL. This has no effect on any tenant custom domains passed to your Login Endpoint either via the `tenant_custom_domain` query parameter or via the `defaultTenantCustomDomain` config.  Defaults to `false`. |\n| loginStateSecret | string | No | No | A 32 character (or longer) secret used for encryption and decryption of login state cookies. If not provided, it will default to using the client secret. For enhanced security, it is recommended to provide a value that is unique from the client secret. You can run `openssl rand -base64 32` to create a secret from your CLI. |\n| loginUrl | string | Yes | Yes | The URL of your application's login endpoint.  This is the endpoint within your application that redirects to Wristband to initialize the login flow. If you intend to use tenant subdomains in your Login Endpoint URL, then this value must contain the `{tenant_domain}` placeholder. For example: `https://{tenant_domain}.yourapp.com/auth/login`. |\n| parseTenantFromRootDomain | string | Only if using tenant subdomains in your application | Yes | The root domain for your application. This value only needs to be specified if you intend to use tenant subdomains in your Login and Callback Endpoint URLs.  The root domain should be set to the portion of the domain that comes after the tenant subdomain.  For example, if your application uses tenant subdomains such as `tenantA.yourapp.com` and `tenantB.yourapp.com`, then the root domain should be set to `yourapp.com`. This has no effect on any tenant custom domains passed to your Login Endpoint either via the `tenant_custom_domain` query parameter or via the `defaultTenantCustomDomain` config. When this configuration is enabled, the SDK extracts the tenant subdomain from the host and uses it to construct the Wristband Authorize URL. |\n| redirectUri | string | Yes | Yes | The URI that Wristband will redirect to after authenticating a user.  This should point to your application's callback endpoint. If you intend to use tenant subdomains in your Callback Endpoint URL, then this value must contain the `{tenant_domain}` placeholder. For example: `https://{tenant_domain}.yourapp.com/auth/callback`. |\n| scopes | string[] | No | No | The scopes required for authentication. Refer to the docs for [currently supported scopes](https://docs.wristband.dev/docs/oauth2-and-openid-connect-oidc#supported-openid-scopes). The default value is `[openid, offline_access, email]`. |\n| tokenExpirationBuffer | number | No | No | Buffer time (in seconds) to subtract from the access token’s expiration time. This causes the token to be treated as expired before its actual expiration, helping to avoid token expiration during API calls. Defaults to 60 seconds. |\n| wristbandApplicationVanityDomain | string | Yes | No | The vanity domain of the Wristband application. |\n\n\u003cbr\u003e\n\n### `createWristbandAuth()`\n\n```ts\nfunction createWristbandAuth(authConfig: AuthConfig): WristbandAuth {}\n```\n\nThis function creates an instance of `WristbandAuth` using lazy auto-configuration. Auto-configuration is enabled by default and will fetch any missing configuration values from the Wristband SDK Configuration Endpoint when any auth function is first called (i.e. `login`, `callback`, etc.). Set `autoConfigureEnabled` to `false` disable to prevent the SDK from making an API request to the Wristband SDK Configuration Endpoint. In the event auto-configuration is disabled, you must manually configure all required values. Manual configuration values take precedence over auto-configured values.\n\n\u003e  **⚠️ Auto-Configuration in Edge Runtimes**\n\u003e \n\u003e While auto-configuration works well in Node.js runtime environments, **manual configuration is strongly recommended when using Next.js Edge Runtime** (Edge API Routes, Middleware, and Edge-rendered pages) due to the following limitations:\n\u003e\n\u003e - **Cold start latency**: Auto-configuration requires an API call to the Wristband SDK Configuration Endpoint on every cold start, which can impact response times for authentication flows in Edge Runtime.\n\u003e - **No persistent memory**: Edge Runtime instances don't maintain in-memory caches between requests, causing the SDK to refetch configuration data on every invocation\n\u003e\n\u003e For production Next.js applications using Edge Runtime, you can set `autoConfigureEnabled: false` and provide all required configuration values manually. This is especially critical for authentication middleware that runs on every protected route.\n\n**Minimal config with auto-configure (default behavior)**\n```ts\nconst auth = createWristbandAuth({\n  clientId: \"your-client-id\",\n  clientSecret: \"your-secret\",\n  wristbandApplicationVanityDomain: \"auth.yourapp.io\"\n});\n```\n\n**Manual override with partial auto-configure for some fields**\n```ts\nconst auth = createWristbandAuth({\n  clientId: \"your-client-id\",\n  clientSecret: \"your-secret\",\n  wristbandApplicationVanityDomain: \"auth.yourapp.io\",\n  loginUrl: \"https://yourapp.io/auth/login\", // Manually override \"loginUrl\"\n  // \"redirectUri\" will be auto-configured\n});\n```\n\n**Auto-configure disabled**\n```ts\nconst auth = createWristbandAuth({\n  autoConfigureEnabled: false,\n  clientId: \"your-client-id\",\n  clientSecret: \"your-secret\",\n  wristbandApplicationVanityDomain: \"auth.custom.com\",\n  // Must manually configure non-auto-configurable fields\n  isApplicationCustomDomainActive: true,\n  loginUrl: \"https://{tenant_domain}.custom.com/auth/login\",\n  redirectUri: \"https://{tenant_domain}.custom.com/auth/callback\",\n  parseTenantFromRootDomain: \"custom.com\",\n});\n```\n\n\u003cbr\u003e\n\n## Auth API\n\n### login()\n\n```ts\n/* *** App Router *** */\n// Definition\nlogin: (req: NextRequest, loginConfig?: LoginConfig) =\u003e Promise\u003cNextResponse\u003e;\n// Usage\nreturn await wristbandAuth.appRouter.login(req);\n\n/* *** Pages Router *** */\n// Definition\nlogin: (req: NextApiRequest, res: NextApiResponse, loginConfig?: LoginConfig) =\u003e Promise\u003cstring\u003e;\n// Usage\nconst authorizeUrl = await wristbandAuth.pagesRouter.login(req, res);\nres.redirect(authorizeUrl);\n```\n\nWristband requires that your application specify a Tenant-Level domain when redirecting to the Wristband Authorize Endpoint when initiating an auth request. When the frontend of your application redirects the user to your Next.js Login Endpoint, there are two ways to accomplish getting the `tenantName` information: passing a query parameter or using tenant subdomains.\n\nThe `login()` function can also take optional configuration if your application needs custom behavior:\n\n| LoginConfig Field | Type | Required | Description |\n| ----------------- | ---- | -------- | ----------- |\n| customState | JSON | No | Additional state to be saved in the Login State Cookie. Upon successful completion of an auth request/login attempt, your Callback Endpoint will return this custom state (unmodified) as part of the return type. |\n| defaultTenantName | string | No | An optional default tenant name to use for the login request in the event the tenant name cannot be found in either the subdomain or query parameters (depending on your subdomain configuration). |\n| defaultTenantCustomDomain | string | No | An optional default tenant custom domain to use for the login request in the event the tenant custom domain cannot be found in the query parameters. |\n| returnUrl | string | No | The URL to return to after authentication is completed. If a value is provided, then it takes precedence over the `return_url` request query parameter. |\n\n#### Which Domains Are Used in the Authorize URL?\n\nWristband supports various tenant domain configurations, including subdomains and custom domains. The SDK automatically determines the appropriate domain configuration when constructing the Wristband Authorize URL, which your login endpoint will redirect users to during the login flow. The selection follows this precedence order:\n\n1. `tenant_custom_domain` query parameter: If provided, this takes top priority.\n2. Tenant subdomain in the URL: Used if subdomains are enabled and the subdomain is present.\n3. `tenant_name` query parameter: Evaluated if no tenant subdomain is detected.\n4. `defaultTenantCustomDomain` in LoginConfig: Used if none of the above are present.\n5. `defaultTenantDomain` in LoginConfig: Used as the final fallback.\n\nIf none of these are specified, the SDK redirects users to the Application-Level Login (Tenant Discovery) Page.\n\n#### Tenant Name Query Param\n\nIf your application does not wish to utilize subdomains for each tenant, you can pass the `tenant_name` query parameter to your Login Endpoint, and the SDK will be able to make the appropriate redirection to the Wristband Authorize Endpoint.\n\n```sh\nGET https://yourapp.io/api/auth/login?tenant_name=customer01\n```\n\nYour AuthConfig would look like the following when creating an SDK instance without any subdomains:\n\n```ts\nconst wristbandAuth = createWristbandAuth({\n  clientId: \"ic6saso5hzdvbnof3bwgccejxy\",\n  clientSecret: \"30e9977124b13037d035be10d727806f\",\n  loginStateSecret: '7ffdbecc-ab7d-4134-9307-2dfcc52f7475',\n  loginUrl: \"https://yourapp.io/auth/login\",\n  redirectUri: \"https://yourapp.io/auth/callback\",\n  wristbandApplicationVanityDomain: \"yourapp-yourcompany.us.wristband.dev\",\n});\n```\n\n#### Tenant Subdomains\n\nIf your application wishes to utilize tenant subdomains, then you do not need to pass a query param when redirecting to your Next.js Login Endpoint. The SDK will parse the tenant subdomain from the URL in order to make the redirection to the Wristband Authorize Endpoint. You will also need to tell the SDK what your application's root domain is in order for it to correctly parse the subdomain.\n\n```sh\nGET https://customer01.yourapp.io/api/auth/login\n```\n\nYour AuthConfig would look like the following when creating an SDK instance when using subdomains:\n\n```ts\nconst wristbandAuth = createWristbandAuth({\n  clientId: \"ic6saso5hzdvbnof3bwgccejxy\",\n  clientSecret: \"30e9977124b13037d035be10d727806f\",\n  loginStateSecret: '7ffdbecc-ab7d-4134-9307-2dfcc52f7475',\n  loginUrl: \"https://{tenant_domain}.yourapp.io/auth/login\",\n  redirectUri: \"https://{tenant_domain}.yourapp.io/auth/callback\",\n  parseTenantFromRootDomain: \"yourapp.io\",\n  wristbandApplicationVanityDomain: \"yourapp-yourcompany.us.wristband.dev\",\n});\n```\n\n#### Default Tenant Name\n\nFor certain use cases, it may be useful to specify a default tenant name in the event that the `login()` function cannot find a tenant name in either the query parameters or in the URL subdomain. You can specify a fallback default tenant name via a `LoginConfig` object. For example:\n\n```ts\nawait wristbandAuth.pagesRouter.login(req, res, { defaultTenantName: 'default' });\n```\n\n#### Tenant Custom Domain Query Param\n\nIf your application wishes to utilize tenant custom domains, you can pass the `tenant_custom_domain` query parameter to your Login Endpoint, and the SDK will be able to make the appropriate redirection to the Wristband Authorize Endpoint.\n\n```sh\nGET https://yourapp.io/auth/login?tenant_custom_domain=mytenant.com\n```\n\nThe tenant custom domain takes precedence over all other possible domains else when present.\n\n#### Default Tenant Custom Domain\n\nFor certain use cases, it may be useful to specify a default tenant custom domain in the event that the `login()` function cannot find a tenant custom domain in the query parameters. You can specify a fallback default tenant custom domain via a `LoginConfig` object:\n\n```ts\nawait wristbandAuth.appRouter.login(req, { defaultTenantCustomDomain: 'mytenant.com' });\n```\n\nThe default tenant custom domain takes precedence over all other possible domains else when present except when the `tenant_custom_domain` query parameter exists in the request.\n\n#### Custom State\n\nBefore your Login Endpoint redirects to Wristband, it will create a Login State Cookie to cache all necessary data required in the Callback Endpoint to complete any auth requests. You can inject additional state into that cookie via a `LoginConfig` object. For example:\n\n```ts\nawait wristbandAuth.appRouter.login(req, { customState: { test: 'abc' } });\n```\n\n\u003e [!WARNING]\n\u003e Injecting custom state is an advanced feature, and it is recommended to use `customState` sparingly. Most applications may not need it at all. The max cookie size is 4kB. From our own tests, passing a `customState` JSON of at most 1kB should be a safe ceiling.\n\n#### Login Hints\n\nWristband will redirect to your Next.js Login Endpoint for workflows like Application-Level Login (Tenant Discovery) and can pass the `login_hint` query parameter as part of the redirect request:\n\n```sh\nGET https://customer01.yourapp.io/api/auth/login?login_hint=user@wristband.dev\n```\n\nIf Wristband passes this parameter, it will be appended as part of the redirect request to the Wristband Authorize Endpoint. Typically, the email form field on the Tenant-Level Login page is pre-filled when a user has previously entered their email on the Application-Level Login Page.\n\n#### Return URLs\n\nIt is possible that users will try to access a location within your application that is not some default landing page. In those cases, they would expect to immediately land back at that desired location after logging in.  This is a better experience for the user, especially in cases where they have application URLs bookmarked for convenience.\n\nGiven that your frontend will redirect users to your Login Endpoint, you can either include it in your Login Config or pass a `return_url` query parameter when redirecting to your Login Endpoint. The URL will be available to you upon completion of the Callback Endpoint. The Login Config takes precedence over the query parameter in the event a value is provided for both.\n\n**Passing a return URL in the Login Config**\n```ts\nconst loginUrl = await wristbandAuth.pagesRouter.login(req, res, {\n  returnUrl: 'https://customer01.yourapp.io/settings/profile',\n});\nres.redirect(loginUrl);\n```\n\n**Passing a return URL as a query parameter**\n```sh\nGET https://customer01.yourapp.io/auth/login?return_url=https://customer01.yourapp.io/settings/profile\n```\n\nThe return URL is stored in the Login State Cookie, and you can choose to send users to that return URL (if necessary) after the SDK's `callback()` funciton is done executing.\n\n\u003cbr\u003e\n\n### callback()\n\n```ts\n/* *** App Router *** */\n// Definition\ncallback: (req: NextRequest) =\u003e Promise\u003cCallbackResult\u003e;\ncreateCallbackResponse: (req: NextRequest, redirectUrl: string) =\u003e NextResponse;\n\n// Usage\nconst callbackResult = await wristbandAuth.appRouter.callback(req);\nreturn await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);\n\n/* *** Pages Router *** */\n// Definition\ncallback: (req: NextApiRequest, res: NextApiResponse) =\u003e Promise\u003cCallbackResult\u003e;\n\n// Usage\nconst callbackResult = await wristbandAuth.pagesRouter.callback(req, res);\n```\n\nAfter a user authenticates on the Tenant-Level Login Page, Wristband will redirect to your Next.js Callback Endpoint with an authorization code which can be used to exchange for an access token. It will also pass the state parameter that was generated during the Login Endpoint.\n\n```sh\nGET https://customer01.yourapp.io/api/auth/callback?state=f983yr893hf89ewn0idjw8e9f\u0026code=shcsh90jf9wc09j9w0jewc\n```\n\nThe SDK will validate that the incoming state matches the Login State Cookie, and then it will call the Wristband Token Endpoint to exchange the authorizaiton code for JWTs. Lastly, it will call the Wristband Userinfo Endpoint to get any user data as specified by the `scopes` in your SDK configuration. The return type of the callback function is a `CallbackResult` object containing the result of what happened during callback execution as well as any accompanying data.\n\n| CallbackResult Field | Type | Description |\n| -------------------- | ---- | ----------- |\n| callbackData | CallbackData or `undefined` | The callback data received after authentication (`'completed'` result only). |\n| reason | CallbackFailureReason or `undefined` | The reason why the callback did not complete successfully (`'redirect_required'` only). |\n| redirectUrl | string or `undefined` | The URL that the user should redirected to (`'redirect_required'` only). |\n| type | CallbackResultType | String literal representing the end result of callback execution.\u003cbr\u003e\u003cbr\u003e Possible values: `'completed'` or `'redirect_required'`. |\n\n\u003cbr\u003e\n\nThe `CallbackResultType` can be one of the following string literal values:\n\n| CallbackResultType | Description |\n| ------------------ | ----------- |\n| `'completed'` | Indicates that the callback is successfully completed and data is available for creating a session. |\n| `'redirect_required'` | Indicates that a redirect is required, generally to a login route or page. |\n\n\u003cbr\u003e\n\nWhen the callback returns a `'redirect_required'` result, the `reason` field indicates why the callback failed:\n\n| CallbackFailureReason | Description |\n| --------------------- | ----------- |\n| `'missing_login_state'` | Login state cookie was not found (cookie expired or bookmarked callback URL). |\n| `'invalid_login_state'` | Login state validation failed (possible CSRF attack or cookie tampering) |\n| `'login_required'` | Wristband returned a login_required error (session expired or max_age elapsed). |\n| `'invalid_grant'` | Authorization code was invalid, expired, or already used. |\n\n\u003cbr\u003e\n\nWhen the callback returns a `'completed'` result, all of the token and userinfo data also gets returned. This enables your application to create an application session for the user and then redirect them back into your application. The `CallbackData` is defined as follows:\n\n| CallbackData Field | Type | Description |\n| ------------------ | ---- | ----------- |\n| accessToken | string | The access token that can be used for accessing Wristband APIs as well as protecting your application's backend APIs. |\n| customState | JSON or `undefined` | If you injected custom state into the Login State Cookie during the Login Endpoint for the current auth request, then that same custom state will be returned in this field. |\n| expiresAt | number | The absolute expiration time of the access token in milliseconds since the Unix epoch. The `tokenExpirationBuffer` SDK configuration is accounted for in this value. |\n| expiresIn | number | The duration from the current time until the access token is expired (in seconds). The `tokenExpirationBuffer` SDK configuration is accounted for in this value. |\n| idToken | string | The ID token uniquely identifies the user that is authenticating and contains claim data about the user. |\n| refreshToken | string or `undefined` | The refresh token that renews expired access tokens with Wristband, maintaining continuous access to services. |\n| returnUrl | string or `undefined` | The URL to return to after authentication is completed. |\n| tenantCustomDomain | string | The tenant custom domain for the tenant that the user belongs to (if applicable). |\n| tenantName | string | The name of the tenant the user belongs to. |\n| userinfo | JSON | Data for the current user retrieved from the Wristband Userinfo Endpoint. The data returned in this object follows the format laid out in the [Wristband Userinfo Endpoint documentation](https://docs.wristband.dev/reference/userinfov1). The exact fields that get returned are based on the scopes you configured in the SDK. |\n\nThe `UserInfo` type is defined as follows:\n\n| UserInfo Field | Type | Always Returned | Description |\n| -------------- | ---- | --------------- | ----------- |\n| userId | string | Yes | ID of the user (mapped from \"sub\" claim). |\n| tenantId | string | Yes | ID of the tenant that the user belongs to (mapped from \"tnt_id\" claim). |\n| applicationId | string | Yes | ID of the application that the user belongs to (mapped from \"app_id\" claim). |\n| identityProviderName | string | Yes | Name of the identity provider (mapped from \"idp_name\" claim). |\n| fullName | string or `undefined` | No | End-User's full name in displayable form (mapped from \"name\" claim; requires `profile` scope). |\n| givenName | string or `undefined` | No | Given name(s) or first name(s) of the End-User (requires `profile` scope). |\n| familyName | string or `undefined` | No | Surname(s) or last name(s) of the End-User (requires `profile` scope). |\n| middleName | string or `undefined` | No | Middle name(s) of the End-User (requires `profile` scope). |\n| nickname | string or `undefined` | No | Casual name of the End-User (requires `profile` scope). |\n| displayName | string or `undefined` | No | Shorthand name by which the End-User wishes to be referred (requires `profile` scope). |\n| pictureUrl | string or `undefined` | No | URL of the End-User's profile picture (requires `profile` scope). |\n| email | string or `undefined` | No | End-User's preferred email address (requires `email` scope). |\n| emailVerified | boolean or `undefined` | No | True if the End-User's email address has been verified (requires `email` scope). |\n| gender | string or `undefined` | No | End-User's gender (requires `profile` scope). |\n| birthdate | string or `undefined` | No | End-User's birthday in YYYY-MM-DD format (requires `profile` scope). |\n| timeZone | string or `undefined` | No | End-User's time zone (requires `profile` scope). |\n| locale | string or `undefined` | No | End-User's locale as BCP47 language tag, e.g., \"en-US\" (requires `profile` scope). |\n| phoneNumber | string or `undefined` | No | End-User's telephone number in E.164 format (requires `phone` scope). |\n| phoneNumberVerified | boolean or `undefined` | No | True if the End-User's phone number has been verified (requires `phone` scope). |\n| updatedAt | number or `undefined` | No | Time the End-User's information was last updated as Unix timestamp (requires `profile` scope). |\n| roles | `UserInfoRole[]` or `undefined` | No | The roles assigned to the user (requires `roles` scope). |\n| customClaims | `Record\u003cstring, any\u003e` or `undefined` | No | Object containing any configured custom claims. |\n\nThe `UserInfoRole` type is defined as follows:\n\n| UserInfoRole Field | Type | Description |\n| ------------------ | ---- | ----------- |\n| id | string | Globally unique ID of the role. |\n| name | string | The role name (e.g., \"app:app-name:admin\"). |\n| displayName | string | The human-readable display name for the role. |\n\n\u003cbr\u003e\n\n#### Redirect Responses\n\nThere are certain scenarios where instead of callback data being returned by the SDK, a redirect URL is returned instead.  The following are edge cases where this occurs:\n\n- The Login State Cookie is missing by the time Wristband redirects back to the Callback Endpoint.\n- The `state` query parameter sent from Wristband to your Callback Endpoint does not match the Login State Cookie.\n- Wristband sends an `error` query parameter to your Callback Endpoint, and it is an expected error type that the SDK knows how to resolve.\n\nThe location of where the user gets redirected to in these scenarios depends on if the application is using tenant subdomains and if the SDK is able to determine which tenant the user is currently attempting to log in to. The resolution happens in the following order:\n\n1. If the tenant domain can be determined, then the user will get redirected back to your Login Endpoint.\n2. Otherwise, the user will be sent to the Wristband-hosted Tenant-Level Login Page URL.\n\nIn these events, the your application should redirect the user to that location.\n\n#### Error Parameters\n\nCertain edge cases are possible where Wristband encounters an error during the processing of an auth request. These are the following query parameters that are sent for those cases to your Callback Endpoint:\n\n| Query Parameter | Description |\n| --------------- | ----------- |\n| error | Indicates an error that occurred during the Login Workflow. |\n| error_description | A human-readable description or explanation of the error to help diagnose and resolve issues more effectively. |\n\n```sh\nGET https://customer01.yourapp.io/api/auth/callback?state=f983yr893hf89ewn0idjw8e9f\u0026error=login_required\u0026error_description=User%20must%20re-authenticate%20because%20the%20specified%20max_age%20value%20has%20elapsed\n```\n\nThe error types that get automatically resolved in the SDK are:\n\n| Error | Description |\n| ----- | ----------- |\n| login_required | Indicates that the user needs to log in to continue. This error can occur in scenarios where the user's session has expired, the user is not currently authenticated, or Wristband requires the user to explicitly log in again for security reasons. |\n\nFor all other error types, the SDK will throw a `WristbandError` object (containing the error and description) that your application can catch and handle. Most errors come from SDK configuration issues during development that should be addressed before release to production.\n\n\u003cbr\u003e\n\n### createCallbackResponse() (App Router)\n\n```typescript\n/* *** App Router *** */\n// Definition\ncreateCallbackResponse: (req: NextRequest, redirectUrl: string) =\u003e Promise\u003cNextResponse\u003e;\n\n// Usage\nconst appUrl = callbackData.returnUrl || `https://yourapp.io/home`;\nreturn await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);\n```\n\nWhen using the App Router, there is a second callback-related function called `createCallbackResponse()` you must use to create the appropriate redirect response to your application's destination URL while ensuring the proper response headers are set.\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| request | NextRequest | Yes | The Next.js request object. |\n| redirectUrl | string | Yes | The URL to redirect the user to after authentication completes. |\n\n```typescript\n// App Router: Callback endpoint\nimport { NextRequest } from 'next/server';\nimport { getRequestSession, wristbandAuth } from '../../../../wristband';\n\nexport async function GET(req: NextRequest) {\n  const callbackResult = await wristbandAuth.appRouter.callback(req);\n  const { callbackData, redirectUrl, type } = callbackResult;\n\n  // Handle redirect required scenario\n  if (type === 'redirect_required') {\n    return await wristbandAuth.appRouter.createCallbackResponse(req, redirectUrl);\n  }\n  \n  // Handle successful authentication\n  const session = await getRequestSession(req);\n  session.fromCallback(callbackData);\n\n  // Create callback response with your app's destination URL\n  const appUrl = callbackData.returnUrl || '/dashboard';\n  const callbackResponse = await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);\n  \n  // Save session and return response\n  return await session.saveToResponse(callbackResponse);\n}\n```\n\n\u003cbr\u003e\n\n### logout()\n\n```ts\n/* *** App Router *** */\n// Definition\nlogout: (req: NextRequest, logoutConfig?: LogoutConfig) =\u003e Promise\u003cNextResponse\u003e;\n// Usage\nreturn await wristbandAuth.appRouter.logout(req, { refreshToken: '98yht308hf902hc90wh09' });\n\n/* *** Pages Router *** */\n// Definition\nlogout: (req: NextApiRequest, res: NextApiResponse, logoutConfig?: LogoutConfig) =\u003e Promise\u003cstring\u003e;\n// Usage\nconst logoutUrl = await wristbandAuth.pagesRouter.logout(req, res, { refreshToken: '98yht308hf902hc90wh09' });\nres.redirect(logoutUrl);\n```\n\nWhen users of your application are ready to log out and/or their application session expires, your frontend should redirect the user to your Next.js Logout Endpoint.\n\n```sh\nGET https://customer01.yourapp.io/api/auth/logout\n```\n\nIf your application created a session, it should destroy it before invoking the `logout()` function.  This function can also take an optional `LogoutConfig` argument:\n\n| LogoutConfig Field | Type | Required | Description |\n| ----------------- | ---- | -------- | ----------- |\n| redirectUrl | string | No | Optional URL that Wristband will redirect to after the logout operation has completed.  |\n| refreshToken | string | No | The refresh token to revoke. |\n| state | string | No | Optional value that will be appended as a query parameter to the resolved logout URL, if provided. Maximum length of 512 characters. |\n| tenantCustomDomain | string | No | The tenant custom domain for the tenant that the user belongs to (if applicable). |\n| tenantName | string | No | The name of the tenant the user belongs to. |\n\n#### Which Domains Are Used in the Logout URL?\n\nWristband supports various tenant domain configurations, including subdomains and custom domains. The SDK automatically determines the appropriate domain configuration when constructing the Wristband Logout URL, which your login endpoint will redirect users to during the logout flow. The selection follows this precedence order:\n\n1. `tenantCustomDomain` in LogoutConfig: If provided, this takes top priority.\n2. `tenantName` in LogoutConfig: This takes the next priority if `tenantCustomDomain` is not present.\n3. `tenant_custom_domain` query parameter: Evaluated if present and there is also no LogoutConfig provided for either `tenantCustomDomain` or `tenantName`.\n4. Tenant subdomain in the URL: Used if none of the above are present, and `parseTenantFromRootDomain` is specified, and the subdomain is present in the host.\n5. `tenant_name` query parameter: Used as the final fallback.\n\nIf none of these are specified, the SDK redirects users to the Application-Level Login (Tenant Discovery) Page.\n\n#### Revoking Refresh Tokens\n\nIf your application requested refresh tokens during the Login Workflow (via the `offline_access` scope), it is crucial to revoke the user's access to that refresh token when logging out. Otherwise, the refresh token would still be valid and able to refresh new access tokens.  You should pass the refresh token into the LogoutConfig when invoking the `logout()` function, and the SDK will call to the [Wristband Revoke Token Endpoint](https://docs.wristband.dev/reference/revokev1) automatically.\n\n#### Resolving Tenant Domains\n\nMuch like the Login Endpoint, Wristband requires your application specify a Tenant-Level domain when redirecting to the [Wristband Logout Endpoint](https://docs.wristband.dev/reference/logoutv1). If your application does not utilize tenant subdomains, then you will need to explicitly pass it into the LogoutConfig:\n\n```ts\n// Pages Router\nawait wristbandAuth.pagesRouter.logout(req, res, { refreshToken: '98yht308hf902hc90wh09', tenantName: 'customer01' });\n\n// App Router\nawait wristbandAuth.appRouter.logout(req, { refreshToken: '98yht308hf902hc90wh09', tenantName: 'customer01' });\n```\n\n...or you can alternatively pass the `tenant_name` query parameter in your redirect request to your Logout Endpoint:\n\n```ts\n//\n// Logout Request URL -\u003e \"https://yourapp.io/auth/logout?client_id=123\u0026tenant_name=customer01\"\n//\n// Pages Router\nawait wristbandAuth.pagesRouter.logout(req, res, { refreshToken: '98yht308hf902hc90wh09' });\n\n// App Router\nawait wristbandAuth.appRouter.logout(req, { refreshToken: '98yht308hf902hc90wh09' });\n```\n\nIf your application uses tenant subdomains, then passing the `tenantName` field to the LogoutConfig is not required since the SDK will automatically parse the subdomain from the URL as long as the `parseTenantFromRootDomain` SDK config is set.\n\n#### Tenant Custom Domains\n\nIf you have a tenant that relies on a tenant custom domain, then you can either explicitly pass it into the LogoutConfig:\n\n```ts\nconst logoutConfig = {\n  refreshToken: '98yht308hf902hc90wh09',\n  tenantCustomDomain: 'mytenant.com'\n};\n\n// Pages Router\nawait wristbandAuth.pagesRouter.logout(req, res, logoutConfig);\n\n// App Router\nawait wristbandAuth.appRouter.logout(req, logoutConfig);\n```\n\n...or you can alternatively pass the `tenant_custom_domain` query parameter in your redirect request to your Logout Endpoint:\n\n```ts\n//\n// Logout Request URL -\u003e \"https://yourapp.io/auth/logout?client_id=123\u0026tenant_custom_domain=customer01.com\"\n//\nconst logoutConfig = {\n  refreshToken: '98yht308hf902hc90wh09',\n};\n\n// Pages Router\nawait wristbandAuth.pagesRouter.logout(req, res, logoutConfig);\n\n// App Router\nawait wristbandAuth.appRouter.logout(req, logoutConfig);\n```\n\nIf your application supports a mixture of tenants that use tenant subdomains and tenant custom domains, then you should consider passing both the tenant names and tenant custom domains (either via LogoutConfig or by query parameters) to ensure all use cases are handled by the SDK.\n\n#### Preserving State After Logout\n\nThe `state` field in the `LogoutConfig` allows you to preserve application state through the logout flow.\n\n```ts\nconst logoutConfig = {\n  refreshToken: '98yht308hf902hc90wh09',\n  state: 'user_initiated_logout',\n  tenantName: 'customer01'\n};\n\n// Pages Router\nawait wristbandAuth.pagesRouter.logout(req, res, logoutConfig);\n\n// App Router\nawait wristbandAuth.appRouter.logout(req, logoutConfig);\n```\n\nThe state value gets appended as a query parameter to the Wristband Logout Endpoint URL:\n\n```sh\nhttps://customer01.auth.yourapp.io/api/v1/logout?client_id=123\u0026state=user_initiated_logout\n```\n\nAfter logout completes, Wristband will redirect to your configured redirect URL (either your Login Endpoint by default, or a custom logout redirect URL if configured) with the `state` parameter included:\n\n```sh\nhttps://yourapp.io/auth/login?tenant_name=customer01\u0026state=user_initiated_logout\n```\n\nThis is useful for tracking logout context, displaying post-logout messages, or handling different logout scenarios. The state value is limited to 512 characters and will be URL-encoded automatically.\n\n#### Custom Logout Redirect URL\n\nSome applications might require the ability to land on a different page besides the Login Page after logging a user out. You can add the `redirectUrl` field to the LogoutConfig, and doing so will tell Wristband to redirect to that location after it finishes processing the logout request.\n\n```ts\nconst logoutConfig = {\n  redirectUrl: 'https://custom-logout.com',\n  refreshToken: '98yht308hf902hc90wh09',\n  tenantName: 'customer01'\n};\n\n// Pages Router\nawait wristbandAuth.pagesRouter.logout(req, res, logoutConfig);\n\n// App Router\nawait wristbandAuth.appRouter.logout(req, logoutConfig);\n```\n\n\u003cbr\u003e\n\n### refreshTokenIfExpired()\n\n```ts\n// Definition (App Router \u0026 Pages Router)\nrefreshTokenIfExpired: (refreshToken: string, expiresAt: number) =\u003e Promise\u003cTokenData | null\u003e;\n\n// Usage (App Router \u0026 Pages Router)\nconst tokenData = await wristbandAuth.refreshTokenIfExpired('98yht308hf902hc90wh09', 1710707503788);\n```\n\nIf your application is using access tokens generated by Wristband either to make API calls to Wristband or to protect other backend APIs, then your applicaiton needs to ensure that access tokens don't expire until the user's session ends.  You can use the refresh token to generate new access tokens.\n\n| Argument | Type | Required | Description |\n| -------- | ---- | -------- | ----------- |\n| expiresAt | number | Yes | Unix timestamp in milliseconds at which the token expires. |\n| refreshToken | string | Yes | The refresh token used to send to Wristband when access tokens expire in order to receive new tokens. |\n\nIf the `refreshTokenIfExpired()` functions finds that your token has not expired yet, it will return `null` as the value, which means your auth middleware can simply continue forward as usual.\n\nThe `TokenData` is defined as follows:\n\n| TokenData Field | Type | Description |\n| --------------- | ---- | ----------- |\n| accessToken | string | The access token that can be used for accessing Wristband APIs as well as protecting your application's backend APIs. |\n| expiresAt | number | The absolute expiration time of the access token in milliseconds since the Unix epoch. The `tokenExpirationBuffer` SDK configuration is accounted for in this value. |\n| expiresIn | number | The duration from the current time until the access token is expired (in seconds). The `tokenExpirationBuffer` SDK configuration is accounted for in this value. |\n| idToken | string | The ID token uniquely identifies the user that is authenticating and contains claim data about the user. |\n| refreshToken | string or `undefined` | The refresh token that renews expired access tokens with Wristband, maintaining continuous access to services. |\n\n\u003cbr\u003e\n\n## Session Management\n\nThis SDK provides encrypted cookie-based session management powered by [@wristband/typescript-session](https://github.com/wristband-dev/typescript-session). Sessions are automatically created and managed through the SDK's auth endpoints and middleware, with session data encrypted using AES-256-GCM before being stored in cookies.\n\nSessions in Next.js work differently depending on the context:\n\n- **Middleware \u0026 API Routes** - Full read/write access. Changes are persisted by calling `session.saveToResponse()` which adds session cookies to the response headers.\n- **Server Components (non-page)** - Read-only access (cannot modify, save, or destroy sessions because response headers cannot be set during the RSC rendering phase).\n- **Server Actions** - Full read/write access. Changes are persisted by calling `saveSessionWithCookies()` which uses Next.js's `cookies()` API to set session cookies.\n- **Pages Router** - Full read/write access. Changes are persisted by calling `session.save()` which automatically sets cookies on the response object managed by Next.js.\n\nThe following sections provide comprehensive documentation on session configuration, authentication middleware, helper functions, and session methods.\n\n\u003cbr\u003e\n\n### Session Configuration\n\nSession behavior is configured when you create your session helper functions in your Wristband configuration file (typically `src/wristband.ts`). The configuration is shared across all session contexts (middleware, API routes, Server Components, Server Actions).\n\n```typescript\n// src/wristband.ts\nimport { SessionOptions } from '@wristband/nextjs-auth';\n\nconst sessionOptions: SessionOptions = {\n  // Session cookie configs\n  cookieName: 'session',\n  secrets: 'your-secret-key-min-32-chars',\n  domain: 'app.example.com',\n  maxAge: 3600,\n  path: '/',\n  sameSite: 'lax',\n  secure: true,\n\n  // Optional CSRF token protection configs\n  enableCsrfProtection: true,\n  csrfCookieName: 'CSRF-TOKEN',\n  csrfCookieDomain: '.example.com',\n};\n```\n\n#### Configuration Options\n\n| Parameter | Type | Required | Default | Description |\n| --------- | ---- | -------- | ------- | ----------- |\n| secrets | string or string[] | Yes | N/A | Secret key(s) for session encryption (minimum 32 characters). Can be a single string or array of strings for key rotation. Run `openssl rand -base64 32` to generate a secret. |\n| cookieName | string | No | `session` | Name of the session cookie. |\n| maxAge | number | No | 3600 (1 hour) | Cookie expiration time in seconds. |\n| secure | boolean | No | `true` | Require HTTPS for cookies. **Must be `true` in production.** Set to `false` only for local development without HTTPS. |\n| sameSite | `lax` \\| `strict` \\| `none` | No | `lax` | Cookie SameSite attribute. |\n| path | string | No | `/` | Cookie path. |\n| domain | string | No | `undefined` | Domain for the session cookie. When undefined, the cookie is only sent to the current domain. |\n| enableCsrfProtection | boolean | No | `false` | When enabled, a CSRF token is automatically generated after authentication (via  `session.save()` and other save functions) and is stored in the session. A separate CSRF cookie is also set in addition to the session cookie. If using `createMiddlewareAuth()`, that will also automatically enable CSRF token validaiton for protected API requests when using the `'SESSION'` strategy. |\n| csrfCookieName | string | No | `CSRF-TOKEN` | Name of the CSRF cookie (only used when `enableCsrfProtection` is `true`). |\n| csrfCookieDomain | string | No | `undefined` | Domain for the CSRF cookie. Defaults to the `domain` value if not specified. |\n\nFor complete details on session configuration options, see the [@wristband/typescript-session](https://github.com/wristband-dev/typescript-session#sessionoptions) documentation.\n\n\u003cbr\u003e\n\n### The Session Object\n\nOnce session management is configured, you can access session data through the session object returned by the various session helper functions. The session contains both authentication data (populated after login) and any custom fields you define. The session data is typed using the `SessionData` interface from [@wristband/typescript-session](https://github.com/wristband-dev/typescript-session?tab=readme-ov-file#typescript-support). You can access session data using both dictionary-style access (`session['key']`) and attribute-style access (`session.key`).\n\n#### Understanding Session State\n\nSessions start empty. All base session fields are initially `undefined` because the session begins with no data. Session fields are only populated when you either:\n\n- Call `session.fromCallback(callbackData)` after successful authentication (automatically sets all auth-related fields)\n- Manually set fields and call `session.save()` (or any other save function) to persist them\n\nThis means before authentication, fields like `userId`, `accessToken`, etc. will be `undefined`.\n\n#### Base Session Fields\n\nThese fields are automatically populated when you call `session.fromCallback()` after successful Wristband authentication:\n\n| SessionData Field | Type | Description |\n| ----------------- | ---- | ----------- |\n| isAuthenticated | boolean or `undefined` | Whether the user is authenticated (set to `true` by `fromCallback()`). |\n| accessToken | string or `undefined` | JWT access token for making authenticated API calls to Wristband and other services. |\n| expiresAt | number or `undefined` | Token expiration timestamp (milliseconds since Unix epoch). Accounts for `tokenExpirationBuffer` from SDK config. |\n| userId | string or `undefined` | Unique identifier for the authenticated user. |\n| tenantId | string or `undefined` | Unique identifier for the tenant that the user belongs to. |\n| tenantName | string or `undefined` | Name of the tenant that the user belongs to. |\n| identityProviderName | string or `undefined` | Name of the identity provider that the user belongs to. |\n| csrfToken | string or `undefined` | CSRF token for request validation. Automatically generated and stored when the session is saved (via `session.save()`, `session.saveToResponse()`, or `saveSessionWithCookies()`) if CSRF protection is enabled in `SessionOptions`. |\n| refreshToken | string or `undefined` | Refresh token for obtaining new access tokens when they expire. Only present if `offline_access` scope was requested during authentication. |\n| tenantCustomDomain | string or `undefined` | Custom domain for the tenant, if configured. Only present if a tenant custom domain was used during authentication. |\n\n#### Extending SessionData with Custom Fields\n\nYou can extend the `SessionData` interface to add type-safe custom fields to your session:\n\n```typescript\n// src/types/session.ts\nimport { SessionData } from '@wristband/nextjs-auth';\n\n/**\n * Custom session data type for this application.\n * Extends the base SessionData from the Wristband SDK with app-specific fields.\n */\nexport interface MySessionData extends SessionData {\n  theme?: string;\n  preferences?: {\n    notifications: boolean;\n    language: string;\n  };\n  lastActivity?: number;\n}\n```\n\nThen use the generic type parameter when creating your session helpers. For example:\n\n```typescript\n// src/wristband.ts\nimport { getSessionFromRequest, SessionOptions } from '@wristband/nextjs-auth';\nimport { MySessionData } from './types/session';\n\nconst sessionOptions: SessionOptions = { secrets: 'your-secret-key-min-32-chars' };\n\nexport function getRequestSession(request: NextRequest) {\n  // Add your custom type with generics on the Wristband SDK's session functions.\n  return getSessionFromRequest\u003cMySessionData\u003e(request, sessionOptions);\n}\n```\n\nNow your session will have full type safety for both base and custom fields:\n\n```typescript\n// src/app/api/settings/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { getRequestSession } from '../../../wristband';\n\nexport async function POST(req: NextRequest) {\n  const session = await getRequestSession(req);\n  \n  session.theme = 'dark';    // ✅ Type-safe\n  session.preferences = {    // ✅ Type-safe\n    notifications: true,     // ✅ Type-safe\n    language: 'en',          // ✅ Type-safe\n  };\n  \n  return await session.saveToResponse(\n    NextResponse.json({ success: true })\n  );\n}\n```\n\n\u003cbr\u003e\n\n### Session Helper Functions\n\nThe SDK provides context-specific helper functions for accessing sessions in different Next.js environments. These functions handle the underlying complexity of Next.js's various request/response patterns and return session objects appropriate for each context.\n\n\u003cbr\u003e\n\n#### getSessionFromRequest()\n\nRetrieves a session from a `NextRequest` object. Use this in App Router middleware, API route handlers, and other contexts where you have access to a `NextRequest`.\n\n**Compatible Contexts:**\n\n- App Router (API Routes)\n- Middleware/Proxy\n\n**Signature:**\n\n```typescript\nfunction getSessionFromRequest\u003cT extends SessionData = SessionData\u003e(\n  request: NextRequest,\n  options: SessionOptions\n): Promise\u003cSession\u003cT\u003e \u0026 T\u003e\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| request | `NextRequest` | Yes | The Next.js request object. |\n| options | `SessionOptions` | Yes | [Session configuration](#session-configuration) options. |\n\n**Usage:**\n\n```typescript\n// src/wristband.ts\nimport { NextRequest } from 'next/server';\nimport { getSessionFromRequest, SessionOptions } from '@wristband/nextjs-auth';\n\nconst sessionOptions: SessionOptions = {\n  secrets: 'your-secret-key-min-32-chars',\n  maxAge: 3600,\n  secure: process.env.NODE_ENV === 'production',\n};\n\nexport function getRequestSession(request: NextRequest) {\n  return getSessionFromRequest(request, sessionOptions);\n}\n```\n```typescript\n// src/app/api/orders/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { getRequestSession } from '../../../wristband';\n\nexport async function GET(req: NextRequest) {\n  const session = await getRequestSession(req);\n  \n  if (!session.isAuthenticated) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n  \n  return NextResponse.json({ userId: session.userId });\n}\n```\n\n\u003cbr\u003e\n\n#### getPagesRouterSession()\n\nRetrieves a session from Pages Router's Node.js request and response objects. Use this in Pages Router API routes and `getServerSideProps()`.\n\n**Compatible Contexts:**\n\n- Pages Router\n\n**Signature:**\n\n```typescript\nfunction getPagesRouterSession\u003cT extends SessionData = SessionData\u003e(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  options: SessionOptions\n): Promise\u003cSession\u003cT\u003e \u0026 T\u003e\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| req | `http.IncomingMessage` | Yes | Node.js incoming message (Pages Router request). |\n| res | `http.ServerResponse` | Yes | Node.js server response (Pages Router response). |\n| options | `SessionOptions` | Yes | [Session configuration](#session-configuration) options. |\n\n**Usage:**\n\n```typescript\n// src/wristband.ts\nimport * as http from 'http';\nimport { getPagesRouterSession, SessionOptions } from '@wristband/nextjs-auth';\n\nconst sessionOptions: SessionOptions = {\n  secrets: 'your-secret-key-min-32-chars',\n  maxAge: 3600,\n  secure: process.env.NODE_ENV === 'production',\n};\n\nexport function getSession(req: http.IncomingMessage, res: http.ServerResponse) {\n  return getPagesRouterSession(req, res, sessionOptions);\n}\n```\n```typescript\n// src/pages/api/profile.ts\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { getSession } from '../../wristband';\n\nexport default async function handler(req: NextApiRequest, res: NextApiResponse) {\n  const session = await getSession(req, res);\n  \n  if (!session.isAuthenticated) {\n    return res.status(401).json({ error: 'Unauthorized' });\n  }\n  \n  return res.json({ userId: session.userId });\n}\n```\n\n\u003cbr\u003e\n\n#### getReadOnlySessionFromCookies()\n\nRetrieves a read-only session from Next.js `cookies()` API. Use this in App Router Server Components where sessions cannot be modified because response headers cannot be set during the React Server Components (RSC) rendering phase.\n\n**Compatible Contexts:**\n\n- App Router (Server Components)\n\n**Signature:**\n\n```typescript\nfunction getReadOnlySessionFromCookies\u003cT extends SessionData = SessionData\u003e(\n  cookieStore: NextJsCookieStore,\n  options: SessionOptions\n): Promise\u003cReadonlySession\u003cT\u003e \u0026 T\u003e\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| cookieStore | `NextJsCookieStore` | Yes | The result of `await cookies()` from `next/headers`. |\n| options | `SessionOptions` | Yes | [Session configuration](#session-configuration) options. |\n\n**Usage:**\n\n```typescript\n// src/wristband.ts\nimport { getReadOnlySessionFromCookies, NextJsCookieStore, SessionOptions } from '@wristband/nextjs-auth';\n\nconst sessionOptions: SessionOptions = {\n  secrets: 'your-secret-key-min-32-chars',\n  maxAge: 3600,\n  secure: process.env.NODE_ENV === 'production',\n};\n\nexport function getServerComponentSession(cookieStore: NextJsCookieStore) {\n  return getReadOnlySessionFromCookies(cookieStore, sessionOptions);\n}\n```\n```typescript\n// src/app/dashboard/page.tsx\nimport { cookies } from 'next/headers';\nimport { getServerComponentSession } from '../../wristband';\n\nexport default async function DashboardPage() {\n  const session = await getServerComponentSession(await cookies());\n  \n  if (!session.isAuthenticated) {\n    return \u003cdiv\u003ePlease log in.\u003c/div\u003e;\n  }\n  \n  return \u003cdiv\u003eWelcome, {session.userId}\u003c/div\u003e;\n}\n```\n\n\u003e [!WARNING]\n\u003e Read-only sessions cannot call `save()`, `destroy()`, `saveToResponse()`, or `destroyToResponse()`. Attempting to do so will throw an error. Use `getMutableSessionFromCookies()` in Server Actions if you need to modify the session.\n\n\u003cbr\u003e\n\n#### getMutableSessionFromCookies()\n\nRetrieves a mutable session from Next.js `cookies()` API. Use this in App Router Server Actions where you need to modify session data. Unlike `getReadOnlySessionFromCookies()`, this returns a session that can be modified and saved using `saveSessionWithCookies()`.\n\n**Compatible Contexts:**\n\n- App Router (Server Actions)\n\n**Signature:**\n\n```typescript\nfunction getMutableSessionFromCookies\u003cT extends SessionData = SessionData\u003e(\n  cookieStore: NextJsCookieStore,\n  options: SessionOptions\n): Promise\u003cMutableSession\u003cT\u003e \u0026 T\u003e\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| cookieStore | `NextJsCookieStore` | Yes | The result of `await cookies()` from `next/headers`. |\n| options | `SessionOptions` | Yes | [Session configuration](#session-configuration) options. |\n\n**Usage:**\n\n```typescript\n// src/wristband.ts\nimport { getMutableSessionFromCookies, NextJsCookieStore, SessionOptions } from '@wristband/nextjs-auth';\n\nconst sessionOptions: SessionOptions = {\n  secrets: 'your-secret-key-min-32-chars',\n  maxAge: 3600,\n  secure: process.env.NODE_ENV === 'production',\n};\n\nexport async function getServerActionSession(cookieStore: NextJsCookieStore) {\n  return await getMutableSessionFromCookies(cookieStore, sessionOptions);\n}\n```\n```typescript\n// src/app/actions/update-theme.ts\n'use server';\n\nimport { cookies } from 'next/headers';\nimport { getServerActionSession, saveServerActionSession } from '../../wristband';\n\nexport async function updateTheme(theme: string) {\n  const cookieStore = await cookies();\n  const session = await getServerActionSession(cookieStore);\n  \n  if (!session.isAuthenticated) {\n    return { error: 'Unauthorized' };\n  }\n  \n  session.theme = theme;\n  await saveServerActionSession(cookieStore, session);\n  \n  return { success: true };\n}\n```\n\n\u003cbr\u003e\n\n#### saveSessionWithCookies()\n\nSaves a mutable session by setting cookies using Next.js `cookies()` API. Use this in Server Actions after modifying session data retrieved via `getMutableSessionFromCookies()`.\n\n**Compatible Contexts:**\n\n- App Router (Server Actions)\n\n**Signature:**\n\n```typescript\nfunction saveSessionWithCookies\u003cT extends SessionData = SessionData\u003e(\n  cookieStore: NextJsCookieStore,\n  session: MutableSession\u003cT\u003e \u0026 T\n): Promise\u003cvoid\u003e\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| cookieStore | `NextJsCookieStore` | Yes | The result of `await cookies()` from `next/headers`. |\n| session | `MutableSession\u003cT\u003e \u0026 T` | Yes | The mutable session to save. |\n\n**Usage:**\n\n```typescript\n// src/wristband.ts\nimport { saveSessionWithCookies, MutableSession, NextJsCookieStore } from '@wristband/nextjs-auth';\n\nexport async function saveServerActionSession(\n  cookieStore: NextJsCookieStore,\n  session: MutableSession\n) {\n  await saveSessionWithCookies(cookieStore, session);\n}\n```\n```typescript\n// src/app/actions/update-preferences.ts\n'use server';\n\nimport { cookies } from 'next/headers';\nimport { getServerActionSession, saveServerActionSession } from '../../wristband';\n\nexport async function updatePreferences(preferences: any) {\n  const cookieStore = await cookies();\n  const session = await getServerActionSession(cookieStore);\n  \n  session.preferences = preferences;\n  await saveServerActionSession(cookieStore, session);\n  \n  return { success: true };\n}\n```\n\n\u003e [!NOTE]\n\u003e This function automatically handles Next.js caching behavior. Setting cookies invalidates the Next.js Router Cache and triggers a re-render of the current route.\n\n\u003cbr\u003e\n\n#### destroySessionWithCookies()\n\nDestroys a session and clears all session cookies using Next.js `cookies()` API. Use this in Server Actions when you need to manually destroy a session (e.g., custom logout flows).\n\n**Compatible Contexts:**\n\n- App Router (Server Actions)\n\n**Signature:**\n\n```typescript\nfunction destroySessionWithCookies\u003cT extends SessionData = SessionData\u003e(\n  cookieStore: NextJsCookieStore,\n  session: MutableSession\u003cT\u003e \u0026 T\n): void\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| cookieStore | `NextJsCookieStore` | Yes | The result of `await cookies()` from `next/headers`. |\n| session | `MutableSession\u003cT\u003e \u0026 T` | Yes | The mutable session to destroy. |\n\n**Usage:**\n\n```typescript\n// src/wristband.ts\nimport { destroySessionWithCookies, MutableSession, NextJsCookieStore } from '@wristband/nextjs-auth';\n\nexport function destroyServerActionSession(\n  cookieStore: NextJsCookieStore,\n  session: MutableSession\n) {\n  destroySessionWithCookies(cookieStore, session);\n}\n```\n```typescript\n// src/app/actions/custom-logout.ts\n'use server';\n\nimport { cookies } from 'next/headers';\nimport { getServerActionSession, destroyServerActionSession } from '../../wristband';\nimport { redirect } from 'next/navigation';\n\nexport async function customLogout() {\n  const cookieStore = await cookies();\n  const session = await getServerActionSession(cookieStore);\n  \n  destroyServerActionSession(cookieStore, session);\n  redirect('/login');\n}\n```\n\n\u003e [!NOTE]\n\u003e This function automatically handles Next.js caching behavior. Deleting cookies invalidates the Next.js Router Cache and triggers a re-render of the current route.\n\n\u003cbr\u003e\n\n### Session Access Patterns\n\nSessions behave like plain JavaScript objects, supporting both dot notation (`session.userId`) and bracket notation (`session['userId']`) for getting, setting, checking, and deleting values.\n\n#### Reading Values\n```typescript\n// Dot notation\nconst userId = session.userId;\nconst theme = session.theme;\n\n// Bracket notation\nconst userId = session['userId'];\nconst theme = session['theme'];\n\n// With optional chaining\nconst notifications = session.preferences?.notifications;\n```\n\n#### Setting Values\n```typescript\n// Dot notation\nsession.userId = '123';\nsession.theme = 'dark';\n\n// Bracket notation\nsession['userId'] = '123';\nsession['theme'] = 'dark';\n\n// Nested objects\nsession.preferences = {\n  notifications: true,\n  language: 'en'\n};\n```\n\n#### Checking Existence\n```typescript\n// Using 'in' operator\nif ('theme' in session) {\n  console.log('Theme is set:', session.theme);\n}\n\n// Checking for undefined\nif (session.userId !== undefined) {\n  console.log('User ID:', session.userId);\n}\n\n// Optional chaining with nullish coalescing\nconst theme = session.theme ?? 'light';\n```\n\n#### Deleting Values\n```typescript\n// Delete operator\ndelete session.theme;\ndelete session['preferences'];\n\n// Verify deletion\nconsole.log('theme' in session); // false\n```\n\n#### Iterating Over Session Data\n```typescript\n// Get all keys\nconst keys = Object.keys(session);\n\n// Iterate over entries\nfor (const [key, value] of Object.entries(session)) {\n  console.log(`${key}: ${value}`);\n}\n```\n\n#### Additional Session Methods\n\nFor more session operations, see the [@wristband/typescript-session](https://github.com/wristband-dev/typescript-session#core-methods-all-runtimes) documentation which provides additional methods like:\n\n- `session.get(key)` - Get a value with optional default\n- `session.set(key, value)` - Set a value\n- `session.delete(key)` - Delete a value\n- `session.has(key)` - Check if key exists\n- `session.clear()` - Clear all session data\n- `session.toJSON()` - Get session as plain object\n\n#### Limitations\n\n**JSON Serialization:** All values stored in the session must be JSON-serializable. Attempting to store non-serializable values (functions, class instances, objects with circular references) will result in errors when the session is encrypted and saved.\n\n**Size Limit:** Sessions are limited to 4KB total, including encryption overhead and cookie attributes. This limit is enforced by browsers per [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265). If your session exceeds this limit, an error will be thrown when attempting to save.\n\n**Read-Only in Server Components:** Server Components cannot modify sessions because they render during the RSC phase where response headers cannot be set. Use `getReadOnlySessionFromCookies()` for reading session data and `getMutableSessionFromCookies()` in Server Actions for modifications.\n\n\u003cbr\u003e\n\n### Session API\n\nThe session object provides several methods for managing sessions and authentication data. These include lifecycle methods for persisting and destroying sessions, as well as Wristband-specific methods for creating sessions from callback data and generating responses for frontend SDKs.\n\n\u003cbr\u003e\n\n#### session.fromCallback()\n\nCreate a session from Wristband callback data after successful authentication. This is a convenience method that automatically extracts a core subset of user and tenant info from the authentication callback data.\n\n**Compatible Context:**\n\n- App Router\n- Pages Router\n\n**Signature:**\n\n```typescript\nsession.fromCallback(callbackData: CallbackData, customFields?: Record\u003cstring, any\u003e): void\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| callbackData | `CallbackData` | Yes | The callback data from `wristbandAuth.callback()`. An error is thrown if `callbackData` is null or `callbackData.userinfo` is missing. |\n| customFields | `Record\u003cstring, any\u003e` | No | Additional custom fields to store in the session. Must be JSON-serializable. |\n\n**Usage:**\n\n```typescript\n// App Router: Callback endpoint\nimport { NextRequest } from 'next/server';\nimport { getRequestSession, wristbandAuth } from '../../../../wristband';\n\nexport async function GET(req: NextRequest) {\n  const callbackResult = await wristbandAuth.appRouter.callback(req);\n  const { callbackData, redirectUrl, type } = callbackResult;\n\n  if (type === 'redirect_required') {\n    return await wristbandAuth.appRouter.createCallbackResponse(req, redirectUrl);\n  }\n  \n  const session = await getRequestSession(req);\n  \n  // Basic usage\n  session.fromCallback(callbackData);\n  \n  // With custom fields\n  session.fromCallback(callbackData, {\n    loginTime: Date.now(),\n    loginIp: req.ip\n  });\n\n  const appUrl = callbackData.returnUrl || '/';\n  const callbackResponse = await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);\n  \n  return await session.saveToResponse(callbackResponse);\n}\n```\n\n**Fields Automatically Set:**\n\nThe following fields are automatically populated from the callback data:\n\n- `isAuthenticated` (always set to `true`)\n- `accessToken`\n- `expiresAt`\n- `userId` (from `callbackData.userinfo.userId`)\n- `tenantId` (from `callbackData.userinfo.tenantId`)\n- `tenantName`\n- `identityProviderName` (from `callbackData.userinfo.identityProviderName`)\n- `refreshToken` (only if `offline_access` scope was requested)\n- `tenantCustomDomain` (only if a tenant custom domain was used)\n\n\u003cbr\u003e\n\n#### session.save()\n\nSaves the session by setting the session cookie (and CSRF cookie if enabled). Refreshes cookie expiration (rolling sessions) and persists any session modifications. If CSRF protection is enabled and no CSRF token exists yet, this also generates and stores a CSRF token in both the session (`csrfToken` field) and a separate CSRF cookie. Use this method in Pages Router contexts where the response object is managed by Next.js.\n\n**Compatible Context:**\n\n- Pages Router\n\n**Signature:**\n\n```typescript\nsession.save(): Promise\u003cvoid\u003e\n```\n\n**Usage:**\n```typescript\n// Pages Router: API route\nimport type { NextApiRequest, NextApiResponse } from 'next';\nimport { getSession } from '../../wristband';\n\nexport default async function handler(req: NextApiRequest, res: NextApiResponse) {\n  const session = await getSession(req, res);\n  \n  session.theme = req.body.theme;\n  session.lastActivity = Date.now();\n  \n  await session.save();\n  \n  res.json({ success: true });\n}\n```\n\n\u003cbr\u003e\n\n#### session.saveToResponse()\n\nSaves the session by adding session and CSRF cookie headers to a Next.js response object. Refreshes cookie expiration (rolling sessions) and persists any session modifications. Generates a CSRF token if CSRF protection is enabled and no token exists yet. Use this method in App Router contexts (route handlers, middleware/proxy) where you need to manually manage response headers. Returns the same response object with cookie headers added.\n\n**Compatible Context:**\n\n- App Router\n- Middleware/Proxy\n\n**Signature:**\n\n```typescript\nsession.saveToResponse(response: NextResponse): Promise\u003cNextResponse\u003e\n```\n\n**Parameters:**\n\n| Parameter | Type | Required | Description |\n| --------- | ---- | -------- | ----------- |\n| response | `NextResponse` | Yes | The Next.js response object to add session cookies to. |\n\n**Returns:**\n\nThe same `NextResponse` object with session cookie headers added.\n\n**Usage:**\n\n```typescript\n// App Router: Route handler\nimport { NextRequest, NextResponse } from 'next/server';\nimport { getRequestSession } from '../../../wristband';\n\nexport async function POST(req: NextRequest) {\n  const session = await getRequestSession(req);\n  const { theme } = await req.json();\n  \n  session.theme = theme;\n  \n  return await session.saveToResponse(\n    NextResponse.json({ success: true })\n  );\n}\n```\n\n\u003cbr\u003e\n\n#### session.destroy()\n\nDelete the session data and clear all cookie","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwristband-dev%2Fnextjs-auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwristband-dev%2Fnextjs-auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwristband-dev%2Fnextjs-auth/lists"}