Skip to main content
Technology & EngineeringAuth Services230 lines

Workos

Build with WorkOS for enterprise SSO and authentication. Use this skill when the

Quick Summary33 lines
You are an auth specialist who integrates WorkOS into projects. WorkOS provides
enterprise-grade authentication features — SSO (SAML/OIDC), directory sync (SCIM),
audit logs, and AuthKit (a complete auth solution) — so you can sell to enterprise
customers without building SSO from scratch.

## Key Points

- Use AuthKit for complete auth — adds SSO when enterprise customers need it
- Use the admin portal for self-service SSO setup — no manual SAML configuration
- Implement token refresh — access tokens expire, refresh tokens extend sessions
- Use directory sync webhooks to auto-provision/deprovision users
- Store organization ID alongside user data for multi-tenant queries
- Use `organizationId` in auth URLs to route to the correct SSO provider
- Building SAML integration from scratch — WorkOS handles the complexity
- Not implementing token refresh — users get logged out unexpectedly
- Storing access tokens in localStorage — use httpOnly cookies
- Ignoring directory sync events — users won't be deprovisioned when they leave
- Hardcoding connection IDs — use organization-level SSO routing
- Not using the admin portal — manual SSO configuration doesn't scale

## Quick Example

```bash
npm install @workos-inc/node
```

```typescript
import { WorkOS } from '@workos-inc/node';

const workos = new WorkOS(process.env.WORKOS_API_KEY!);
```
skilldb get auth-services-skills/WorkosFull skill: 230 lines
Paste into your CLAUDE.md or agent config

WorkOS Authentication Integration

You are an auth specialist who integrates WorkOS into projects. WorkOS provides enterprise-grade authentication features — SSO (SAML/OIDC), directory sync (SCIM), audit logs, and AuthKit (a complete auth solution) — so you can sell to enterprise customers without building SSO from scratch.

Core Philosophy

Enterprise SSO without the pain

Adding SAML SSO to your app normally takes weeks of work per customer. WorkOS abstracts the SAML/OIDC complexity — you integrate once, and every enterprise customer can connect their identity provider (Okta, Azure AD, Google Workspace) through a self-service admin portal.

AuthKit = complete auth

AuthKit is WorkOS's full authentication solution — email/password, magic links, OAuth, MFA, and SSO in one. It's free up to 1M MAU for the base auth, with SSO as an add-on for enterprise deals.

Directory sync = automatic provisioning

WorkOS syncs your user directory with your customer's identity provider via SCIM. When they add or remove an employee in Okta, your app automatically provisions or deprovisions the user.

Setup

Install

npm install @workos-inc/node

Initialize

import { WorkOS } from '@workos-inc/node';

const workos = new WorkOS(process.env.WORKOS_API_KEY!);

Environment variables

WORKOS_API_KEY=sk_test_...
WORKOS_CLIENT_ID=client_...
WORKOS_REDIRECT_URI=http://localhost:3000/api/auth/callback

Key Techniques

AuthKit (complete auth flow)

// app/api/auth/login/route.ts
import { WorkOS } from '@workos-inc/node';

const workos = new WorkOS(process.env.WORKOS_API_KEY!);

export async function GET() {
  const authorizationUrl = workos.userManagement.getAuthorizationUrl({
    provider: 'authkit',
    redirectUri: process.env.WORKOS_REDIRECT_URI!,
    clientId: process.env.WORKOS_CLIENT_ID!,
  });

  return Response.redirect(authorizationUrl);
}

// app/api/auth/callback/route.ts
export async function GET(req: Request) {
  const url = new URL(req.url);
  const code = url.searchParams.get('code')!;

  const { user, accessToken, refreshToken } = await workos.userManagement.authenticateWithCode({
    code,
    clientId: process.env.WORKOS_CLIENT_ID!,
  });

  // Set session cookies
  const headers = new Headers();
  headers.append('Set-Cookie', `access_token=${accessToken}; Path=/; HttpOnly; Secure; SameSite=Lax`);
  headers.append('Set-Cookie', `refresh_token=${refreshToken}; Path=/; HttpOnly; Secure; SameSite=Lax`);
  headers.set('Location', '/dashboard');

  return new Response(null, { status: 302, headers });
}

SSO (enterprise single sign-on)

// Redirect to SSO provider
export async function GET(req: Request) {
  const url = new URL(req.url);
  const orgId = url.searchParams.get('org');

  const authorizationUrl = workos.userManagement.getAuthorizationUrl({
    organizationId: orgId!, // Routes to org's configured SSO provider
    redirectUri: process.env.WORKOS_REDIRECT_URI!,
    clientId: process.env.WORKOS_CLIENT_ID!,
  });

  return Response.redirect(authorizationUrl);
}

// Or use connection ID directly
const authorizationUrl = workos.userManagement.getAuthorizationUrl({
  connectionId: 'conn_...',
  redirectUri: process.env.WORKOS_REDIRECT_URI!,
  clientId: process.env.WORKOS_CLIENT_ID!,
});

Session management

import { cookies } from 'next/headers';

export async function validateSession() {
  const accessToken = (await cookies()).get('access_token')?.value;
  if (!accessToken) return null;

  try {
    const { user } = await workos.userManagement.getUser(accessToken);
    return user;
  } catch {
    // Try refresh
    const refreshToken = (await cookies()).get('refresh_token')?.value;
    if (!refreshToken) return null;

    try {
      const { accessToken: newToken, refreshToken: newRefresh } =
        await workos.userManagement.authenticateWithRefreshToken({
          refreshToken,
          clientId: process.env.WORKOS_CLIENT_ID!,
        });

      // Update cookies with new tokens
      (await cookies()).set('access_token', newToken, { httpOnly: true, secure: true });
      (await cookies()).set('refresh_token', newRefresh, { httpOnly: true, secure: true });

      const { user } = await workos.userManagement.getUser(newToken);
      return user;
    } catch {
      return null;
    }
  }
}

Organizations

// Create organization
const org = await workos.organizations.createOrganization({
  name: 'Acme Corp',
  domains: ['acme.com'],
});

// List user's organizations
const { data: memberships } = await workos.userManagement.listOrganizationMemberships({
  userId: user.id,
});

Directory sync (SCIM)

// List directory users
const { data: directoryUsers } = await workos.directorySync.listUsers({
  directory: directoryId,
});

// Webhook for directory events
export async function POST(req: Request) {
  const payload = await req.json();

  switch (payload.event) {
    case 'dsync.user.created':
      await db.insert(users).values({
        email: payload.data.emails[0].value,
        name: `${payload.data.first_name} ${payload.data.last_name}`,
        orgId: payload.data.directory_id,
      });
      break;
    case 'dsync.user.deleted':
      await db.delete(users).where(eq(users.email, payload.data.emails[0].value));
      break;
    case 'dsync.group.created':
      // Handle team/group provisioning
      break;
  }

  return new Response('OK');
}

Admin portal (self-service SSO setup)

// Generate admin portal link for customer to configure SSO
const { link } = await workos.portal.generateLink({
  organization: orgId,
  intent: 'sso',           // or 'dsync', 'audit_logs', 'log_streams'
  returnUrl: 'https://yourapp.com/settings',
});

// Redirect customer to the portal
return Response.redirect(link);

Best Practices

  • Use AuthKit for complete auth — adds SSO when enterprise customers need it
  • Use the admin portal for self-service SSO setup — no manual SAML configuration
  • Implement token refresh — access tokens expire, refresh tokens extend sessions
  • Use directory sync webhooks to auto-provision/deprovision users
  • Store organization ID alongside user data for multi-tenant queries
  • Use organizationId in auth URLs to route to the correct SSO provider

Anti-Patterns

  • Building SAML integration from scratch — WorkOS handles the complexity
  • Not implementing token refresh — users get logged out unexpectedly
  • Storing access tokens in localStorage — use httpOnly cookies
  • Ignoring directory sync events — users won't be deprovisioned when they leave
  • Hardcoding connection IDs — use organization-level SSO routing
  • Not using the admin portal — manual SSO configuration doesn't scale

Install this skill directly: skilldb add auth-services-skills

Get CLI access →