Skip to content

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

License

Notifications You must be signed in to change notification settings

workos/authkit-tanstack-start

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

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

Setup (3 Steps)

1. Configure Middleware

Create or update src/start.ts:

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:

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:

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 (
    <AuthKitProvider>
      <Outlet />
    </AuthKitProvider>
  );
}

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

WorkOS Dashboard Configuration

  1. Go to WorkOS Dashboard
  2. Navigate to Redirects and add your callback URL: http://localhost:3000/api/auth/callback
  3. (Optional) Set a default Logout URI under Redirects for sign-out redirects

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, 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 <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 intent

import { useAuth } from '@workos/authkit-tanstack-react-start/client';

function ProfileButton() {
  const { user, loading, signOut } = useAuth();

  if (loading) return <div>Loading...</div>;

  if (!user) return <a href="/signin">Sign In</a>;

  return (
    <div>
      <span>{user.email}</span>
      <button onClick={() => signOut()}>Sign Out</button>
    </div>
  );
}

Signing Out

Server-side (in route loader):

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):

const { signOut } = useAuth();

await signOut({ 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 loader
const auth = await switchToOrganization({
  data: { organizationId: 'org_456' },
});

// Session now has org_456's role, permissions, etc.

Client-side:

const { switchToOrganization, organizationId } = useAuth();

await switchToOrganization('org_456');
// Auth state updates automatically

Protected Routes

Use layout routes to protect multiple pages:

// 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

Server Functions

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

getAuth()

Retrieves the current user session.

const { user } = await getAuth();

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.

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.

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 fails

Returns: UserInfo with updated organization claims

getSignInUrl(options?)

Generates a sign-in URL for redirecting to AuthKit.

// 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.

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.

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

Route Handlers

handleCallbackRoute

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

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

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

function MyComponent() {
  const { user, loading, signOut } = useAuth();

  if (loading) return <div>Loading...</div>;
  if (!user) return <div>Not signed in</div>;

  return (
    <div>
      <p>{user.email}</p>
      <button onClick={() => signOut()}>Sign Out</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';

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 <button onClick={callApi}>Fetch Data</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';

function ClaimsDisplay() {
  const claims = useTokenClaims();

  if (!claims) return null;

  return (
    <div>
      <p>Session ID: {claims.sid}</p>
      <p>Organization: {claims.org_id}</p>
      <p>Role: {claims.role}</p>
    </div>
  );
}

Middleware

authkitMiddleware()

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

Already shown in setup, but can be imported separately if needed.

TypeScript

This library is fully typed. Common types:

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:

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

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

// 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 <a href={signInUrl}>Sign In with AuthKit</a>;
  }

  return <div>Welcome, {user.firstName}!</div>;
}

Protected Route Layout

// 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

Organization Switcher

import { useAuth } from '@workos/authkit-tanstack-react-start/client';

function OrgSwitcher() {
  const { organizationId, switchToOrganization } = useAuth();

  return (
    <select
      value={organizationId || ''}
      onChange={(e) => switchToOrganization(e.target.value)}
    >
      <option value="org_123">Acme Corp</option>
      <option value="org_456">Other Company</option>
    </select>
  );
}

Accessing User in Multiple Places

Loader (server-side):

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

Component (from loader data):

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

Client hook (reactive):

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

Troubleshooting

"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 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.

"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 } = await getAuth(); // ❌ Runs on client during hydration
};

Right:

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:

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.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages