Skip to content

Repository files navigation

AuthKit TanStack Start

Authentication and session management for TanStack Start applications using WorkOS AuthKit.

Note

This library is designed for TanStack Start v1.0+. TanStack Start is currently in beta - expect some API changes as the framework evolves.

Installation

npm install @workos/authkit-tanstack-react-start
pnpm add @workos/authkit-tanstack-react-start

Quickstart

Environment Variables

Create a .env file in your project root with the following required variables:

WORKOS_CLIENT_ID="client_..."# Get from WorkOS dashboard
WORKOS_API_KEY="sk_test_..."# Get from WorkOS dashboard
WORKOS_REDIRECT_URI="http://localhost:3000/api/auth/callback"
WORKOS_COOKIE_PASSWORD="..."# Min 32 characters

Generate a secure cookie password (32+ characters):

openssl rand -base64 24

Optional Configuration

VariableDefaultDescription
WORKOS_COOKIE_MAX_AGE34560000 (400 days)Cookie lifetime in seconds
WORKOS_COOKIE_NAMEwos-sessionSession cookie name
WORKOS_COOKIE_DOMAINNoneCookie domain (for multi-domain sessions)
WORKOS_COOKIE_SAMESITElaxSameSite attribute (lax, strict, none)
WORKOS_API_HOSTNAMEapi.workos.comWorkOS API hostname

Setup (3 Steps)

1. Configure Middleware

Create or update src/start.ts:

import{createStart,createCsrfMiddleware}from'@tanstack/react-start';import{authkitMiddleware}from'@workos/authkit-tanstack-react-start';// Reject cross-site requests to server-function RPC endpoints.constcsrfMiddleware=createCsrfMiddleware({filter: (ctx)=>ctx.handlerType==='serverFn',});exportconststartInstance=createStart(()=>({requestMiddleware: [csrfMiddleware,authkitMiddleware()],}));

Why createCsrfMiddleware? TanStack Start applies CSRF protection to server functions automatically — but only when your app doesn't define its own startInstance. Registering authkitMiddleware means you do, which silently opts you out of that default. Adding createCsrfMiddleware back restores it. It's a pure header check (Sec-Fetch-Site / Origin / Referer) with no tokens and no interaction with the AuthKit session cookie; list it before authkitMiddleware so cross-site requests are rejected before any session work runs. If you handle CSRF another way, omit it — Start will warn in dev, which you can silence with tanstackStart({ serverFns: { disableCsrfMiddlewareWarning: true } }).

2. Create Callback Route

Create src/routes/api/auth/callback.tsx:

import{createFileRoute}from'@tanstack/react-router';import{handleCallbackRoute}from'@workos/authkit-tanstack-react-start';exportconstRoute=createFileRoute('/api/auth/callback')({server: {handlers: {GET: handleCallbackRoute(),},},});

Make sure this matches your WORKOS_REDIRECT_URI environment variable.

3. Create Sign-In URL

Create a route that initiates the AuthKit sign-in flow. This route is used as the Sign-in URL (also known as initiate_login_uri) in your WorkOS dashboard settings.

Create src/routes/api/auth/sign-in.tsx:

import{createFileRoute}from'@tanstack/react-router';import{getSignInUrl}from'@workos/authkit-tanstack-react-start';exportconstRoute=createFileRoute('/api/auth/sign-in')({server: {handlers: {GET: async({ request }: {request: Request})=>{constreturnPathname=newURL(request.url).searchParams.get('returnPathname');consturl=awaitgetSignInUrl(returnPathname ? {data: { returnPathname }} : undefined);returnnewResponse(null,{status: 307,headers: {Location: url},});},},},});

