WorkOS
WorkOS AuthStrategy — verifies WorkOS AuthKit / SSO JWTs via JWKS (no API key) and maps org_id → tenantId.
@voltro/plugin-auth-workos integrates WorkOS AuthKit / SSO: an AuthStrategy (conforming to @voltro/protocol) that verifies WorkOS-issued JWTs against the JWKS endpoint (public-key verify — no API key), maps org_id to the framework's tenantId, and composes with other strategies (your password cookie, an API key, another IdP). It's a thin specialization of the shared jwtBearerStrategy — it adds WorkOS's defaults and performs no verification of its own. Verified claims arrive on the Subject as metadata.provider === 'workos' + metadata.claims.
Install
pnpm add @voltro/plugin-auth-workosWiring
Add workosStrategy(...) to auth.strategies in your api's app.config.ts. The built-in signed-cookie password strategy always runs first; your strategies run after it, in order, until one matcheds (or one faileds).
// app.config.ts
import { workosStrategy } from '@voltro/plugin-auth-workos'
export default {
type: 'api' as const,
name: 'myApi',
store: 'postgres' as const,
auth: {
strategies: [
workosStrategy({
clientId: process.env.WORKOS_CLIENT_ID!, // client_01H… — builds JWKS URL + audience
// defaultTenantId: 'public', // fallback when claims expose no org_id
}),
],
},
}clientId is the only required option. From it: the JWKS URL defaults to https://api.workos.com/sso/jwks/<clientId>, the issuer to https://api.workos.com, and the audience to the clientId. The cookie defaults to wos-session (set by AuthKit's cookie-mode SDK; pass null for header-only).
Tenant resolution maps from claims.org_id, then defaultTenantId. Supply tenantIdFromClaims(claims) to derive it (returning null is a failed verdict). Verified claims arrive as WorkosClaims (sub, email, org_id, role, permissions, …).
To accept your own password sessions AND WorkOS during a migration, build the chain explicitly with composeAuthStrategies (see auth strategies):
import { composeAuthStrategies } from '@voltro/protocol'
import { voltroPasswordStrategy } from '@voltro/plugin-auth'
import { workosStrategy } from '@voltro/plugin-auth-workos'
const resolve = composeAuthStrategies([
voltroPasswordStrategy(),
workosStrategy({ clientId: process.env.WORKOS_CLIENT_ID! }),
])Options
| Option | Default | Notes |
|---|---|---|
clientId |
— (required) | client_01H…. Builds JWKS URL + audience. |
jwksUrl |
https://api.workos.com/sso/jwks/<clientId> |
Override. |
issuer |
https://api.workos.com |
Override. |
cookieName |
'wos-session' |
AuthKit cookie mode; null for header-only. |
tenantIdFromClaims |
(uses org_id) |
Derive tenantId; null → failed. |
defaultTenantId |
(none) | Fallback when claims expose no org_id. |
scopesFromClaims |
(none) | Map claims (AuthKit's permissions[] / role) → Subject.scopes. |
The audience is the clientId; algorithms are pinned to ['RS256', 'ES256'] (no alg-confusion).
Environment variables
None read directly — pass clientId explicitly (typically from your own defineEnv-declared vars).
Security
Server-only; fails closed — a forged, expired, or wrong-audience token, or one with no resolvable tenant, yields failed, never matched. JWKS public-key verify only; no WorkOS API key is ever handled.
Hosted-login primitives (SSO login)
Beyond the verify-only workosStrategy, the package exports two primitives for the hosted-login flow — WorkOS logs the user in and your app mints its own session (an alternate front door alongside password login, not a replacement session authority):
workosAuthorizationUrl({ clientId, redirectUri, state? })→ the AuthKit login URL to redirect the browser to.workosAuthenticateWithCode({ clientId, apiKey, code })→ exchanges the callbackcodefor aWorkosProfile(workosUserId,email,firstName,lastName,organizationId). Server-only (carries the API key).
Both are transport-thin (raw fetch, no @workos-inc/node dependency). Wire them into a GET /auth/workos/login + GET /auth/workos/callback route pair, find-or-provision your user in the callback, then issueSession(...). Full worked example: WorkOS SSO login.
Passwordless (Magic Auth)
For passwordless login — an email one-time code, no password and no browser redirect — the package exports the WorkOS Magic Auth pair:
workosSendMagicAuthCode({ apiKey, email })→ creates + emails a one-time code toemail. Server-only (the API key is sent as a Bearer token).workosAuthenticateWithMagicAuth({ clientId, apiKey, email, code })→ verifies the submittedcodeand returns the sameWorkosProfileas the code exchange.
Wire them into a POST /auth/passwordless/start (send the code) + POST /auth/passwordless/verify (verify → find-or-provision → issueSession(...)) route pair — the same mint-your-own-session model as the hosted-login flow, without the redirect. Both are transport-thin (raw fetch).