or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

backend-api-client.mdclient-components.mdclient-hooks.mderror-handling.mdindex.mdmiddleware-and-route-protection.mdserver-auth-app-router.mdserver-auth-pages-router.mdsetup-and-provider.mdwebhooks.md
tile.json

server-auth-app-router.mddocs/

Server-Side Authentication (App Router)

Server-side authentication utilities for Next.js App Router. These functions provide access to authentication state and user data in Server Components, Route Handlers, and Server Actions.

Key Information for Agents

Required Setup:

  • clerkMiddleware() must be configured in middleware.ts for auth() to work
  • CLERK_SECRET_KEY environment variable required for server-side auth
  • ClerkProvider must wrap application (for client-side integration)
  • All functions are async and must be awaited

Default Behaviors:

  • auth() returns userId as null when signed out (not undefined)
  • auth.protect() throws/redirects if not authenticated or authorized
  • currentUser() returns null if not authenticated (makes API call to Clerk backend)
  • currentUser() is automatically deduped per request (multiple calls = one API call)
  • treatPendingAsSignedOut: false by default (pending sessions treated as signed in)
  • acceptsToken: 'session_token' by default (can accept 'm2m_token', 'oauth_token', or 'any')
  • auth.protect() redirects to sign-in for document requests, returns 404 for API requests when not authenticated
  • auth.protect() returns 404 when authenticated but not authorized (regardless of request type)

Threading Model:

  • All functions execute in Node.js server runtime (not browser)
  • Functions are async and return Promises
  • auth() calls in same request share session state (middleware processes once)
  • currentUser() calls are deduped via fetch() (one API call per request)
  • Multiple auth() calls in same component/route handler share same auth state

Lifecycle:

  • auth() reads session from request (processed by middleware)
  • currentUser() fetches user from Clerk backend API (counts towards rate limit)
  • Auth state is request-scoped (new request = new auth check)
  • auth.protect() throws error that Next.js catches and handles (redirect or 404)

Edge Cases:

  • Pending sessions: Use treatPendingAsSignedOut: true to treat as signed out
  • Token types: Use acceptsToken to accept M2M or OAuth tokens
  • auth.protect() behavior differs for document vs API requests (redirect vs 404)
  • currentUser() makes API call (slower than auth(), use when you need full user object)
  • auth() is lighter weight (no API call, just reads session from middleware)
  • Custom redirect URLs in auth.protect() override environment variables
  • redirectToSignIn() and redirectToSignUp() never return (trigger redirect immediately)

Exceptions:

  • Missing middleware causes auth() to throw error
  • auth.protect() throws/redirects if not authenticated (never returns normally)
  • currentUser() throws if Clerk API call fails
  • Invalid token type in acceptsToken causes authentication to fail
  • redirectToSignIn() and redirectToSignUp() never return (TypeScript never type)

Capabilities

auth() Function

Returns the Auth object with authentication state and redirect methods. Only available in App Router server contexts.

/**
 * Returns Auth object with authentication state
 * Available in Server Components, Route Handlers, and Server Actions
 * Requires clerkMiddleware() to be configured
 * @param options - Optional configuration for auth behavior
 * @returns Promise resolving to SessionAuthWithRedirect object
 */
function auth(options?: AuthOptions): Promise<SessionAuthWithRedirect>;

interface AuthOptions {
  /**
   * If true, treats pending sessions as signed out
   * Useful for conditional rendering based on auth state
   * @default false
   */
  treatPendingAsSignedOut?: boolean;

  /**
   * Token type to accept for authentication
   * @default 'session_token'
   */
  acceptsToken?: 'session_token' | 'm2m_token' | 'oauth_token' | 'any';
}

interface SessionAuthWithRedirect {
  /**
   * Current user's ID, null if signed out
   */
  userId: string | null;

  /**
   * Current session ID, null if signed out
   */
  sessionId: string | null;

  /**
   * Active organization ID, null if no active organization
   */
  orgId: string | null;

  /**
   * Current user's role in active organization
   */
  orgRole: string | null;

  /**
   * Active organization slug
   */
  orgSlug: string | null;

  /**
   * Current user's permissions in active organization
   */
  orgPermissions: string[] | null;

  /**
   * Actor identifier for impersonation scenarios
   */
  actor: any | null;

  /**
   * Session status: 'active', 'expired', 'abandoned', 'removed', 'pending'
   */
  sessionStatus: string;

  /**
   * Get session token or custom JWT template
   * @param options - Token retrieval options
   * @returns Promise resolving to token string or null
   */
  getToken: (options?: GetTokenOptions) => Promise<string | null>;

  /**
   * Check if user has permission or role
   * @param params - Permission or role to check
   * @returns True if user has permission/role
   */
  has: (params: HasParams) => boolean;

  /**
   * Redirect user to sign-in page
   * Can only access URLs defined via environment variables or clerkMiddleware
   * @param options - Redirect options
   * @returns Never returns, triggers redirect
   */
  redirectToSignIn: (options?: RedirectOptions) => never;

  /**
   * Redirect user to sign-up page
   * Can only access URLs defined via environment variables or clerkMiddleware
   * @param options - Redirect options
   * @returns Never returns, triggers redirect
   */
  redirectToSignUp: (options?: RedirectOptions) => never;
}

interface GetTokenOptions {
  /**
   * JWT template name for custom tokens
   */
  template?: string;

  /**
   * Skip token cache and fetch fresh token
   */
  skipCache?: boolean;
}

interface HasParams {
  /**
   * Permission to check (e.g., 'org:team:manage')
   */
  permission?: string;

  /**
   * Role to check (e.g., 'admin', 'member')
   */
  role?: string;
}

interface RedirectOptions {
  /**
   * URL to redirect back to after authentication
   * If null, no return URL is set
   */
  returnBackUrl?: string | URL | null;
}

Usage Example - Server Component:

import { auth } from '@clerk/nextjs/server';

export default async function DashboardPage() {
  const { userId, sessionId, orgId } = await auth();

  if (!userId) {
    return <div>Not authenticated</div>;
  }

  return (
    <div>
      <h1>Dashboard</h1>
      <p>User ID: {userId}</p>
      <p>Session ID: {sessionId}</p>
      {orgId && <p>Organization ID: {orgId}</p>}
    </div>
  );
}

Usage Example - Route Handler:

import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export async function GET() {
  const { userId } = await auth();

  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Fetch user-specific data
  const data = await fetchUserData(userId);

  return NextResponse.json({ data });
}

Usage Example - Server Action:

'use server';

import { auth } from '@clerk/nextjs/server';

export async function updateProfile(formData: FormData) {
  const { userId } = await auth();

  if (!userId) {
    throw new Error('Unauthorized');
  }

  const name = formData.get('name') as string;

  // Update user profile
  await updateUserProfile(userId, { name });

  return { success: true };
}

Usage Example - Get Token:

import { auth } from '@clerk/nextjs/server';

export async function GET() {
  const { userId, getToken } = await auth();

  if (!userId) {
    return new Response('Unauthorized', { status: 401 });
  }

  // Get default session token
  const token = await getToken();

  // Get custom JWT template
  const supabaseToken = await getToken({ template: 'supabase' });

  // Use token for external API calls
  const response = await fetch('https://api.example.com/data', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return response;
}

Usage Example - Check Permissions:

import { auth } from '@clerk/nextjs/server';

export default async function AdminPage() {
  const { userId, has } = await auth();

  if (!userId) {
    return <div>Not authenticated</div>;
  }

  const isAdmin = has({ role: 'admin' });
  const canManageTeam = has({ permission: 'org:team:manage' });

  if (!isAdmin) {
    return <div>Access denied</div>;
  }

  return (
    <div>
      <h1>Admin Panel</h1>
      {canManageTeam && <button>Manage Team</button>}
    </div>
  );
}

Usage Example - Redirect Methods:

import { auth } from '@clerk/nextjs/server';

export default async function ProtectedPage() {
  const { userId, redirectToSignIn } = await auth();

  if (!userId) {
    // Redirects to sign-in with return URL
    redirectToSignIn({ returnBackUrl: '/dashboard' });
  }

  return <div>Protected content</div>;
}

auth.protect() Method

Protects routes with authentication and authorization checks. Automatically handles redirects and error responses.

/**
 * Protects routes with authentication/authorization checks
 * Returns Auth object if authorized, redirects/404 otherwise
 * @param params - Authorization parameters or condition function
 * @param options - Protection options
 * @returns Promise resolving to SignedInAuthObject if authorized
 */
auth.protect(
  params?: CheckAuthorizationParams | AuthorizationCondition,
  options?: AuthProtectOptions
): Promise<SignedInAuthObject>;

interface CheckAuthorizationParams {
  /**
   * Required permission for authorization
   */
  permission?: string;

  /**
   * Required role for authorization
   */
  role?: string;
}

type AuthorizationCondition = (
  has: (params: HasParams) => boolean
) => boolean;

interface AuthProtectOptions {
  /**
   * Token type to accept for authentication
   * @default 'session_token'
   */
  token?: 'session_token' | 'm2m_token' | 'oauth_token' | 'any';

  /**
   * Custom URL to redirect to if not authenticated
   */
  unauthenticatedUrl?: string;

  /**
   * Custom URL to redirect to if not authorized
   */
  unauthorizedUrl?: string;
}

interface SignedInAuthObject {
  userId: string;
  sessionId: string;
  orgId: string | null;
  orgRole: string | null;
  orgSlug: string | null;
  orgPermissions: string[] | null;
  actor: any | null;
  sessionStatus: string;
  getToken: (options?: GetTokenOptions) => Promise<string | null>;
  has: (params: HasParams) => boolean;
}

Protection Behavior Table:

AuthenticatedAuthorizedRequest Typeauth.protect() will
YesYesAnyReturn Auth object
YesNoAnyReturn 404 error
NoNoDocument (page)Redirect to sign-in
NoNoAPI requestReturn 404 error

Usage Example - Basic Protection:

import { auth } from '@clerk/nextjs/server';

export default async function ProtectedPage() {
  // Throws error if not authenticated, redirects to sign-in
  const { userId } = await auth.protect();

  // This code only runs if user is authenticated
  return <div>Welcome {userId}</div>;
}

Usage Example - Role-Based Protection:

import { auth } from '@clerk/nextjs/server';

export default async function AdminPage() {
  // Returns 404 if user doesn't have admin role
  const { userId } = await auth.protect({ role: 'admin' });

  return <div>Admin Panel for {userId}</div>;
}

Usage Example - Permission-Based Protection:

import { auth } from '@clerk/nextjs/server';

export default async function TeamManagementPage() {
  // Returns 404 if user doesn't have permission
  await auth.protect({ permission: 'org:team:manage' });

  return <div>Team Management</div>;
}

Usage Example - Custom Condition:

import { auth } from '@clerk/nextjs/server';

export default async function FeaturePage() {
  // Custom authorization logic
  await auth.protect((has) => {
    return has({ permission: 'feature:access' }) || has({ role: 'admin' });
  });

  return <div>Feature Content</div>;
}

Usage Example - Route Handler:

import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export async function POST(req: Request) {
  // Protects API route, returns 404 if not authenticated
  const { userId } = await auth.protect();

  const body = await req.json();

  // Process authenticated request
  await createResource(userId, body);

  return NextResponse.json({ success: true });
}

Usage Example - Custom Redirect URLs:

import { auth } from '@clerk/nextjs/server';

export default async function PremiumPage() {
  await auth.protect(
    { role: 'premium' },
    {
      unauthenticatedUrl: '/sign-in',
      unauthorizedUrl: '/upgrade',
    }
  );

  return <div>Premium Content</div>;
}

Usage Example - Machine-to-Machine Token:

import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export async function POST(req: Request) {
  // Accept M2M tokens for API authentication
  const authObject = await auth.protect({ token: 'm2m_token' });

  // authObject.isAuthenticated is true for M2M tokens
  return NextResponse.json({ success: true });
}

currentUser() Function

Returns the current User object from Clerk's backend or null if not authenticated.

/**
 * Returns the Backend User object of currently active user
 * Available in Server Components, Route Handlers, and Server Actions
 * Automatically deduped per request via fetch()
 * Counts towards Backend API rate limit
 * @param options - Optional configuration
 * @returns Promise resolving to User object or null
 */
function currentUser(options?: CurrentUserOptions): Promise<User | null>;

interface CurrentUserOptions {
  /**
   * If true, treats pending sessions as signed out
   * @default false
   */
  treatPendingAsSignedOut?: boolean;
}

interface User {
  /**
   * User's unique identifier
   */
  id: string;

  /**
   * User's email addresses
   */
  emailAddresses: EmailAddress[];

  /**
   * User's phone numbers
   */
  phoneNumbers: PhoneNumber[];

  /**
   * User's external OAuth accounts
   */
  externalAccounts: ExternalAccount[];

  /**
   * User's web3 wallets
   */
  web3Wallets: Web3Wallet[];

  /**
   * User's passkeys
   */
  passkeys: Passkey[];

  /**
   * User's first name
   */
  firstName: string | null;

  /**
   * User's last name
   */
  lastName: string | null;

  /**
   * User's username
   */
  username: string | null;

  /**
   * User's profile image URL
   */
  imageUrl: string;

  /**
   * Whether user has uploaded a profile image
   */
  hasImage: boolean;

  /**
   * User's primary email address ID
   */
  primaryEmailAddressId: string | null;

  /**
   * User's primary phone number ID
   */
  primaryPhoneNumberId: string | null;

  /**
   * User's primary web3 wallet ID
   */
  primaryWeb3WalletId: string | null;

  /**
   * User's public metadata (readable by anyone)
   */
  publicMetadata: Record<string, any>;

  /**
   * User's private metadata (readable only by backend)
   */
  privateMetadata: Record<string, any>;

  /**
   * User's unsafe metadata (readable/writable by client)
   */
  unsafeMetadata: Record<string, any>;

  /**
   * Whether user is banned
   */
  banned: boolean;

  /**
   * Whether user is locked
   */
  locked: boolean;

  /**
   * Timestamp when user was created
   */
  createdAt: number;

  /**
   * Timestamp when user was last updated
   */
  updatedAt: number;

  /**
   * Timestamp of last sign-in
   */
  lastSignInAt: number | null;

  /**
   * Whether two-factor authentication is enabled
   */
  twoFactorEnabled: boolean;

  /**
   * Whether backup codes are enabled
   */
  backupCodeEnabled: boolean;

  /**
   * Password digest (hashed password)
   */
  passwordEnabled: boolean;

  /**
   * Legal accepted status
   */
  legalAcceptedAt: number | null;

  /**
   * Create email address verification
   */
  createEmailAddress: (params: CreateEmailAddressParams) => Promise<EmailAddress>;

  /**
   * Get OAuth access token
   */
  getOAuthAccessToken: (provider: string) => Promise<OAuthAccessToken[]>;
}

Usage Example - Server Component:

import { currentUser } from '@clerk/nextjs/server';

export default async function ProfilePage() {
  const user = await currentUser();

  if (!user) {
    return <div>Not signed in</div>;
  }

  return (
    <div>
      <h1>{user.firstName} {user.lastName}</h1>
      <p>Email: {user.emailAddresses[0]?.emailAddress}</p>
      <p>Username: {user.username}</p>
      <img src={user.imageUrl} alt="Profile" />
      <p>Member since: {new Date(user.createdAt).toLocaleDateString()}</p>
    </div>
  );
}

Usage Example - Route Handler:

import { currentUser } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export async function GET() {
  const user = await currentUser();

  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  return NextResponse.json({
    id: user.id,
    email: user.emailAddresses[0]?.emailAddress,
    name: `${user.firstName} ${user.lastName}`,
    imageUrl: user.imageUrl,
  });
}

Usage Example - Access Metadata:

import { currentUser } from '@clerk/nextjs/server';

export default async function DashboardPage() {
  const user = await currentUser();

  if (!user) {
    return <div>Not signed in</div>;
  }

  // Access different metadata types
  const preferences = user.publicMetadata.preferences;
  const subscription = user.publicMetadata.subscription;
  const customData = user.unsafeMetadata.customData;

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Subscription: {subscription?.tier}</p>
      <p>Theme: {preferences?.theme}</p>
    </div>
  );
}

Usage Example - Server Action:

'use server';

import { currentUser } from '@clerk/nextjs/server';

export async function getUserProfile() {
  const user = await currentUser();

  if (!user) {
    throw new Error('Not authenticated');
  }

  return {
    id: user.id,
    email: user.emailAddresses[0]?.emailAddress,
    fullName: `${user.firstName} ${user.lastName}`,
    imageUrl: user.imageUrl,
    metadata: user.publicMetadata,
  };
}

Performance Note:

currentUser() uses fetch() internally, which means:

  • Automatically deduped within the same request
  • Multiple calls to currentUser() in the same request only make one API call
  • Counts towards Backend API rate limits
  • Use auth() if you only need userId/sessionId (lighter weight)

Reverification Error Utilities

Utilities for triggering reverification flows in Route Handlers and Server Actions.

/**
 * Returns a reverification error response
 * Use in Route Handlers to trigger reverification flow
 * Returns 403 response with reverification metadata
 * @param missingConfig - Optional reverification configuration
 * @returns Response object with 403 status
 */
function reverificationErrorResponse<MC extends ReverificationConfig>(
  missingConfig?: MC
): Response;

/**
 * Throws a reverification error
 * Use in Server Actions to trigger reverification flow
 * Returns error object with reverification metadata
 * @param missingConfig - Optional reverification configuration
 * @returns Reverification error object
 */
function reverificationError<MC extends ReverificationConfig>(
  missingConfig?: MC
): ReverificationError<{ metadata?: { reverification?: MC } }>;

interface ReverificationConfig {
  /**
   * Maximum age in seconds for reverification
   */
  maxAgeInSeconds?: number;

  /**
   * Additional reverification level
   */
  level?: string;
}

Usage Example - Route Handler:

import { auth, reverificationErrorResponse } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export async function DELETE(req: Request) {
  const { userId, sessionId } = await auth();

  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Check if action requires reverification
  const requiresReverification = true; // Your logic here

  if (requiresReverification) {
    // Return reverification error response
    return reverificationErrorResponse({
      maxAgeInSeconds: 300, // Require reverification within 5 minutes
      level: 'strict',
    });
  }

  // Perform sensitive operation
  await deleteSensitiveData(userId);

  return NextResponse.json({ success: true });
}

Usage Example - Server Action:

'use server';

import { auth, reverificationError } from '@clerk/nextjs/server';

export async function deleteSensitiveData() {
  const { userId } = await auth();

  if (!userId) {
    throw new Error('Unauthorized');
  }

  // Check if action requires reverification
  const requiresReverification = true; // Your logic here

  if (requiresReverification) {
    // Throw reverification error
    throw reverificationError({
      maxAgeInSeconds: 300,
      level: 'strict',
    });
  }

  // Perform sensitive operation
  await performDeletion(userId);

  return { success: true };
}

Requirements

All App Router server authentication functions require:

  1. ClerkProvider wrapping your application
  2. clerkMiddleware() configured in middleware.ts
  3. Environment variables properly set (NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY)
// middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server';

export default clerkMiddleware();

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    '/(api|trpc)(.*)',
  ],
};