Warning
This is prerelease software. APIs may change without notice.
Toolkit for building WorkOS AuthKit framework integrations.
Handles JWT verification, session encryption, and token refresh orchestration. You build the framework-specific glue.
pnpm add @workos/authkit-session| Layer | Class | Purpose |
|---|---|---|
| Core | AuthKitCore |
JWT verification (JWKS with caching), session encryption (AES-256-CBC), token refresh orchestration |
| Operations | AuthOperations |
WorkOS API calls: signOut, refreshSession, authorization URLs |
| Helpers | CookieSessionStorage |
Base class with secure cookie defaults |
| Orchestration | AuthService |
Reference implementation combining all layers |
WORKOS_CLIENT_ID=your-client-id
WORKOS_API_KEY=your-api-key
WORKOS_REDIRECT_URI=https://yourdomain.com/auth/callback
WORKOS_COOKIE_PASSWORD=must-be-at-least-32-characters-long-secretOr programmatically:
import { configure } from '@workos/authkit-session';
configure({
clientId: 'your-client-id',
apiKey: 'your-api-key',
redirectUri: 'https://yourdomain.com/auth/callback',
cookiePassword: 'must-be-at-least-32-characters-long-secret',
});import {
CookieSessionStorage,
parseCookieHeader,
} from '@workos/authkit-session';
export class MyFrameworkStorage extends CookieSessionStorage<
Request,
Response
> {
async getSession(request: Request): Promise<string | null> {
const cookieHeader = request.headers.get('cookie');
if (!cookieHeader) return null;
return parseCookieHeader(cookieHeader)[this.cookieName] ?? null;
}
// Optional: override if your framework can mutate responses
protected async applyHeaders(
response: Response | undefined,
headers: Record<string, string>,
): Promise<{ response: Response }> {
const newResponse = response
? new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: new Headers(response.headers),
})
: new Response();
Object.entries(headers).forEach(([key, value]) => {
newResponse.headers.append(key, value);
});
return { response: newResponse };
}
}CookieSessionStorage provides this.cookieName, this.cookieOptions, buildSetCookie(), and implements saveSession()/clearSession() using your applyHeaders().
import { createAuthService } from '@workos/authkit-session';
export const authService = createAuthService({
sessionStorageFactory: config => new MyFrameworkStorage(config),
});export const authMiddleware = () => {
return createMiddleware().server(async args => {
const { auth, refreshedSessionData } = await authService.withAuth(
args.request,
);
const result = await args.next({
context: { auth: () => auth },
});
// CRITICAL: Persist refreshed tokens to cookie
if (refreshedSessionData) {
const { headers } = await authService.saveSession(
undefined,
refreshedSessionData,
);
if (headers?.['Set-Cookie']) {
const newResponse = new Response(result.response.body, {
status: result.response.status,
statusText: result.response.statusText,
headers: result.response.headers,
});
newResponse.headers.set('Set-Cookie', headers['Set-Cookie']);
return { ...result, response: newResponse };
}
}
return result;
});
};If you skip applying Set-Cookie, refreshed tokens never persist. Next request sees the old expired token → infinite refresh loop.
withAuth() returns a discriminated union. If auth.user exists, all other properties exist:
const { auth } = await authService.withAuth(request);
if (!auth.user) {
return redirect('/login');
}
// TypeScript knows these exist (no ! needed)
auth.sessionId; // string
auth.accessToken; // string
auth.claims.sid; // string| Environment Variable | Config Key | Description |
|---|---|---|
WORKOS_CLIENT_ID |
clientId |
WorkOS client ID |
WORKOS_API_KEY |
apiKey |
WorkOS API key |
WORKOS_REDIRECT_URI |
redirectUri |
OAuth callback URL |
WORKOS_COOKIE_PASSWORD |
cookiePassword |
32+ char encryption key |
WORKOS_COOKIE_NAME |
cookieName |
Cookie name (default: wos_session) |
WORKOS_COOKIE_MAX_AGE |
cookieMaxAge |
Cookie lifetime in seconds |
WORKOS_COOKIE_DOMAIN |
cookieDomain |
Cookie domain |
WORKOS_COOKIE_SAME_SITE |
cookieSameSite |
lax, strict, or none |
Environment variables override programmatic config.
// Authentication
authService.withAuth(request) // → { auth, refreshedSessionData? }
authService.handleCallback(request, response, { code, state })
authService.getSession(request) // → Session | null
authService.saveSession(response, sessionData) // → { response?, headers? }
authService.clearSession(response)
// WorkOS Operations
authService.signOut(sessionId, { returnTo }) // → { logoutUrl, clearCookieHeader }
authService.refreshSession(session, organizationId?)
authService.switchOrganization(session, organizationId)
// URL Generation
authService.getAuthorizationUrl(options)
authService.getSignInUrl(options)
authService.getSignUpUrl(options)For maximum control, use the primitives directly:
import {
AuthKitCore,
AuthOperations,
getWorkOS,
getConfigurationProvider,
} from '@workos/authkit-session';
const config = getConfigurationProvider();
const client = getWorkOS(config.getConfig());
const core = new AuthKitCore(config, client, encryption);
const operations = new AuthOperations(core, client, config);
// Use core.validateAndRefresh(), core.encryptSession(), etc.- JWKS Caching: Keys fetched on-demand, cached for process lifetime.
josehandles key rotation automatically. - Token Expiry Buffer: 60 seconds default. Tokens refresh before actual expiry.
- Session Encryption: AES-256-CBC + SHA-256 HMAC via
iron-webcrypto. - Lazy Initialization:
createAuthService()defers initialization until first use, allowingconfigure()to be called later.
See @workos/authkit-tanstack-start for a complete example.
MIT