Back to list
ADLenehan

workos-integration

by ADLenehan

Safe hosting for the non-technical

0🍴 0📅 Jan 13, 2026

SKILL.md


name: workos-integration description: Patterns for WorkOS SSO integration in Battery. Use this skill when implementing authentication, organization management, directory sync, or session handling.

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

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

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

SSO Authentication

Authorization URL

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

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

async function createOrganization(name: string, domains: string[]) {
  const org = await workos.organizations.createOrganization({
    name,
    allowProfilesOutsideOrganization: false,
    domains,
  })

  return org
}

List Organizations

async function listOrganizations() {
  const { data: organizations } = await workos.organizations.listOrganizations({
    limit: 100,
  })

  return organizations
}

Get Organization

async function getOrganization(orgId: string) {
  return workos.organizations.getOrganization(orgId)
}

SSO Connection Setup

List Connections

async function getOrgConnections(organizationId: string) {
  const { data: connections } = await workos.sso.listConnections({
    organizationId,
  })

  return connections
}

Generate a link for IT admins to configure SSO:

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

async function listDirectoryUsers(directoryId: string) {
  const { data: users } = await workos.directorySync.listUsers({
    directory: directoryId,
  })

  return users
}

Webhook Events

Handle directory sync webhooks:

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

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

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

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

  1. Organization-first: Always scope operations to an organization
  2. Admin Portal: Let IT admins self-service SSO setup
  3. Webhook reliability: Use idempotency keys, handle retries
  4. Session security: Short-lived tokens, secure cookies
  5. Role mapping: Map IdP groups to Battery roles via directory sync

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon