workos-sso
npx skills add https://github.com/workos/skills --skill workos-sso
Agent 安装分布
Skill 文档
WorkOS Single Sign-On
Step 1: Fetch Documentation (BLOCKING)
STOP. Do not proceed until complete.
WebFetch these URLs in order â they are the source of truth for SSO implementation:
- https://workos.com/docs/sso/test-sso
- https://workos.com/docs/sso/single-logout
- https://workos.com/docs/sso/signing-certificates
- https://workos.com/docs/sso/sign-in-consent
- https://workos.com/docs/sso/saml-security
- https://workos.com/docs/sso/redirect-uris
- https://workos.com/docs/sso/login-flows
- https://workos.com/docs/sso/launch-checklist
If this skill conflicts with fetched docs, follow the docs.
Step 2: Pre-Flight Validation
Environment Variables
Check for required variables in .env.local or environment:
WORKOS_API_KEYâ starts withsk_(production) orsk_test_(staging)WORKOS_CLIENT_IDâ starts withclient_
Verify: Both keys exist and have correct prefixes. Staging keys access Test Identity Provider by default.
Redirect URI Configuration
Confirm callback URL is registered in WorkOS Dashboard:
- Log into https://dashboard.workos.com/
- Navigate to API Keys â Redirect URIs
- Verify your callback URL (e.g.,
https://your-app.com/sso/callback) is listed
Critical: Exact match required â trailing slashes and protocols must match.
Step 3: Install SDK
Detect package manager from lockfile, install WorkOS SDK:
# Detect which lockfile exists
ls package-lock.json yarn.lock pnpm-lock.yaml bun.lockb 2>/dev/null
# Install matching package manager
npm install @workos-inc/node
# OR
yarn add @workos-inc/node
# OR
pnpm add @workos-inc/node
# OR
bun add @workos-inc/node
Verify: SDK package exists in node_modules before continuing.
ls node_modules/@workos-inc/node/package.json
Step 4: Login Flow Selection (Decision Tree)
User initiates SSO from?
|
+-- Your app's login page
| --> Service Provider-Initiated (SP-initiated)
| --> User enters email â redirect to IdP â redirect back
| --> IMPLEMENTATION: Step 5
|
+-- Identity Provider portal
--> Identity Provider-Initiated (IdP-initiated)
--> User selects your app from IdP â redirect to your app
--> IMPLEMENTATION: Step 6 (callback only, no authorization URL)
Both flows require the same callback handler. Implement Step 5 for SP-initiated, then Step 6 for IdP-initiated support.
Step 5: Implement SP-Initiated SSO
5.1: Generate Authorization URL
Create endpoint to start SSO flow. Check fetched docs for exact method signature.
Typical pattern (verify against docs):
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS(process.env.WORKOS_API_KEY);
// Generate authorization URL
const authorizationUrl = workos.sso.getAuthorizationURL({
clientId: process.env.WORKOS_CLIENT_ID,
redirectUri: 'https://your-app.com/sso/callback',
// ONE OF:
organization: 'org_123', // For specific org
// OR
provider: 'GoogleOAuth', // For specific provider
// OR
domain: 'example.com', // For domain-based routing
});
// Redirect user to authorizationUrl
Decision tree for user identification:
How to identify which IdP?
|
+-- You know the organization ID
| --> Use organization parameter
|
+-- User enters email
| --> Extract domain â use domain parameter
| --> WorkOS routes to correct IdP
|
+-- Multi-provider login page
--> Use provider parameter (GoogleOAuth, MicrosoftOAuth, etc.)
Check fetched docs for supported provider values.
5.2: State Parameter (Optional but Recommended)
For CSRF protection and context preservation:
const state = generateRandomString(); // Your implementation
storeInSession(state); // Your session mechanism
const authorizationUrl = workos.sso.getAuthorizationURL({
// ... other params
state: state,
});
Verify state in callback handler (Step 6.2).
Step 6: Implement Callback Handler
6.1: Create Callback Route
Create handler at your configured redirect URI path (e.g., /sso/callback).
Check fetched docs for authenticateWithCode method signature:
// Callback handler receives: code, state parameters
const { code, state, error, error_description } = request.query;
// Handle errors first (Step 6.3)
if (error) {
// See Step 6.3
}
// Exchange code for profile
const profile = await workos.sso.authenticateWithCode({
clientId: process.env.WORKOS_CLIENT_ID,
code: code,
});
// profile contains:
// - profile.id (unique user ID)
// - profile.email
// - profile.firstName, profile.lastName
// - profile.organizationId
// - profile.connectionId
6.2: State Validation (If Used)
const storedState = getFromSession(); // Your session mechanism
if (state !== storedState) {
throw new Error('State mismatch â possible CSRF attack');
}
6.3: Error Handling (CRITICAL)
Required error codes to handle:
switch (error) {
case 'signin_consent_denied':
// User denied consent at IdP
// Action: Show "Contact your admin" message
// Do NOT retry automatically â user explicitly declined
break;
case 'invalid_request':
// Configuration issue (wrong redirect URI, client ID, etc.)
// Action: Log error, show generic "Configuration error"
// Fix: Check Dashboard settings
break;
case 'access_denied':
// IdP rejected authentication
// Action: Show "Access denied by your organization"
break;
default:
// Generic error
// error_description contains details
// Log for debugging, show generic error to user
}
Check fetched docs for complete error code list.
6.4: Session Management
After successful authentication:
// Create session with profile data
const session = {
userId: profile.id,
email: profile.email,
organizationId: profile.organizationId,
};
// Store session (your implementation)
setUserSession(session);
// Redirect to app dashboard
redirect('/dashboard');
Critical for IdP-initiated: Callback handler must work WITHOUT prior authorization URL generation. User arrives directly from IdP with code.
Step 7: Test with Test Identity Provider
7.1: Verify Test Organization Exists
Navigate to Dashboard staging environment:
- Log into https://dashboard.workos.com/
- Switch to staging environment (top right)
- Navigate to Organizations
- Confirm “Test Organization” exists with active SSO connection
If missing: Your staging environment should have this by default. Contact WorkOS support.
7.2: Test SP-Initiated Flow
# Start your app
npm run dev
# Navigate to login page
open http://localhost:3000/login
# Enter test email: test@example.com
# Should redirect to Test IdP
# Click "Sign in" on Test IdP
# Should redirect back to your app with successful authentication
Verify: Check callback handler receives code parameter, not error.
7.3: Test IdP-Initiated Flow (CRITICAL)
Important: Disable AuthKit first if enabled:
- Navigate to https://dashboard.workos.com/authentication
- Toggle AuthKit OFF
- Save
Then test:
- Navigate to Test SSO page: https://dashboard.workos.com/test-sso
- Click “Test IdP-initiated SSO” link
- Should land directly on Test IdP
- Click “Sign in”
- Should redirect to your callback handler
If fails: Check callback handler doesn’t require prior authorization URL generation.
7.4: Test Error Scenarios
From Test SSO page, test:
- Consent Denied: Click “Deny” on Test IdP â verify
signin_consent_deniederror handled - Guest Email: Use
guest@different-domain.comâ verify profile contains guest email - Generic Error: Click “Simulate Error” â verify
erroranderror_descriptionhandled
Step 8: Configure Production Organization
8.1: Create Organization
In Dashboard production environment:
- Navigate to Organizations
- Click “Create organization”
- Enter customer name (e.g., “Acme Corp”)
- Enter domain (e.g., “acme.com”)
- Save
8.2: Enable SSO Connection
Decision tree for setup method:
Who configures SSO?
|
+-- Your team (white-glove setup)
| --> Manual connection creation (Step 8.3)
|
+-- Customer admin (self-serve)
--> Admin Portal invitation (Step 8.4)
8.3: Manual Connection Setup (White-Glove)
- Open organization in Dashboard
- Click “Create connection”
- Select identity provider (Okta, Azure AD, Google, etc.)
- Follow provider-specific instructions from fetched docs
- Test connection with “Test Connection” button
Check workos-integrations skill for provider-specific setup details.
8.4: Admin Portal Invitation (Self-Serve)
- Open organization in Dashboard
- Click “Invite admin”
- Select “Single Sign-On”
- Enter customer admin email OR copy setup link
- Customer follows Admin Portal instructions
Verify: Connection status changes to “Active” after customer completes setup.
Step 9: Single Logout (Optional)
Check fetched docs for current support status. As of last update:
- Supported for OpenID Connect connections only
- Not supported for SAML connections
- Limited scenarios
Implementation (if supported)
// RP-initiated logout
const logoutUrl = workos.sso.getLogoutURL({
sessionId: profile.sessionId, // From authentication response
});
// Redirect user to logoutUrl
// This logs out from your app AND IdP
Fallback: If Single Logout not supported, implement local logout only:
// Clear local session
clearUserSession();
redirect('/login');
// User remains logged into IdP (standard behavior)
Verification Checklist (ALL MUST PASS)
Run these commands to verify integration:
# 1. Environment variables exist
grep -E "WORKOS_API_KEY|WORKOS_CLIENT_ID" .env.local || echo "FAIL: Missing env vars"
# 2. SDK installed
ls node_modules/@workos-inc/node/package.json || echo "FAIL: SDK not installed"
# 3. Callback route exists (adjust path to your implementation)
find . -name "*callback*" -o -name "*sso*" | grep -v node_modules
# 4. Test authentication with curl (requires running app)
curl -I http://localhost:3000/sso/callback?code=test 2>/dev/null | grep -E "200|302" || echo "FAIL: Callback not responding"
# 5. Production build succeeds
npm run build
Manual verification:
- SP-initiated flow works with Test IdP
- IdP-initiated flow works with Test IdP
-
signin_consent_deniederror displays helpful message - Callback handler logs profile data correctly
- Session persists across page reloads
- Production organization has active SSO connection
Error Recovery
“Connection not found”
Root cause: Authorization URL uses organization parameter, but organization has no active SSO connection.
Fix:
- Check organization in Dashboard
- Verify SSO connection status is “Active”
- For test: Use “Test Organization” in staging environment
- For production: Customer must complete Admin Portal setup
“Redirect URI mismatch”
Root cause: Callback URL doesn’t match Dashboard configuration EXACTLY.
Fix:
- Check actual callback URL in browser (after IdP redirect)
- Log into Dashboard â API Keys â Redirect URIs
- Add exact URL including protocol and trailing slash
- Common mismatch:
http://localhost:3000/callback/vshttp://localhost:3000/callback
“Invalid client credentials”
Root cause: Wrong WORKOS_CLIENT_ID or WORKOS_API_KEY.
Fix:
- Verify keys in Dashboard â API Keys
- Check environment (staging keys work only in staging)
- Confirm no extra whitespace in
.env.local - Restart app after changing env vars
“State mismatch” or “Invalid state”
Root cause: State parameter implementation broken or missing.
Fix:
- If not using state: Remove state validation from callback handler
- If using state: Check session storage mechanism works
- Verify state generation uses cryptographically secure random
- Check session doesn’t expire between authorization URL and callback
Callback receives error=invalid_request
Root cause: Configuration error in authorization URL generation.
Fix:
- Check parameters passed to
getAuthorizationURL:clientIdmatches DashboardredirectUrimatches Dashboard exactly- At least one of:
organization,provider, ordomain
- Check SDK version supports your parameter combination
- Review fetched docs for required parameters
User sees “This app hasn’t been verified” (Google OAuth)
Root cause: Google SSO connection not verified with Google.
Fix:
- This is expected for development/staging
- For production: Follow Google OAuth verification process
- Alternatively: Use Test IdP or different provider for testing
- Users can click “Advanced” â “Go to [app] (unsafe)” to proceed (dev only)
IdP-initiated flow lands on 404
Root cause: Callback handler doesn’t handle missing state parameter.
Fix:
- Make
stateparameter optional in callback handler - IdP-initiated flow doesn’t include state (by design)
- Check:
const state = request.query.state || null;
“cookies was called outside request scope” (Next.js 15+)
Root cause: Async cookie operations not properly awaited.
Fix:
- Ensure callback handler is async
- Await all session operations
- Check session library compatibility with Next.js 15+
- See
workos-authkit-nextjsskill for Next.js-specific patterns
Related Skills
- workos-authkit-nextjs: Higher-level Next.js SSO integration with UI components
- workos-integrations: Provider-specific SSO setup instructions (Okta, Azure AD, Google, etc.)
- workos-admin-portal: Self-serve SSO setup for customers
- workos-directory-sync: Sync user directories from IdPs after SSO is configured
- workos-rbac: Add role-based access control after SSO authentication
- workos-domain-verification: Verify domain ownership before enabling SSO