@wristband/nextjs-auth
v4.0.2
Published
SDK for integrating your Next.js application with Wristband. Handles user authentication, session management, and token management.
Readme
Wristband Multi-Tenant Authentication SDK for Next.js
Enterprise-ready authentication for multi-tenant Next.js applications using OAuth 2.1 and OpenID Connect standards. It works for both the Next.js App Router and Pages Router.
Overview
This SDK provides complete authentication integration with Wristband, including:
- Login flow - Redirect to Wristband and handle OAuth callbacks
- Session management - Encrypted cookie-based sessions with optional CSRF token protection
- Token handling - Automatic access token refresh and validation
- Logout flow - Token revocation and session cleanup
- Multi-tenancy - Support for tenant subdomains and custom domains
Learn more about Wristband's authentication patterns:
💡 Learn by Example
Want to see the SDK in action? Check out our Next.js demo applications. The demos showcase real-world authentication patterns and best practices.
Table of Contents
- Migrating From Older SDK Versions
- Prerequisites
- Installation
- Usage
- Wristband Auth Configuration Options
- Auth API
- Session Management
- Authentication Middleware
- Related Wristband SDKs
- Wristband Multi-Tenant Next.js Demo Apps
- Questions
Migrating From Older SDK Versions
On an older version of our SDK? Check out our migration guide:
- Instructions for migrating to Version 4.x
- Instructions for migrating to Version 3.x
- Instructions for migrating to Version 2.x
Prerequisites
⚡ Try Our Next.js Quickstart!
For the fastest way to get started with Next.js authentication, follow our Quick Start Guide. 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.
Before installing, ensure you have:
Installation
# With npm
npm install @wristband/nextjs-auth
# Or with yarn
yarn add @wristband/nextjs-auth
# Or with pnpm
pnpm add @wristband/nextjs-authUsage
1) Initialize the Auth SDK
First, 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.
// src/wristband.ts
import { createWristbandAuth } from '@wristband/nextjs-auth';
/**
* Wristband authentication instance for handling login, callback, and logout flows.
*/
export const wristbandAuth = createWristbandAuth({
clientId: "replace-me-with-your-client-id",
clientSecret: "replace-me-with-your-client-secret",
wristbandApplicationVanityDomain: "replace-me-with-your-vanity-domain",
});💡 Disabling Secure Cookies in Local Development
By default,
WristbandAuthcreates 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, setdangerouslyDisableSecureCookies: true. However, be sure to re-enable secure cookies before deploying to production.
2) Set Up Session Management
Wristband provides encrypted cookie-based session management built directly into this SDK, powered by @wristband/typescript-session. Add basic session configuration to enable the auth endpoints (Login, Callback, etc.) in the next steps.
App Router
// src/wristband.ts (continued - add to existing file)
import { NextRequest } from 'next/server';
import { getSessionFromRequest, SessionOptions } from '@wristband/nextjs-auth';
// ...
/**
* Session configuration for authentication.
*
* IMPORTANT: Use a strong 32+ character secret in production and set secure: true
*/
const sessionOptions: SessionOptions = {
secrets: 'dummyval-b5c1-463a-812c-0d8db87c0ec5', // 32+ character secret
maxAge: 3600, // 1 hour in seconds
secure: process.env.NODE_ENV === 'production', // Must be true in Production
};
/**
* Retrieves the session from a NextRequest.
*
* Use in:
* - App Router API Route Handlers
* - Middleware/proxy functions
*/
export function getRequestSession(request: NextRequest) {
return getSessionFromRequest(request, sessionOptions);
}Pages Router
// src/wristband.ts (continued - add to existing file)
import * as http from 'http';
import { getPagesRouterSession, SessionOptions } from '@wristband/nextjs-auth';
// ...
/**
* Session configuration for authentication.
*
* IMPORTANT: Use a strong 32+ character secret in production and set secure: true
*/
const sessionOptions: SessionOptions = {
secrets: 'dummyval-b5c1-463a-812c-0d8db87c0ec5', // 32+ character secret
maxAge: 3600, // 1 hour in seconds
secure: process.env.NODE_ENV === 'production', // Must be true in Production
};
/**
* Retrieves session from Pages Router API routes and SSR functions.
*
* Use in:
* - Pages Router API Route Handlers
* - getServerSideProps()
*/
export function getSession(req: http.IncomingMessage, res: http.ServerResponse) {
return getPagesRouterSession(req, res, sessionOptions);
}3) Add Auth Endpoints
There are four core API endpoints your Next.js server should expose to facilitate authentication workflows in Wristband:
- Login Endpoint
- Callback Endpoint
- Logout Endpoint
- Session Endpoint
You'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:
- Token Endpoint (optional)
Login Endpoint
The goal of the Login Endpoint is to initiate an auth request by redirecting to the Wristband Authorization Endpoint. 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.
App Router
// src/app/api/auth/login/route.ts
import type { NextRequest } from 'next/server';
import { wristbandAuth } from '../../../../wristband';
// Login Endpoint at "/api/auth/login" (route can be wherever you prefer)
export async function GET(req: NextRequest) {
return await wristbandAuth.appRouter.login(req);
}Pages Router
// src/pages/api/auth/login.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { wristbandAuth } from '../../../wristband';
// Login Endpoint at "/api/auth/login" (route can be wherever you prefer)
export default async function loginEndpoint(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
res.status(405).end();
return;
}
const authorizeUrl = await wristbandAuth.pagesRouter.login(req, res);
res.redirect(authorizeUrl);
}Callback Endpoint
The 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 to fetch necessary JWTs, call the Wristband Userinfo Endpoint to get the user's data, and create a session for the application containing the JWTs and user data.
App Router
// src/app/api/auth/callback/route.ts
import { NextRequest } from 'next/server';
import { getRequestSession, wristbandAuth } from '../../../../wristband';
// Callback Endpoint at "/api/auth/callback" (route can be wherever you prefer)
export async function GET(req: NextRequest) {
const callbackResult = await wristbandAuth.appRouter.callback(req);
const { callbackData, redirectUrl, type } = callbackResult;
if (type === 'redirect_required') {
return await wristbandAuth.appRouter.createCallbackResponse(req, redirectUrl);
}
// Set authentication data into the session
const session = await getRequestSession(req);
session.fromCallback(callbackData);
// Create the response that will send the user back to your application.
const appUrl = callbackData.returnUrl || `<your_app_home_url>`;
const callbackResponse = await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);
// Save session headers to the response; then redirect to your app.
return await session.saveToResponse(callbackResponse);
}Pages Router
// src/pages/api/auth/callback.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession, wristbandAuth } from '../../../wristband';
// Callback Endpoint at "/api/auth/callback" (route can be wherever you prefer)
export default async function callbackEndpoint(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
res.status(405).end();
return;
}
const callbackResult = await wristbandAuth.pagesRouter.callback(req, res);
const { callbackData, redirectUrl, type } = callbackResult;
if (type === 'redirect_required') {
res.redirect(redirectUrl);
return;
}
// Save authentication data in the session
const session = await getSession(req, res);
session.fromCallback(callbackData);
await session.save();
// Send the user back to the application.
res.redirect(callbackData.returnUrl || `<your_app_home_url>`);
}Logout Endpoint
The 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 will occur. It then will redirect to the Wristband Logout Endpoint 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).
App Router
// src/app/api/auth/logout/route.ts
import type { NextRequest } from 'next/server';
import { getRequestSession, wristbandAuth } from '../../../../wristband';
// Logout Endpoint at "/api/auth/logout" (route can be wherever you prefer)
export async function GET(req: NextRequest) {
const session = await getRequestSession(req);
// Create the logout redirect response
const logoutResponse = await wristbandAuth.appRouter.logout(req, {
refreshToken: session.refreshToken,
tenantCustomDomain: session.tenantCustomDomain,
tenantName: session.tenantName,
});
// Always destroy session before redirecting.
return await session.destroyToResponse(logoutResponse);
});Pages Router
// src/pages/api/auth/logout.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession, wristbandAuth } from '../../../wristband';
// Logout Endpoint at "/api/auth/logout" (route can be wherever you prefer)
export default async function logoutEndpoint(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
res.status(405).end();
return;
}
// Create the logout redirect URL
const session = await getSession(req, res);
const logoutUrl = await wristbandAuth.pagesRouter.logout(req, res, {
refreshToken: session.refreshToken,
tenantCustomDomain: session.tenantCustomDomain,
tenantName: session.tenantName,
});
// Always destroy session before redirecting.
session.destroy();
res.redirect(logoutUrl);
});Session Endpoint
[!NOTE] This endpoint is required for Wristband frontend SDKs to function. For more details, see the Wristband Session Management documentation.
Wristband 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.
⚠️ Important: This endpoint must be protected with authentication middleware, which is shown in Section 4.
App Router
// src/app/api/auth/session/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getRequestSession } from '../../../../wristband';
// Session Endpoint at "/api/auth/session" (route can be wherever you prefer)
export async function GET(req: NextRequest) {
const session = await getRequestSession(req);
const sessionResponse = session.getSessionResponse({ foo: 'bar' });
return NextResponse.json(sessionResponse, {
headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache' },
});
});Page Router
// src/pages/api/auth/session.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from '../../../wristband';
// Session Endpoint at "/api/auth/session" (route can be wherever you prefer)
export default async function sessionEndpoint(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
res.status(405).end();
return;
}
const session = await getSession(req, res);
const sessionResponse = session.getSessionResponse({ foo: 'bar' });
res.setHeader('Cache-Control', 'no-store');
res.setHeader('Pragma', 'no-cache');
res.status(200).json(sessionResponse);
}Response Type
The Session Endpoint returns the SessionResponse type to your frontend:
{
"tenantId": "tenant_abc123",
"userId": "user_xyz789",
"metadata": {
"foo": "bar",
// Any other optional data you provide...
}
}Token Endpoint (Optional)
[!NOTE] 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.
If your application doesn't need frontend access to tokens (e.g., all API calls go through your backend), you can skip this endpoint.
Some 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.
Create 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.
⚠️ Important: This endpoint must be protected with authentication middleware, which is shown in Section 4.
App Router
// src/app/api/auth/token/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getRequestSession } from '../../../../wristband';
// Token Endpoint at "/api/auth/token" (route can be wherever you prefer)
export async function GET(req: NextRequest) {
const session = await getRequestSession(req);
const tokenResponse = session.getTokenResponse();
return NextResponse.json(tokenResponse, {
headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache' },
});
});Page Router
// src/pages/api/auth/token.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from '../../../wristband';
// Token Endpoint at "/api/auth/token" (route can be wherever you prefer)
export default async function tokenEndpoint(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
res.status(405).end();
return;
}
const session = await getSession(req, res);
const tokenResponse = session.getTokenResponse();
res.setHeader('Cache-Control', 'no-store');
res.setHeader('Pragma', 'no-cache');
res.status(200).json(tokenResponse);
}Response Type
The Token Endpoint returns the TokenResponse type to your frontend:
{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": 1735689600000
}Your frontend can then use the accessToken in the Authorization header when making API requests:
const tokenResponse = await fetch('/api/auth/token');
const { accessToken } = await tokenResponse.json();
// Use token to call Wristband API
const userResponse = await fetch('https://<your-wristband-app-vanity-domain>/api/v1/users/123', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});4) Protect Your Pages, Actions, and APIs
Once your auth endpoints are set up, protect your application routes with authentication middleware and add session helpers for accessing session data in different contexts.
Set Up Authentication Middleware
In 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:
What middleware protects:
- ✅ API Routes (App and Pages Router) - All routes matching
protectedApispatterns - ✅ Session & Token Endpoints -
/api/auth/sessionand/api/auth/tokenare automatically protected when using'SESSION'strategy (customizable viasessionConfig.sessionEndpointandsessionConfig.tokenEndpointconfig) - ✅ Pages (Pages Router) - Server-rendered pages matching
protectedPagespatterns - ✅ Server Components that are pages (App Router) - Page components matching
protectedPagespatterns
What middleware does NOT protect:
- ❌ Server Actions (App Router) - Must use
createServerActionAuth()for manual checks - ❌ 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.
- ❌ 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 frontend SDK.
Add middleware configuration to your wristband.ts file:
// src/wristband.ts (continued - add to existing file)
// ...
/**
* Authentication middleware that protects routes in Next.js middleware.
*
* Automatically handles:
* - Session validation for protected routes
* - Token refresh when access tokens expire
* - 401 responses for unauthenticated API requests
* - Login redirects for unauthenticated page requests
*/
export const requireMiddlewareAuth = wristbandAuth.createMiddlewareAuth({
authStrategies: ['SESSION'],
sessionConfig: { sessionOptions },
protectedApis: ['/api/v1(.*)'], // Regex patterns for protected API routes
protectedPages: ['/', '/dashboard', '/settings(.*)'], // Regex patterns for protected pages
});Now 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).
Next.js 16+:
// src/proxy.ts
import { NextRequest } from 'next/server';
import { requireMiddlewareAuth } from './wristband';
export async function proxy(req: NextRequest) {
return await requireMiddlewareAuth(req);
}
export const config = {
/*
* Match all paths except for:
* 1. /_next (Next.js internals)
* 2. /fonts (inside /public)
* 3. /examples (inside /public)
* 4. all root files inside /public (e.g. /favicon.ico)
*/
matcher: ['/((?!_next|fonts|examples|[\\w-]+\\.\\w+).*)'],
};Next.js 15 and earlier:
// src/middleware.ts
import { NextRequest } from 'next/server';
import { requireMiddlewareAuth } from './wristband';
export async function middleware(req: NextRequest) {
return await requireMiddlewareAuth(req);
}
export const config = {
/*
* Match all paths except for:
* 1. /_next (Next.js internals)
* 2. /fonts (inside /public)
* 3. /examples (inside /public)
* 4. all root files inside /public (e.g. /favicon.ico)
*/
matcher: ['/((?!_next|fonts|examples|[\\w-]+\\.\\w+).*)'],
};The middleware automatically:
- ✅ Validates authentication - Checks each auth strategy in order until one succeeds
- ✅ Refreshes expired tokens - When using
'SESSION'strategy AND whenrefreshTokenandexpiresAtare present in session - ✅ Extends session expiration - Rolling session window on each authenticated request (
'SESSION'strategy only) - ✅ Returns 401 for API Routes - Unauthenticated requests to protected API routes
- ✅ Redirects pages to login - Unauthenticated requests to protected pages (customizable via
onPageUnauthenticated) - ✅ Auto-protects auth endpoints - Session and Token Endpoints protected by default (
'SESSION'strategy only) - ✅ Auto-bypasses Server Actions - Server Action routes skip middleware protection (must manually check auth)
Protect Server Actions (App Router Only)
Server 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:
// src/wristband.ts (continued - add to existing file)
// ...
/**
* Authentication helper for Server Actions.
*
* Server Actions bypass Next.js middleware, so they must perform their own auth checks.
* This helper validates the session and automatically refreshes expired tokens.
*/
export const requireServerActionAuth = wristbandAuth.appRouter.createServerActionAuth({
sessionOptions,
});Here's an example of how to use it in your Server Actions:
// src/app/actions/my-action.ts
'use server';
import { cookies } from 'next/headers';
import { requireServerActionAuth } from './wristband';
export async function updateUserProfile(formData: FormData) {
// The helper function will return you the current session if authentication succeeds.
const cookieStore = await cookies();
const { authenticated, reason, session } = await requireServerActionAuth(cookieStore);
// Check authentication result
if (!authenticated) {
return { error: 'Unauthorized', reason };
}
// Access the authenticated session
const { userId } = session;
// ...your business logic here...
return { success: true };
}Manual Session Access (Optional)
In 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:
- API Route Handlers (App Router & Pages Router)
- Server Components (App Router) - Read-only access
- Server Actions (App Router) - For advanced session mutations beyond
createServerActionAuth() getServerSideProps()(Pages Router)
App Router: Server Components (Read-Only)
Be 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:
// src/wristband.ts (continued - add to existing file)
import { getReadOnlySessionFromCookies, NextJsCookieStore } from '@wristband/nextjs-auth';
// ...
/**
* Retrieves read-only session for Server Components.
*/
export function getServerComponentSession(cookieStore: NextJsCookieStore) {
return getReadOnlySessionFromCookies(cookieStore, sessionOptions);
}Here's an example of how to use it in your Server Components:
// src/app/dashboard/page.tsx
import { cookies } from 'next/headers';
import { getServerComponentSession } from '../../wristband';
export default async function DashboardPage() {
const cookieStore = await cookies();
const session = await getServerComponentSession(cookieStore);
const { isAuthenticated, userId } = session;
if (!isAuthenticated) {
return <div>Please log in.</div>;
}
return <div>Welcome, {userId}</div>;
}App Router: Server Actions (Advanced)
For advanced use cases where you need direct session manipulation without using createServerActionAuth(), add these helpers:
// src/wristband.ts (continued - add to existing file)
import {
getMutableSessionFromCookies,
saveSessionWithCookies,
destroySessionWithCookies,
MutableSession,
} from '@wristband/nextjs-auth';
// ...
/**
* Retrieves mutable session for Server Actions.
* Call saveServerActionSession() after modifying to persist changes.
*/
export async function getServerActionSession(cookies: NextJsCookieStore) {
return await getMutableSessionFromCookies(cookies, sessionOptions);
}
/**
* Saves modified session data back to cookies (Server Actions only).
*/
export async function saveServerActionSession(cookies: NextJsCookieStore, session: MutableSession) {
await saveSessionWithCookies(cookies, session);
}
/**
* Destroys session and clears cookies (Server Actions only).
*/
export function destroyServerActionSession(cookies: NextJsCookieStore, session: MutableSession) {
destroySessionWithCookies(cookies, session);
}Here's an example of how to use it in your Server Actions:
// src/app/actions/my-action.ts
'use server';
import { cookies } from 'next/headers';
import {
destroyServerActionSession,
getServerActionSession,
saveServerActionSession
} from '../../wristband';
export async function customAction() {
// Get session (without performing auth check)
const cookieStore = await cookies();
const session = await getServerActionSession(cookieStore);
// Manually peform auth check
if (!session.isAuthenticated) {
// Destroy session
destroyServerActionSession(cookieStore, session);
return { error: 'Unauthorized' };
}
// Modify session and save changes
session.customField = 'value';
await saveServerActionSession(cookieStore, session);
return { success: true };
}App Router: API Routes
API routes for the App Router can use the getRequestSession() helper already defined in Section 2:
// src/app/api/orders/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getRequestSession } from '../../../../wristband';
export async function GET(req: NextRequest) {
const session = await getRequestSession(req);
if (!session.isAuthenticated) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.json({ orders: [], userId: session.userId });
}Pages Router: API Routes and getServerSideProps()
API routes for the Pages Router can use the getSession() helper already defined in Section 2:
API Route:
// src/pages/api/profile.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from '../../wristband';
export default async function apiRouteHandler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession(req, res);
if (!session.isAuthenticated) {
return res.status(401).json({ error: 'Unauthorized' });
}
return res.json({ userId: session.userId });
}SSR:
// pages/dashboard.tsx
import type { GetServerSideProps } from 'next';
import { getSession } from '../wristband';
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context.req, context.res);
if (!session.isAuthenticated) {
return {
redirect: { destination: '/api/auth/login', permanent: false },
};
}
return {
props: { userId: session.userId },
};
};
export default function Dashboard({ userId }: { userId: string }) {
return <div>Welcome, {userId}</div>;
}5) Pass Your Access Token to Downstream APIs
[!NOTE] This is only applicable if you wish to call Wristband's APIs directly or protect your application's other downstream backend APIs.
If 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.
Authorization: Bearer <access_token_value>For 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:
const session = await getRequestSession(req);
const { accessToken, userId } = session;
const userResponse = await fetch(`https://yourapp-yourcompany.us.wristband.dev/api/v1/users/${userId}`, {
method: 'GET',
headers: { Authorization: `Bearer ${accessToken}` },
});
if (userResponse.status === 401) {
redirect('/api/auth/login');
return null;
}
const user = await userResponse.json();
console.log(user); // Output -> { id: 123, ... }Using Access Tokens from the Frontend
For scenarios where your frontend needs to make direct API calls with the user's access token, use the Token Endpoint to securely retrieve the current access token.
Wristband Auth Configuration Options
The createWristbandAuth() function is used to instatiate the Wristband SDK. It takes an AuthConfig type as an argument.
Auth Config Options
AuthConfig contains the full set of options for integrating Wristband auth, including required, optional, and auto-configured values.
| AuthConfig Field | Type | Required | Auto-Configurable | Description |
| ---------------- | ---- | -------- | ----------------- | ----------- |
| 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. |
| clientId | string | Yes | No | The ID of the Wristband client. |
| clientSecret | string | Yes | No | The client's secret. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| scopes | string[] | No | No | The scopes required for authentication. Refer to the docs for currently supported scopes. The default value is [openid, offline_access, email]. |
| 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. |
| wristbandApplicationVanityDomain | string | Yes | No | The vanity domain of the Wristband application. |
createWristbandAuth()
function createWristbandAuth(authConfig: AuthConfig): WristbandAuth {}This 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.
⚠️ Auto-Configuration in Edge Runtimes
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:
- 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.
- No persistent memory: Edge Runtime instances don't maintain in-memory caches between requests, causing the SDK to refetch configuration data on every invocation
For production Next.js applications using Edge Runtime, you can set
autoConfigureEnabled: falseand provide all required configuration values manually. This is especially critical for authentication middleware that runs on every protected route.
Minimal config with auto-configure (default behavior)
const auth = createWristbandAuth({
clientId: "your-client-id",
clientSecret: "your-secret",
wristbandApplicationVanityDomain: "auth.yourapp.io"
});Manual override with partial auto-configure for some fields
const auth = createWristbandAuth({
clientId: "your-client-id",
clientSecret: "your-secret",
wristbandApplicationVanityDomain: "auth.yourapp.io",
loginUrl: "https://yourapp.io/auth/login", // Manually override "loginUrl"
// "redirectUri" will be auto-configured
});Auto-configure disabled
const auth = createWristbandAuth({
autoConfigureEnabled: false,
clientId: "your-client-id",
clientSecret: "your-secret",
wristbandApplicationVanityDomain: "auth.custom.com",
// Must manually configure non-auto-configurable fields
isApplicationCustomDomainActive: true,
loginUrl: "https://{tenant_domain}.custom.com/auth/login",
redirectUri: "https://{tenant_domain}.custom.com/auth/callback",
parseTenantFromRootDomain: "custom.com",
});Auth API
login()
/* *** App Router *** */
// Definition
login: (req: NextRequest, loginConfig?: LoginConfig) => Promise<NextResponse>;
// Usage
return await wristbandAuth.appRouter.login(req);
/* *** Pages Router *** */
// Definition
login: (req: NextApiRequest, res: NextApiResponse, loginConfig?: LoginConfig) => Promise<string>;
// Usage
const authorizeUrl = await wristbandAuth.pagesRouter.login(req, res);
res.redirect(authorizeUrl);Wristband 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.
The login() function can also take optional configuration if your application needs custom behavior:
| LoginConfig Field | Type | Required | Description |
| ----------------- | ---- | -------- | ----------- |
| 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. |
| 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). |
| 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. |
| 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. |
Which Domains Are Used in the Authorize URL?
Wristband 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:
tenant_custom_domainquery parameter: If provided, this takes top priority.- Tenant subdomain in the URL: Used if subdomains are enabled and the subdomain is present.
tenant_namequery parameter: Evaluated if no tenant subdomain is detected.defaultTenantCustomDomainin LoginConfig: Used if none of the above are present.defaultTenantDomainin LoginConfig: Used as the final fallback.
If none of these are specified, the SDK redirects users to the Application-Level Login (Tenant Discovery) Page.
Tenant Name Query Param
If 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.
GET https://yourapp.io/api/auth/login?tenant_name=customer01Your AuthConfig would look like the following when creating an SDK instance without any subdomains:
const wristbandAuth = createWristbandAuth({
clientId: "ic6saso5hzdvbnof3bwgccejxy",
clientSecret: "30e9977124b13037d035be10d727806f",
loginStateSecret: '7ffdbecc-ab7d-4134-9307-2dfcc52f7475',
loginUrl: "https://yourapp.io/auth/login",
redirectUri: "https://yourapp.io/auth/callback",
wristbandApplicationVanityDomain: "yourapp-yourcompany.us.wristband.dev",
});Tenant Subdomains
If 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.
GET https://customer01.yourapp.io/api/auth/loginYour AuthConfig would look like the following when creating an SDK instance when using subdomains:
const wristbandAuth = createWristbandAuth({
clientId: "ic6saso5hzdvbnof3bwgccejxy",
clientSecret: "30e9977124b13037d035be10d727806f",
loginStateSecret: '7ffdbecc-ab7d-4134-9307-2dfcc52f7475',
loginUrl: "https://{tenant_domain}.yourapp.io/auth/login",
redirectUri: "https://{tenant_domain}.yourapp.io/auth/callback",
parseTenantFromRootDomain: "yourapp.io",
wristbandApplicationVanityDomain: "yourapp-yourcompany.us.wristband.dev",
});Default Tenant Name
For 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:
await wristbandAuth.pagesRouter.login(req, res, { defaultTenantName: 'default' });Tenant Custom Domain Query Param
If 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.
GET https://yourapp.io/auth/login?tenant_custom_domain=mytenant.comThe tenant custom domain takes precedence over all other possible domains else when present.
Default Tenant Custom Domain
For 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:
await wristbandAuth.appRouter.login(req, { defaultTenantCustomDomain: 'mytenant.com' });The 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.
Custom State
Before 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:
await wristbandAuth.appRouter.login(req, { customState: { test: 'abc' } });[!WARNING] Injecting custom state is an advanced feature, and it is recommended to use
customStatesparingly. Most applications may not need it at all. The max cookie size is 4kB. From our own tests, passing acustomStateJSON of at most 1kB should be a safe ceiling.
Login Hints
Wristband 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:
GET https://customer01.yourapp.io/api/auth/[email protected]If 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.
Return URLs
It 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.
Given 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.
Passing a return URL in the Login Config
const loginUrl = await wristbandAuth.pagesRouter.login(req, res, {
returnUrl: 'https://customer01.yourapp.io/settings/profile',
});
res.redirect(loginUrl);Passing a return URL as a query parameter
GET https://customer01.yourapp.io/auth/login?return_url=https://customer01.yourapp.io/settings/profileThe 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.
callback()
/* *** App Router *** */
// Definition
callback: (req: NextRequest) => Promise<CallbackResult>;
createCallbackResponse: (req: NextRequest, redirectUrl: string) => NextResponse;
// Usage
const callbackResult = await wristbandAuth.appRouter.callback(req);
return await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);
/* *** Pages Router *** */
// Definition
callback: (req: NextApiRequest, res: NextApiResponse) => Promise<CallbackResult>;
// Usage
const callbackResult = await wristbandAuth.pagesRouter.callback(req, res);After 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.
GET https://customer01.yourapp.io/api/auth/callback?state=f983yr893hf89ewn0idjw8e9f&code=shcsh90jf9wc09j9w0jewcThe 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.
| CallbackResult Field | Type | Description |
| -------------------- | ---- | ----------- |
| callbackData | CallbackData or undefined | The callback data received after authentication ('completed' result only). |
| reason | CallbackFailureReason or undefined | The reason why the callback did not complete successfully ('redirect_required' only). |
| redirectUrl | string or undefined | The URL that the user should redirected to ('redirect_required' only). |
| type | CallbackResultType | String literal representing the end result of callback execution. Possible values: 'completed' or 'redirect_required'. |
The CallbackResultType can be one of the following string literal values:
| CallbackResultType | Description |
| ------------------ | ----------- |
| 'completed' | Indicates that the callback is successfully completed and data is available for creating a session. |
| 'redirect_required' | Indicates that a redirect is required, generally to a login route or page. |
When the callback returns a 'redirect_required' result, the reason field indicates why the callback failed:
| CallbackFailureReason | Description |
| --------------------- | ----------- |
| 'missing_login_state' | Login state cookie was not found (cookie expired or bookmarked callback URL). |
| 'invalid_login_state' | Login state validation failed (possible CSRF attack or cookie tampering) |
| 'login_required' | Wristband returned a login_required error (session expired or max_age elapsed). |
| 'invalid_grant' | Authorization code was invalid, expired, or already used. |
When 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:
| CallbackData Field | Type | Description |
| ------------------ | ---- | ----------- |
| accessToken | string | The access token that can be used for accessing Wristband APIs as well as protecting your application's backend APIs. |
| 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. |
| 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. |
| 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. |
| idToken | string | The ID token uniquely identifies the user that is authenticating and contains claim data about the user. |
| refreshToken | string or undefined | The refresh token that renews expired access tokens with Wristband, maintaining continuous access to services. |
| returnUrl | string or undefined | The URL to return to after authentication is completed. |
| tenantCustomDomain | string | The tenant custom domain for the tenant that the user belongs to (if applicable). |
| tenantName | string | The name of the tenant the user belongs to. |
| 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. The exact fields that get returned are based on the scopes you configured in the SDK. |
The UserInfo type is defined as follows:
| UserInfo Field | Type | Always Returned | Description |
| -------------- | ---- | --------------- | ----------- |
| userId | string | Yes | ID of the user (mapped from "sub" claim). |
| tenantId | string | Yes | ID of the tenant that the user belongs to (mapped from "tnt_id" claim). |
| applicationId | string | Yes | ID of the application that the user belongs to (mapped from "app_id" claim). |
| identityProviderName | string | Yes | Name of the identity provider (mapped from "idp_name" claim). |
| fullName | string or undefined | No | End-User's full name in displayable form (mapped from "name" claim; requires profile scope). |
| givenName | string or undefined | No | Given name(s) or first name(s) of the End-User (requires profile scope). |
| familyName | string or undefined | No | Surname(s) or last name(s) of the End-User (requires profile scope). |
| middleName | string or undefined | No | Middle name(s) of the End-User (requires profile scope). |
| nickname | string or undefined | No | Casual name of the End-User (requires profile scope). |
| displayName | string or undefined | No | Shorthand name by which the End-User wishes to be referred (requires profile scope). |
| pictureUrl | string or undefined | No | URL of the End-User's profile picture (requires profile scope). |
| email | string or undefined | No | End-User's preferred email address (requires email scope). |
| emailVerified | boolean or undefined | No | True if the End-User's email address has been verified (requires email scope). |
| gender | string or undefined | No | End-User's gender (requires profile scope). |
| birthdate | string or undefined | No | End-User's birthday in YYYY-MM-DD format (requires profile scope). |
| timeZone | string or undefined | No | End-User's time zone (requires profile scope). |
| locale | string or undefined | No | End-User's locale as BCP47 language tag, e.g., "en-US" (requires profile scope). |
| phoneNumber | string or undefined | No | End-User's telephone number in E.164 format (requires phone scope). |
| phoneNumberVerified | boolean or undefined | No | True if the End-User's phone number has been verified (requires phone scope). |
| updatedAt | number or undefined | No | Time the End-User's information was last updated as Unix timestamp (requires profile scope). |
| roles | UserInfoRole[] or undefined | No | The roles assigned to the user (requires roles scope). |
| customClaims | Record<string, any> or undefined | No | Object containing any configured custom claims. |
The UserInfoRole type is defined as follows:
| UserInfoRole Field | Type | Description | | ------------------ | ---- | ----------- | | id | string | Globally unique ID of the role. | | name | string | The role name (e.g., "app:app-name:admin"). | | displayName | string | The human-readable display name for the role. |
Redirect Responses
There 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:
- The Login State Cookie is missing by the time Wristband redirects back to the Callback Endpoint.
- The
statequery parameter sent from Wristband to your Callback Endpoint does not match the Login State Cookie. - Wristband sends an
errorquery parameter to your Callback Endpoint, and it is an expected error type that the SDK knows how to resolve.
The 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:
- If the tenant domain can be determined, then the user will get redirected back to your Login Endpoint.
- Otherwise, the user will be sent to the Wristband-hosted Tenant-Level Login Page URL.
In these events, the your application should redirect the user to that location.
Error Parameters
Certain 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:
| Query Parameter | Description | | --------------- | ----------- | | error | Indicates an error that occurred during the Login Workflow. | | error_description | A human-readable description or explanation of the error to help diagnose and resolve issues more effectively. |
GET https://customer01.yourapp.io/api/auth/callback?state=f983yr893hf89ewn0idjw8e9f&error=login_required&error_description=User%20must%20re-authenticate%20because%20the%20specified%20max_age%20value%20has%20elapsedThe error types that get automatically resolved in the SDK are:
| Error | Description | | ----- | ----------- | | 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. |
For 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.
createCallbackResponse() (App Router)
/* *** App Router *** */
// Definition
createCallbackResponse: (req: NextRequest, redirectUrl: string) => Promise<NextResponse>;
// Usage
const appUrl = callbackData.returnUrl || `https://yourapp.io/home`;
return await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);When 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.
| Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | request | NextRequest | Yes | The Next.js request object. | | redirectUrl | string | Yes | The URL to redirect the user to after authentication completes. |
// App Router: Callback endpoint
import { NextRequest } from 'next/server';
import { getRequestSession, wristbandAuth } from '../../../../wristband';
export async function GET(req: NextRequest) {
const callbackResult = await wristbandAuth.appRouter.callback(req);
const { callbackData, redirectUrl, type } = callbackResult;
// Handle redirect required scenario
if (type === 'redirect_required') {
return await wristbandAuth.appRouter.createCallbackResponse(req, redirectUrl);
}
// Handle successful authentication
const session = await getRequestSession(req);
session.fromCallback(callbackData);
// Create callback response with your app's destination URL
const appUrl = callbackData.returnUrl || '/dashboard';
const callbackResponse = await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);
// Save session and return response
return await session.saveToResponse(callbackResponse);
}logout()
/* *** App Router *** */
// Definition
logout: (req: NextRequest, logoutConfig?: LogoutConfig) => Promise<NextResponse>;
// Usage
return await wristbandAuth.appRouter.logout(req, { refreshToken: '98yht308hf902hc90wh09' });
/* *** Pages Router *** */
// Definition
logout: (req: NextApiRequest, res: NextApiResponse, logoutConfig?: LogoutConfig) => Promise<string>;
// Usage
const logoutUrl = await wristbandAuth.pagesRouter.logout(req, res, { refreshToken: '98yht308hf902hc90wh09' });
res.redirect(logoutUrl);When 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.
GET https://customer01.yourapp.io/api/auth/logoutIf your application created a session, it should destroy it before invoking the logout() function. This function can also take an optional LogoutConfig argument:
| LogoutConfig Field | Type | Required | Description | | ----------------- | ---- | -------- | ----------- | | redirectUrl | string | No | Optional URL that Wristband will redirect to after the logout operation has completed. | | refreshToken | string | No | The refresh token to revoke. | | 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. | | tenantCustomDomain | string | No | The tenant custom domain for the tenant that the user belongs to (if applicable). | | tenantName | string | No | The name of the tenant the user belongs to. |
Which Domains Are Used in the Logout URL?
Wristband 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:
tenantCustomDomainin LogoutConfig: If provided, this takes top priority.- `tenantNam
