The WorkOS library for TanStack React Start provides convenient helpers for authentication and session management using WorkOS & AuthKit with TanStack React Start.
npm install @workos/authkit-tanstack-react-startAuthentication 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.
``bash`
npm install @workos/authkit-tanstack-react-start
`bash`
pnpm add @workos/authkit-tanstack-react-start
Create a .env file in your project root with the following required variables:
`bash`
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):
`bash`
openssl rand -base64 24
#### Optional Configuration
| Variable | Default | Description |
| ------------------------ | --------------------- | -------------------------------------------- |
| WORKOS_COOKIE_MAX_AGE | 34560000 (400 days) | Cookie lifetime in seconds |WORKOS_COOKIE_NAME
| | wos-session | Session cookie name |WORKOS_COOKIE_DOMAIN
| | None | Cookie domain (for multi-domain sessions) |WORKOS_COOKIE_SAMESITE
| | lax | SameSite attribute (lax, strict, none) |WORKOS_API_HOSTNAME
| | api.workos.com | WorkOS API hostname |
#### 1. Configure Middleware
Create or update src/start.ts:
`typescript
import { createStart } from '@tanstack/react-start';
import { authkitMiddleware } from '@workos/authkit-tanstack-react-start';
export const startInstance = createStart(() => ({
requestMiddleware: [authkitMiddleware()],
}));
`
#### 2. Create Callback Route
Create src/routes/api/auth/callback.tsx:
`typescript
import { createFileRoute } from '@tanstack/react-router';
import { handleCallbackRoute } from '@workos/authkit-tanstack-react-start';
export const Route = createFileRoute('/api/auth/callback')({
server: {
handlers: {
GET: handleCallbackRoute(),
},
},
});
`
Make sure this matches your WORKOS_REDIRECT_URI environment variable.
#### 3. 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:
`typescript
import { AuthKitProvider } from '@workos/authkit-tanstack-react-start/client';
import { Outlet, createRootRoute } from '@tanstack/react-router';
export const Route = createRootRoute({
component: RootComponent,
});
function RootComponent() {
return (
);
}
`
If you're only using server-side authentication (getAuth() in loaders), you can skip this step.
1. Go to WorkOS Dashboard and navigate to the Redirects page.
2. Under Redirect URIs, add your callback URL: http://localhost:3000/api/auth/callback
3. Under Sign-out redirect, set the URL where you want users to be redirected after signing out. If you don't set a sign-out redirect URL, you must set the App homepage URL instead — WorkOS will redirect users there when no sign-out redirect is specified.
Note: If you don't set either the Sign-out redirect or the App homepage URL, WorkOS will redirect users to an error page.
Use getAuth() in route loaders or server functions to access the current session:
`typescript
import { createFileRoute, redirect } from '@tanstack/react-router';
import { getAuth, getSignInUrl } from '@workos/authkit-tanstack-react-start';
export const Route = createFileRoute('/dashboard')({
loader: async () => {
const { user } = await getAuth();
if (!user) {
const signInUrl = await getSignInUrl();
throw redirect({ href: signInUrl });
}
return { user };
},
component: DashboardPage,
});
function DashboardPage() {
const { user } = Route.useLoaderData();
return
$3
For client components that need reactive auth state, use the
useAuth() hook:`typescript
'use client'; // Not actually needed in TanStack Start, but shows intentimport { useAuth } from '@workos/authkit-tanstack-react-start/client';
function ProfileButton() {
const { user, loading, signOut } = useAuth();
if (loading) return
Loading...; if (!user) return Sign In;
return (
{user.email}
);
}
`$3
Server-side (in route loader):
`typescript
import { signOut } from '@workos/authkit-tanstack-react-start';export const Route = createFileRoute('/logout')({
loader: async () => {
await signOut(); // Redirects to WorkOS logout, then back to '/'
},
});
`Client-side (from useAuth hook):
`typescript
const { signOut } = useAuth();await signOut({ returnTo: '/goodbye' });
`$3
Switch the active organization for multi-org users:
Server-side:
`typescript
import { switchToOrganization } from '@workos/authkit-tanstack-react-start';// In a server function or loader
const auth = await switchToOrganization({
data: { organizationId: 'org_456' },
});
// Session now has org_456's role, permissions, etc.
`Client-side:
`typescript
const { switchToOrganization, organizationId } = useAuth();await switchToOrganization('org_456');
// Auth state updates automatically
`$3
Use layout routes to protect multiple pages:
`typescript
// src/routes/_authenticated.tsx
import { createFileRoute, redirect } from '@tanstack/react-router';
import { getAuth, getSignInUrl } from '@workos/authkit-tanstack-react-start';export const Route = createFileRoute('/_authenticated')({
loader: async ({ location }) => {
const { user } = await getAuth();
if (!user) {
const signInUrl = await getSignInUrl({
data: { returnPathname: location.pathname },
});
throw redirect({ href: signInUrl });
}
return { user };
},
});
// Now all routes under _authenticated require auth:
// - _authenticated/dashboard.tsx
// - _authenticated/profile.tsx
// etc.
`API Reference
$3
These functions can be called from route loaders, server functions, or server route handlers.
####
getAuth()Retrieves the current user session.
`typescript
const { user } = await getAuth();if (user) {
console.log(user.email);
console.log(user.firstName);
}
`Returns:
UserInfo | NoUserInfoUserInfo 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.
`typescript
await signOut();
await signOut({ 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.
`typescript
const auth = await switchToOrganization({
data: {
organizationId: 'org_123',
returnTo: '/dashboard', // optional
},
});
`Options:
-
organizationId - The organization ID to switch to (required)
- returnTo - Path to redirect to if auth failsReturns:
UserInfo with updated organization claims####
getSignInUrl(options?)Generates a sign-in URL for redirecting to AuthKit.
`typescript
// Basic usage
const url = await getSignInUrl();// With return path
const url = await getSignInUrl({
data: { returnPathname: '/dashboard' },
});
`Options:
-
returnPathname - Path to return to after sign-in####
getSignUpUrl(options?)Generates a sign-up URL for redirecting to AuthKit.
`typescript
const url = await getSignUpUrl();
const url = await getSignUpUrl({
data: { returnPathname: '/onboarding' },
});
`Options:
-
returnPathname - Path to return to after sign-up####
getAuthorizationUrl(options)Advanced: Generate a custom authorization URL with full control.
`typescript
const url = await getAuthorizationUrl({
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$3
####
handleCallbackRouteHandles the OAuth callback from WorkOS. Use this in your callback route.
Basic usage:
`typescript
import { createFileRoute } from '@tanstack/react-router';
import { handleCallbackRoute } from '@workos/authkit-tanstack-react-start';export const Route = createFileRoute('/api/auth/callback')({
server: {
handlers: {
GET: handleCallbackRoute(),
},
},
});
`With hooks for custom logic:
`typescript
export const Route = createFileRoute('/api/auth/callback')({
server: {
handlers: {
GET: handleCallbackRoute({
onSuccess: async ({ user, authenticationMethod }) => {
// Create user record in your database
await db.users.upsert({ id: user.id, email: user.email });
// Track analytics
analytics.track('User Signed In', { method: authenticationMethod });
},
onError: ({ error, request }) => {
// Custom error handling
console.error('Auth failed:', error);
return new Response(JSON.stringify({ error: 'Authentication failed' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
},
}),
},
},
});
`Options:
-
onSuccess?: (data) => Promise - Called after successful authentication with user data, tokens, and authentication method
- onError?: ({ error, request }) => Response - Custom error handler that returns a Response
- returnPathname?: string - Override the redirect path after authentication (defaults to state or /)$3
Available from
@workos/authkit-tanstack-react-start/client. Requires wrapper.####
useAuth(options?)Access authentication state and methods in client components.
`typescript
import { useAuth } from '@workos/authkit-tanstack-react-start/client';function MyComponent() {
const { user, loading, signOut } = useAuth();
if (loading) return
Loading...;
if (!user) return Not signed in; return (
{user.email}
);
}
`Options:
-
ensureSignedIn?: boolean - If true, automatically triggers sign-in flow for unauthenticated usersReturns:
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.
`typescript
import { useAccessToken } from '@workos/authkit-tanstack-react-start/client';function ApiCaller() {
const { accessToken, loading, getAccessToken } = useAccessToken();
const callApi = async () => {
const token = await getAccessToken(); // Always fresh
const response = await fetch('/api/data', {
headers: { Authorization:
Bearer ${token} },
});
}; return ;
}
`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.
`typescript
import { useTokenClaims } from '@workos/authkit-tanstack-react-start/client';function ClaimsDisplay() {
const claims = useTokenClaims();
if (!claims) return null;
return (
Session ID: {claims.sid}
Organization: {claims.org_id}
Role: {claims.role}
);
}
`$3
####
authkitMiddleware(options?)Processes authentication on every request. Validates tokens, refreshes sessions, and provides auth context to server functions.
`typescript
import { authkitMiddleware } from '@workos/authkit-tanstack-react-start';// Basic usage
authkitMiddleware();
// 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.TypeScript
This library is fully typed. Common types:
`typescript
import type {
User,
Session,
UserInfo,
NoUserInfo,
Impersonator
} from '@workos/authkit-tanstack-react-start';// User object from WorkOS
const user: User = {
id: string;
email: string;
firstName: string | null;
lastName: string | null;
emailVerified: boolean;
profilePictureUrl: string | null;
// ... more fields
};
// Auth result from getAuth()
const auth: UserInfo | NoUserInfo = await getAuth();
`Route loaders get full type inference:
`typescript
export const Route = createFileRoute('/profile')({
loader: async () => {
const { user } = await getAuth();
return { user }; // Fully typed
},
component: ProfilePage,
});function ProfilePage() {
const { user } = Route.useLoaderData(); // user is typed!
}
`How It Works
$3
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$3
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
$3
- 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 laterCommon Patterns
$3
`typescript
// Get sign-in URL in loader
export const Route = createFileRoute('/')({
loader: async () => {
const { user } = await getAuth();
const signInUrl = await getSignInUrl();
return { user, signInUrl };
},
component: HomePage,
});function HomePage() {
const { user, signInUrl } = Route.useLoaderData();
if (!user) {
return Sign In with AuthKit;
}
return
Welcome, {user.firstName}!;
}
`$3
`typescript
// src/routes/_authenticated.tsx
import { createFileRoute, redirect } from '@tanstack/react-router';
import { getAuth, getSignInUrl } from '@workos/authkit-tanstack-react-start';export const Route = createFileRoute('/_authenticated')({
loader: async ({ location }) => {
const { user } = await getAuth();
if (!user) {
const signInUrl = await getSignInUrl({
data: { returnPathname: location.pathname },
});
throw redirect({ href: signInUrl });
}
return { user };
},
});
// All child routes require authentication:
// - _authenticated/dashboard.tsx
// - _authenticated/settings.tsx
`$3
`typescript
import { useAuth } from '@workos/authkit-tanstack-react-start/client';function OrgSwitcher() {
const { organizationId, switchToOrganization } = useAuth();
return (
value={organizationId || ''}
onChange={(e) => switchToOrganization(e.target.value)}
>
);
}
`$3
Loader (server-side):
`typescript
loader: async () => {
const { user, organizationId, role } = await getAuth();
return { user, organizationId, role };
};
`Component (from loader data):
`typescript
function MyPage() {
const { user } = Route.useLoaderData();
// ...
}
`Client hook (reactive):
`typescript
function MyClientComponent() {
const { user, loading } = useAuth();
// Updates on session changes
}
`Troubleshooting
$3
You forgot to add
authkitMiddleware() to src/start.ts. See step 1 in setup.$3
You're calling
useAuth() but haven't wrapped your app with . See step 3 in setup.If you don't need client hooks, use
getAuth() in loaders instead.$3
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$3
Make sure you're importing from the right path:
`typescript
// Server functions
import { getAuth, signOut } from '@workos/authkit-tanstack-react-start';// Client hooks
import { useAuth } from '@workos/authkit-tanstack-react-start/client';
`Don't import client hooks in server code or vice versa.
$3
You're trying to call a server function from a
beforeLoad hook or client component.Wrong:
`typescript
beforeLoad: async () => {
const { user } = await getAuth(); // ❌ Runs on client during hydration
};
`Right:
`typescript
loader: async () => {
const { user } = await getAuth(); // ✅ 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:
`bash
cd example
pnpm install
pnpm dev
``- TanStack Start: v1.132.0+
- TanStack Router: v1.132.0+
- React: 18.0+
- Node.js: 18+
- WorkOS AuthKit Documentation
- TanStack Start Documentation
- WorkOS Node SDK
MIT