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-pages-router.mddocs/

Server-Side Authentication (Pages Router)

Server-side authentication utilities for Next.js Pages Router. These functions provide access to authentication state in getServerSideProps and API routes.

Key Information for Agents

Required Setup:

  • CLERK_SECRET_KEY environment variable required
  • ClerkProvider must wrap application in pages/_app.tsx
  • buildClerkProps() required in getServerSideProps for SSR auth state
  • getAuth() is synchronous (no await needed)

Default Behaviors:

  • getAuth() is synchronous (returns immediately, no Promise)
  • getAuth() returns userId as null when signed out (not undefined)
  • buildClerkProps() must be spread into page props for ClerkProvider
  • treatPendingAsSignedOut: false by default
  • acceptsToken: 'session_token' by default
  • getToken() is async (returns Promise) even though getAuth() is sync
  • has() function is synchronous (returns boolean immediately)

Threading Model:

  • getAuth() executes synchronously in Node.js server runtime
  • getToken() is async and must be awaited
  • buildClerkProps() is synchronous (returns object immediately)
  • Multiple getAuth() calls in same request return same auth state
  • clerkClient() calls are async and must be awaited

Lifecycle:

  • getAuth() reads session from request object (NextApiRequest)
  • Auth state is request-scoped (new request = new auth check)
  • buildClerkProps() serializes auth state for client-side hydration
  • Props from buildClerkProps() must be passed to ClerkProvider via pageProps

Edge Cases:

  • getAuth() in API routes: Pass req parameter
  • getAuth() in getServerSideProps: Pass ctx.req parameter
  • buildClerkProps() must be called in getServerSideProps, not API routes
  • Missing buildClerkProps() causes client-side auth state mismatch
  • getToken() can return null if session invalid or expired
  • has() returns false if no active organization (even with permission)
  • Pending sessions: Use treatPendingAsSignedOut: true to treat as signed out

Exceptions:

  • Missing CLERK_SECRET_KEY causes getAuth() to throw error
  • Invalid request object causes getAuth() to fail
  • getToken() throws if Clerk not initialized or session invalid
  • buildClerkProps() throws if request object invalid

Capabilities

getAuth() Function

Returns the Auth object from the request. Synchronous function for Pages Router server contexts.

/**
 * Retrieves authentication state from request object
 * Available in getServerSideProps and API routes
 * Synchronous operation - does not return a Promise
 * @param req - Next.js API request object
 * @param options - Optional configuration
 * @returns SessionAuthObject with authentication state
 */
function getAuth(
  req: NextApiRequest,
  options?: GetAuthOptions
): SessionAuthObject;

interface GetAuthOptions {
  /**
   * Optional secret key override
   * If not provided, uses CLERK_SECRET_KEY environment variable
   */
  secretKey?: string;

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

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

interface SessionAuthObject {
  /**
   * 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;

  /**
   * Additional debug information
   */
  debug: () => any;
}

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;
}

Usage Example - API Route:

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId } = getAuth(req);

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

  return res.status(200).json({ userId });
}

Usage Example - Get Token:

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { userId, getToken } = getAuth(req);

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

  // 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}`,
    },
  });

  const data = await response.json();

  return res.status(200).json(data);
}

Usage Example - Check Permissions:

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId, has, orgRole } = getAuth(req);

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

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

  if (!isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  return res.status(200).json({
    userId,
    orgRole,
    isAdmin,
    canManageTeam,
  });
}

Usage Example - getServerSideProps:

import { getAuth } from '@clerk/nextjs/server';
import type { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { userId, sessionId } = getAuth(ctx.req);

  if (!userId) {
    return {
      redirect: {
        destination: '/sign-in',
        permanent: false,
      },
    };
  }

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

  return {
    props: {
      userId,
      sessionId,
      userData,
    },
  };
};

export default function ProtectedPage({ userId, userData }: any) {
  return (
    <div>
      <h1>Protected Page</h1>
      <p>User ID: {userId}</p>
      <pre>{JSON.stringify(userData, null, 2)}</pre>
    </div>
  );
}

Usage Example - Organization Context:

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId, orgId, orgRole, orgSlug, orgPermissions } = getAuth(req);

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

  if (!orgId) {
    return res.status(400).json({ error: 'No active organization' });
  }

  return res.status(200).json({
    userId,
    organization: {
      id: orgId,
      role: orgRole,
      slug: orgSlug,
      permissions: orgPermissions,
    },
  });
}

buildClerkProps() Function

Builds Clerk props for server-side rendering in getServerSideProps. Used to pass authentication state to client components.

/**
 * Builds Clerk props for SSR in getServerSideProps
 * Informs client-side helpers of authentication state
 * @param req - Next.js API request object
 * @param authState - Optional initial auth state
 * @returns Object with __clerk_ssr_state for ClerkProvider
 */
function buildClerkProps(
  req: NextApiRequest,
  authState?: BuildClerkPropsInitState
): Record<string, unknown>;

interface BuildClerkPropsInitState {
  /**
   * Optional user object to include in SSR state
   */
  user?: User | null;

  /**
   * Optional session object to include in SSR state
   */
  session?: Session | null;

  /**
   * Optional organization object to include in SSR state
   */
  organization?: Organization | null;
}

Usage Example - Basic:

import { getAuth, buildClerkProps } from '@clerk/nextjs/server';
import type { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { userId } = getAuth(ctx.req);

  if (!userId) {
    return {
      redirect: {
        destination: '/sign-in',
        permanent: false,
      },
    };
  }

  // Build Clerk props for client-side hydration
  return {
    props: {
      ...buildClerkProps(ctx.req),
    },
  };
};

export default function Page() {
  return <div>Protected content</div>;
}

Usage Example - With User Data:

import { getAuth, buildClerkProps, clerkClient } from '@clerk/nextjs/server';
import type { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { userId } = getAuth(ctx.req);

  if (!userId) {
    return {
      redirect: {
        destination: '/sign-in',
        permanent: false,
      },
    };
  }

  // Fetch user from Clerk backend
  const client = await clerkClient();
  const user = await client.users.getUser(userId);

  // Pass user to client-side
  return {
    props: {
      ...buildClerkProps(ctx.req, { user }),
    },
  };
};

export default function ProfilePage() {
  return <div>Profile page with SSR user data</div>;
}

Usage Example - With Session and Organization:

import { getAuth, buildClerkProps, clerkClient } from '@clerk/nextjs/server';
import type { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { userId, sessionId, orgId } = getAuth(ctx.req);

  if (!userId) {
    return {
      redirect: {
        destination: '/sign-in',
        permanent: false,
      },
    };
  }

  const client = await clerkClient();

  // Fetch user, session, and organization
  const user = await client.users.getUser(userId);
  const session = sessionId
    ? await client.sessions.getSession(sessionId)
    : null;
  const organization = orgId
    ? await client.organizations.getOrganization({ organizationId: orgId })
    : null;

  return {
    props: {
      ...buildClerkProps(ctx.req, { user, session, organization }),
    },
  };
};

export default function DashboardPage() {
  return <div>Dashboard with full SSR state</div>;
}

Usage Example - Combined with Custom Props:

import { getAuth, buildClerkProps } from '@clerk/nextjs/server';
import type { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { userId } = getAuth(ctx.req);

  if (!userId) {
    return {
      redirect: {
        destination: '/sign-in',
        permanent: false,
      },
    };
  }

  // Fetch additional data
  const posts = await fetchUserPosts(userId);
  const settings = await fetchUserSettings(userId);

  return {
    props: {
      ...buildClerkProps(ctx.req),
      posts,
      settings,
    },
  };
};

interface PageProps {
  posts: Post[];
  settings: Settings;
}

export default function UserDashboard({ posts, settings }: PageProps) {
  return (
    <div>
      <h1>Dashboard</h1>
      <PostList posts={posts} />
      <SettingsPanel settings={settings} />
    </div>
  );
}

Integration with ClerkProvider

For Pages Router, buildClerkProps must be passed to ClerkProvider in _app.tsx:

/**
 * Pages Router ClerkProvider setup
 * Spread pageProps to pass buildClerkProps data
 */
// pages/_app.tsx
import { ClerkProvider } from '@clerk/nextjs';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ClerkProvider {...pageProps}>
      <Component {...pageProps} />
    </ClerkProvider>
  );
}

Working with clerkClient

Combine getAuth() with clerkClient() to access Clerk's Backend API:

/**
 * Using getAuth() with clerkClient()
 * Access Backend API for user/organization management
 */
import { getAuth, clerkClient } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { userId } = getAuth(req);

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

  const client = await clerkClient();

  // Get user details
  const user = await client.users.getUser(userId);

  // Update user metadata
  await client.users.updateUserMetadata(userId, {
    publicMetadata: {
      lastApiAccess: new Date().toISOString(),
    },
  });

  return res.status(200).json({
    id: user.id,
    email: user.emailAddresses[0]?.emailAddress,
    metadata: user.publicMetadata,
  });
}

API Route Protection Patterns

Pattern 1: Simple Authentication Check

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId } = getAuth(req);

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

  // Handle authenticated request
  return res.status(200).json({ message: 'Success' });
}

Pattern 2: Role-Based Authorization

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId, has } = getAuth(req);

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

  if (!has({ role: 'admin' })) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  // Handle admin request
  return res.status(200).json({ message: 'Admin action completed' });
}

Pattern 3: Organization Context

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId, orgId, orgRole } = getAuth(req);

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

  if (!orgId) {
    return res.status(400).json({ error: 'Organization required' });
  }

  // Handle organization-scoped request
  return res.status(200).json({
    message: 'Success',
    organizationId: orgId,
    role: orgRole,
  });
}

Pattern 4: Method-Based Routing

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { userId } = getAuth(req);

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

  switch (req.method) {
    case 'GET':
      return handleGet(req, res, userId);
    case 'POST':
      return handlePost(req, res, userId);
    case 'DELETE':
      return handleDelete(req, res, userId);
    default:
      return res.status(405).json({ error: 'Method not allowed' });
  }
}

function handleGet(
  req: NextApiRequest,
  res: NextApiResponse,
  userId: string
) {
  // Handle GET request
  return res.status(200).json({ userId });
}

function handlePost(
  req: NextApiRequest,
  res: NextApiResponse,
  userId: string
) {
  // Handle POST request
  return res.status(201).json({ userId, created: true });
}

function handleDelete(
  req: NextApiRequest,
  res: NextApiResponse,
  userId: string
) {
  // Handle DELETE request
  return res.status(200).json({ userId, deleted: true });
}

Error Handling

import { getAuth } from '@clerk/nextjs/server';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const { userId, getToken } = getAuth(req);

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

    const token = await getToken();

    if (!token) {
      return res.status(401).json({ error: 'Invalid session' });
    }

    // Perform authenticated operations
    const result = await performOperation(userId, token);

    return res.status(200).json(result);
  } catch (error) {
    console.error('API error:', error);
    return res.status(500).json({ error: 'Internal server error' });
  }
}

Requirements

Pages Router authentication functions require:

  1. ClerkProvider in _app.tsx with pageProps spread
  2. Environment variables properly set (NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY)
  3. buildClerkProps passed from getServerSideProps when using SSR
// pages/_app.tsx
import { ClerkProvider } from '@clerk/nextjs';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ClerkProvider {...pageProps}>
      <Component {...pageProps} />
    </ClerkProvider>
  );
}