In the WorkOS dashboard Redirects page, set the Sign-in URL to match this route (e.g., http://localhost:3000/api/auth/sign-in).

Important

The Sign-in URL is required for features like impersonation to work correctly. Without it, WorkOS-initiated flows (such as impersonating a user from the dashboard) will fail because they cannot complete the PKCE/CSRF verification that this library enforces on every callback.

4. Add Provider (Optional - only needed for client hooks)

If you want to use useAuth() or other client hooks, wrap your app with AuthKitProvider in src/routes/__root.tsx:

import{AuthKitProvider}from'@workos/authkit-tanstack-react-start/client';import{Outlet,createRootRoute}from'@tanstack/react-router';exportconstRoute=createRootRoute({component: RootComponent,});functionRootComponent(){return(<AuthKitProvider><Outlet/></AuthKitProvider>);}

If you're only using server-side authentication (getAuth() in loaders), you can skip this step.

WorkOS Dashboard Configuration

Open the Redirects page in the WorkOS dashboard and configure:

  1. Redirect URIs — add your callback URL: http://localhost:3000/api/auth/callback
  2. Sign-in URL — set to the route from step 3 above: http://localhost:3000/api/auth/sign-in. Required for WorkOS-initiated flows like dashboard impersonation.
  3. Sign-out redirect — where to send users after sign-out. If unset, WorkOS falls back to the App homepage URL; if neither is set, WorkOS shows an error page.

Usage

Server-Side Authentication

Use getAuth() in route loaders or server functions to access the current session:

import{createFileRoute,redirect}from'@tanstack/react-router';import{getAuth}from'@workos/authkit-tanstack-react-start';exportconstRoute=createFileRoute('/dashboard')({loader: async()=>{const{ user }=awaitgetAuth();if(!user){throwredirect({href: '/api/auth/sign-in'});}return{ user };},component: DashboardPage,});functionDashboardPage(){const{ user }=Route.useLoaderData();return<div>Welcome,{user.firstName}!</div>;}

Client-Side Hooks

For client components that need reactive auth state, use the useAuth() hook:

'use client';// Not actually needed in TanStack Start, but shows intentimport{useAuth}from'@workos/authkit-tanstack-react-start/client';functionProfileButton(){const{ user, loading, signOut }=useAuth();if(loading)return<div>Loading...</div>;if(!user)return<ahref="/signin">SignIn</a>;return(<div><span>{user.email}</span><buttononClick={()=>signOut()}>SignOut</button></div>);}

Signing Out

Server-side (in route loader):

import{signOut}from'@workos/authkit-tanstack-react-start';exportconstRoute=createFileRoute('/logout')({loader: async()=>{awaitsignOut();// Redirects to WorkOS logout, then back to '/'},});

Client-side (from useAuth hook):

const{ signOut }=useAuth();awaitsignOut({returnTo: '/goodbye'});

Organization Switching

Switch the active organization for multi-org users:

Server-side:

import{switchToOrganization}from'@workos/authkit-tanstack-react-start';// In a server function or loaderconstauth=awaitswitchToOrganization({data: {organizationId: 'org_456'},});// Session now has org_456's role, permissions, etc.

Client-side:

const{ switchToOrganization, organizationId }=useAuth();awaitswitchToOrganization('org_456');// Auth state updates automatically

Protected Routes

Use layout routes to protect multiple pages:

// src/routes/_authenticated.tsximport{createFileRoute,redirect}from'@tanstack/react-router';import{getAuth}from'@workos/authkit-tanstack-react-start';exportconstRoute=createFileRoute('/_authenticated')({loader: async({ location })=>{const{ user }=awaitgetAuth();if(!user){constreturnPathname=encodeURIComponent(location.pathname);throwredirect({href: `/api/auth/sign-in?returnPathname=${returnPathname}`});}return{ user };},});// Now all routes under _authenticated require auth:// - _authenticated/dashboard.tsx// - _authenticated/profile.tsx// etc.

API Reference

Server Functions

These functions can be called from route loaders, server functions, or server route handlers.

getAuth()

Retrieves the current user session.

const{ user }=awaitgetAuth();if(user){console.log(user.email);console.log(user.firstName);}

Returns:UserInfo | NoUserInfo

UserInfo fields:

  • user - The authenticated user object
  • sessionId - WorkOS session ID
  • organizationId - Active organization (if in org context)
  • role - User's role in the organization
  • roles - Array of role strings
  • permissions - Array of permission strings
  • entitlements - Array of entitlement strings
  • featureFlags - Array of feature flag strings
  • impersonator - Impersonator details (if being impersonated)
  • accessToken - JWT access token

signOut(options?)

Signs out the current user and redirects to WorkOS logout.

awaitsignOut();awaitsignOut({data: {returnTo: '/goodbye'}});

Options:

  • returnTo - Path to redirect to after logout (default: /)

switchToOrganization(options)

Switches to a different organization and refreshes the session with new claims.

constauth=awaitswitchToOrganization({data: {organizationId: 'org_123',returnTo: '/dashboard',// optional},});

Options:

  • organizationId - The organization ID to switch to (required)
  • returnTo - Path to redirect to if auth fails

Returns:UserInfo with updated organization claims

getSignInUrl(options?)

Generates a sign-in URL for redirecting to AuthKit.

// Basic usageconsturl=awaitgetSignInUrl();// With return pathconsturl=awaitgetSignInUrl({data: {returnPathname: '/dashboard'},});

Options:

  • returnPathname - Path to return to after sign-in

getSignUpUrl(options?)

Generates a sign-up URL for redirecting to AuthKit.

consturl=awaitgetSignUpUrl();consturl=awaitgetSignUpUrl({data: {returnPathname: '/onboarding'},});

Options:

  • returnPathname - Path to return to after sign-up

getAuthorizationUrl(options)

Advanced: Generate a custom authorization URL with full control.

consturl=awaitgetAuthorizationUrl({data: {screenHint: 'sign-in',returnPathname: '/dashboard',redirectUri: 'https://example.com/callback',// override default},});

Options:

  • screenHint - 'sign-in' or 'sign-up'
  • returnPathname - Return path after authentication
  • redirectUri - Override the default redirect URI

Route Handlers

handleCallbackRoute

Handles the OAuth callback from WorkOS. Use this in your callback route.

Basic usage:

import{createFileRoute}from'@tanstack/react-router';import{handleCallbackRoute}from'@workos/authkit-tanstack-react-start';exportconstRoute=createFileRoute('/api/auth/callback')({server: {handlers: {GET: handleCallbackRoute(),},},});

With a sign-in error page (browser-friendly default):

exportconstRoute=createFileRoute('/api/auth/callback')({server: {handlers: {GET: handleCallbackRoute({errorRedirectUrl: '/sign-in?error=auth_failed',}),},},});

The user lands on /sign-in?error=auth_failed (a route you own) instead of seeing raw JSON. Verifier-delete cookies are still attached.

With Sentry capture:

import*asSentryfrom'@sentry/node';exportconstRoute=createFileRoute('/api/auth/callback')({server: {handlers: {GET: handleCallbackRoute({onSuccess: async({ user, authenticationMethod })=>{awaitdb.users.upsert({id: user.id,email: user.email});analytics.track('User Signed In',{method: authenticationMethod});},onError: ({ error, request })=>{Sentry.captureException(error,{extra: {url: request.url}});returnResponse.redirect(newURL('/sign-in?error=auth_failed',request.url));},}),},},});

onError runs for every callback failure (missing code, state mismatch, token exchange failure, onSuccess throws). The SDK already emits a console.error for every failure, so if you wire Sentry's console.error ingestion you don't need to call Sentry.captureException yourself.

Options:

  • onSuccess?: (data) => Promise<void> — Called after successful authentication with user data, tokens, and authentication method.
  • onError?: ({ error, request }) => Response — Custom error handler that returns a Response. Errors thrown from inside onError are NOT caught by the SDK.
  • errorRedirectUrl?: string — URL (absolute or relative) to redirect to on callback failure when onError is not set. If both are set, onError wins. Set this at route-construction time only — do not derive from request input (it would be an open-redirect vector).
  • returnPathname?: string — Override the success-path redirect after authentication. Does not apply to errors.

Client Hooks

Available from @workos/authkit-tanstack-react-start/client. Requires <AuthKitProvider> wrapper.

useAuth(options?)

Access authentication state and methods in client components.

import{useAuth}from'@workos/authkit-tanstack-react-start/client';functionMyComponent(){const{ user, loading, signOut }=useAuth();if(loading)return<div>Loading...</div>;if(!user)return<div>Notsignedin</div>;return(<div><p>{user.email}</p><buttononClick={()=>signOut()}>SignOut</button></div>);}

Options:

  • ensureSignedIn?: boolean - If true, automatically triggers sign-in flow for unauthenticated users

Returns:AuthContextType with:

  • user - Current user or null
  • loading - Loading state
  • sessionId, organizationId, role, roles, permissions, entitlements, featureFlags, impersonator
  • getAuth() - Refresh auth state
  • refreshAuth(options) - Refresh session with optional org switch
  • signOut(options) - Sign out
  • switchToOrganization(orgId) - Switch organizations

useAccessToken()

Manage access tokens with automatic refresh.

import{useAccessToken}from'@workos/authkit-tanstack-react-start/client';functionApiCaller(){const{ accessToken, loading, getAccessToken }=useAccessToken();constcallApi=async()=>{consttoken=awaitgetAccessToken();// Always freshconstresponse=awaitfetch('/api/data',{headers: {Authorization: `Bearer ${token}`},});};return<buttononClick={callApi}>FetchData</button>;}

Returns:

  • accessToken - Current token (may be stale)
  • loading - Loading state
  • error - Last error or null
  • refresh() - Manually refresh token
  • getAccessToken() - Get guaranteed fresh token

useTokenClaims()

Parse and decode JWT claims from the access token.

import{useTokenClaims}from'@workos/authkit-tanstack-react-start/client';functionClaimsDisplay(){constclaims=useTokenClaims();if(!claims)returnnull;return(<div><p>SessionID: {claims.sid}</p><p>Organization: {claims.org_id}</p><p>Role: {claims.role}</p></div>);}

Middleware

authkitMiddleware(options?)

Processes authentication on every request. Validates tokens, refreshes sessions, and provides auth context to server functions.

import{authkitMiddleware}from'@workos/authkit-tanstack-react-start';// Basic usageauthkitMiddleware();// With custom redirect URI (e.g., for Vercel preview deployments)authkitMiddleware({redirectUri: 'https://preview-123.example.com/api/auth/callback',});

Options:

  • redirectUri - Override the default redirect URI from WORKOS_REDIRECT_URI. Useful for dynamic environments like preview deployments.

CSRF: Registering authkitMiddleware in requestMiddleware opts your app out of the CSRF middleware TanStack Start applies by default. Pair it with createCsrfMiddleware (from @tanstack/react-start) to protect your server-function RPC endpoints — see step 1 of setup.

TypeScript

This library is fully typed. Common types:

importtype{User,Session,UserInfo,NoUserInfo,Impersonator}from'@workos/authkit-tanstack-react-start';// User object from WorkOSconstuser: User={id: string;
email: string;
firstName: string|null;
lastName: string|null;
emailVerified: boolean;
profilePictureUrl: string|null;// ... more fields};// Auth result from getAuth()constauth: UserInfo|NoUserInfo=awaitgetAuth();

Route loaders get full type inference:

exportconstRoute=createFileRoute('/profile')({loader: async()=>{const{ user }=awaitgetAuth();return{ user };// Fully typed},component: ProfilePage,});functionProfilePage(){const{ user }=Route.useLoaderData();// user is typed!}

How It Works

Server-Side Flow

  1. Middleware runs on every request - validates/refreshes session, stores auth in context
  2. Route loaders call getAuth() - retrieves auth from middleware context
  3. No client bundle bloat - server functions create RPC boundaries automatically

Client-Side Flow (with Provider)

  1. Provider wraps app - provides auth context to hooks
  2. Hooks call server actions - fetch auth state via RPC
  3. State updates automatically - on tab focus, refresh, org switch

Why the Provider is Optional

  • Server-only apps: Just use getAuth() in loaders - no provider needed
  • Client hooks needed: Add provider to use useAuth(), useAccessToken(), etc.
  • Flexibility: Start server-only, add client hooks later

Common Patterns

Sign In Flow

Link to the Sign-in URL you created in setup step 3. The endpoint handles generating the AuthKit URL and setting the PKCE cookie.

exportconstRoute=createFileRoute('/')({loader: async()=>{const{ user }=awaitgetAuth();return{ user };},component: HomePage,});functionHomePage(){const{ user }=Route.useLoaderData();if(!user){return<ahref="/api/auth/sign-in">SignInwithAuthKit</a>;}return<div>Welcome,{user.firstName}!</div>;}

Protected Route Layout

// src/routes/_authenticated.tsximport{createFileRoute,redirect}from'@tanstack/react-router';import{getAuth}from'@workos/authkit-tanstack-react-start';exportconstRoute=createFileRoute('/_authenticated')({loader: async({ location })=>{const{ user }=awaitgetAuth();if(!user){constreturnPathname=encodeURIComponent(location.pathname);throwredirect({href: `/api/auth/sign-in?returnPathname=${returnPathname}`});}return{ user };},});// All child routes require authentication:// - _authenticated/dashboard.tsx// - _authenticated/settings.tsx

Organization Switcher

import{useAuth}from'@workos/authkit-tanstack-react-start/client';functionOrgSwitcher(){const{ organizationId, switchToOrganization }=useAuth();return(<selectvalue={organizationId||''}onChange={(e)=>switchToOrganization(e.target.value)}><optionvalue="org_123">AcmeCorp</option><optionvalue="org_456">OtherCompany</option></select>);}

Accessing User in Multiple Places

Loader (server-side):

loader: async()=>{const{ user, organizationId, role }=awaitgetAuth();return{ user, organizationId, role };};

Component (from loader data):

functionMyPage(){const{ user }=Route.useLoaderData();// ...}

Client hook (reactive):

functionMyClientComponent(){const{ user, loading }=useAuth();// Updates on session changes}

Troubleshooting

Missing required auth parameter when impersonating from the WorkOS dashboard

This error occurs when WorkOS-initiated flows (like dashboard impersonation) redirect directly to your callback URL without going through your application's sign-in flow. Because this library enforces PKCE/CSRF verification on every callback, the request is rejected when the required state parameter is missing.

Fix: Configure a Sign-in URL in your WorkOS dashboard so impersonation flows route through your app first, letting PKCE/state be set up before redirecting to WorkOS.

"AuthKit middleware is not configured"

You forgot to add authkitMiddleware() to src/start.ts. See step 1 in setup.

"useAuth must be used within an AuthKitProvider"

You're calling useAuth() but haven't wrapped your app with <AuthKitProvider>. See step 3 in setup.

If you don't need client hooks, use getAuth() in loaders instead.

Environment variable errors on startup

The middleware validates configuration on first request. If you see errors about missing variables:

  1. Check your .env file exists
  2. Verify all required variables are set
  3. Ensure WORKOS_COOKIE_PASSWORD is 32+ characters
  4. Restart your dev server after changing env vars

Types not working / Import errors

Make sure you're importing from the right path:

// Server functionsimport{getAuth,signOut}from'@workos/authkit-tanstack-react-start';// Client hooksimport{useAuth}from'@workos/authkit-tanstack-react-start/client';

Don't import client hooks in server code or vice versa.

"can only be called on the server"

You're trying to call a server function from a beforeLoad hook or client component.

Wrong:

beforeLoad: async()=>{const{ user }=awaitgetAuth();// ❌ Runs on client during hydration};

Right:

loader: async()=>{const{ user }=awaitgetAuth();// ✅ Server-only during SSR};

Use useAuth() client hook for client components, or move logic to a loader.

Example Application

Check the /example directory for a complete working application demonstrating:

  • Server-side authentication in loaders
  • Client-side hooks with provider
  • Protected routes
  • Organization switching
  • Sign in/out flows
  • Access token management

Run it locally:

cd example
pnpm install
pnpm dev

Framework Compatibility

  • TanStack Start: v1.132.0+
  • TanStack Router: v1.132.0+
  • React: 18.0+
  • Node.js: 18+

Related

License

MIT

About

The WorkOS library for TanStack Start provides convenient helpers for authentication and session management using WorkOS & AuthKit with TanStack Start.

Topics

Resources

Stars

41 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages