Agent skill
workos-integration
Patterns for WorkOS SSO integration in Battery. Use this skill when implementing authentication, organization management, directory sync, or session handling.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/workos-integration
SKILL.md
WorkOS Integration Patterns
Overview
Battery uses WorkOS for:
- SSO federation (customers connect their Okta/Azure AD)
- Organization management (multi-tenancy)
- Directory sync (user provisioning)
SDK Setup
typescript
import { WorkOS } from '@workos-inc/node'
const workos = new WorkOS(process.env.WORKOS_API_KEY!)
SSO Authentication
Authorization URL
typescript
// Generate SSO login URL
async function getAuthorizationUrl(
organizationId: string,
redirectUri: string
) {
const authorizationUrl = workos.sso.getAuthorizationUrl({
organization: organizationId,
redirectUri,
clientId: process.env.WORKOS_CLIENT_ID!,
})
return authorizationUrl
}
Handle Callback
typescript
// app/api/auth/callback/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const code = request.nextUrl.searchParams.get('code')
if (!code) {
return NextResponse.redirect('/login?error=missing_code')
}
try {
const { profile } = await workos.sso.getProfileAndToken({
code,
clientId: process.env.WORKOS_CLIENT_ID!,
})
// Create session, set cookies, etc.
// profile contains: id, email, firstName, lastName, organizationId
return NextResponse.redirect('/dashboard')
} catch (error) {
return NextResponse.redirect('/login?error=auth_failed')
}
}
Organization Management
Create Organization
typescript
async function createOrganization(name: string, domains: string[]) {
const org = await workos.organizations.createOrganization({
name,
allowProfilesOutsideOrganization: false,
domains,
})
return org
}
List Organizations
typescript
async function listOrganizations() {
const { data: organizations } = await workos.organizations.listOrganizations({
limit: 100,
})
return organizations
}
Get Organization
typescript
async function getOrganization(orgId: string) {
return workos.organizations.getOrganization(orgId)
}
SSO Connection Setup
List Connections
typescript
async function getOrgConnections(organizationId: string) {
const { data: connections } = await workos.sso.listConnections({
organizationId,
})
return connections
}
Admin Portal Link
Generate a link for IT admins to configure SSO:
typescript
async function getAdminPortalLink(organizationId: string) {
const { link } = await workos.portal.generateLink({
organization: organizationId,
intent: 'sso', // or 'dsync' for directory sync
returnUrl: `${process.env.APP_URL}/settings/sso`,
})
return link
}
Directory Sync
List Directory Users
typescript
async function listDirectoryUsers(directoryId: string) {
const { data: users } = await workos.directorySync.listUsers({
directory: directoryId,
})
return users
}
Webhook Events
Handle directory sync webhooks:
typescript
// app/api/webhooks/workos/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const payload = await request.text()
const sigHeader = request.headers.get('workos-signature')!
const webhook = workos.webhooks.constructEvent({
payload,
sigHeader,
secret: process.env.WORKOS_WEBHOOK_SECRET!,
})
switch (webhook.event) {
case 'dsync.user.created':
await handleUserCreated(webhook.data)
break
case 'dsync.user.deleted':
await handleUserDeleted(webhook.data)
break
case 'dsync.group.user_added':
await handleUserAddedToGroup(webhook.data)
break
}
return NextResponse.json({ received: true })
}
Session Management
Session Token
typescript
interface BatterySession {
userId: string
email: string
organizationId: string
role: 'admin' | 'member'
expiresAt: number
}
// Create session after SSO callback
async function createSession(profile: Profile): Promise<string> {
const session: BatterySession = {
userId: profile.id,
email: profile.email,
organizationId: profile.organizationId,
role: await getUserRole(profile.id, profile.organizationId),
expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
}
return signJWT(session)
}
Middleware
typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { verifySession } from '@/lib/auth'
export async function middleware(request: NextRequest) {
const session = await verifySession(request)
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Add session to request headers for server components
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-battery-user-id', session.userId)
requestHeaders.set('x-battery-org-id', session.organizationId)
return NextResponse.next({
request: { headers: requestHeaders },
})
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
}
Auth Gateway Pattern
Battery wraps deployed apps with an auth proxy:
typescript
// Deployed app requests flow through Battery auth gateway
// 1. User hits {app}.{org}.battery.app
// 2. Battery checks for valid session cookie
// 3. If no session, redirect to WorkOS SSO
// 4. After SSO, set session cookie and proxy to Vercel app
interface AuthGatewayConfig {
appId: string
organizationId: string
allowedRoles?: string[]
}
Key Patterns
- Organization-first: Always scope operations to an organization
- Admin Portal: Let IT admins self-service SSO setup
- Webhook reliability: Use idempotency keys, handle retries
- Session security: Short-lived tokens, secure cookies
- Role mapping: Map IdP groups to Battery roles via directory sync
Didn't find tool you were looking for?