Overview
How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol.
Voltro's auth story ships as a plugin: @voltro/plugin-auth. It's server-side primitives + client-side React glue, no third-party redirect dance, no managed-service dependency.
Built-in (password): password sign-up / sign-in, cookie-based sessions (multi-key rotation + sliding-window auto-renewal), rehash-on-verify, magic-link + password-reset flows, passkeys (WebAuthn), CSRF, session enumeration + revocation, multi-tenant memberships + switch-tenant, the typed Subject, TOTP/MFA enrolment, and a SubjectProvider React context + passkey ceremony helper (from the browser-safe @voltro/plugin-auth/web subpath). The single authRoutesPlugin() mounts every route under /auth.
Pluggable identity: auth is a strategy protocol, not just the password flow. First-party plugins ship for WorkOS, Kinde, Clerk, Auth0, Supabase, and generic OIDC; the shared jwtBearerStrategy covers any other OIDC provider; API-key callers use apiKeyStrategy from @voltro/protocol/apikey; you can write your own. Strategies stack, so you can run password + an external IdP side by side.
The pieces
┌────────────────────────────────────────────────────────────────┐
│ Browser │
│ POST /auth/signin ────────┐ │
│ Cookie: voltro:session=... │ │
└───────────────────────────────┬─┘ │
│ Set-Cookie (HttpOnly, Secure) │
▼ │
┌────────────────────────────────────────────────────────────────┐
│ api app │
│ handleSignIn → verifyPassword → issueSession │
│ readSession → HMAC verify → Subject │
└────────────────────────────────────────────────────────────────┘
▲
│ ctx.subject (typed)
│
┌───────────────────────────────────────────────────────────────┐
│ Every query / mutation / workflow │
│ ctx.subject.id (string on a user; null on anonymous) │
│ ctx.subject.tenantId (string on a user; string|null anon) │
└───────────────────────────────────────────────────────────────┘What's in this section
- Passwords — hashing (scrypt), verification, why not bcrypt/argon2
- Sessions — issuing, verifying, the signed-cookie payload
- The Subject — what's in
ctx.subject, anonymous fallback - Auth strategies — the
AuthStrategyprotocol, the resolver chain, writing your own - External identity providers — WorkOS, Kinde, Clerk, and the shared
jwtBearerStrategy - HTTP handlers —
handleSignIn,handleSignUp,handleSignOut - User stores —
memoryUserStore,postgresUserStore, custom backends - React on the web side —
SubjectProvider,useSubject,RequireAuth - Cookie security — the production checklist
Schema tables
Two tables ship from @voltro/plugin-auth/schema:
import { usersTable, sessionsTable } from '@voltro/plugin-auth/schema'
export const users = usersTable
export const sessions = sessionsTablepostgresUserStore reads + writes the users table only — sessions are stateless signed cookies, so the default flow never touches sessionsTable. It ships for apps that opt into DB-backed session enumeration / "sign out other devices"; the stateless default leaves it empty. Don't redeclare these yourself — extend via additional columns in a sibling table linked by userId, not by modifying these.
Why password is the default (and IdPs are strategies, not the base)
You can run Clerk / WorkOS / Kinde — they're first-party strategies. But the built-in password flow is the default, and external IdPs plug in underneath the framework's own Subject, because:
- Cookie sovereignty. With the built-in flow your app owns the session — no redirect to a third-party SSO domain, no managed-service dependency for a feature every B2B SaaS needs.
- Multi-tenant model is yours. Even when an external IdP authenticates the user,
tenantIdand the typedSubjectstay the framework's, not the vendor's. The IdP's claims ride along undermetadata.claims; they don't replace the model. See external IdPs. - Self-host friendly. Password auth needs nothing external. Reach for an IdP when you want SSO/SCIM/enterprise federation, not because the framework forces a service on you.
Strategies stack, so adopting an IdP is additive — keep password sessions working while new sign-ups flow through the IdP, no big-bang cutover.