🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@workos/authkit-session

Package Overview
Dependencies
Maintainers
6
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@workos/authkit-session - npm Package Compare versions

Comparing version
0.3.4
to
0.4.0
+36
dist/core/pkce/cookieOptions.d.ts
import type { AuthKitConfig } from '../config/types.js';
import type { CookieOptions } from '../session/types.js';
/** Name of the PKCE verifier cookie on the wire. */
export declare const PKCE_COOKIE_NAME = "wos-auth-verifier";
/**
* PKCE verifier cookie lifetime (seconds). Matches the 10-minute convention
* used by Arctic, openid-client, Clerk, and Okta for short-lived OAuth state
* cookies — long enough to accommodate slow sign-ins (out-of-band 2FA, tab
* switching) and short enough to keep the replay window tight if the sealed
* blob leaks.
*
* The same value is used as both the `Max-Age` cookie attribute and the
* sealed payload's TTL, so both layers reject a stale blob.
*/
export declare const PKCE_COOKIE_MAX_AGE = 600;
/**
* Compute PKCE verifier cookie options from config + the resolved redirect URI.
*
* Behavior:
* - `sameSite: 'strict'` is downgraded to `'lax'` so the cookie survives the
* cross-site redirect back from WorkOS.
* - `sameSite: 'none'` is preserved (iframe/embed flows require it).
* - `secure` is forced to `true` when `sameSite === 'none'` (required by
* modern browsers). Otherwise it is inferred from the redirect URI's
* protocol, defaulting fail-closed to `true` on invalid/missing URLs.
* - `path` is always `/`. The cookie gets sent on every same-origin request
* during the 10-minute window, which is fine — it's HttpOnly and expires
* quickly. The tradeoff favors DX: path-scoped cookies are invisible in
* Chrome DevTools' Application panel from any page outside the scoped
* path, which makes PKCE wiring look broken even when it works.
*
* Internal helper — not exported from the package. Callers get cookie options
* indirectly via `AuthService.createSignIn` / `clearPendingVerifier`.
*/
export declare function getPKCECookieOptions(config: AuthKitConfig, redirectUri?: string): CookieOptions;
//# sourceMappingURL=cookieOptions.d.ts.map
{"version":3,"file":"cookieOptions.d.ts","sourceRoot":"","sources":["../../../src/core/pkce/cookieOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAEpD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,aAAa,EACrB,WAAW,CAAC,EAAE,MAAM,GACnB,aAAa,CAuBf"}
/** Name of the PKCE verifier cookie on the wire. */
export const PKCE_COOKIE_NAME = 'wos-auth-verifier';
/**
* PKCE verifier cookie lifetime (seconds). Matches the 10-minute convention
* used by Arctic, openid-client, Clerk, and Okta for short-lived OAuth state
* cookies — long enough to accommodate slow sign-ins (out-of-band 2FA, tab
* switching) and short enough to keep the replay window tight if the sealed
* blob leaks.
*
* The same value is used as both the `Max-Age` cookie attribute and the
* sealed payload's TTL, so both layers reject a stale blob.
*/
export const PKCE_COOKIE_MAX_AGE = 600;
/**
* Compute PKCE verifier cookie options from config + the resolved redirect URI.
*
* Behavior:
* - `sameSite: 'strict'` is downgraded to `'lax'` so the cookie survives the
* cross-site redirect back from WorkOS.
* - `sameSite: 'none'` is preserved (iframe/embed flows require it).
* - `secure` is forced to `true` when `sameSite === 'none'` (required by
* modern browsers). Otherwise it is inferred from the redirect URI's
* protocol, defaulting fail-closed to `true` on invalid/missing URLs.
* - `path` is always `/`. The cookie gets sent on every same-origin request
* during the 10-minute window, which is fine — it's HttpOnly and expires
* quickly. The tradeoff favors DX: path-scoped cookies are invisible in
* Chrome DevTools' Application panel from any page outside the scoped
* path, which makes PKCE wiring look broken even when it works.
*
* Internal helper — not exported from the package. Callers get cookie options
* indirectly via `AuthService.createSignIn` / `clearPendingVerifier`.
*/
export function getPKCECookieOptions(config, redirectUri) {
const configuredSameSite = (config.cookieSameSite ?? 'lax').toLowerCase();
const sameSite = configuredSameSite === 'none' ? 'none' : 'lax';
const urlString = redirectUri ?? config.redirectUri;
let secure = true;
if (urlString && sameSite !== 'none') {
try {
secure = new URL(urlString).protocol === 'https:';
}
catch {
// Fail-closed: secure stays true.
}
}
return {
path: '/',
httpOnly: true,
secure,
sameSite,
maxAge: PKCE_COOKIE_MAX_AGE,
...(config.cookieDomain ? { domain: config.cookieDomain } : {}),
};
}
//# sourceMappingURL=cookieOptions.js.map
{"version":3,"file":"cookieOptions.js","sourceRoot":"","sources":["../../../src/core/pkce/cookieOptions.ts"],"names":[],"mappings":"AAGA,oDAAoD;AACpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEvC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAqB,EACrB,WAAoB;IAEpB,MAAM,kBAAkB,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1E,MAAM,QAAQ,GACZ,kBAAkB,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IAEjD,MAAM,SAAS,GAAG,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC;IACpD,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,SAAS,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM;QACN,QAAQ;QACR,MAAM,EAAE,mBAAmB;QAC3B,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC"}
import type { WorkOS } from '@workos-inc/node';
import type { AuthKitConfig } from '../config/types.js';
import type { CookieOptions, GetAuthorizationUrlOptions, SessionEncryption } from '../session/types.js';
/**
* Maximum UTF-8 byte length for caller-supplied `options.state`. Enforced
* as an early, user-facing check so large custom state fails with a clear
* error before any crypto work — ahead of the authoritative sealed-cookie
* length guard below.
*/
export declare const PKCE_MAX_STATE_BYTES = 2048;
/**
* Maximum serialized `Set-Cookie` header length (bytes) for the verifier.
* Browsers start dropping cookies past ~4096 bytes per RFC 6265; 3800
* leaves headroom for proxies that cap lower and for the handful of
* cookie attributes whose size isn't known until serialization time
* (notably `Domain=` when `cookieDomain` is set).
*/
export declare const PKCE_MAX_COOKIE_BYTES = 3800;
/**
* Internal authorization-URL generation result.
*
* Not exported: AuthService consumes `sealedState` + `cookieOptions` to write
* the verifier cookie via `storage.setCookie`, then returns only `{ url, response?, headers? }`
* to callers.
*/
export interface GeneratedAuthorizationUrl {
url: string;
sealedState: string;
cookieOptions: CookieOptions;
}
/**
* Generate a WorkOS authorization URL bound to a PKCE verifier.
*
* Returns the URL, the sealed state blob (used as both the OAuth `state`
* query param AND the cookie value — identical string; the callback does a
* byte-compare before decrypting), and the cookie options the verifier
* cookie should be written with.
*/
export declare function generateAuthorizationUrl(params: {
client: WorkOS;
config: AuthKitConfig;
encryption: SessionEncryption;
options: GetAuthorizationUrlOptions;
}): Promise<GeneratedAuthorizationUrl>;
//# sourceMappingURL=generateAuthorizationUrl.d.ts.map
{"version":3,"file":"generateAuthorizationUrl.d.ts","sourceRoot":"","sources":["../../../src/core/pkce/generateAuthorizationUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,KAAK,EACV,aAAa,EACb,0BAA0B,EAC1B,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAI7B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,iBAAiB,CAAC;IAC9B,OAAO,EAAE,0BAA0B,CAAC;CACrC,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAmErC"}
import { PKCEPayloadTooLargeError } from '../errors.js';
import { serializeCookie } from '../session/serializeCookie.js';
import { getPKCECookieOptions, PKCE_COOKIE_NAME } from './cookieOptions.js';
import { sealState } from './state.js';
/**
* Maximum UTF-8 byte length for caller-supplied `options.state`. Enforced
* as an early, user-facing check so large custom state fails with a clear
* error before any crypto work — ahead of the authoritative sealed-cookie
* length guard below.
*/
export const PKCE_MAX_STATE_BYTES = 2048;
/**
* Maximum serialized `Set-Cookie` header length (bytes) for the verifier.
* Browsers start dropping cookies past ~4096 bytes per RFC 6265; 3800
* leaves headroom for proxies that cap lower and for the handful of
* cookie attributes whose size isn't known until serialization time
* (notably `Domain=` when `cookieDomain` is set).
*/
export const PKCE_MAX_COOKIE_BYTES = 3800;
/**
* Generate a WorkOS authorization URL bound to a PKCE verifier.
*
* Returns the URL, the sealed state blob (used as both the OAuth `state`
* query param AND the cookie value — identical string; the callback does a
* byte-compare before decrypting), and the cookie options the verifier
* cookie should be written with.
*/
export async function generateAuthorizationUrl(params) {
const { client, config, encryption, options } = params;
const redirectUri = options.redirectUri ?? config.redirectUri;
// Early bound on caller-supplied `state` so oversized input fails with a
// clear error before any crypto work. The authoritative guard on the
// serialized cookie runs below — this one just avoids confusing users.
if (options.state !== undefined) {
const stateBytes = new TextEncoder().encode(options.state).byteLength;
if (stateBytes > PKCE_MAX_STATE_BYTES) {
throw new PKCEPayloadTooLargeError(`Custom OAuth state is ${stateBytes} bytes, exceeds supported limit of ${PKCE_MAX_STATE_BYTES} bytes. ` +
`The sealed state is stored as the wos-auth-verifier cookie; oversized values would be silently dropped by the browser.`);
}
}
const pkce = await client.pkce.generate();
const nonce = crypto.randomUUID();
const sealedState = await sealState(encryption, config.cookiePassword, {
nonce,
codeVerifier: pkce.codeVerifier,
returnPathname: options.returnPathname,
customState: options.state,
// Persisted so handleCallback's verifier-delete cookie uses the same
// computed attributes as the original setCookie — notably `secure`,
// which depends on the redirect URI's protocol.
redirectUri: options.redirectUri,
});
// Authoritative guard: measure the actual Set-Cookie header the adapter
// will emit. Catches cases the input-only check can't, like oversized
// returnPathname combined with near-max state, or an unusually long
// cookieDomain attribute.
const cookieOptions = getPKCECookieOptions(config, redirectUri);
const serialized = serializeCookie(PKCE_COOKIE_NAME, sealedState, cookieOptions);
const cookieBytes = new TextEncoder().encode(serialized).byteLength;
if (cookieBytes > PKCE_MAX_COOKIE_BYTES) {
throw new PKCEPayloadTooLargeError(`Sealed PKCE verifier cookie is ${cookieBytes} bytes, exceeds supported limit of ${PKCE_MAX_COOKIE_BYTES} bytes. ` +
`Reduce the size of options.state, options.returnPathname, or options.redirectUri.`);
}
const url = client.userManagement.getAuthorizationUrl({
provider: 'authkit',
clientId: config.clientId,
redirectUri,
screenHint: options.screenHint,
organizationId: options.organizationId,
loginHint: options.loginHint,
prompt: options.prompt,
state: sealedState,
codeChallenge: pkce.codeChallenge,
codeChallengeMethod: pkce.codeChallengeMethod,
});
return {
url,
sealedState,
cookieOptions,
};
}
//# sourceMappingURL=generateAuthorizationUrl.js.map
{"version":3,"file":"generateAuthorizationUrl.js","sourceRoot":"","sources":["../../../src/core/pkce/generateAuthorizationUrl.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAMhE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAe1C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,MAK9C;IACC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IACvD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC;IAE9D,yEAAyE;IACzE,qEAAqE;IACrE,uEAAuE;IACvE,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC;QACtE,IAAI,UAAU,GAAG,oBAAoB,EAAE,CAAC;YACtC,MAAM,IAAI,wBAAwB,CAChC,yBAAyB,UAAU,sCAAsC,oBAAoB,UAAU;gBACrG,wHAAwH,CAC3H,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAElC,MAAM,WAAW,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,cAAc,EAAE;QACrE,KAAK;QACL,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,WAAW,EAAE,OAAO,CAAC,KAAK;QAC1B,qEAAqE;QACrE,oEAAoE;QACpE,gDAAgD;QAChD,WAAW,EAAE,OAAO,CAAC,WAAW;KACjC,CAAC,CAAC;IAEH,wEAAwE;IACxE,sEAAsE;IACtE,oEAAoE;IACpE,0BAA0B;IAC1B,MAAM,aAAa,GAAG,oBAAoB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,eAAe,CAChC,gBAAgB,EAChB,WAAW,EACX,aAAa,CACd,CAAC;IACF,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC;IACpE,IAAI,WAAW,GAAG,qBAAqB,EAAE,CAAC;QACxC,MAAM,IAAI,wBAAwB,CAChC,kCAAkC,WAAW,sCAAsC,qBAAqB,UAAU;YAChH,mFAAmF,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,mBAAmB,CAAC;QACpD,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,WAAW;QACX,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,WAAW;QAClB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;KAC9C,CAAC,CAAC;IAEH,OAAO;QACL,GAAG;QACH,WAAW;QACX,aAAa;KACd,CAAC;AACJ,CAAC"}
import * as v from 'valibot';
import type { SessionEncryption } from '../session/types.js';
/**
* Runtime schema for the sealed PKCE state blob.
*
* Validated at unseal time as defense-in-depth against any future code path
* accidentally sealing the wrong shape. Shape mismatch is treated as an
* integrity failure (same category as a tampered blob).
*
* `issuedAt` (ms since epoch) lets `unsealState` enforce the TTL on its own
* rather than trusting the encryption adapter's `ttl` handling. Custom
* `SessionEncryption` implementations may silently ignore the `ttl` field —
* the payload-level age check closes that gap.
*/
export declare const StateSchema: v.ObjectSchema<{
readonly nonce: v.StringSchema<undefined>;
readonly codeVerifier: v.StringSchema<undefined>;
readonly issuedAt: v.NumberSchema<undefined>;
readonly returnPathname: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
readonly customState: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
/**
* The per-request `redirectUri` override resolved at sign-in time, if the
* caller supplied one. Persisted so `handleCallback` can recompute the
* exact cookie options used at `setCookie` time — notably `secure`, which
* is inferred from the redirect URI's protocol. Omitted when the caller
* relied on the configured default.
*/
readonly redirectUri: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
}, undefined>;
export type PKCEState = v.InferOutput<typeof StateSchema>;
export type PKCEStateInput = Omit<PKCEState, 'issuedAt'>;
/**
* Seal a PKCE state object for use as both the OAuth `state` query param
* and the `wos-auth-verifier` cookie value. Seal embeds a 600s TTL and
* stamps `issuedAt` so the unseal path can enforce age independently.
*/
export declare function sealState(encryption: SessionEncryption, password: string, state: PKCEStateInput): Promise<string>;
/**
* Unseal a PKCE state blob, enforcing TTL and shape.
*
* Age is verified twice:
* 1. The encryption adapter's `ttl` check (iron-webcrypto enforces this by
* default — custom adapters MAY ignore it).
* 2. A payload-level `issuedAt` comparison against `Date.now()`. This is the
* authoritative check — it runs regardless of how the adapter treats
* `ttl`.
*
* Any failure — expired TTL, tamper, wrong password, or schema mismatch —
* is wrapped as `SessionEncryptionError`. Callers differentiate via the
* `cause` chain, not the message string.
*/
export declare function unsealState(encryption: SessionEncryption, password: string, sealed: string): Promise<PKCEState>;
//# sourceMappingURL=state.d.ts.map
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/pkce/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAE7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAgB7D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW;;;;;;IAMtB;;;;;;OAMG;;aAEH,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,WAAW,CAAC,CAAC;AAE1D,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AAEzD;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,SAAS,CAAC,CAgCpB"}
import * as v from 'valibot';
import { SessionEncryptionError } from '../errors.js';
import { PKCE_COOKIE_MAX_AGE } from './cookieOptions.js';
/**
* Clock-skew tolerance (ms) for the payload-level `issuedAt` check.
*
* Applied asymmetrically, only to the negative (future-`issuedAt`) side:
* allows a sign-in issued on node A to be verified on node B whose clock
* is up to 60s behind without failing a legitimate callback. The upper
* bound (expiry) is strict — `PKCE_COOKIE_MAX_AGE` with no grace — so
* this payload check is always at least as strict as iron-webcrypto's
* symmetric 60s skew on `ttl`, making it the authoritative expiry guard
* regardless of how a custom `SessionEncryption` adapter treats `ttl`.
*/
const PKCE_CLOCK_SKEW_MS = 60_000;
/**
* Runtime schema for the sealed PKCE state blob.
*
* Validated at unseal time as defense-in-depth against any future code path
* accidentally sealing the wrong shape. Shape mismatch is treated as an
* integrity failure (same category as a tampered blob).
*
* `issuedAt` (ms since epoch) lets `unsealState` enforce the TTL on its own
* rather than trusting the encryption adapter's `ttl` handling. Custom
* `SessionEncryption` implementations may silently ignore the `ttl` field —
* the payload-level age check closes that gap.
*/
export const StateSchema = v.object({
nonce: v.string(),
codeVerifier: v.string(),
issuedAt: v.number(),
returnPathname: v.optional(v.string()),
customState: v.optional(v.string()),
/**
* The per-request `redirectUri` override resolved at sign-in time, if the
* caller supplied one. Persisted so `handleCallback` can recompute the
* exact cookie options used at `setCookie` time — notably `secure`, which
* is inferred from the redirect URI's protocol. Omitted when the caller
* relied on the configured default.
*/
redirectUri: v.optional(v.string()),
});
/**
* Seal a PKCE state object for use as both the OAuth `state` query param
* and the `wos-auth-verifier` cookie value. Seal embeds a 600s TTL and
* stamps `issuedAt` so the unseal path can enforce age independently.
*/
export async function sealState(encryption, password, state) {
return encryption.sealData({ ...state, issuedAt: Date.now() }, {
password,
ttl: PKCE_COOKIE_MAX_AGE,
});
}
/**
* Unseal a PKCE state blob, enforcing TTL and shape.
*
* Age is verified twice:
* 1. The encryption adapter's `ttl` check (iron-webcrypto enforces this by
* default — custom adapters MAY ignore it).
* 2. A payload-level `issuedAt` comparison against `Date.now()`. This is the
* authoritative check — it runs regardless of how the adapter treats
* `ttl`.
*
* Any failure — expired TTL, tamper, wrong password, or schema mismatch —
* is wrapped as `SessionEncryptionError`. Callers differentiate via the
* `cause` chain, not the message string.
*/
export async function unsealState(encryption, password, sealed) {
let raw;
try {
raw = await encryption.unsealData(sealed, {
password,
ttl: PKCE_COOKIE_MAX_AGE,
});
}
catch (cause) {
throw new SessionEncryptionError('Failed to unseal PKCE state', cause);
}
const result = v.safeParse(StateSchema, raw);
if (!result.success) {
throw new SessionEncryptionError('Malformed PKCE state payload', result.issues);
}
const ageMs = Date.now() - result.output.issuedAt;
if (ageMs < -PKCE_CLOCK_SKEW_MS || ageMs > PKCE_COOKIE_MAX_AGE * 1000) {
// Attach age context so production logs can diagnose clock-skew issues
// via the `cause` chain without leaking the sealed blob itself.
throw new SessionEncryptionError('PKCE state expired', new Error(`age=${ageMs}ms, maxAge=${PKCE_COOKIE_MAX_AGE * 1000}ms, skew=±${PKCE_CLOCK_SKEW_MS}ms`));
}
return result.output;
}
//# sourceMappingURL=state.js.map
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/core/pkce/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CACpC,CAAC,CAAC;AAMH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,UAA6B,EAC7B,QAAgB,EAChB,KAAqB;IAErB,OAAO,UAAU,CAAC,QAAQ,CACxB,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAClC;QACE,QAAQ;QACR,GAAG,EAAE,mBAAmB;KACzB,CACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAA6B,EAC7B,QAAgB,EAChB,MAAc;IAEd,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,UAAU,CAAC,UAAU,CAAU,MAAM,EAAE;YACjD,QAAQ;YACR,GAAG,EAAE,mBAAmB;SACzB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,sBAAsB,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,sBAAsB,CAC9B,8BAA8B,EAC9B,MAAM,CAAC,MAAM,CACd,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;IAClD,IAAI,KAAK,GAAG,CAAC,kBAAkB,IAAI,KAAK,GAAG,mBAAmB,GAAG,IAAI,EAAE,CAAC;QACtE,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,IAAI,sBAAsB,CAC9B,oBAAoB,EACpB,IAAI,KAAK,CACP,OAAO,KAAK,cAAc,mBAAmB,GAAG,IAAI,aAAa,kBAAkB,IAAI,CACxF,CACF,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC"}
import type { CookieOptions } from './types.js';
/**
* Serialize a `Set-Cookie` header value from (name, value, options).
*
* Shared between `CookieSessionStorage` (writes the header on response) and
* the PKCE flow's pre-write size guard (must measure the exact bytes the
* browser will see, including attribute overhead, to detect oversized
* verifier cookies before they silently get dropped).
*/
export declare function serializeCookie(name: string, value: string, options: CookieOptions, { expired }?: {
expired?: boolean;
}): string;
//# sourceMappingURL=serializeCookie.d.ts.map
{"version":3,"file":"serializeCookie.d.ts","sourceRoot":"","sources":["../../../src/core/session/serializeCookie.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,EACtB,EAAE,OAAO,EAAE,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,MAAM,CAiBR"}
/**
* Serialize a `Set-Cookie` header value from (name, value, options).
*
* Shared between `CookieSessionStorage` (writes the header on response) and
* the PKCE flow's pre-write size guard (must measure the exact bytes the
* browser will see, including attribute overhead, to detect oversized
* verifier cookies before they silently get dropped).
*/
export function serializeCookie(name, value, options, { expired } = {}) {
const parts = [`${name}=${encodeURIComponent(value)}`];
if (options.path)
parts.push(`Path=${options.path}`);
if (options.domain)
parts.push(`Domain=${options.domain}`);
if (options.maxAge != null || expired)
parts.push(`Max-Age=${expired ? 0 : options.maxAge}`);
if (options.httpOnly)
parts.push('HttpOnly');
if (options.secure)
parts.push('Secure');
if (options.sameSite) {
const capitalized = options.sameSite.charAt(0).toUpperCase() +
options.sameSite.slice(1).toLowerCase();
parts.push(`SameSite=${capitalized}`);
}
if (options.priority)
parts.push(`Priority=${options.priority}`);
if (options.partitioned)
parts.push('Partitioned');
return parts.join('; ');
}
//# sourceMappingURL=serializeCookie.js.map
{"version":3,"file":"serializeCookie.js","sourceRoot":"","sources":["../../../src/core/session/serializeCookie.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,KAAa,EACb,OAAsB,EACtB,EAAE,OAAO,KAA4B,EAAE;IAEvC,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACvD,IAAI,OAAO,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3D,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,IAAI,OAAO;QACnC,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,WAAW,GACf,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjE,IAAI,OAAO,CAAC,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACnD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
import type { AuthKitConfig } from '../config/types.js';
import { getPKCECookieOptions } from './cookieOptions.js';
const baseConfig: AuthKitConfig = {
clientId: 'client_test',
apiKey: 'sk_test',
redirectUri: 'https://app.example.com/callback',
cookiePassword: 'x'.repeat(48),
} as AuthKitConfig;
describe('getPKCECookieOptions', () => {
describe('sameSite behavior', () => {
it.each([
['strict', 'lax'],
['lax', 'lax'],
['none', 'none'],
] as const)(
'maps cookieSameSite=%s to sameSite=%s',
(configured, expected) => {
const config = { ...baseConfig, cookieSameSite: configured };
const opts = getPKCECookieOptions(config);
expect(opts.sameSite).toBe(expected);
},
);
it('defaults to lax when cookieSameSite is undefined', () => {
const opts = getPKCECookieOptions(baseConfig);
expect(opts.sameSite).toBe('lax');
});
it('accepts mixed-case sameSite values', () => {
const config = { ...baseConfig, cookieSameSite: 'NONE' as any };
const opts = getPKCECookieOptions(config);
expect(opts.sameSite).toBe('none');
});
});
describe('secure flag', () => {
it('derives secure=true from https redirectUri', () => {
const opts = getPKCECookieOptions({
...baseConfig,
redirectUri: 'https://app.example.com/callback',
});
expect(opts.secure).toBe(true);
});
it('derives secure=false from http redirectUri', () => {
const opts = getPKCECookieOptions({
...baseConfig,
redirectUri: 'http://localhost:3000/callback',
});
expect(opts.secure).toBe(false);
});
it('prefers explicit redirectUri arg over config', () => {
const opts = getPKCECookieOptions(
{ ...baseConfig, redirectUri: 'https://prod.example.com/callback' },
'http://localhost:3000/callback',
);
expect(opts.secure).toBe(false);
});
it('fail-closes to secure=true on invalid URL', () => {
const opts = getPKCECookieOptions({
...baseConfig,
redirectUri: 'not-a-valid-url',
});
expect(opts.secure).toBe(true);
});
it('forces secure=true when sameSite=none regardless of protocol', () => {
const opts = getPKCECookieOptions({
...baseConfig,
cookieSameSite: 'none',
redirectUri: 'http://localhost:3000/callback',
});
expect(opts.secure).toBe(true);
});
});
describe('invariants', () => {
it('always sets httpOnly=true', () => {
const opts = getPKCECookieOptions(baseConfig);
expect(opts.httpOnly).toBe(true);
});
it('always sets maxAge=600', () => {
const opts = getPKCECookieOptions(baseConfig);
expect(opts.maxAge).toBe(600);
});
});
describe('path', () => {
it.each([
['config callback path', 'https://app.example.com/callback'],
['nested callback path', 'https://app.example.com/auth/v2/callback'],
['host-only', 'https://app.example.com'],
['invalid url', 'not-a-valid-url'],
])(
"is always '/' regardless of redirectUri (%s)",
(_label, redirectUri) => {
const opts = getPKCECookieOptions({ ...baseConfig, redirectUri });
expect(opts.path).toBe('/');
},
);
it("is '/' even when per-call redirectUri override is supplied", () => {
const opts = getPKCECookieOptions(
baseConfig,
'https://app.example.com/some/other/path',
);
expect(opts.path).toBe('/');
});
});
describe('domain passthrough', () => {
it('includes domain when configured', () => {
const opts = getPKCECookieOptions({
...baseConfig,
cookieDomain: '.example.com',
});
expect(opts.domain).toBe('.example.com');
});
it('omits domain when not configured', () => {
const opts = getPKCECookieOptions(baseConfig);
expect(opts.domain).toBeUndefined();
});
});
});
import type { AuthKitConfig } from '../config/types.js';
import type { CookieOptions } from '../session/types.js';
/** Name of the PKCE verifier cookie on the wire. */
export const PKCE_COOKIE_NAME = 'wos-auth-verifier';
/**
* PKCE verifier cookie lifetime (seconds). Matches the 10-minute convention
* used by Arctic, openid-client, Clerk, and Okta for short-lived OAuth state
* cookies — long enough to accommodate slow sign-ins (out-of-band 2FA, tab
* switching) and short enough to keep the replay window tight if the sealed
* blob leaks.
*
* The same value is used as both the `Max-Age` cookie attribute and the
* sealed payload's TTL, so both layers reject a stale blob.
*/
export const PKCE_COOKIE_MAX_AGE = 600;
/**
* Compute PKCE verifier cookie options from config + the resolved redirect URI.
*
* Behavior:
* - `sameSite: 'strict'` is downgraded to `'lax'` so the cookie survives the
* cross-site redirect back from WorkOS.
* - `sameSite: 'none'` is preserved (iframe/embed flows require it).
* - `secure` is forced to `true` when `sameSite === 'none'` (required by
* modern browsers). Otherwise it is inferred from the redirect URI's
* protocol, defaulting fail-closed to `true` on invalid/missing URLs.
* - `path` is always `/`. The cookie gets sent on every same-origin request
* during the 10-minute window, which is fine — it's HttpOnly and expires
* quickly. The tradeoff favors DX: path-scoped cookies are invisible in
* Chrome DevTools' Application panel from any page outside the scoped
* path, which makes PKCE wiring look broken even when it works.
*
* Internal helper — not exported from the package. Callers get cookie options
* indirectly via `AuthService.createSignIn` / `clearPendingVerifier`.
*/
export function getPKCECookieOptions(
config: AuthKitConfig,
redirectUri?: string,
): CookieOptions {
const configuredSameSite = (config.cookieSameSite ?? 'lax').toLowerCase();
const sameSite: 'lax' | 'none' =
configuredSameSite === 'none' ? 'none' : 'lax';
const urlString = redirectUri ?? config.redirectUri;
let secure = true;
if (urlString && sameSite !== 'none') {
try {
secure = new URL(urlString).protocol === 'https:';
} catch {
// Fail-closed: secure stays true.
}
}
return {
path: '/',
httpOnly: true,
secure,
sameSite,
maxAge: PKCE_COOKIE_MAX_AGE,
...(config.cookieDomain ? { domain: config.cookieDomain } : {}),
};
}
import type { WorkOS } from '@workos-inc/node';
import type { AuthKitConfig } from '../config/types.js';
import { PKCEPayloadTooLargeError } from '../errors.js';
import { serializeCookie } from '../session/serializeCookie.js';
import type {
CookieOptions,
GetAuthorizationUrlOptions,
SessionEncryption,
} from '../session/types.js';
import { getPKCECookieOptions, PKCE_COOKIE_NAME } from './cookieOptions.js';
import { sealState } from './state.js';
/**
* Maximum UTF-8 byte length for caller-supplied `options.state`. Enforced
* as an early, user-facing check so large custom state fails with a clear
* error before any crypto work — ahead of the authoritative sealed-cookie
* length guard below.
*/
export const PKCE_MAX_STATE_BYTES = 2048;
/**
* Maximum serialized `Set-Cookie` header length (bytes) for the verifier.
* Browsers start dropping cookies past ~4096 bytes per RFC 6265; 3800
* leaves headroom for proxies that cap lower and for the handful of
* cookie attributes whose size isn't known until serialization time
* (notably `Domain=` when `cookieDomain` is set).
*/
export const PKCE_MAX_COOKIE_BYTES = 3800;
/**
* Internal authorization-URL generation result.
*
* Not exported: AuthService consumes `sealedState` + `cookieOptions` to write
* the verifier cookie via `storage.setCookie`, then returns only `{ url, response?, headers? }`
* to callers.
*/
export interface GeneratedAuthorizationUrl {
url: string;
sealedState: string;
cookieOptions: CookieOptions;
}
/**
* Generate a WorkOS authorization URL bound to a PKCE verifier.
*
* Returns the URL, the sealed state blob (used as both the OAuth `state`
* query param AND the cookie value — identical string; the callback does a
* byte-compare before decrypting), and the cookie options the verifier
* cookie should be written with.
*/
export async function generateAuthorizationUrl(params: {
client: WorkOS;
config: AuthKitConfig;
encryption: SessionEncryption;
options: GetAuthorizationUrlOptions;
}): Promise<GeneratedAuthorizationUrl> {
const { client, config, encryption, options } = params;
const redirectUri = options.redirectUri ?? config.redirectUri;
// Early bound on caller-supplied `state` so oversized input fails with a
// clear error before any crypto work. The authoritative guard on the
// serialized cookie runs below — this one just avoids confusing users.
if (options.state !== undefined) {
const stateBytes = new TextEncoder().encode(options.state).byteLength;
if (stateBytes > PKCE_MAX_STATE_BYTES) {
throw new PKCEPayloadTooLargeError(
`Custom OAuth state is ${stateBytes} bytes, exceeds supported limit of ${PKCE_MAX_STATE_BYTES} bytes. ` +
`The sealed state is stored as the wos-auth-verifier cookie; oversized values would be silently dropped by the browser.`,
);
}
}
const pkce = await client.pkce.generate();
const nonce = crypto.randomUUID();
const sealedState = await sealState(encryption, config.cookiePassword, {
nonce,
codeVerifier: pkce.codeVerifier,
returnPathname: options.returnPathname,
customState: options.state,
// Persisted so handleCallback's verifier-delete cookie uses the same
// computed attributes as the original setCookie — notably `secure`,
// which depends on the redirect URI's protocol.
redirectUri: options.redirectUri,
});
// Authoritative guard: measure the actual Set-Cookie header the adapter
// will emit. Catches cases the input-only check can't, like oversized
// returnPathname combined with near-max state, or an unusually long
// cookieDomain attribute.
const cookieOptions = getPKCECookieOptions(config, redirectUri);
const serialized = serializeCookie(
PKCE_COOKIE_NAME,
sealedState,
cookieOptions,
);
const cookieBytes = new TextEncoder().encode(serialized).byteLength;
if (cookieBytes > PKCE_MAX_COOKIE_BYTES) {
throw new PKCEPayloadTooLargeError(
`Sealed PKCE verifier cookie is ${cookieBytes} bytes, exceeds supported limit of ${PKCE_MAX_COOKIE_BYTES} bytes. ` +
`Reduce the size of options.state, options.returnPathname, or options.redirectUri.`,
);
}
const url = client.userManagement.getAuthorizationUrl({
provider: 'authkit',
clientId: config.clientId,
redirectUri,
screenHint: options.screenHint,
organizationId: options.organizationId,
loginHint: options.loginHint,
prompt: options.prompt,
state: sealedState,
codeChallenge: pkce.codeChallenge,
codeChallengeMethod: pkce.codeChallengeMethod,
});
return {
url,
sealedState,
cookieOptions,
};
}
import { AuthKitCore } from '../AuthKitCore.js';
import sessionEncryption from '../encryption/ironWebcryptoEncryption.js';
import {
OAuthStateMismatchError,
PKCECookieMissingError,
PKCEPayloadTooLargeError,
} from '../errors.js';
import {
generateAuthorizationUrl,
PKCE_MAX_COOKIE_BYTES,
PKCE_MAX_STATE_BYTES,
} from './generateAuthorizationUrl.js';
const config = {
clientId: 'test-client-id',
apiKey: 'test-api-key',
redirectUri: 'https://app.example.com/callback',
cookiePassword: 'this-is-a-test-password-that-is-32-characters-long!',
} as const;
const mockClient = {
pkce: {
generate: async () => ({
codeVerifier: 'verifier-' + crypto.randomUUID(),
codeChallenge: 'challenge-' + crypto.randomUUID(),
codeChallengeMethod: 'S256',
}),
},
userManagement: {
getAuthorizationUrl: (opts: any) => {
const params = new URLSearchParams({
client_id: opts.clientId,
redirect_uri: opts.redirectUri,
state: opts.state ?? '',
code_challenge: opts.codeChallenge ?? '',
code_challenge_method: opts.codeChallengeMethod ?? '',
});
return `https://api.workos.com/sso/authorize?${params.toString()}`;
},
getJwksUrl: () => 'https://api.workos.com/sso/jwks/test-client-id',
},
} as const;
function makeCore() {
return new AuthKitCore(config as any, mockClient as any, sessionEncryption);
}
function generate(
options: Parameters<typeof generateAuthorizationUrl>[0]['options'] = {},
) {
return generateAuthorizationUrl({
client: mockClient as any,
config: config as any,
encryption: sessionEncryption,
options,
});
}
// Tamper, missing cookie, missing state, TTL, and single-byte custom-state
// round-trip are covered in state.spec.ts and AuthKitCore.spec.ts. These
// tests exercise the integration seam (generateAuthorizationUrl →
// verifyCallbackState) for behaviors that only emerge when both modules
// run together.
describe('PKCE end-to-end round-trip', () => {
it('verifyCallbackState recovers the sealed state after generateAuthorizationUrl', async () => {
const core = makeCore();
const { sealedState } = await generate({
returnPathname: '/dashboard',
state: 'user-opaque',
});
const result = await core.verifyCallbackState({
stateFromUrl: sealedState,
cookieValue: sealedState,
});
expect(result.returnPathname).toBe('/dashboard');
expect(result.customState).toBe('user-opaque');
expect(typeof result.codeVerifier).toBe('string');
expect(typeof result.nonce).toBe('string');
});
it('empty-string cookieValue is treated as missing (falsy check)', async () => {
const core = makeCore();
const { sealedState } = await generate();
await expect(
core.verifyCallbackState({ stateFromUrl: sealedState, cookieValue: '' }),
).rejects.toThrow(PKCECookieMissingError);
});
it('concurrent sign-ins produce distinct sealedStates (cross-flow rejection)', async () => {
const core = makeCore();
const a = await generate();
const b = await generate();
expect(a.sealedState).not.toBe(b.sealedState);
await expect(
core.verifyCallbackState({
stateFromUrl: a.sealedState,
cookieValue: b.sealedState,
}),
).rejects.toThrow(OAuthStateMismatchError);
});
});
describe('PKCE payload size guards', () => {
it(`accepts custom state at the ${PKCE_MAX_STATE_BYTES}-byte supported limit`, async () => {
const state = 'a'.repeat(PKCE_MAX_STATE_BYTES);
await expect(generate({ state })).resolves.toMatchObject({
sealedState: expect.any(String),
});
});
it(`rejects custom state ${PKCE_MAX_STATE_BYTES + 1} bytes or larger before sealing`, async () => {
const state = 'a'.repeat(PKCE_MAX_STATE_BYTES + 1);
await expect(generate({ state })).rejects.toThrow(PKCEPayloadTooLargeError);
});
it('counts state size in UTF-8 bytes, not JS characters (multibyte input)', async () => {
// 2-byte UTF-8 char; 1025 chars = 2050 bytes > 2048.
const state = '\u00e9'.repeat(1025);
await expect(generate({ state })).rejects.toThrow(PKCEPayloadTooLargeError);
});
it('rejects oversized serialized cookie from large returnPathname + near-limit state', async () => {
// Under the per-field state cap, but the sealed cookie still overflows
// once returnPathname is concatenated into the payload.
const state = 'a'.repeat(PKCE_MAX_STATE_BYTES);
const returnPathname = '/' + 'p'.repeat(2048);
await expect(generate({ state, returnPathname })).rejects.toThrow(
PKCEPayloadTooLargeError,
);
});
it('shrinks the effective budget when cookieDomain adds attribute bytes', async () => {
// cookieDomain=<long> is serialized into every Set-Cookie and counts
// against the browser's per-cookie budget. A state payload that fits
// without a Domain attribute can push the serialized header over the
// limit when Domain is present.
const longDomain = 'sub.' + 'd'.repeat(200) + '.example.com';
const configWithDomain = { ...config, cookieDomain: longDomain } as any;
const state = 'a'.repeat(PKCE_MAX_STATE_BYTES);
await expect(
generateAuthorizationUrl({
client: mockClient as any,
config: configWithDomain,
encryption: sessionEncryption,
options: { state, returnPathname: '/' + 'p'.repeat(700) },
}),
).rejects.toThrow(PKCEPayloadTooLargeError);
});
it(`error message names PKCE_MAX_COOKIE_BYTES (${PKCE_MAX_COOKIE_BYTES})`, async () => {
const state = 'a'.repeat(PKCE_MAX_STATE_BYTES);
const returnPathname = '/' + 'p'.repeat(2048);
try {
await generate({ state, returnPathname });
throw new Error('expected throw');
} catch (err) {
expect(err).toBeInstanceOf(PKCEPayloadTooLargeError);
expect((err as Error).message).toContain(String(PKCE_MAX_COOKIE_BYTES));
}
});
});
import sessionEncryption from '../encryption/ironWebcryptoEncryption.js';
import { SessionEncryptionError } from '../errors.js';
import { sealState, unsealState, type PKCEStateInput } from './state.js';
const testPassword = 'this-is-a-test-password-that-is-32-characters-long!';
const validState: PKCEStateInput = {
nonce: '8b7b1c32-7d8f-44f9-aa51-4c3a6c8fb8d9',
codeVerifier: 'verifier-12345678901234567890123456789012345678',
returnPathname: '/dashboard',
customState: 'user-custom',
};
describe('PKCE state seal/unseal', () => {
it('round-trips a valid state', async () => {
const sealed = await sealState(sessionEncryption, testPassword, validState);
const unsealed = await unsealState(sessionEncryption, testPassword, sealed);
expect(unsealed).toMatchObject(validState);
expect(typeof unsealed.issuedAt).toBe('number');
});
it('round-trips without optional fields', async () => {
const minimal: PKCEStateInput = {
nonce: 'n',
codeVerifier: 'v',
};
const sealed = await sealState(sessionEncryption, testPassword, minimal);
const unsealed = await unsealState(sessionEncryption, testPassword, sealed);
expect(unsealed).toMatchObject(minimal);
expect(typeof unsealed.issuedAt).toBe('number');
});
it('stamps issuedAt with Date.now() at seal time', async () => {
vi.useFakeTimers();
try {
const fixedNow = new Date('2026-01-01T00:00:00.000Z').getTime();
vi.setSystemTime(fixedNow);
const sealed = await sealState(
sessionEncryption,
testPassword,
validState,
);
const unsealed = await unsealState(
sessionEncryption,
testPassword,
sealed,
);
expect(unsealed.issuedAt).toBe(fixedNow);
} finally {
vi.useRealTimers();
}
});
it.each<[string, string]>([
['dots', 'has.many.dots.in.it'],
['JSON-like', '{"key":"value","nested":{"a":1}}'],
['2KB payload', 'x'.repeat(2048)],
])(
'preserves %s customState byte-identically',
async (_label, customState) => {
const input: PKCEStateInput = {
nonce: 'n',
codeVerifier: 'v',
customState,
};
const sealed = await sealState(sessionEncryption, testPassword, input);
const unsealed = await unsealState(
sessionEncryption,
testPassword,
sealed,
);
expect(unsealed.customState).toBe(customState);
},
);
it('throws SessionEncryptionError on tampered ciphertext', async () => {
const sealed = await sealState(sessionEncryption, testPassword, validState);
// Flip one char in the middle of the sealed string (avoiding the ~2 suffix).
const mid = Math.floor(sealed.length / 2);
const tampered =
sealed.slice(0, mid) +
(sealed[mid] === 'a' ? 'b' : 'a') +
sealed.slice(mid + 1);
await expect(
unsealState(sessionEncryption, testPassword, tampered),
).rejects.toThrow(SessionEncryptionError);
});
it('throws SessionEncryptionError with wrong password', async () => {
const sealed = await sealState(sessionEncryption, testPassword, validState);
const wrongPassword =
'this-is-a-completely-different-password-that-is-long';
await expect(
unsealState(sessionEncryption, wrongPassword, sealed),
).rejects.toThrow(SessionEncryptionError);
});
it('throws SessionEncryptionError on schema mismatch', async () => {
// Seal something that passes encryption but fails the StateSchema check.
const wrongShape = await sessionEncryption.sealData(
{ foo: 'bar', baz: 42 },
{ password: testPassword, ttl: 600 },
);
await expect(
unsealState(sessionEncryption, testPassword, wrongShape),
).rejects.toThrow(SessionEncryptionError);
await expect(
unsealState(sessionEncryption, testPassword, wrongShape),
).rejects.toThrow(/Malformed PKCE state payload/);
});
describe('TTL enforcement', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('succeeds well before expiry', async () => {
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await sealState(
sessionEncryption,
testPassword,
validState,
);
vi.setSystemTime(new Date('2026-01-01T00:09:59.000Z')); // +599s
const unsealed = await unsealState(
sessionEncryption,
testPassword,
sealed,
);
expect(unsealed).toMatchObject(validState);
});
it('throws just past the 600s payload-level expiry', async () => {
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await sealState(
sessionEncryption,
testPassword,
validState,
);
// Payload-level expiry is strict at 600s (no positive skew grace).
vi.setSystemTime(new Date('2026-01-01T00:10:01.000Z')); // +601s
await expect(
unsealState(sessionEncryption, testPassword, sealed),
).rejects.toThrow(SessionEncryptionError);
});
it('rejects payloads older than 600s even when the encryptor ignores ttl', async () => {
// Custom SessionEncryption that ignores the ttl option — simulates an
// adapter that would let a stale blob through. The payload-level
// issuedAt check in unsealState is the authoritative guard.
const ignoresTtl = {
sealData: (data: unknown, options: { password: string }) =>
sessionEncryption.sealData(data, { password: options.password }),
unsealData: <T>(sealed: string, options: { password: string }) =>
sessionEncryption.unsealData<T>(sealed, {
password: options.password,
}),
};
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await sealState(ignoresTtl, testPassword, validState);
// 605s — inside iron's default 60s skew grace, outside the strict 600s
// payload-level check.
vi.setSystemTime(new Date('2026-01-01T00:10:05.000Z'));
await expect(
unsealState(ignoresTtl, testPassword, sealed),
).rejects.toThrow(/PKCE state expired/);
});
it('tolerates bounded negative skew (issuedAt up to 60s ahead of now)', async () => {
// Simulates sign-in on a node whose clock is ahead of the callback
// node by 30s — within the 60s tolerance.
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await sessionEncryption.sealData(
{ ...validState, issuedAt: Date.now() + 30_000 },
{ password: testPassword, ttl: 600 },
);
const unsealed = await unsealState(
sessionEncryption,
testPassword,
sealed,
);
expect(unsealed).toMatchObject(validState);
});
it('rejects payloads meaningfully in the future (issuedAt beyond skew)', async () => {
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await sessionEncryption.sealData(
{ ...validState, issuedAt: Date.now() + 120_000 },
{ password: testPassword, ttl: 600 },
);
await expect(
unsealState(sessionEncryption, testPassword, sealed),
).rejects.toThrow(/PKCE state expired/);
});
});
});
import * as v from 'valibot';
import { SessionEncryptionError } from '../errors.js';
import type { SessionEncryption } from '../session/types.js';
import { PKCE_COOKIE_MAX_AGE } from './cookieOptions.js';
/**
* Clock-skew tolerance (ms) for the payload-level `issuedAt` check.
*
* Applied asymmetrically, only to the negative (future-`issuedAt`) side:
* allows a sign-in issued on node A to be verified on node B whose clock
* is up to 60s behind without failing a legitimate callback. The upper
* bound (expiry) is strict — `PKCE_COOKIE_MAX_AGE` with no grace — so
* this payload check is always at least as strict as iron-webcrypto's
* symmetric 60s skew on `ttl`, making it the authoritative expiry guard
* regardless of how a custom `SessionEncryption` adapter treats `ttl`.
*/
const PKCE_CLOCK_SKEW_MS = 60_000;
/**
* Runtime schema for the sealed PKCE state blob.
*
* Validated at unseal time as defense-in-depth against any future code path
* accidentally sealing the wrong shape. Shape mismatch is treated as an
* integrity failure (same category as a tampered blob).
*
* `issuedAt` (ms since epoch) lets `unsealState` enforce the TTL on its own
* rather than trusting the encryption adapter's `ttl` handling. Custom
* `SessionEncryption` implementations may silently ignore the `ttl` field —
* the payload-level age check closes that gap.
*/
export const StateSchema = v.object({
nonce: v.string(),
codeVerifier: v.string(),
issuedAt: v.number(),
returnPathname: v.optional(v.string()),
customState: v.optional(v.string()),
/**
* The per-request `redirectUri` override resolved at sign-in time, if the
* caller supplied one. Persisted so `handleCallback` can recompute the
* exact cookie options used at `setCookie` time — notably `secure`, which
* is inferred from the redirect URI's protocol. Omitted when the caller
* relied on the configured default.
*/
redirectUri: v.optional(v.string()),
});
export type PKCEState = v.InferOutput<typeof StateSchema>;
export type PKCEStateInput = Omit<PKCEState, 'issuedAt'>;
/**
* Seal a PKCE state object for use as both the OAuth `state` query param
* and the `wos-auth-verifier` cookie value. Seal embeds a 600s TTL and
* stamps `issuedAt` so the unseal path can enforce age independently.
*/
export async function sealState(
encryption: SessionEncryption,
password: string,
state: PKCEStateInput,
): Promise<string> {
return encryption.sealData(
{ ...state, issuedAt: Date.now() },
{
password,
ttl: PKCE_COOKIE_MAX_AGE,
},
);
}
/**
* Unseal a PKCE state blob, enforcing TTL and shape.
*
* Age is verified twice:
* 1. The encryption adapter's `ttl` check (iron-webcrypto enforces this by
* default — custom adapters MAY ignore it).
* 2. A payload-level `issuedAt` comparison against `Date.now()`. This is the
* authoritative check — it runs regardless of how the adapter treats
* `ttl`.
*
* Any failure — expired TTL, tamper, wrong password, or schema mismatch —
* is wrapped as `SessionEncryptionError`. Callers differentiate via the
* `cause` chain, not the message string.
*/
export async function unsealState(
encryption: SessionEncryption,
password: string,
sealed: string,
): Promise<PKCEState> {
let raw: unknown;
try {
raw = await encryption.unsealData<unknown>(sealed, {
password,
ttl: PKCE_COOKIE_MAX_AGE,
});
} catch (cause) {
throw new SessionEncryptionError('Failed to unseal PKCE state', cause);
}
const result = v.safeParse(StateSchema, raw);
if (!result.success) {
throw new SessionEncryptionError(
'Malformed PKCE state payload',
result.issues,
);
}
const ageMs = Date.now() - result.output.issuedAt;
if (ageMs < -PKCE_CLOCK_SKEW_MS || ageMs > PKCE_COOKIE_MAX_AGE * 1000) {
// Attach age context so production logs can diagnose clock-skew issues
// via the `cause` chain without leaking the sealed blob itself.
throw new SessionEncryptionError(
'PKCE state expired',
new Error(
`age=${ageMs}ms, maxAge=${PKCE_COOKIE_MAX_AGE * 1000}ms, skew=±${PKCE_CLOCK_SKEW_MS}ms`,
),
);
}
return result.output;
}
import type { CookieOptions } from './types.js';
/**
* Serialize a `Set-Cookie` header value from (name, value, options).
*
* Shared between `CookieSessionStorage` (writes the header on response) and
* the PKCE flow's pre-write size guard (must measure the exact bytes the
* browser will see, including attribute overhead, to detect oversized
* verifier cookies before they silently get dropped).
*/
export function serializeCookie(
name: string,
value: string,
options: CookieOptions,
{ expired }: { expired?: boolean } = {},
): string {
const parts = [`${name}=${encodeURIComponent(value)}`];
if (options.path) parts.push(`Path=${options.path}`);
if (options.domain) parts.push(`Domain=${options.domain}`);
if (options.maxAge != null || expired)
parts.push(`Max-Age=${expired ? 0 : options.maxAge}`);
if (options.httpOnly) parts.push('HttpOnly');
if (options.secure) parts.push('Secure');
if (options.sameSite) {
const capitalized =
options.sameSite.charAt(0).toUpperCase() +
options.sameSite.slice(1).toLowerCase();
parts.push(`SameSite=${capitalized}`);
}
if (options.priority) parts.push(`Priority=${options.priority}`);
if (options.partitioned) parts.push('Partitioned');
return parts.join('; ');
}
+20
-0
import type { Impersonator, User, WorkOS } from '@workos-inc/node';
import type { AuthKitConfig } from './config/types.js';
import { type PKCEState } from './pkce/state.js';
import type { BaseTokenClaims, CustomClaims, Session, SessionEncryption } from './session/types.js';

@@ -74,2 +75,21 @@ /**

/**
* Verify the OAuth callback state against the PKCE verifier cookie and
* return the unsealed state blob.
*
* Core owns encryption, so verification lives here rather than on
* AuthService. Byte-compares the query-string `state` against the cookie
* value BEFORE attempting to decrypt — smaller crypto attack surface and
* no decryption cost on obvious mismatches.
*
* @throws {OAuthStateMismatchError} state missing or does not match cookie
* @throws {PKCECookieMissingError} cookie not sent — typically a proxy or
* Set-Cookie propagation issue on the adapter's callback path
* @throws {SessionEncryptionError} seal tampered, wrong password, TTL expired,
* or schema mismatch on the unsealed payload
*/
verifyCallbackState(params: {
stateFromUrl: string | undefined;
cookieValue: string | undefined;
}): Promise<PKCEState>;
/**
* Refresh tokens using WorkOS API.

@@ -76,0 +96,0 @@ *

+1
-1

@@ -1,1 +0,1 @@

{"version":3,"file":"AuthKitCore.d.ts","sourceRoot":"","sources":["../../src/core/AuthKitCore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,OAAO,EACP,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;GAYG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,QAAQ,CAAS;gBAGvB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,iBAAiB;IAQ/B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAI3B;IAEF;;;;;OAKG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASlD;;;;;;OAMG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO;IAU5D;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAK1B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAa,GAAG,YAAY,EAC3C,KAAK,EAAE,MAAM,GACZ,eAAe,GAAG,aAAa;IAQlC;;;;;;OAMG;IACG,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAYvD;;;;;;OAMG;IACG,cAAc,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYhE;;;;;;;;OAQG;IACG,aAAa,CACjB,YAAY,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD,OAAO,CAAC;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,IAAI,CAAC;QACX,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;KACxC,CAAC;IAoBF;;;;;;;;;;;OAWG;IACG,kBAAkB,CAAC,aAAa,GAAG,YAAY,EACnD,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GACrD,OAAO,CAAC;QACT,KAAK,EAAE,OAAO,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,eAAe,GAAG,aAAa,CAAC;KACzC,CAAC;CA8CH"}
{"version":3,"file":"AuthKitCore.d.ts","sourceRoot":"","sources":["../../src/core/AuthKitCore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAOvD,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,OAAO,EACP,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;GAYG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,QAAQ,CAAS;gBAGvB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,iBAAiB;IAQ/B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAI3B;IAEF;;;;;OAKG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASlD;;;;;;OAMG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO;IAU5D;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAK1B;;;;;;OAMG;IACH,gBAAgB,CAAC,aAAa,GAAG,YAAY,EAC3C,KAAK,EAAE,MAAM,GACZ,eAAe,GAAG,aAAa;IAQlC;;;;;;OAMG;IACG,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAYvD;;;;;;OAMG;IACG,cAAc,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYhE;;;;;;;;;;;;;;OAcG;IACG,mBAAmB,CAAC,MAAM,EAAE;QAChC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;QACjC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;KACjC,GAAG,OAAO,CAAC,SAAS,CAAC;IA2BtB;;;;;;;;OAQG;IACG,aAAa,CACjB,YAAY,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD,OAAO,CAAC;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,IAAI,CAAC;QACX,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;KACxC,CAAC;IAoBF;;;;;;;;;;;OAWG;IACG,kBAAkB,CAAC,aAAa,GAAG,YAAY,EACnD,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GACrD,OAAO,CAAC;QACT,KAAK,EAAE,OAAO,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,eAAe,GAAG,aAAa,CAAC;KACzC,CAAC;CA8CH"}
import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
import { once } from '../utils.js';
import { SessionEncryptionError, TokenRefreshError } from './errors.js';
import { constantTimeEqual, once } from '../utils.js';
import { OAuthStateMismatchError, PKCECookieMissingError, SessionEncryptionError, TokenRefreshError, } from './errors.js';
import { unsealState } from './pkce/state.js';
/**

@@ -83,4 +84,4 @@ * AuthKitCore provides pure business logic for authentication operations.

}
catch (error) {
throw new Error('Invalid token');
catch (cause) {
throw new Error('Invalid token', { cause });
}

@@ -124,2 +125,33 @@ }

/**
* Verify the OAuth callback state against the PKCE verifier cookie and
* return the unsealed state blob.
*
* Core owns encryption, so verification lives here rather than on
* AuthService. Byte-compares the query-string `state` against the cookie
* value BEFORE attempting to decrypt — smaller crypto attack surface and
* no decryption cost on obvious mismatches.
*
* @throws {OAuthStateMismatchError} state missing or does not match cookie
* @throws {PKCECookieMissingError} cookie not sent — typically a proxy or
* Set-Cookie propagation issue on the adapter's callback path
* @throws {SessionEncryptionError} seal tampered, wrong password, TTL expired,
* or schema mismatch on the unsealed payload
*/
async verifyCallbackState(params) {
const { stateFromUrl, cookieValue } = params;
if (!stateFromUrl) {
throw new OAuthStateMismatchError('Missing state parameter from callback URL');
}
if (!cookieValue) {
throw new PKCECookieMissingError('PKCE verifier cookie missing — cannot verify OAuth state. Ensure Set-Cookie headers are propagated on redirects.');
}
const encoder = new TextEncoder();
const urlBytes = encoder.encode(stateFromUrl);
const cookieBytes = encoder.encode(cookieValue);
if (!constantTimeEqual(urlBytes, cookieBytes)) {
throw new OAuthStateMismatchError('OAuth state mismatch');
}
return unsealState(this.encryption, this.config.cookiePassword, cookieValue);
}
/**
* Refresh tokens using WorkOS API.

@@ -126,0 +158,0 @@ *

@@ -1,1 +0,1 @@

{"version":3,"file":"AuthKitCore.js","sourceRoot":"","sources":["../../src/core/AuthKitCore.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"AuthKitCore.js","sourceRoot":"","sources":["../../src/core/AuthKitCore.ts"],"names":[],"mappings":"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"}

@@ -13,4 +13,5 @@ import type { SessionEncryption as SessionEncryptionInterface } from '../session/types.js';

}): Promise<string>;
unsealData<T = unknown>(encryptedData: string, { password }: {
unsealData<T = unknown>(encryptedData: string, { password, ttl }: {
password: string;
ttl?: number | undefined;
}): Promise<T>;

@@ -17,0 +18,0 @@ }

@@ -1,1 +0,1 @@

{"version":3,"file":"ironWebcryptoEncryption.d.ts","sourceRoot":"","sources":["../../../src/core/encryption/ironWebcryptoEncryption.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,IAAI,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAE3F;;GAEG;AACH,qBAAa,iBAAkB,YAAW,0BAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAO;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAGzC,OAAO,CAAC,SAAS;IAaX,QAAQ,CACZ,IAAI,EAAE,OAAO,EACb,EAAE,QAAQ,EAAE,GAAO,EAAE,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE;IAgCjE,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1B,aAAa,EAAE,MAAM,EACrB,EAAE,QAAQ,EAAE,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GACjC,OAAO,CAAC,CAAC,CAAC;CAoCd;AAED,QAAA,MAAM,uBAAuB,mBAA0B,CAAC;AAExD,eAAe,uBAAuB,CAAC"}
{"version":3,"file":"ironWebcryptoEncryption.d.ts","sourceRoot":"","sources":["../../../src/core/encryption/ironWebcryptoEncryption.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,IAAI,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAE3F;;GAEG;AACH,qBAAa,iBAAkB,YAAW,0BAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAO;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAGzC,OAAO,CAAC,SAAS;IAaX,QAAQ,CACZ,IAAI,EAAE,OAAO,EACb,EAAE,QAAQ,EAAE,GAAO,EAAE,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE;IAgCjE,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1B,aAAa,EAAE,MAAM,EACrB,EAAE,QAAQ,EAAE,GAAO,EAAE,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GACpE,OAAO,CAAC,CAAC,CAAC;CAoCd;AAED,QAAA,MAAM,uBAAuB,mBAA0B,CAAC;AAExD,eAAe,uBAAuB,CAAC"}

@@ -43,3 +43,3 @@ import { seal as sealData, unseal as unsealData } from 'iron-webcrypto';

// Decrypt data from iron-session with HMAC verification
async unsealData(encryptedData, { password }) {
async unsealData(encryptedData, { password, ttl = 0 }) {
// First, parse the seal to extract the version

@@ -63,3 +63,3 @@ const { sealWithoutVersion, tokenVersion } = this.parseSeal(encryptedData);

},
ttl: 0,
ttl: ttl * 1000, // Convert seconds to milliseconds. 0 = no TTL enforcement.
timestampSkewSec: 60,

@@ -66,0 +66,0 @@ localtimeOffsetMsec: 0,

@@ -1,1 +0,1 @@

{"version":3,"file":"ironWebcryptoEncryption.js","sourceRoot":"","sources":["../../../src/core/encryption/ironWebcryptoEncryption.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGxE;;GAEG;AACH,MAAM,OAAO,iBAAiB;IACX,gBAAgB,GAAG,GAAG,CAAC;IACvB,mBAAmB,GAAG,CAAC,CAAC;IAEzC,oDAAoD;IAC5C,SAAS,CAAC,IAAY;QAI5B,MAAM,CAAC,kBAAkB,GAAG,EAAE,EAAE,oBAAoB,CAAC,GAAG,IAAI,CAAC,KAAK,CAChE,IAAI,CAAC,gBAAgB,CACtB,CAAC;QACF,MAAM,YAAY,GAChB,oBAAoB,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;QAC3E,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,CAAC;IAC9C,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,QAAQ,CACZ,IAAa,EACb,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,EAAkD;QAErE,0CAA0C;QAC1C,MAAM,WAAW,GAAG;YAClB,EAAE,EAAE,GAAG;YACP,MAAM,EAAE,QAAQ;SACjB,CAAC;QAEF,sEAAsE;QACtE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE;YAC7C,UAAU,EAAE;gBACV,QAAQ,EAAE,GAAG;gBACb,SAAS,EAAE,aAAa;gBACxB,UAAU,EAAE,CAAC;gBACb,iBAAiB,EAAE,EAAE;aACtB;YACD,SAAS,EAAE;gBACT,QAAQ,EAAE,GAAG;gBACb,SAAS,EAAE,QAAQ;gBACnB,UAAU,EAAE,CAAC;gBACb,iBAAiB,EAAE,EAAE;aACtB;YACD,GAAG,EAAE,GAAG,GAAG,IAAI,EAAE,kCAAkC;YACnD,gBAAgB,EAAE,EAAE;YACpB,mBAAmB,EAAE,CAAC;SACvB,CAAC,CAAC;QAEH,2DAA2D;QAC3D,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;IACtE,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,UAAU,CACd,aAAqB,EACrB,EAAE,QAAQ,EAAwB;QAElC,+CAA+C;QAC/C,MAAM,EAAE,kBAAkB,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAE3E,qDAAqD;QACrD,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;QAEpC,uCAAuC;QACvC,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,kBAAkB,EAAE,WAAW,EAAE;YAC7D,UAAU,EAAE;gBACV,QAAQ,EAAE,GAAG;gBACb,SAAS,EAAE,aAAa;gBACxB,UAAU,EAAE,CAAC;gBACb,iBAAiB,EAAE,EAAE;aACtB;YACD,SAAS,EAAE;gBACT,QAAQ,EAAE,GAAG;gBACb,SAAS,EAAE,QAAQ;gBACnB,UAAU,EAAE,CAAC;gBACb,iBAAiB,EAAE,EAAE;aACtB;YACD,GAAG,EAAE,CAAC;YACN,gBAAgB,EAAE,EAAE;YACpB,mBAAmB,EAAE,CAAC;SACvB,CAAC,CAAC;QAEH,oCAAoC;QACpC,IAAI,YAAY,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,IAAS,CAAC;QACnB,CAAC;aAAM,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;YACjC,4DAA4D;YAC5D,OAAO,EAAE,GAAI,IAAY,CAAC,UAAU,EAAO,CAAC;QAC9C,CAAC;QAED,OAAO,IAAS,CAAC;IACnB,CAAC;CACF;AAED,MAAM,uBAAuB,GAAG,IAAI,iBAAiB,EAAE,CAAC;AAExD,eAAe,uBAAuB,CAAC"}
{"version":3,"file":"ironWebcryptoEncryption.js","sourceRoot":"","sources":["../../../src/core/encryption/ironWebcryptoEncryption.ts"],"names":[],"mappings":"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"}

@@ -11,2 +11,11 @@ export declare class AuthKitError extends Error {

}
export declare class OAuthStateMismatchError extends AuthKitError {
constructor(message: string, cause?: unknown);
}
export declare class PKCECookieMissingError extends AuthKitError {
constructor(message: string, cause?: unknown);
}
export declare class PKCEPayloadTooLargeError extends AuthKitError {
constructor(message: string, cause?: unknown);
}
export declare class TokenRefreshError extends AuthKitError {

@@ -13,0 +22,0 @@ readonly userId?: string;

@@ -1,1 +0,1 @@

{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAa,SAAQ,KAAK;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAMzE;AAED,qBAAa,sBAAuB,SAAQ,YAAY;gBAC1C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,qBAAa,oBAAqB,SAAQ,YAAY;gBACxC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAG1B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;CAOpD"}
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAa,SAAQ,KAAK;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAMzE;AAED,qBAAa,sBAAuB,SAAQ,YAAY;gBAC1C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,qBAAa,oBAAqB,SAAQ,YAAY;gBACxC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,qBAAa,uBAAwB,SAAQ,YAAY;gBAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,qBAAa,sBAAuB,SAAQ,YAAY;gBAC1C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,qBAAa,wBAAyB,SAAQ,YAAY;gBAC5C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAG1B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;CAOpD"}

@@ -22,2 +22,20 @@ export class AuthKitError extends Error {

}
export class OAuthStateMismatchError extends AuthKitError {
constructor(message, cause) {
super(message, cause);
this.name = 'OAuthStateMismatchError';
}
}
export class PKCECookieMissingError extends AuthKitError {
constructor(message, cause) {
super(message, cause);
this.name = 'PKCECookieMissingError';
}
}
export class PKCEPayloadTooLargeError extends AuthKitError {
constructor(message, cause) {
super(message, cause);
this.name = 'PKCEPayloadTooLargeError';
}
}
export class TokenRefreshError extends AuthKitError {

@@ -24,0 +42,0 @@ userId;

@@ -1,1 +0,1 @@

{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,IAAI,CAAuB;IAE3B,YAAY,OAAe,EAAE,KAAe,EAAE,IAA0B;QACtE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,YAAY;IACtD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,YAAY;IACpD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IACxC,MAAM,CAAU;IAChB,SAAS,CAAU;IAE5B,YACE,OAAe,EACf,KAAe,EACf,OAAiD;QAEjD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,SAAS,CAAC;IACtC,CAAC;CACF"}
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,IAAI,CAAuB;IAE3B,YAAY,OAAe,EAAE,KAAe,EAAE,IAA0B;QACtE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,YAAY;IACtD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,YAAY;IACpD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,YAAY;IACvD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,YAAY;IACtD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,YAAY;IACxD,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IACxC,MAAM,CAAU;IAChB,SAAS,CAAU;IAE5B,YACE,OAAe,EACf,KAAe,EACf,OAAiD;QAEjD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,SAAS,CAAC;IACtC,CAAC;CACF"}
import type { AuthKitConfig } from '../config/types.js';
import type { HeadersBag, SessionStorage } from './types.js';
export interface CookieOptions {
path?: string;
domain?: string;
maxAge?: number;
expires?: Date;
httpOnly?: boolean;
secure?: boolean;
sameSite?: 'lax' | 'strict' | 'none';
priority?: 'low' | 'medium' | 'high';
partitioned?: boolean;
}
import type { CookieOptions, HeadersBag, SessionStorage } from './types.js';
export type { CookieOptions } from './types.js';
export declare abstract class CookieSessionStorage<TRequest, TResponse> implements SessionStorage<TRequest, TResponse> {

@@ -21,4 +11,24 @@ protected cookieName: string;

} | void>;
protected buildSetCookie(value: string, expired?: boolean): string;
abstract getSession(request: TRequest): Promise<string | null>;
protected serializeCookie(name: string, value: string, options: CookieOptions, flags?: {
expired?: boolean;
}): string;
/**
* Read a named cookie from the framework-specific request.
*
* Implementations MUST return the URL-decoded value — the inverse of
* `serializeCookie`'s `encodeURIComponent` on write. Returning the raw
* on-wire bytes will silently break byte-comparison against the original
* value (e.g. PKCE state verification) for any seal containing characters
* that `encodeURIComponent` escapes.
*/
abstract getCookie(request: TRequest, name: string): Promise<string | null>;
setCookie(response: TResponse | undefined, name: string, value: string, options: CookieOptions): Promise<{
response?: TResponse;
headers?: HeadersBag;
}>;
clearCookie(response: TResponse | undefined, name: string, options: CookieOptions): Promise<{
response?: TResponse;
headers?: HeadersBag;
}>;
getSession(request: TRequest): Promise<string | null>;
saveSession(response: TResponse | undefined, sessionData: string): Promise<{

@@ -28,3 +38,3 @@ response?: TResponse;

}>;
clearSession(response: TResponse): Promise<{
clearSession(response: TResponse | undefined): Promise<{
response?: TResponse;

@@ -31,0 +41,0 @@ headers?: HeadersBag;

@@ -1,1 +0,1 @@

{"version":3,"file":"CookieSessionStorage.d.ts","sourceRoot":"","sources":["../../../src/core/session/CookieSessionStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,8BAAsB,oBAAoB,CACxC,QAAQ,EACR,SAAS,CACT,YAAW,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC9C,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;gBAEpC,MAAM,EAAE,aAAa;cA2BjB,YAAY,CAC1B,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAI1C,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM;IAkBlE,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAExD,WAAW,CACf,QAAQ,EAAE,SAAS,GAAG,SAAS,EAC/B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAMpD,YAAY,CAChB,QAAQ,EAAE,SAAS,GAClB,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;CAK3D"}
{"version":3,"file":"CookieSessionStorage.d.ts","sourceRoot":"","sources":["../../../src/core/session/CookieSessionStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5E,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,8BAAsB,oBAAoB,CACxC,QAAQ,EACR,SAAS,CACT,YAAW,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC9C,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;gBAEpC,MAAM,EAAE,aAAa;cA8BjB,YAAY,CAC1B,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAI1C,SAAS,CAAC,eAAe,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,EACtB,KAAK,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAChC,MAAM;IAIT;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAErE,SAAS,CACb,QAAQ,EAAE,SAAS,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAMpD,WAAW,CACf,QAAQ,EAAE,SAAS,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAM1D,UAAU,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIrD,WAAW,CACT,QAAQ,EAAE,SAAS,GAAG,SAAS,EAC/B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAS1D,YAAY,CACV,QAAQ,EAAE,SAAS,GAAG,SAAS,GAC9B,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;CAG3D"}

@@ -0,1 +1,2 @@

import { serializeCookie } from './serializeCookie.js';
export class CookieSessionStorage {

@@ -5,3 +6,6 @@ cookieName;

constructor(config) {
this.cookieName = config.cookieName ?? 'wos_session';
// Matches the canonical default in ConfigurationProvider. This fallback
// only fires when a caller instantiates the class with a config that
// hasn't been resolved through the provider.
this.cookieName = config.cookieName ?? 'wos-session';
const sameSite = config.cookieSameSite ?? 'lax';

@@ -32,36 +36,25 @@ // Infer secure flag from redirectUri protocol

}
buildSetCookie(value, expired) {
const a = [`${this.cookieName}=${encodeURIComponent(value)}`];
const o = this.cookieOptions;
if (o.path)
a.push(`Path=${o.path}`);
if (o.domain)
a.push(`Domain=${o.domain}`);
if (o.maxAge || expired)
a.push(`Max-Age=${expired ? 0 : o.maxAge}`);
if (o.httpOnly)
a.push('HttpOnly');
if (o.secure)
a.push('Secure');
if (o.sameSite) {
const capitalizedSameSite = o.sameSite.charAt(0).toUpperCase() + o.sameSite.slice(1).toLowerCase();
a.push(`SameSite=${capitalizedSameSite}`);
}
if (o.priority)
a.push(`Priority=${o.priority}`);
if (o.partitioned)
a.push('Partitioned');
return a.join('; ');
serializeCookie(name, value, options, flags = {}) {
return serializeCookie(name, value, options, flags);
}
async saveSession(response, sessionData) {
const header = this.buildSetCookie(sessionData);
async setCookie(response, name, value, options) {
const header = this.serializeCookie(name, value, options);
const mutated = await this.applyHeaders(response, { 'Set-Cookie': header });
return mutated ?? { headers: { 'Set-Cookie': header } };
}
async clearSession(response) {
const header = this.buildSetCookie('', true);
async clearCookie(response, name, options) {
const header = this.serializeCookie(name, '', options, { expired: true });
const mutated = await this.applyHeaders(response, { 'Set-Cookie': header });
return mutated ?? { headers: { 'Set-Cookie': header } };
}
getSession(request) {
return this.getCookie(request, this.cookieName);
}
saveSession(response, sessionData) {
return this.setCookie(response, this.cookieName, sessionData, this.cookieOptions);
}
clearSession(response) {
return this.clearCookie(response, this.cookieName, this.cookieOptions);
}
}
//# sourceMappingURL=CookieSessionStorage.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"CookieSessionStorage.js","sourceRoot":"","sources":["../../../src/core/session/CookieSessionStorage.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"CookieSessionStorage.js","sourceRoot":"","sources":["../../../src/core/session/CookieSessionStorage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAKvD,MAAM,OAAgB,oBAAoB;IAI9B,UAAU,CAAS;IACV,aAAa,CAAgB;IAEhD,YAAY,MAAqB;QAC/B,wEAAwE;QACxE,qEAAqE;QACrE,6CAA6C;QAC7C,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,aAAa,CAAC;QAErD,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,IAAI,KAAK,CAAC;QAEhD,8CAA8C;QAC9C,6DAA6D;QAC7D,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;gBACxC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,iDAAiD;YACnD,CAAC;QACH,CAAC;QAED,IAAI,CAAC,aAAa,GAAG;YACnB,IAAI,EAAE,GAAG;YACT,QAAQ,EAAE,IAAI;YACd,QAAQ;YACR,MAAM;YACN,MAAM,EAAE,MAAM,CAAC,YAAY,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,WAAW;YAC9D,MAAM,EAAE,MAAM,CAAC,YAAY;SAC5B,CAAC;IACJ,CAAC;IAES,KAAK,CAAC,YAAY,CAC1B,SAAgC,EAChC,QAAoB;QAEpB,+EAA+E;IACjF,CAAC;IAES,eAAe,CACvB,IAAY,EACZ,KAAa,EACb,OAAsB,EACtB,QAA+B,EAAE;QAEjC,OAAO,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC;IAaD,KAAK,CAAC,SAAS,CACb,QAA+B,EAC/B,IAAY,EACZ,KAAa,EACb,OAAsB;QAEtB,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5E,OAAO,OAAO,IAAI,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,WAAW,CACf,QAA+B,EAC/B,IAAY,EACZ,OAAsB;QAEtB,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5E,OAAO,OAAO,IAAI,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;IAC1D,CAAC;IAED,UAAU,CAAC,OAAiB;QAC1B,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAClD,CAAC;IAED,WAAW,CACT,QAA+B,EAC/B,WAAmB;QAEnB,OAAO,IAAI,CAAC,SAAS,CACnB,QAAQ,EACR,IAAI,CAAC,UAAU,EACf,WAAW,EACX,IAAI,CAAC,aAAa,CACnB,CAAC;IACJ,CAAC;IAED,YAAY,CACV,QAA+B;QAE/B,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IACzE,CAAC;CACF"}

@@ -24,4 +24,4 @@ import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';

}
catch (error) {
throw new Error('Invalid token');
catch (cause) {
throw new Error('Invalid token', { cause });
}

@@ -28,0 +28,0 @@ }

@@ -1,1 +0,1 @@

{"version":3,"file":"TokenManager.js","sourceRoot":"","sources":["../../../src/core/session/TokenManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAItC,MAAM,OAAO,YAAY;IACf,QAAQ,CAAS;IACjB,MAAM,CAAS;IAEvB,YAAY,QAAgB,EAAE,MAAc;QAC1C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAEgB,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CACxC,kBAAkB,CAChB,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAC9D,CACF,CAAC;IAEF,KAAK,CAAC,WAAW,CAAC,KAAa;QAC7B,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,gBAAgB,CACd,KAAa;QAEb,IAAI,CAAC;YACH,OAAO,SAAS,CAAkC,KAAK,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,eAAe,CAAC,KAAa,EAAE,aAAsB;QACnD,MAAM,UAAU,GAAG,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,MAAM,GAAG,aAAa,IAAI,EAAE,CAAC,CAAC,sBAAsB;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,0BAA0B;QAC7E,OAAO,UAAU,GAAG,WAAW,IAAI,MAAM,CAAC;IAC5C,CAAC;IAED,kBAAkB,CAAC,KAAa;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC;IAC5B,CAAC;CACF;AAED,eAAe,YAAY,CAAC"}
{"version":3,"file":"TokenManager.js","sourceRoot":"","sources":["../../../src/core/session/TokenManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAItC,MAAM,OAAO,YAAY;IACf,QAAQ,CAAS;IACjB,MAAM,CAAS;IAEvB,YAAY,QAAgB,EAAE,MAAc;QAC1C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAEgB,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CACxC,kBAAkB,CAChB,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAC9D,CACF,CAAC;IAEF,KAAK,CAAC,WAAW,CAAC,KAAa;QAC7B,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,gBAAgB,CACd,KAAa;QAEb,IAAI,CAAC;YACH,OAAO,SAAS,CAAkC,KAAK,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,eAAe,CAAC,KAAa,EAAE,aAAsB;QACnD,MAAM,UAAU,GAAG,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,MAAM,GAAG,aAAa,IAAI,EAAE,CAAC,CAAC,sBAAsB;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,0BAA0B;QAC7E,OAAO,UAAU,GAAG,WAAW,IAAI,MAAM,CAAC;IAC5C,CAAC;IAED,kBAAkB,CAAC,KAAa;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC;IAC5B,CAAC;CACF;AAED,eAAe,YAAY,CAAC"}

@@ -68,4 +68,46 @@ import type { Impersonator, User } from '@workos-inc/node';

}
/**
* Map of HTTP response headers.
*
* `Set-Cookie` MUST be represented as `string[]` when multiple values exist
* — adapters must append each entry as its own header, never comma-join.
* A comma-joined `Set-Cookie` string is not a valid single HTTP header.
*/
export type HeadersBag = Record<string, string | string[]>;
export interface SessionStorage<TRequest, TResponse, TOptions = unknown> {
/**
* Read a named cookie from a request.
*
* MUST return the URL-decoded value (the inverse of `setCookie`'s
* `encodeURIComponent` on write). Returning raw on-wire bytes will break
* byte-comparison against the original value — notably PKCE state
* verification — for any seal format that contains characters
* `encodeURIComponent` escapes (`+`, `=`, etc.).
*
* @param request Framework-specific request object.
* @param name Cookie name.
* @returns The URL-decoded cookie value or null if absent.
*/
getCookie(request: TRequest, name: string): Promise<string | null>;
/**
* Write a named cookie to a response.
* @param response Framework-specific response object (or undefined to emit headers only).
* @param name Cookie name.
* @param value Cookie value (will be URL-encoded).
* @param options Per-call cookie options.
*/
setCookie(response: TResponse | undefined, name: string, value: string, options: CookieOptions): Promise<{
response?: TResponse;
headers?: HeadersBag;
}>;
/**
* Clear a named cookie by emitting a Set-Cookie with Max-Age=0.
* @param response Framework-specific response object (or undefined to emit headers only).
* @param name Cookie name.
* @param options Cookie options (must match those used at set time, especially `path`).
*/
clearCookie(response: TResponse | undefined, name: string, options: CookieOptions): Promise<{
response?: TResponse;
headers?: HeadersBag;
}>;
getSession(request: TRequest): Promise<string | null>;

@@ -99,4 +141,18 @@ /**

password: string;
ttl?: number | undefined;
}) => Promise<T>;
}
/**
* Result shape returned by `createAuthorization` / `createSignIn` / `createSignUp`.
*
* The verifier cookie is written internally via `SessionStorage.setCookie` — callers
* only need to redirect the browser to `url` and apply any returned `headers`/`response`.
*/
export interface GetAuthorizationUrlResult {
url: string;
}
export type CreateAuthorizationResult<TResponse> = GetAuthorizationUrlResult & {
response?: TResponse;
headers?: HeadersBag;
};
export interface CookieOptions {

@@ -122,7 +178,20 @@ path?: string;

prompt?: 'login' | 'none' | 'consent' | 'select_account';
/** Custom state to pass through the OAuth flow. Returned in handleCallback. */
/**
* Custom state to pass through the OAuth flow. Returned in handleCallback.
*
* Bounded at 2048 UTF-8 bytes. The value is sealed alongside the PKCE
* verifier and stored in the `wos-auth-verifier` cookie; oversized values
* would push the cookie past the per-cookie browser limit (~4 KB) and
* would be silently dropped. Values over the supported limit throw
* `PKCEPayloadTooLargeError` at sign-in time.
*
* Note: total sealed payload size — not just `state` — drives the hard
* failure. Combining a large `returnPathname`, `redirectUri`, or
* `cookieDomain` with near-limit `state` can still overflow.
*/
state?: string;
}
/**
* Options for getAuthorizationUrl, including screenHint
* Options for `createAuthorization` / `createSignIn` / `createSignUp`,
* including the `screenHint` selector used by the sign-in/sign-up variants.
*/

@@ -129,0 +198,0 @@ export interface GetAuthorizationUrlOptions extends AuthUrlOptions {

@@ -1,1 +0,1 @@

{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/session/types.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/session/types.ts"],"names":[],"mappings":"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"}

@@ -31,3 +31,3 @@ /**

export { getWorkOS } from './core/client/workos.js';
export { AuthKitError, SessionEncryptionError, TokenValidationError, TokenRefreshError, } from './core/errors.js';
export { AuthKitError, OAuthStateMismatchError, PKCECookieMissingError, PKCEPayloadTooLargeError, SessionEncryptionError, TokenValidationError, TokenRefreshError, } from './core/errors.js';
export * from './core/session/types.js';

@@ -34,0 +34,0 @@ export * from './core/config/types.js';

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAKzD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAKhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAK9E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAK5F,OAAO,EACL,SAAS,EACT,SAAS,EACT,wBAAwB,EACxB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAK/E,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKpD,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAK1B,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,YAAY,EACV,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,sBAAsB,GACvB,MAAM,kBAAkB,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAKzD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAKhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAK9E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAK5F,OAAO,EACL,SAAS,EACT,SAAS,EACT,wBAAwB,EACxB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAK/E,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKpD,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAK1B,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,YAAY,EACV,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,sBAAsB,GACvB,MAAM,kBAAkB,CAAC"}

@@ -52,3 +52,3 @@ /**

// ============================================
export { AuthKitError, SessionEncryptionError, TokenValidationError, TokenRefreshError, } from './core/errors.js';
export { AuthKitError, OAuthStateMismatchError, PKCECookieMissingError, PKCEPayloadTooLargeError, SessionEncryptionError, TokenValidationError, TokenRefreshError, } from './core/errors.js';
// ============================================

@@ -55,0 +55,0 @@ // Type Exports

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,+CAA+C;AAC/C,aAAa;AACb,+CAA+C;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,+CAA+C;AAC/C,6BAA6B;AAC7B,+CAA+C;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,+CAA+C;AAC/C,kBAAkB;AAClB,+CAA+C;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAE9E,+CAA+C;AAC/C,sBAAsB;AACtB,+CAA+C;AAC/C,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAE5F,+CAA+C;AAC/C,gBAAgB;AAChB,+CAA+C;AAC/C,OAAO,EACL,SAAS,EACT,SAAS,EACT,wBAAwB,EACxB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAE/E,+CAA+C;AAC/C,iBAAiB;AACjB,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,+CAA+C;AAC/C,SAAS;AACT,+CAA+C;AAC/C,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,+CAA+C;AAC/C,eAAe;AACf,+CAA+C;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,+CAA+C;AAC/C,aAAa;AACb,+CAA+C;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,+CAA+C;AAC/C,6BAA6B;AAC7B,+CAA+C;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,+CAA+C;AAC/C,kBAAkB;AAClB,+CAA+C;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAE9E,+CAA+C;AAC/C,sBAAsB;AACtB,+CAA+C;AAC/C,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAE5F,+CAA+C;AAC/C,gBAAgB;AAChB,+CAA+C;AAC/C,OAAO,EACL,SAAS,EACT,SAAS,EACT,wBAAwB,EACxB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAE/E,+CAA+C;AAC/C,iBAAiB;AACjB,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,+CAA+C;AAC/C,SAAS;AACT,+CAA+C;AAC/C,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,+CAA+C;AAC/C,eAAe;AACf,+CAA+C;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC"}
import type { WorkOS } from '@workos-inc/node';
import type { AuthKitCore } from '../core/AuthKitCore.js';
import type { AuthKitConfig } from '../core/config/types.js';
import type { AuthResult, GetAuthorizationUrlOptions, Session } from '../core/session/types.js';
import { type GeneratedAuthorizationUrl } from '../core/pkce/generateAuthorizationUrl.js';
import type { AuthResult, GetAuthorizationUrlOptions, Session, SessionEncryption } from '../core/session/types.js';
/**

@@ -21,3 +22,4 @@ * AuthOperations provides high-level authentication operations.

private config;
constructor(core: AuthKitCore, client: WorkOS, config: AuthKitConfig);
private encryption;
constructor(core: AuthKitCore, client: WorkOS, config: AuthKitConfig, encryption: SessionEncryption);
/**

@@ -65,21 +67,21 @@ * Get the WorkOS logout URL.

/**
* Get authorization URL for WorkOS authentication.
* Create a PKCE-bound WorkOS authorization URL.
*
* State encoding format: `{internal}.{userState}` where internal is URL-safe
* base64 encoded JSON containing returnPathname. This allows customers to
* pass their own state through the OAuth flow.
* Returns the URL, the sealed state blob (to be used as both the cookie
* value and — already present in the URL — the OAuth `state` param), and
* the cookie options the verifier cookie should be written with.
* AuthService dispatches the write via `SessionStorage.setCookie`.
*
* @param options - Authorization URL options (returnPathname, screenHint, state, etc.)
* @returns The authorization URL
* @param options - returnPathname, screenHint, custom state, redirectUri, etc.
*/
getAuthorizationUrl(options?: GetAuthorizationUrlOptions): Promise<string>;
createAuthorization(options?: GetAuthorizationUrlOptions): Promise<GeneratedAuthorizationUrl>;
/**
* Convenience method: Get sign-in URL.
* Convenience method: Create sign-in authorization URL.
*/
getSignInUrl(options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<string>;
createSignIn(options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<GeneratedAuthorizationUrl>;
/**
* Convenience method: Get sign-up URL.
* Convenience method: Create sign-up authorization URL.
*/
getSignUpUrl(options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<string>;
createSignUp(options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<GeneratedAuthorizationUrl>;
}
//# sourceMappingURL=AuthOperations.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"AuthOperations.d.ts","sourceRoot":"","sources":["../../src/operations/AuthOperations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EACV,UAAU,EACV,0BAA0B,EAC1B,OAAO,EACR,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAgB;gBAElB,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa;IAMpE;;;;;;;;;OASG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAOxE;;;;;;;;;OASG;IACG,kBAAkB,CACtB,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;QACT,IAAI,EAAE,UAAU,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAKF;;;;;;;;;OASG;IACG,cAAc,CAClB,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;QACT,IAAI,EAAE,UAAU,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IA8BF;;;;;;;;;OASG;IACG,mBAAmB,CACvB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,CAAC;IA2BlB;;OAEG;IACG,YAAY,CAChB,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,YAAY,CAAM,GAC3D,OAAO,CAAC,MAAM,CAAC;IAOlB;;OAEG;IACG,YAAY,CAChB,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,YAAY,CAAM,GAC3D,OAAO,CAAC,MAAM,CAAC;CAMnB"}
{"version":3,"file":"AuthOperations.d.ts","sourceRoot":"","sources":["../../src/operations/AuthOperations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,KAAK,yBAAyB,EAE/B,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EACV,UAAU,EACV,0BAA0B,EAC1B,OAAO,EACP,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAoB;gBAGpC,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,iBAAiB;IAQ/B;;;;;;;;;OASG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAOxE;;;;;;;;;OASG;IACG,kBAAkB,CACtB,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;QACT,IAAI,EAAE,UAAU,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAKF;;;;;;;;;OASG;IACG,cAAc,CAClB,OAAO,EAAE,OAAO,EAChB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;QACT,IAAI,EAAE,UAAU,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IA8BF;;;;;;;;;OASG;IACG,mBAAmB,CACvB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,yBAAyB,CAAC;IASrC;;OAEG;IACG,YAAY,CAChB,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,YAAY,CAAM,GAC3D,OAAO,CAAC,yBAAyB,CAAC;IAOrC;;OAEG;IACG,YAAY,CAChB,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,YAAY,CAAM,GAC3D,OAAO,CAAC,yBAAyB,CAAC;CAMtC"}

@@ -0,1 +1,2 @@

import { generateAuthorizationUrl, } from '../core/pkce/generateAuthorizationUrl.js';
/**

@@ -17,6 +18,8 @@ * AuthOperations provides high-level authentication operations.

config;
constructor(core, client, config) {
encryption;
constructor(core, client, config, encryption) {
this.core = core;
this.client = client;
this.config = config;
this.encryption = encryption;
}

@@ -87,39 +90,24 @@ /**

/**
* Get authorization URL for WorkOS authentication.
* Create a PKCE-bound WorkOS authorization URL.
*
* State encoding format: `{internal}.{userState}` where internal is URL-safe
* base64 encoded JSON containing returnPathname. This allows customers to
* pass their own state through the OAuth flow.
* Returns the URL, the sealed state blob (to be used as both the cookie
* value and — already present in the URL — the OAuth `state` param), and
* the cookie options the verifier cookie should be written with.
* AuthService dispatches the write via `SessionStorage.setCookie`.
*
* @param options - Authorization URL options (returnPathname, screenHint, state, etc.)
* @returns The authorization URL
* @param options - returnPathname, screenHint, custom state, redirectUri, etc.
*/
async getAuthorizationUrl(options = {}) {
// Build the combined state parameter (matches authkit-nextjs format)
const internalState = options.returnPathname
? btoa(JSON.stringify({ returnPathname: options.returnPathname }))
.replace(/\+/g, '-')
.replace(/\//g, '_')
: null;
// If both internal and custom state, combine as internal.custom
// Otherwise use whichever is provided
const state = internalState && options.state
? `${internalState}.${options.state}`
: internalState || options.state || undefined;
return this.client.userManagement.getAuthorizationUrl({
provider: 'authkit',
redirectUri: options.redirectUri ?? this.config.redirectUri,
screenHint: options.screenHint,
organizationId: options.organizationId,
loginHint: options.loginHint,
prompt: options.prompt,
clientId: this.config.clientId,
state,
async createAuthorization(options = {}) {
return generateAuthorizationUrl({
client: this.client,
config: this.config,
encryption: this.encryption,
options,
});
}
/**
* Convenience method: Get sign-in URL.
* Convenience method: Create sign-in authorization URL.
*/
async getSignInUrl(options = {}) {
return this.getAuthorizationUrl({
async createSignIn(options = {}) {
return this.createAuthorization({
...options,

@@ -130,6 +118,6 @@ screenHint: 'sign-in',

/**
* Convenience method: Get sign-up URL.
* Convenience method: Create sign-up authorization URL.
*/
async getSignUpUrl(options = {}) {
return this.getAuthorizationUrl({
async createSignUp(options = {}) {
return this.createAuthorization({
...options,

@@ -136,0 +124,0 @@ screenHint: 'sign-up',

@@ -1,1 +0,1 @@

{"version":3,"file":"AuthOperations.js","sourceRoot":"","sources":["../../src/operations/AuthOperations.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"AuthOperations.js","sourceRoot":"","sources":["../../src/operations/AuthOperations.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,wBAAwB,GACzB,MAAM,0CAA0C,CAAC;AAQlD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,cAAc;IACjB,IAAI,CAAc;IAClB,MAAM,CAAS;IACf,MAAM,CAAgB;IACtB,UAAU,CAAoB;IAEtC,YACE,IAAiB,EACjB,MAAc,EACd,MAAqB,EACrB,UAA6B;QAE7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED;;;;;;;;;OASG;IACH,YAAY,CAAC,SAAiB,EAAE,OAA+B;QAC7D,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC;YAC7C,SAAS;YACT,QAAQ,EAAE,OAAO,EAAE,QAAQ;SAC5B,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,kBAAkB,CACtB,OAAgB,EAChB,cAAsB;QAKtB,wDAAwD;QACxD,OAAO,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,cAAc,CAClB,OAAgB,EAChB,cAAuB;QAKvB,6DAA6D;QAC7D,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB,CACxE,OAAO,EACP,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,CAChC,CAAC;QAEF,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAEpE,MAAM,IAAI,GAAe;YACvB,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,SAAS,EAAE,MAAM,CAAC,GAAG;YACrB,YAAY,EAAE,UAAU,CAAC,YAAY;YACrC,WAAW,EAAE,UAAU,CAAC,WAAW;YACnC,YAAY,EAAE,UAAU,CAAC,YAAY;YACrC,MAAM;YACN,cAAc,EAAE,MAAM,CAAC,MAAM;YAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,YAAY,EAAE,MAAM,CAAC,aAAa;SACnC,CAAC;QAEF,OAAO;YACL,IAAI;YACJ,gBAAgB;SACjB,CAAC;IACJ,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,mBAAmB,CACvB,UAAsC,EAAE;QAExC,OAAO,wBAAwB,CAAC;YAC9B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAChB,UAA0D,EAAE;QAE5D,OAAO,IAAI,CAAC,mBAAmB,CAAC;YAC9B,GAAG,OAAO;YACV,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAChB,UAA0D,EAAE;QAE5D,OAAO,IAAI,CAAC,mBAAmB,CAAC;YAC9B,GAAG,OAAO;YACV,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;IACL,CAAC;CACF"}
import type { WorkOS } from '@workos-inc/node';
import type { AuthKitConfig } from '../core/config/types.js';
import type { AuthResult, CustomClaims, GetAuthorizationUrlOptions, HeadersBag, Session, SessionEncryption, SessionStorage } from '../core/session/types.js';
import type { AuthResult, CreateAuthorizationResult, CustomClaims, GetAuthorizationUrlOptions, HeadersBag, Session, SessionEncryption, SessionStorage } from '../core/session/types.js';
/**

@@ -15,3 +15,3 @@ * Framework-agnostic authentication service.

* - `handleCallback()` - Process OAuth callback
* - `signOut()`, `getSignInUrl()`, etc. - Delegate to AuthOperations
* - `signOut()`, `createSignIn()`, etc. - Delegate to AuthOperations
*

@@ -102,14 +102,35 @@ * **Used by:** @workos/authkit-tanstack-react-start

/**
* Get authorization URL - delegates to AuthOperations.
* Create an authorization URL and write the PKCE verifier cookie.
*
* @param response - Framework-specific response object (may be undefined —
* adapters that mutate responses in-place will get back a mutated copy).
* @param options - screenHint, returnPathname, custom state, redirectUri, etc.
* @returns The URL to redirect the browser to, plus storage's
* `{ response?, headers? }` carrying the verifier `Set-Cookie`.
*/
getAuthorizationUrl(options?: GetAuthorizationUrlOptions): Promise<string>;
createAuthorization(response: TResponse | undefined, options?: GetAuthorizationUrlOptions): Promise<CreateAuthorizationResult<TResponse>>;
/**
* Convenience: Get sign-in URL.
* Convenience: Create sign-in URL and write the PKCE verifier cookie.
*/
getSignInUrl(options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<string>;
createSignIn(response: TResponse | undefined, options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<CreateAuthorizationResult<TResponse>>;
/**
* Convenience: Get sign-up URL.
* Convenience: Create sign-up URL and write the PKCE verifier cookie.
*/
getSignUpUrl(options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<string>;
createSignUp(response: TResponse | undefined, options?: Omit<GetAuthorizationUrlOptions, 'screenHint'>): Promise<CreateAuthorizationResult<TResponse>>;
/**
* Emit a `Set-Cookie` header that clears the PKCE verifier cookie.
*
* Use on any exit path where a sign-in was started (verifier cookie
* written) but `handleCallback` will not run to clear it — OAuth error
* responses, missing `code`, early bail-outs.
*
* Pass `options.redirectUri` on requests that used a per-request
* `redirectUri` override at sign-in time, so the delete cookie's computed
* attributes (notably `secure`) match what was originally set.
*/
clearPendingVerifier(response: TResponse | undefined, options?: Pick<GetAuthorizationUrlOptions, 'redirectUri'>): Promise<{
response?: TResponse;
headers?: HeadersBag;
}>;
/**
* Get the WorkOS client instance.

@@ -121,12 +142,20 @@ * Useful for direct API calls not covered by AuthKit.

* Handle OAuth callback.
* This creates a new session after successful authentication.
*
* @param request - Framework-specific request (not currently used)
* @param response - Framework-specific response
* @param options - OAuth callback options (code, state)
* @returns Updated response, return pathname, and auth response
* Reads the verifier cookie via storage, verifies it against the URL
* `state`, exchanges the code, saves the session, and — on success —
* also emits a verifier-delete `Set-Cookie` so `HeadersBag['Set-Cookie']`
* carries both entries as a `string[]`. Adapters MUST append each
* `Set-Cookie` as its own header (never comma-join).
*
* Error-path cleanup: if any step after the cookie read throws
* (state mismatch, tampered seal, code-exchange failure, session-save
* failure), `handleCallback` attempts a best-effort `clearCookie` on the
* response before rethrowing so the verifier does not linger for its full
* 600s TTL. Response-mutating adapters get the delete `Set-Cookie`
* applied in place; headers-only adapters should still call
* `clearPendingVerifier` in their catch block to capture the headers bag.
*/
handleCallback(_request: TRequest, response: TResponse, options: {
handleCallback(request: TRequest, response: TResponse, options: {
code: string;
state?: string;
state: string | undefined;
}): Promise<{

@@ -139,3 +168,17 @@ response: TResponse | undefined;

}>;
/**
* Best-effort verifier cleanup on a `handleCallback` error path.
*
* Mutates the response in place for response-mutating adapters.
* Swallows storage errors — cleanup must never mask the original failure.
* The original error is always rethrown by the caller.
*
* `schemeAgnostic` (pre-unseal failures only): drops the `Secure` attribute
* on the delete when `sameSite === 'lax'` so the browser accepts the
* `Set-Cookie` over http:// dev callbacks. Cookie replacement matches on
* (name, domain, path), not `Secure`, so the tuple-matched original cookie
* is still cleared regardless of its original `Secure` flag.
*/
private bestEffortClearVerifier;
}
//# sourceMappingURL=AuthService.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"AuthService.d.ts","sourceRoot":"","sources":["../../src/service/AuthService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,0BAA0B,EAC1B,UAAU,EACV,OAAO,EACP,iBAAiB,EACjB,cAAc,EACf,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;;GAcG;AACH,qBAAa,WAAW,CAAC,QAAQ,EAAE,SAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAG9B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,EAC5C,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,iBAAiB;IAS/B;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,aAAa,GAAG,YAAY,EACzC,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC;QACT,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC;QAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;KAC/B,CAAC;IAsCF;;;;;OAKG;IACG,UAAU,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAQ5D;;;;;;OAMG;IACG,WAAW,CACf,QAAQ,EAAE,SAAS,GAAG,SAAS,EAC/B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAI1D;;;;;OAKG;IACG,YAAY,CAChB,QAAQ,EAAE,SAAS,GAClB,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAI1D;;;;;;;;;OASG;IACG,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9B,OAAO,CAAC;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,SAAS,CAAC;QACrB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB,CAAC;IAMF;;OAEG;IACG,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM;;;;IAIjE;;OAEG;IACG,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,MAAM;;;;IAI9D;;OAEG;IACG,mBAAmB,CAAC,OAAO,GAAE,0BAA+B;IAIlE;;OAEG;IACG,YAAY,CAChB,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,YAAY,CAAM;IAK9D;;OAEG;IACG,YAAY,CAChB,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,YAAY,CAAM;IAK9D;;;OAGG;IACH,SAAS,IAAI,MAAM;IAInB;;;;;;;;OAQG;IACG,cAAc,CAClB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;CA8D5C"}
{"version":3,"file":"AuthService.d.ts","sourceRoot":"","sources":["../../src/service/AuthService.ts"],"names":[],"mappings":"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"}
import { AuthKitCore } from '../core/AuthKitCore.js';
import { getPKCECookieOptions, PKCE_COOKIE_NAME, } from '../core/pkce/cookieOptions.js';
import { AuthOperations } from '../operations/AuthOperations.js';
/**
* Merge two `HeadersBag` values. `Set-Cookie` matching is case-insensitive;
* existing key casing is preserved. Multiple `Set-Cookie` values are
* concatenated into a `string[]`. Other keys are shallow-merged (second wins).
*/
function mergeHeaderBags(a, b) {
if (!a)
return b;
if (!b)
return a;
const merged = { ...a };
let setCookieKey = Object.keys(merged).find(k => k.toLowerCase() === 'set-cookie');
for (const [key, value] of Object.entries(b)) {
if (key.toLowerCase() !== 'set-cookie') {
merged[key] = value;
continue;
}
if (!setCookieKey) {
merged[key] = value;
setCookieKey = key;
continue;
}
const left = merged[setCookieKey];
const leftArr = Array.isArray(left) ? left : [left];
const rightArr = Array.isArray(value) ? value : [value];
merged[setCookieKey] = [...leftArr, ...rightArr];
}
return merged;
}
/**
* Framework-agnostic authentication service.

@@ -14,3 +44,3 @@ *

* - `handleCallback()` - Process OAuth callback
* - `signOut()`, `getSignInUrl()`, etc. - Delegate to AuthOperations
* - `signOut()`, `createSignIn()`, etc. - Delegate to AuthOperations
*

@@ -30,3 +60,3 @@ * **Used by:** @workos/authkit-tanstack-react-start

this.core = new AuthKitCore(config, client, encryption);
this.operations = new AuthOperations(this.core, client, config);
this.operations = new AuthOperations(this.core, client, config, encryption);
}

@@ -135,20 +165,48 @@ /**

/**
* Get authorization URL - delegates to AuthOperations.
* Create an authorization URL and write the PKCE verifier cookie.
*
* @param response - Framework-specific response object (may be undefined —
* adapters that mutate responses in-place will get back a mutated copy).
* @param options - screenHint, returnPathname, custom state, redirectUri, etc.
* @returns The URL to redirect the browser to, plus storage's
* `{ response?, headers? }` carrying the verifier `Set-Cookie`.
*/
async getAuthorizationUrl(options = {}) {
return this.operations.getAuthorizationUrl(options);
async createAuthorization(response, options = {}) {
const { url, sealedState, cookieOptions } = await this.operations.createAuthorization(options);
const write = await this.storage.setCookie(response, PKCE_COOKIE_NAME, sealedState, cookieOptions);
return { url, ...write };
}
/**
* Convenience: Get sign-in URL.
* Convenience: Create sign-in URL and write the PKCE verifier cookie.
*/
async getSignInUrl(options = {}) {
return this.operations.getSignInUrl(options);
async createSignIn(response, options = {}) {
return this.createAuthorization(response, {
...options,
screenHint: 'sign-in',
});
}
/**
* Convenience: Get sign-up URL.
* Convenience: Create sign-up URL and write the PKCE verifier cookie.
*/
async getSignUpUrl(options = {}) {
return this.operations.getSignUpUrl(options);
async createSignUp(response, options = {}) {
return this.createAuthorization(response, {
...options,
screenHint: 'sign-up',
});
}
/**
* Emit a `Set-Cookie` header that clears the PKCE verifier cookie.
*
* Use on any exit path where a sign-in was started (verifier cookie
* written) but `handleCallback` will not run to clear it — OAuth error
* responses, missing `code`, early bail-outs.
*
* Pass `options.redirectUri` on requests that used a per-request
* `redirectUri` override at sign-in time, so the delete cookie's computed
* attributes (notably `secure`) match what was originally set.
*/
async clearPendingVerifier(response, options) {
return this.storage.clearCookie(response, PKCE_COOKIE_NAME, getPKCECookieOptions(this.config, options?.redirectUri));
}
/**
* Get the WorkOS client instance.

@@ -162,67 +220,95 @@ * Useful for direct API calls not covered by AuthKit.

* Handle OAuth callback.
* This creates a new session after successful authentication.
*
* @param request - Framework-specific request (not currently used)
* @param response - Framework-specific response
* @param options - OAuth callback options (code, state)
* @returns Updated response, return pathname, and auth response
* Reads the verifier cookie via storage, verifies it against the URL
* `state`, exchanges the code, saves the session, and — on success —
* also emits a verifier-delete `Set-Cookie` so `HeadersBag['Set-Cookie']`
* carries both entries as a `string[]`. Adapters MUST append each
* `Set-Cookie` as its own header (never comma-join).
*
* Error-path cleanup: if any step after the cookie read throws
* (state mismatch, tampered seal, code-exchange failure, session-save
* failure), `handleCallback` attempts a best-effort `clearCookie` on the
* response before rethrowing so the verifier does not linger for its full
* 600s TTL. Response-mutating adapters get the delete `Set-Cookie`
* applied in place; headers-only adapters should still call
* `clearPendingVerifier` in their catch block to capture the headers bag.
*/
async handleCallback(_request, response, options) {
// Authenticate with WorkOS using the OAuth code
const authResponse = await this.client.userManagement.authenticateWithCode({
code: options.code,
clientId: this.config.clientId,
});
// Create and save the new session
const session = {
accessToken: authResponse.accessToken,
refreshToken: authResponse.refreshToken,
user: authResponse.user,
impersonator: authResponse.impersonator,
};
const encryptedSession = await this.core.encryptSession(session);
const { response: updatedResponse, headers } = await this.saveSession(response, encryptedSession);
// Parse state: format is `{internal}.{userState}` or legacy `{base64JSON}`
let returnPathname = '/';
let customState;
if (options.state) {
if (options.state.includes('.')) {
const [internal, ...rest] = options.state.split('.');
customState = rest.join('.'); // Rejoin in case userState contains dots
try {
// Reverse URL-safe base64 encoding and decode
const decoded = (internal ?? '')
.replace(/-/g, '+')
.replace(/_/g, '/');
const parsed = JSON.parse(atob(decoded));
returnPathname = parsed.returnPathname || '/';
}
catch {
// Malformed internal state, use default
}
}
else {
try {
const parsed = JSON.parse(atob(options.state));
if (parsed.returnPathname) {
returnPathname = parsed.returnPathname;
}
else {
customState = options.state;
}
}
catch {
customState = options.state;
}
}
async handleCallback(request, response, options) {
const cookieValue = await this.storage.getCookie(request, PKCE_COOKIE_NAME);
let unsealed;
try {
unsealed = await this.core.verifyCallbackState({
stateFromUrl: options.state,
cookieValue: cookieValue ?? undefined,
});
}
return {
response: updatedResponse,
headers,
returnPathname,
state: customState,
authResponse,
};
catch (err) {
// Pre-unseal failure — we don't know the per-request redirectUri
// override, so emit a scheme-agnostic delete: same (name, domain,
// path) tuple as the original set (what the browser uses to match
// for replacement), with `Secure` dropped in the `sameSite: 'lax'`
// case so the Set-Cookie is accepted over http:// callbacks too.
// The `sameSite: 'none'` case already forces Secure on both set and
// clear, so there's no scheme-mismatch risk there.
await this.bestEffortClearVerifier(response, undefined, {
schemeAgnostic: true,
});
throw err;
}
const { codeVerifier, returnPathname, customState, redirectUri } = unsealed;
const clearOptions = getPKCECookieOptions(this.config, redirectUri);
try {
const authResponse = await this.client.userManagement.authenticateWithCode({
code: options.code,
clientId: this.config.clientId,
codeVerifier,
});
const session = {
accessToken: authResponse.accessToken,
refreshToken: authResponse.refreshToken,
user: authResponse.user,
impersonator: authResponse.impersonator,
};
const encryptedSession = await this.core.encryptSession(session);
const save = await this.storage.saveSession(response, encryptedSession);
const clear = await this.storage.clearCookie(save.response ?? response, PKCE_COOKIE_NAME, clearOptions);
return {
response: clear.response ?? save.response,
headers: mergeHeaderBags(save.headers, clear.headers),
returnPathname: returnPathname ?? '/',
state: customState,
authResponse,
};
}
catch (err) {
await this.bestEffortClearVerifier(response, redirectUri);
throw err;
}
}
/**
* Best-effort verifier cleanup on a `handleCallback` error path.
*
* Mutates the response in place for response-mutating adapters.
* Swallows storage errors — cleanup must never mask the original failure.
* The original error is always rethrown by the caller.
*
* `schemeAgnostic` (pre-unseal failures only): drops the `Secure` attribute
* on the delete when `sameSite === 'lax'` so the browser accepts the
* `Set-Cookie` over http:// dev callbacks. Cookie replacement matches on
* (name, domain, path), not `Secure`, so the tuple-matched original cookie
* is still cleared regardless of its original `Secure` flag.
*/
async bestEffortClearVerifier(response, redirectUri, { schemeAgnostic = false } = {}) {
const options = getPKCECookieOptions(this.config, redirectUri);
if (schemeAgnostic && options.sameSite === 'lax') {
options.secure = false;
}
try {
await this.storage.clearCookie(response, PKCE_COOKIE_NAME, options);
}
catch {
// Swallow: cleanup is opportunistic; callers get the original error.
}
}
}
//# sourceMappingURL=AuthService.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["../../src/service/AuthService.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["../../src/service/AuthService.ts"],"names":[],"mappings":"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"}

@@ -1,1 +0,1 @@

{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/service/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE;IAC9D,qBAAqB,EAAE,CACrB,MAAM,EAAE,aAAa,KAClB,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IAClD,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,iBAAiB,CAAC;CAClE,GAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAoCnC"}
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/service/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE;IAC9D,qBAAqB,EAAE,CACrB,MAAM,EAAE,aAAa,KAClB,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IAClD,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,iBAAiB,CAAC;CAClE,GAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAuCnC"}

@@ -53,5 +53,6 @@ import { once } from '../utils.js';

refreshSession: (session, organizationId) => getService().refreshSession(session, organizationId),
getAuthorizationUrl: opts => getService().getAuthorizationUrl(opts),
getSignInUrl: opts => getService().getSignInUrl(opts),
getSignUpUrl: opts => getService().getSignUpUrl(opts),
createAuthorization: (response, opts) => getService().createAuthorization(response, opts),
createSignIn: (response, opts) => getService().createSignIn(response, opts),
createSignUp: (response, opts) => getService().createSignUp(response, opts),
clearPendingVerifier: (response, opts) => getService().clearPendingVerifier(response, opts),
getWorkOS: () => getService().getWorkOS(),

@@ -58,0 +59,0 @@ handleCallback: (request, response, opts) => getService().handleCallback(request, response, opts),

@@ -1,1 +0,1 @@

{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/service/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,iBAAiB,MAAM,+CAA+C,CAAC;AAK9E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,iBAAiB,CAAsB,OAMtD;IACC,MAAM,EACJ,qBAAqB,EACrB,aAAa,GAAG,GAAG,EAAE,CAAC,SAAS,EAAE,EACjC,iBAAiB,GAAG,GAAG,EAAE,CAAC,iBAAiB,GAC5C,GAAG,OAAO,CAAC;IAEZ,0DAA0D;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE;QAC3B,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC7C,OAAO,IAAI,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,yDAAyD;IACzD,sFAAsF;IACtF,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;QACnD,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QACvD,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,CACrC,UAAU,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC;QACjD,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;QAC7D,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;QACnE,kBAAkB,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,CAC9C,UAAU,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,cAAc,CAAC;QAC1D,cAAc,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,CAC1C,UAAU,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC;QACtD,mBAAmB,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC;QACnE,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC;QACrD,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC;QACrD,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE;QACzC,cAAc,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAC1C,UAAU,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC;KACnB,CAAC;AACxC,CAAC"}
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/service/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,iBAAiB,MAAM,+CAA+C,CAAC;AAK9E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,iBAAiB,CAAsB,OAMtD;IACC,MAAM,EACJ,qBAAqB,EACrB,aAAa,GAAG,GAAG,EAAE,CAAC,SAAS,EAAE,EACjC,iBAAiB,GAAG,GAAG,EAAE,CAAC,iBAAiB,GAC5C,GAAG,OAAO,CAAC;IAEZ,0DAA0D;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE;QAC3B,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC7C,OAAO,IAAI,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,yDAAyD;IACzD,sFAAsF;IACtF,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;QACnD,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QACvD,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,CACrC,UAAU,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC;QACjD,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;QAC7D,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;QACnE,kBAAkB,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,CAC9C,UAAU,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,cAAc,CAAC;QAC1D,cAAc,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,CAC1C,UAAU,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC;QACtD,mBAAmB,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CACtC,UAAU,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC;QAClD,YAAY,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC;QAC3E,YAAY,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC;QAC3E,oBAAoB,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CACvC,UAAU,EAAE,CAAC,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC;QACnD,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE;QACzC,cAAc,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAC1C,UAAU,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC;KACnB,CAAC;AACxC,CAAC"}

@@ -9,2 +9,18 @@ /**

export declare function once<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
/**
* Constant-time byte-array equality.
*
* Used for comparing security-sensitive values (OAuth state, tokens, MACs)
* where early-return on mismatch would leak timing information about how
* many leading bytes matched.
*
* Runtime-portable: no `node:crypto`, no `Buffer` — works in Node, browsers,
* and edge runtimes. Callers produce `Uint8Array`s via `TextEncoder`.
*
* Contract:
* - Returns false immediately if lengths differ (length is not secret).
* - Once past the length gate, inspects EVERY byte regardless of mismatches.
* - No branches whose count depends on byte values.
*/
export declare function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean;
//# sourceMappingURL=utils.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EACnD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,GAC9B,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAW7B"}
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EACnD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,GAC9B,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAW7B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAKvE"}

@@ -19,2 +19,25 @@ /**

}
/**
* Constant-time byte-array equality.
*
* Used for comparing security-sensitive values (OAuth state, tokens, MACs)
* where early-return on mismatch would leak timing information about how
* many leading bytes matched.
*
* Runtime-portable: no `node:crypto`, no `Buffer` — works in Node, browsers,
* and edge runtimes. Callers produce `Uint8Array`s via `TextEncoder`.
*
* Contract:
* - Returns false immediately if lengths differ (length is not secret).
* - Once past the length gate, inspects EVERY byte regardless of mismatches.
* - No branches whose count depends on byte values.
*/
export function constantTimeEqual(a, b) {
if (a.length !== b.length)
return false;
let diff = 0;
for (let i = 0; i < a.length; i++)
diff |= a[i] ^ b[i];
return diff === 0;
}
//# sourceMappingURL=utils.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,EAA+B;IAE/B,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,MAAe,CAAC;IAEpB,OAAO,CAAC,GAAG,IAAW,EAAW,EAAE;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YACrB,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,EAA+B;IAE/B,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,MAAe,CAAC;IAEpB,OAAO,CAAC,GAAG,IAAW,EAAW,EAAE;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YACrB,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAa,EAAE,CAAa;IAC5D,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACzD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC"}
{
"name": "@workos/authkit-session",
"version": "0.3.4",
"version": "0.4.0",
"description": "Framework-agnostic authentication library for WorkOS with pluggable storage adapters",
"keywords": [],
"license": "MIT",
"author": "WorkOS",
"repository": {
"type": "git",
"url": "https://github.com/workos/authkit-session.git"
},
"files": [
"dist",
"src",
"README.md"
],
"type": "module",

@@ -14,8 +26,7 @@ "main": "./dist/index.js",

},
"keywords": [],
"author": "WorkOS",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/workos/authkit-session.git"
"dependencies": {
"@workos-inc/node": "^8.0.0",
"iron-webcrypto": "^2.0.0",
"jose": "^6.1.3",
"valibot": "^1.3.1"
},

@@ -25,19 +36,10 @@ "devDependencies": {

"@vitest/coverage-v8": "^4.0.17",
"prettier": "^3.7.4",
"oxfmt": "^0.42.0",
"oxlint": "^1.57.0",
"typescript": "^5.9.3",
"vitest": "^4.0.17"
},
"dependencies": {
"@workos-inc/node": "8.0.0",
"iron-webcrypto": "^2.0.0",
"jose": "^6.1.3"
},
"engines": {
"node": ">=20.0.0"
},
"files": [
"dist",
"src",
"README.md"
],
"scripts": {

@@ -48,4 +50,5 @@ "clean": "rm -rf dist",

"dev": "tsc -p tsconfig.build.json --watch",
"prettier": "prettier --check .",
"format": "prettier --write .",
"format": "oxfmt --write .",
"format:check": "oxfmt --check .",
"lint": "oxlint .",
"typecheck": "tsc --noEmit",

@@ -52,0 +55,0 @@ "test": "vitest --run",

+94
-27

@@ -52,6 +52,3 @@ # @workos/authkit-session

```typescript
import {
CookieSessionStorage,
parseCookieHeader,
} from '@workos/authkit-session';
import { CookieSessionStorage } from '@workos/authkit-session';

@@ -62,6 +59,10 @@ export class MyFrameworkStorage extends CookieSessionStorage<

> {
async getSession(request: Request): Promise<string | null> {
const cookieHeader = request.headers.get('cookie');
if (!cookieHeader) return null;
return parseCookieHeader(cookieHeader)[this.cookieName] ?? null;
async getCookie(request: Request, name: string): Promise<string | null> {
const header = request.headers.get('cookie');
if (!header) return null;
for (const part of header.split(';')) {
const [k, ...rest] = part.trim().split('=');
if (k === name) return decodeURIComponent(rest.join('='));
}
return null;
}

@@ -72,3 +73,3 @@

response: Response | undefined,
headers: Record<string, string>,
headers: Record<string, string | string[]>,
): Promise<{ response: Response }> {

@@ -83,5 +84,9 @@ const newResponse = response

Object.entries(headers).forEach(([key, value]) => {
newResponse.headers.append(key, value);
});
for (const [key, value] of Object.entries(headers)) {
if (Array.isArray(value)) {
for (const v of value) newResponse.headers.append(key, v);
} else {
newResponse.headers.append(key, value);
}
}

@@ -93,3 +98,3 @@ return { response: newResponse };

`CookieSessionStorage` provides `this.cookieName`, `this.cookieOptions`, `buildSetCookie()`, and implements `saveSession()`/`clearSession()` using your `applyHeaders()`.
`CookieSessionStorage` provides `this.cookieName`, `this.cookieOptions`, and generic `setCookie`/`clearCookie`/`serializeCookie` primitives. `getSession`/`saveSession`/`clearSession` are one-line wrappers — you only implement `getCookie`.

@@ -125,3 +130,4 @@ ### 3. Create Service

);
if (headers?.['Set-Cookie']) {
const setCookie = headers?.['Set-Cookie'];
if (setCookie) {
const newResponse = new Response(result.response.body, {

@@ -132,3 +138,7 @@ status: result.response.status,

});
newResponse.headers.set('Set-Cookie', headers['Set-Cookie']);
// Append each entry — never `.set()` with an array (comma-joined
// Set-Cookie is not a valid single HTTP header).
for (const v of Array.isArray(setCookie) ? setCookie : [setCookie]) {
newResponse.headers.append('Set-Cookie', v);
}
return { ...result, response: newResponse };

@@ -170,3 +180,3 @@ }

| `WORKOS_COOKIE_PASSWORD` | `cookiePassword` | 32+ char encryption key |
| `WORKOS_COOKIE_NAME` | `cookieName` | Cookie name (default: `wos_session`) |
| `WORKOS_COOKIE_NAME` | `cookieName` | Cookie name (default: `wos-session`) |
| `WORKOS_COOKIE_MAX_AGE` | `cookieMaxAge` | Cookie lifetime in seconds |

@@ -191,12 +201,68 @@ | `WORKOS_COOKIE_DOMAIN` | `cookieDomain` | Cookie domain |

// WorkOS Operations
authService.signOut(sessionId, { returnTo }) // → { logoutUrl, clearCookieHeader }
authService.signOut(sessionId, { returnTo }) // → { logoutUrl, response?, headers? }
authService.refreshSession(session, organizationId?)
authService.switchOrganization(session, organizationId)
// URL Generation
authService.getAuthorizationUrl(options)
authService.getSignInUrl(options)
authService.getSignUpUrl(options)
// URL Generation — write verifier cookie, return { url, response?, headers? }
authService.createAuthorization(response, options)
authService.createSignIn(response, options)
authService.createSignUp(response, options)
// Error-path cleanup for the PKCE verifier cookie
// (response may be `undefined` for headers-only adapters)
authService.clearPendingVerifier(response, { redirectUri? })
```
### PKCE verifier cookie (`wos-auth-verifier`)
This library binds every OAuth sign-in to a PKCE code verifier, so a leaked
`state` value on its own cannot be used to complete a session hijack.
The verifier is sealed into a single blob that serves two roles:
1. It is sent to WorkOS as the OAuth `state` query parameter.
2. It is set as a short-lived HTTP-only cookie (`wos-auth-verifier`, 10 min).
The cookie is written and read through `SessionStorage`. Callers don't see
sealed blobs or cookie options:
```typescript
// Sign in: library writes the verifier cookie via storage, returns the URL + headers
const { url, headers } = await authService.createSignIn(response, {
returnPathname: '/dashboard',
});
return new Response(null, {
status: 302,
headers: { ...headers, Location: url },
});
// Callback: library reads the verifier via storage, byte-compares, then exchanges
await authService.handleCallback(request, response, {
code,
state, // from URL
});
```
On success, `handleCallback` returns a `Set-Cookie` entry in `headers` as a
`string[]` with two values — the session cookie AND a clear for the verifier
cookie. Adapters must append each entry as its own `Set-Cookie` HTTP header
(never comma-join). The bag key is case-insensitive — `mergeHeaderBags`
preserves the adapter's casing — so look it up that way:
```ts
const setCookie =
result.headers?.['Set-Cookie'] ?? result.headers?.['set-cookie'];
if (setCookie) {
for (const v of Array.isArray(setCookie) ? setCookie : [setCookie]) {
response.headers.append('Set-Cookie', v);
}
}
```
Mismatched state and cookie raise `OAuthStateMismatchError`. A missing cookie
(typical cause: Set-Cookie stripped by a proxy) raises
`PKCECookieMissingError`. On either error path — or any early bail-out before
`handleCallback` runs — call `authService.clearPendingVerifier(response)` to
emit a delete header.
### Direct Access (Advanced)

@@ -210,10 +276,11 @@

AuthOperations,
getConfigurationProvider,
getWorkOS,
getConfigurationProvider,
sessionEncryption,
} 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);
const config = getConfigurationProvider().getConfig();
const client = getWorkOS();
const core = new AuthKitCore(config, client, sessionEncryption);
const operations = new AuthOperations(core, client, config, sessionEncryption);

@@ -226,3 +293,3 @@ // Use core.validateAndRefresh(), core.encryptSession(), etc.

- **JWKS Caching**: Keys fetched on-demand, cached for process lifetime. `jose` handles key rotation automatically.
- **Token Expiry Buffer**: 60 seconds default. Tokens refresh before actual expiry.
- **Token Refresh**: `validateAndRefresh` refreshes when `verifyToken` fails (i.e. when the access token is expired or invalid). `isTokenExpiring(token, buffer)` is available as a separate helper for callers that want to proactively refresh before expiry.
- **Session Encryption**: AES-256-CBC + SHA-256 HMAC via `iron-webcrypto`.

@@ -229,0 +296,0 @@ - **Lazy Initialization**: `createAuthService()` defers initialization until first use, allowing `configure()` to be called later.

@@ -262,2 +262,111 @@ import { AuthKitCore } from './AuthKitCore.js';

});
describe('validateAndRefresh()', () => {
// Decodable JWT with sid + exp + org_id. Signature is garbage → verifyToken false.
const oldJwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInNpZCI6InNlc3Npb25fOTk5IiwiZXhwIjoxMDAwMDAwMDAwLCJvcmdfaWQiOiJvcmdfYWJjIn0.sig';
const newJwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInNpZCI6InNlc3Npb25fbmV3IiwiZXhwIjozMDAwMDAwMDAwfQ.sig';
function makeRefreshClient(capture?: { opts?: any }) {
return {
userManagement: {
getJwksUrl: () => 'https://api.workos.com/sso/jwks/test-client-id',
authenticateWithRefreshToken: async (opts: any) => {
if (capture) capture.opts = opts;
return {
accessToken: newJwt,
refreshToken: 'new-rt',
user: mockUser,
impersonator: undefined,
};
},
},
};
}
it('forces refresh when token invalid and returns new session', async () => {
const testCore = new AuthKitCore(
mockConfig as any,
makeRefreshClient() as any,
mockEncryption as any,
);
const session = {
accessToken: oldJwt,
refreshToken: 'rt',
user: mockUser,
impersonator: undefined,
};
const result = await testCore.validateAndRefresh(session);
expect(result.refreshed).toBe(true);
expect(result.session.accessToken).toBe(newJwt);
});
it('propagates explicit organizationId into refresh', async () => {
const capture: { opts?: any } = {};
const testCore = new AuthKitCore(
mockConfig as any,
makeRefreshClient(capture) as any,
mockEncryption as any,
);
await testCore.validateAndRefresh(
{
accessToken: oldJwt,
refreshToken: 'rt',
user: mockUser,
impersonator: undefined,
},
{ organizationId: 'org_explicit' },
);
expect(capture.opts?.organizationId).toBe('org_explicit');
});
it('continues when access token is unparseable', async () => {
const testCore = new AuthKitCore(
mockConfig as any,
makeRefreshClient() as any,
mockEncryption as any,
);
const session = {
accessToken: 'not-a-jwt',
refreshToken: 'rt',
user: mockUser,
impersonator: undefined,
};
const result = await testCore.validateAndRefresh(session);
expect(result.refreshed).toBe(true);
});
});
describe('verifyCallbackState()', () => {
it('throws OAuthStateMismatchError when stateFromUrl missing', async () => {
await expect(
core.verifyCallbackState({
stateFromUrl: undefined,
cookieValue: 'x',
}),
).rejects.toMatchObject({ name: 'OAuthStateMismatchError' });
});
it('throws PKCECookieMissingError when cookie missing', async () => {
await expect(
core.verifyCallbackState({
stateFromUrl: 'x',
cookieValue: undefined,
}),
).rejects.toMatchObject({ name: 'PKCECookieMissingError' });
});
it('throws OAuthStateMismatchError when values differ', async () => {
await expect(
core.verifyCallbackState({ stateFromUrl: 'a', cookieValue: 'b' }),
).rejects.toMatchObject({ name: 'OAuthStateMismatchError' });
});
});
});
import type { Impersonator, User, WorkOS } from '@workos-inc/node';
import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
import { once } from '../utils.js';
import { constantTimeEqual, once } from '../utils.js';
import type { AuthKitConfig } from './config/types.js';
import { SessionEncryptionError, TokenRefreshError } from './errors.js';
import {
OAuthStateMismatchError,
PKCECookieMissingError,
SessionEncryptionError,
TokenRefreshError,
} from './errors.js';
import { type PKCEState, unsealState } from './pkce/state.js';
import type {

@@ -107,4 +113,4 @@ BaseTokenClaims,

return decodeJwt<BaseTokenClaims & TCustomClaims>(token);
} catch (error) {
throw new Error('Invalid token');
} catch (cause) {
throw new Error('Invalid token', { cause });
}

@@ -152,2 +158,47 @@ }

/**
* Verify the OAuth callback state against the PKCE verifier cookie and
* return the unsealed state blob.
*
* Core owns encryption, so verification lives here rather than on
* AuthService. Byte-compares the query-string `state` against the cookie
* value BEFORE attempting to decrypt — smaller crypto attack surface and
* no decryption cost on obvious mismatches.
*
* @throws {OAuthStateMismatchError} state missing or does not match cookie
* @throws {PKCECookieMissingError} cookie not sent — typically a proxy or
* Set-Cookie propagation issue on the adapter's callback path
* @throws {SessionEncryptionError} seal tampered, wrong password, TTL expired,
* or schema mismatch on the unsealed payload
*/
async verifyCallbackState(params: {
stateFromUrl: string | undefined;
cookieValue: string | undefined;
}): Promise<PKCEState> {
const { stateFromUrl, cookieValue } = params;
if (!stateFromUrl) {
throw new OAuthStateMismatchError(
'Missing state parameter from callback URL',
);
}
if (!cookieValue) {
throw new PKCECookieMissingError(
'PKCE verifier cookie missing — cannot verify OAuth state. Ensure Set-Cookie headers are propagated on redirects.',
);
}
const encoder = new TextEncoder();
const urlBytes = encoder.encode(stateFromUrl);
const cookieBytes = encoder.encode(cookieValue);
if (!constantTimeEqual(urlBytes, cookieBytes)) {
throw new OAuthStateMismatchError('OAuth state mismatch');
}
return unsealState(
this.encryption,
this.config.cookiePassword,
cookieValue,
);
}
/**
* Refresh tokens using WorkOS API.

@@ -154,0 +205,0 @@ *

@@ -67,2 +67,64 @@ import { SessionEncryption } from './ironWebcryptoEncryption.js';

});
describe('TTL enforcement', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('unseals successfully before TTL expires', async () => {
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await encryption.sealData(testData, {
password: testPassword,
ttl: 600,
});
// Advance less than TTL
vi.setSystemTime(new Date('2026-01-01T00:09:00.000Z')); // +540s
const unsealed = await encryption.unsealData(sealed, {
password: testPassword,
ttl: 600,
});
expect(unsealed).toEqual(testData);
});
it('throws when TTL has expired on unseal', async () => {
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await encryption.sealData(testData, {
password: testPassword,
ttl: 1,
});
// Advance past TTL + skew (60s default)
vi.setSystemTime(new Date('2026-01-01T00:02:00.000Z'));
await expect(
encryption.unsealData(sealed, {
password: testPassword,
ttl: 1,
}),
).rejects.toThrow();
});
it('preserves session-cookie flow: seal without TTL, unseal without TTL', async () => {
// This is the invariant AuthKitCore.encryptSession/decryptSession relies on.
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
const sealed = await encryption.sealData(testData, {
password: testPassword,
// no ttl — matches AuthKitCore.encryptSession
});
// Advance time substantially — session cookies must still unseal.
vi.setSystemTime(new Date('2027-01-01T00:00:00.000Z'));
const unsealed = await encryption.unsealData(sealed, {
password: testPassword,
// no ttl — matches AuthKitCore.decryptSession
});
expect(unsealed).toEqual(testData);
});
});
});

@@ -61,3 +61,3 @@ import { seal as sealData, unseal as unsealData } from 'iron-webcrypto';

encryptedData: string,
{ password }: { password: string },
{ password, ttl = 0 }: { password: string; ttl?: number | undefined },
): Promise<T> {

@@ -84,3 +84,3 @@ // First, parse the seal to extract the version

},
ttl: 0,
ttl: ttl * 1000, // Convert seconds to milliseconds. 0 = no TTL enforcement.
timestampSkewSec: 60,

@@ -87,0 +87,0 @@ localtimeOffsetMsec: 0,

import {
AuthKitError,
OAuthStateMismatchError,
PKCECookieMissingError,
PKCEPayloadTooLargeError,
SessionEncryptionError,

@@ -9,91 +12,17 @@ TokenValidationError,

describe('AuthKitError', () => {
it('creates error with message', () => {
const error = new AuthKitError('Test error');
it('carries message, cause, and arbitrary data', () => {
const cause = new Error('Original');
const data = { userId: '123' };
const error = new AuthKitError('Test', cause, data);
expect(error.message).toBe('Test error');
expect(error.message).toBe('Test');
expect(error.name).toBe('AuthKitError');
expect(error).toBeInstanceOf(Error);
});
it('creates error with cause', () => {
const originalError = new Error('Original error');
const error = new AuthKitError('Test error', originalError);
expect(error.cause).toBe(originalError);
});
it('creates error with data', () => {
const data = { userId: '123', action: 'login' };
const error = new AuthKitError('Test error', undefined, data);
expect(error.cause).toBe(cause);
expect(error.data).toEqual(data);
});
it('creates error with cause and data', () => {
const originalError = new Error('Original error');
const data = { userId: '123' };
const error = new AuthKitError('Test error', originalError, data);
expect(error.cause).toBe(originalError);
expect(error.data).toEqual(data);
});
});
describe('SessionEncryptionError', () => {
it('creates error with correct name', () => {
const error = new SessionEncryptionError('Encryption failed');
expect(error.name).toBe('SessionEncryptionError');
expect(error.message).toBe('Encryption failed');
expect(error).toBeInstanceOf(AuthKitError);
expect(error).toBeInstanceOf(Error);
});
it('creates error with cause', () => {
const originalError = new Error('Crypto error');
const error = new SessionEncryptionError(
'Encryption failed',
originalError,
);
expect(error.cause).toBe(originalError);
});
});
describe('TokenValidationError', () => {
it('creates error with correct name', () => {
const error = new TokenValidationError('Token invalid');
expect(error.name).toBe('TokenValidationError');
expect(error.message).toBe('Token invalid');
expect(error).toBeInstanceOf(AuthKitError);
expect(error).toBeInstanceOf(Error);
});
it('creates error with cause', () => {
const originalError = new Error('JWT malformed');
const error = new TokenValidationError('Token invalid', originalError);
expect(error.cause).toBe(originalError);
});
});
describe('TokenRefreshError', () => {
it('creates error with correct name', () => {
const error = new TokenRefreshError('Refresh failed');
expect(error.name).toBe('TokenRefreshError');
expect(error.message).toBe('Refresh failed');
expect(error).toBeInstanceOf(AuthKitError);
expect(error).toBeInstanceOf(Error);
});
it('creates error with cause', () => {
const originalError = new Error('Network error');
const error = new TokenRefreshError('Refresh failed', originalError);
expect(error.cause).toBe(originalError);
});
it('creates error with userId and sessionId', () => {
describe('TokenRefreshError context', () => {
it('captures optional userId and sessionId', () => {
const error = new TokenRefreshError('Refresh failed', undefined, {

@@ -108,33 +37,3 @@ userId: 'user_123',

it('creates error with cause and context', () => {
const originalError = new Error('Network error');
const error = new TokenRefreshError('Refresh failed', originalError, {
userId: 'user_123',
sessionId: 'session_456',
});
expect(error.cause).toBe(originalError);
expect(error.userId).toBe('user_123');
expect(error.sessionId).toBe('session_456');
});
it('creates error with partial context (userId only)', () => {
const error = new TokenRefreshError('Refresh failed', undefined, {
userId: 'user_123',
});
expect(error.userId).toBe('user_123');
expect(error.sessionId).toBeUndefined();
});
it('creates error with partial context (sessionId only)', () => {
const error = new TokenRefreshError('Refresh failed', undefined, {
sessionId: 'session_456',
});
expect(error.userId).toBeUndefined();
expect(error.sessionId).toBe('session_456');
});
it('has undefined properties when no context provided', () => {
it('leaves context fields undefined when omitted', () => {
const error = new TokenRefreshError('Refresh failed');

@@ -147,34 +46,26 @@

describe('error inheritance', () => {
it('maintains proper inheritance chain', () => {
const sessionError = new SessionEncryptionError('test');
const tokenError = new TokenValidationError('test');
const refreshError = new TokenRefreshError('test');
describe('error subclasses', () => {
const cases: Array<[string, new (msg: string) => AuthKitError]> = [
['SessionEncryptionError', SessionEncryptionError],
['TokenValidationError', TokenValidationError],
['TokenRefreshError', TokenRefreshError],
['OAuthStateMismatchError', OAuthStateMismatchError],
['PKCECookieMissingError', PKCECookieMissingError],
['PKCEPayloadTooLargeError', PKCEPayloadTooLargeError],
];
expect(sessionError).toBeInstanceOf(SessionEncryptionError);
expect(sessionError).toBeInstanceOf(AuthKitError);
expect(sessionError).toBeInstanceOf(Error);
it.each(cases)(
'%s preserves name, message, and AuthKitError inheritance',
(name, Ctor) => {
const error = new Ctor('msg');
expect(tokenError).toBeInstanceOf(TokenValidationError);
expect(tokenError).toBeInstanceOf(AuthKitError);
expect(tokenError).toBeInstanceOf(Error);
expect(refreshError).toBeInstanceOf(TokenRefreshError);
expect(refreshError).toBeInstanceOf(AuthKitError);
expect(refreshError).toBeInstanceOf(Error);
});
it('can be caught as AuthKitError', () => {
const errors = [
new SessionEncryptionError('test'),
new TokenValidationError('test'),
new TokenRefreshError('test'),
];
errors.forEach(error => {
expect(error.name).toBe(name);
expect(error.message).toBe('msg');
expect(error).toBeInstanceOf(Ctor);
expect(error).toBeInstanceOf(AuthKitError);
expect(() => {
throw error;
}).toThrow(AuthKitError);
});
});
},
);
});

@@ -26,2 +26,23 @@ export class AuthKitError extends Error {

export class OAuthStateMismatchError extends AuthKitError {
constructor(message: string, cause?: unknown) {
super(message, cause);
this.name = 'OAuthStateMismatchError';
}
}
export class PKCECookieMissingError extends AuthKitError {
constructor(message: string, cause?: unknown) {
super(message, cause);
this.name = 'PKCECookieMissingError';
}
}
export class PKCEPayloadTooLargeError extends AuthKitError {
constructor(message: string, cause?: unknown) {
super(message, cause);
this.name = 'PKCEPayloadTooLargeError';
}
}
export class TokenRefreshError extends AuthKitError {

@@ -28,0 +49,0 @@ readonly userId?: string;

@@ -22,3 +22,3 @@ import { CookieSessionStorage } from './CookieSessionStorage.js';

class TestCookieSessionStorage extends CookieSessionStorage<string, string> {
async getSession(): Promise<string | null> {
async getCookie(_request: string, _name: string): Promise<string | null> {
return null;

@@ -114,3 +114,3 @@ }

const session = await storage.getSession();
const session = await storage.getSession('request');
expect(session).toBeNull();

@@ -128,3 +128,3 @@

describe('buildSetCookie', () => {
describe('serializeCookie', () => {
it('capitalizes SameSite values for Safari compatibility', async () => {

@@ -145,2 +145,78 @@ const testCases = [

});
describe('generic cookie primitives', () => {
it('setCookie emits a Set-Cookie with the passed name, value, and options', async () => {
const storage = new TestCookieSessionStorage(createMockConfig());
const result = await storage.setCookie(undefined, 'foo', 'bar', {
path: '/x',
maxAge: 60,
httpOnly: true,
secure: true,
sameSite: 'lax',
});
const header = result.headers?.['Set-Cookie'];
expect(header).toContain('foo=bar');
expect(header).toContain('Path=/x');
expect(header).toContain('Max-Age=60');
expect(header).toContain('HttpOnly');
expect(header).toContain('Secure');
expect(header).toContain('SameSite=Lax');
});
it('clearCookie emits Max-Age=0 with empty value', async () => {
const storage = new TestCookieSessionStorage(createMockConfig());
const result = await storage.clearCookie(undefined, 'foo', {
path: '/x',
});
const header = result.headers?.['Set-Cookie'] as string;
expect(header).toContain('foo=;');
expect(header).toContain('Max-Age=0');
});
it('getCookie is abstract — subclass must implement it', async () => {
const storage = new TestCookieSessionStorage(createMockConfig());
const value = await storage.getCookie('request', 'anything');
expect(value).toBeNull();
});
it('getSession wraps getCookie with the configured cookie name', async () => {
class CapturingStorage extends CookieSessionStorage<string, string> {
captured?: { name: string };
async getCookie(_req: string, name: string): Promise<string | null> {
this.captured = { name };
return 'stored-value';
}
}
const storage = new CapturingStorage(
createMockConfig({ cookieName: 'my-session' }),
);
const value = await storage.getSession('request');
expect(storage.captured).toEqual({ name: 'my-session' });
expect(value).toBe('stored-value');
});
it('saveSession and setCookie produce identical headers for the session cookie', async () => {
const config = createMockConfig();
const storage = new TestCookieSessionStorage(config);
const saved = await storage.saveSession(undefined, 'data');
const explicit = await storage.setCookie(
undefined,
storage['cookieName'],
'data',
storage['cookieOptions'],
);
expect(saved.headers?.['Set-Cookie']).toBe(
explicit.headers?.['Set-Cookie'],
);
});
});
});
import type { AuthKitConfig } from '../config/types.js';
import type { HeadersBag, SessionStorage } from './types.js';
import { serializeCookie } from './serializeCookie.js';
import type { CookieOptions, HeadersBag, SessionStorage } from './types.js';
export interface CookieOptions {
path?: string;
domain?: string;
maxAge?: number;
expires?: Date;
httpOnly?: boolean;
secure?: boolean;
sameSite?: 'lax' | 'strict' | 'none';
priority?: 'low' | 'medium' | 'high';
partitioned?: boolean;
}
export type { CookieOptions } from './types.js';

@@ -24,3 +15,6 @@ export abstract class CookieSessionStorage<

constructor(config: AuthKitConfig) {
this.cookieName = config.cookieName ?? 'wos_session';
// Matches the canonical default in ConfigurationProvider. This fallback
// only fires when a caller instantiates the class with a config that
// hasn't been resolved through the provider.
this.cookieName = config.cookieName ?? 'wos-session';

@@ -58,27 +52,29 @@ const sameSite = config.cookieSameSite ?? 'lax';

protected buildSetCookie(value: string, expired?: boolean): string {
const a = [`${this.cookieName}=${encodeURIComponent(value)}`];
const o = this.cookieOptions;
if (o.path) a.push(`Path=${o.path}`);
if (o.domain) a.push(`Domain=${o.domain}`);
if (o.maxAge || expired) a.push(`Max-Age=${expired ? 0 : o.maxAge}`);
if (o.httpOnly) a.push('HttpOnly');
if (o.secure) a.push('Secure');
if (o.sameSite) {
const capitalizedSameSite =
o.sameSite.charAt(0).toUpperCase() + o.sameSite.slice(1).toLowerCase();
a.push(`SameSite=${capitalizedSameSite}`);
}
if (o.priority) a.push(`Priority=${o.priority}`);
if (o.partitioned) a.push('Partitioned');
return a.join('; ');
protected serializeCookie(
name: string,
value: string,
options: CookieOptions,
flags: { expired?: boolean } = {},
): string {
return serializeCookie(name, value, options, flags);
}
abstract getSession(request: TRequest): Promise<string | null>;
/**
* Read a named cookie from the framework-specific request.
*
* Implementations MUST return the URL-decoded value — the inverse of
* `serializeCookie`'s `encodeURIComponent` on write. Returning the raw
* on-wire bytes will silently break byte-comparison against the original
* value (e.g. PKCE state verification) for any seal containing characters
* that `encodeURIComponent` escapes.
*/
abstract getCookie(request: TRequest, name: string): Promise<string | null>;
async saveSession(
async setCookie(
response: TResponse | undefined,
sessionData: string,
name: string,
value: string,
options: CookieOptions,
): Promise<{ response?: TResponse; headers?: HeadersBag }> {
const header = this.buildSetCookie(sessionData);
const header = this.serializeCookie(name, value, options);
const mutated = await this.applyHeaders(response, { 'Set-Cookie': header });

@@ -88,9 +84,33 @@ return mutated ?? { headers: { 'Set-Cookie': header } };

async clearSession(
response: TResponse,
async clearCookie(
response: TResponse | undefined,
name: string,
options: CookieOptions,
): Promise<{ response?: TResponse; headers?: HeadersBag }> {
const header = this.buildSetCookie('', true);
const header = this.serializeCookie(name, '', options, { expired: true });
const mutated = await this.applyHeaders(response, { 'Set-Cookie': header });
return mutated ?? { headers: { 'Set-Cookie': header } };
}
getSession(request: TRequest): Promise<string | null> {
return this.getCookie(request, this.cookieName);
}
saveSession(
response: TResponse | undefined,
sessionData: string,
): Promise<{ response?: TResponse; headers?: HeadersBag }> {
return this.setCookie(
response,
this.cookieName,
sessionData,
this.cookieOptions,
);
}
clearSession(
response: TResponse | undefined,
): Promise<{ response?: TResponse; headers?: HeadersBag }> {
return this.clearCookie(response, this.cookieName, this.cookieOptions);
}
}

@@ -35,4 +35,4 @@ import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';

return decodeJwt<BaseTokenClaims & TCustomClaims>(token);
} catch (error) {
throw new Error('Invalid token');
} catch (cause) {
throw new Error('Invalid token', { cause });
}

@@ -39,0 +39,0 @@ }

@@ -75,5 +75,53 @@ import type { Impersonator, User } from '@workos-inc/node';

/**
* Map of HTTP response headers.
*
* `Set-Cookie` MUST be represented as `string[]` when multiple values exist
* — adapters must append each entry as its own header, never comma-join.
* A comma-joined `Set-Cookie` string is not a valid single HTTP header.
*/
export type HeadersBag = Record<string, string | string[]>;
export interface SessionStorage<TRequest, TResponse, TOptions = unknown> {
/**
* Read a named cookie from a request.
*
* MUST return the URL-decoded value (the inverse of `setCookie`'s
* `encodeURIComponent` on write). Returning raw on-wire bytes will break
* byte-comparison against the original value — notably PKCE state
* verification — for any seal format that contains characters
* `encodeURIComponent` escapes (`+`, `=`, etc.).
*
* @param request Framework-specific request object.
* @param name Cookie name.
* @returns The URL-decoded cookie value or null if absent.
*/
getCookie(request: TRequest, name: string): Promise<string | null>;
/**
* Write a named cookie to a response.
* @param response Framework-specific response object (or undefined to emit headers only).
* @param name Cookie name.
* @param value Cookie value (will be URL-encoded).
* @param options Per-call cookie options.
*/
setCookie(
response: TResponse | undefined,
name: string,
value: string,
options: CookieOptions,
): Promise<{ response?: TResponse; headers?: HeadersBag }>;
/**
* Clear a named cookie by emitting a Set-Cookie with Max-Age=0.
* @param response Framework-specific response object (or undefined to emit headers only).
* @param name Cookie name.
* @param options Cookie options (must match those used at set time, especially `path`).
*/
clearCookie(
response: TResponse | undefined,
name: string,
options: CookieOptions,
): Promise<{ response?: TResponse; headers?: HeadersBag }>;
/*

@@ -121,2 +169,3 @@ * Extract session data from a request object

password: string;
ttl?: number | undefined;
},

@@ -126,2 +175,17 @@ ) => Promise<T>;

/**
* Result shape returned by `createAuthorization` / `createSignIn` / `createSignUp`.
*
* The verifier cookie is written internally via `SessionStorage.setCookie` — callers
* only need to redirect the browser to `url` and apply any returned `headers`/`response`.
*/
export interface GetAuthorizationUrlResult {
url: string;
}
export type CreateAuthorizationResult<TResponse> = GetAuthorizationUrlResult & {
response?: TResponse;
headers?: HeadersBag;
};
export interface CookieOptions {

@@ -148,3 +212,15 @@ path?: string;

prompt?: 'login' | 'none' | 'consent' | 'select_account';
/** Custom state to pass through the OAuth flow. Returned in handleCallback. */
/**
* Custom state to pass through the OAuth flow. Returned in handleCallback.
*
* Bounded at 2048 UTF-8 bytes. The value is sealed alongside the PKCE
* verifier and stored in the `wos-auth-verifier` cookie; oversized values
* would push the cookie past the per-cookie browser limit (~4 KB) and
* would be silently dropped. Values over the supported limit throw
* `PKCEPayloadTooLargeError` at sign-in time.
*
* Note: total sealed payload size — not just `state` — drives the hard
* failure. Combining a large `returnPathname`, `redirectUri`, or
* `cookieDomain` with near-limit `state` can still overflow.
*/
state?: string;

@@ -154,3 +230,4 @@ }

/**
* Options for getAuthorizationUrl, including screenHint
* Options for `createAuthorization` / `createSignIn` / `createSignUp`,
* including the `screenHint` selector used by the sign-in/sign-up variants.
*/

@@ -157,0 +234,0 @@ export interface GetAuthorizationUrlOptions extends AuthUrlOptions {

@@ -66,2 +66,5 @@ /**

AuthKitError,
OAuthStateMismatchError,
PKCECookieMissingError,
PKCEPayloadTooLargeError,
SessionEncryptionError,

@@ -68,0 +71,0 @@ TokenValidationError,

@@ -0,1 +1,3 @@

import sessionEncryption from '../core/encryption/ironWebcryptoEncryption.js';
import { unsealState } from '../core/pkce/state.js';
import { AuthOperations } from './AuthOperations.js';

@@ -7,3 +9,4 @@

cookieName: 'wos-session',
};
cookiePassword: 'this-is-a-test-password-that-is-32-characters-long!',
} as const;

@@ -46,19 +49,41 @@ const mockUser = {

const mockClient = {
userManagement: {
getLogoutUrl: ({ sessionId, returnTo }: any) =>
`https://api.workos.com/sso/logout?session_id=${sessionId}&return_to=${returnTo || ''}`,
getAuthorizationUrl: ({ clientId, redirectUri, screenHint, state }: any) =>
`https://api.workos.com/sso/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&screen_hint=${screenHint || ''}&state=${state || ''}`,
},
};
function makeAuthUrlClient(capture?: { last?: Record<string, unknown> }) {
return {
pkce: {
generate: async () => ({
codeVerifier: 'generated-verifier-1234567890abcdef',
codeChallenge: 'generated-challenge',
codeChallengeMethod: 'S256',
}),
},
userManagement: {
getLogoutUrl: ({ sessionId, returnTo }: any) =>
`https://api.workos.com/sso/logout?session_id=${sessionId}&return_to=${returnTo || ''}`,
getAuthorizationUrl: (opts: any) => {
if (capture) capture.last = opts;
const params = new URLSearchParams({
client_id: opts.clientId,
redirect_uri: opts.redirectUri,
state: opts.state ?? '',
screen_hint: opts.screenHint ?? '',
code_challenge: opts.codeChallenge ?? '',
code_challenge_method: opts.codeChallengeMethod ?? '',
});
return `https://api.workos.com/sso/authorize?${params.toString()}`;
},
},
};
}
describe('AuthOperations', () => {
let operations: AuthOperations;
let capture: { last?: Record<string, unknown> };
beforeEach(() => {
capture = {};
operations = new AuthOperations(
mockCore as any,
mockClient as any,
makeAuthUrlClient(capture) as any,
mockConfig as any,
sessionEncryption,
);

@@ -153,4 +178,5 @@ });

coreWithSpy as any,
mockClient as any,
makeAuthUrlClient() as any,
mockConfig as any,
sessionEncryption,
);

@@ -199,4 +225,5 @@

coreWithSpy as any,
mockClient as any,
makeAuthUrlClient() as any,
mockConfig as any,
sessionEncryption,
);

@@ -220,85 +247,103 @@

describe('getAuthorizationUrl()', () => {
it('returns WorkOS authorization URL', async () => {
const result = await operations.getAuthorizationUrl();
describe('createAuthorization()', () => {
it('returns url + sealedState + cookieOptions for storage dispatch', async () => {
const result = await operations.createAuthorization();
expect(result).toContain('client_id=test-client-id');
expect(result).toContain('redirect_uri');
expect(typeof result.url).toBe('string');
expect(typeof result.sealedState).toBe('string');
expect(result.sealedState.length).toBeGreaterThan(0);
expect(result.cookieOptions.maxAge).toBe(600);
expect(result.cookieOptions.path).toBe('/');
expect(result.url).toContain('client_id=test-client-id');
});
it('encodes returnPathname in URL-safe base64 state', async () => {
const result = await operations.getAuthorizationUrl({
it('passes the sealedState as the URL state param (identical string)', async () => {
const result = await operations.createAuthorization();
const urlState = decodeURIComponent(
new URL(result.url).searchParams.get('state') ?? '',
);
expect(urlState).toBe(result.sealedState);
});
it('includes codeChallenge + codeChallengeMethod in WorkOS URL', async () => {
await operations.createAuthorization();
expect(capture.last?.codeChallenge).toBe('generated-challenge');
expect(capture.last?.codeChallengeMethod).toBe('S256');
});
it('seals returnPathname into the state blob', async () => {
const result = await operations.createAuthorization({
returnPathname: '/dashboard',
});
const unsealed = await unsealState(
sessionEncryption,
mockConfig.cookiePassword,
result.sealedState,
);
expect(result).toContain('state=');
const stateMatch = result.match(/state=([^&]+)/);
expect(stateMatch).toBeTruthy();
// Decode URL-safe base64: reverse - to +, _ to /
const urlSafeState = stateMatch![1]!;
const standardBase64 = urlSafeState.replace(/-/g, '+').replace(/_/g, '/');
const decoded = JSON.parse(atob(standardBase64));
expect(decoded.returnPathname).toBe('/dashboard');
expect(unsealed.returnPathname).toBe('/dashboard');
expect(unsealed.codeVerifier).toBe('generated-verifier-1234567890abcdef');
});
it('combines internal state with custom user state', async () => {
const result = await operations.getAuthorizationUrl({
returnPathname: '/profile',
it('seals customState into the state blob', async () => {
const result = await operations.createAuthorization({
state: 'my-custom-state',
});
const unsealed = await unsealState(
sessionEncryption,
mockConfig.cookiePassword,
result.sealedState,
);
expect(result).toContain('state=');
const stateMatch = result.match(/state=([^&]+)/);
expect(stateMatch).toBeTruthy();
// State should be in format: internal.userState
const fullState = stateMatch![1]!;
expect(fullState).toContain('.');
const [internal, userState] = fullState.split('.');
expect(userState).toBe('my-custom-state');
// Decode internal part
const standardBase64 = internal!.replace(/-/g, '+').replace(/_/g, '/');
const decoded = JSON.parse(atob(standardBase64));
expect(decoded.returnPathname).toBe('/profile');
expect(unsealed.customState).toBe('my-custom-state');
});
it('passes custom state as-is when no returnPathname', async () => {
const result = await operations.getAuthorizationUrl({
state: 'only-user-state',
it('seals both returnPathname and customState together', async () => {
const result = await operations.createAuthorization({
returnPathname: '/profile',
state: 'custom',
});
const unsealed = await unsealState(
sessionEncryption,
mockConfig.cookiePassword,
result.sealedState,
);
expect(result).toContain('state=');
const stateMatch = result.match(/state=([^&]+)/);
expect(stateMatch).toBeTruthy();
// State should be passed through directly without internal wrapper
expect(stateMatch![1]).toBe('only-user-state');
expect(unsealed.returnPathname).toBe('/profile');
expect(unsealed.customState).toBe('custom');
});
it('includes screenHint when provided', async () => {
const result = await operations.getAuthorizationUrl({
const result = await operations.createAuthorization({
screenHint: 'sign-up',
});
expect(result).toContain('screen_hint=sign-up');
expect(result.url).toContain('screen_hint=sign-up');
});
it('generates a unique nonce per call (concurrent last-flow-wins)', async () => {
const a = await operations.createAuthorization();
const b = await operations.createAuthorization();
expect(a.sealedState).not.toBe(b.sealedState);
});
});
describe('getSignInUrl()', () => {
describe('createSignIn()', () => {
it('returns authorization URL with sign-in hint', async () => {
const result = await operations.getSignInUrl();
const result = await operations.createSignIn();
expect(result).toContain('screen_hint=sign-in');
expect(result.url).toContain('screen_hint=sign-in');
});
});
describe('getSignUpUrl()', () => {
describe('createSignUp()', () => {
it('returns authorization URL with sign-up hint', async () => {
const result = await operations.getSignUpUrl();
const result = await operations.createSignUp();
expect(result).toContain('screen_hint=sign-up');
expect(result.url).toContain('screen_hint=sign-up');
});
});
});
import type { WorkOS } from '@workos-inc/node';
import type { AuthKitCore } from '../core/AuthKitCore.js';
import type { AuthKitConfig } from '../core/config/types.js';
import {
type GeneratedAuthorizationUrl,
generateAuthorizationUrl,
} from '../core/pkce/generateAuthorizationUrl.js';
import type {

@@ -8,2 +12,3 @@ AuthResult,

Session,
SessionEncryption,
} from '../core/session/types.js';

@@ -27,7 +32,14 @@

private config: AuthKitConfig;
private encryption: SessionEncryption;
constructor(core: AuthKitCore, client: WorkOS, config: AuthKitConfig) {
constructor(
core: AuthKitCore,
client: WorkOS,
config: AuthKitConfig,
encryption: SessionEncryption,
) {
this.core = core;
this.client = client;
this.config = config;
this.encryption = encryption;
}

@@ -120,37 +132,19 @@

/**
* Get authorization URL for WorkOS authentication.
* Create a PKCE-bound WorkOS authorization URL.
*
* State encoding format: `{internal}.{userState}` where internal is URL-safe
* base64 encoded JSON containing returnPathname. This allows customers to
* pass their own state through the OAuth flow.
* Returns the URL, the sealed state blob (to be used as both the cookie
* value and — already present in the URL — the OAuth `state` param), and
* the cookie options the verifier cookie should be written with.
* AuthService dispatches the write via `SessionStorage.setCookie`.
*
* @param options - Authorization URL options (returnPathname, screenHint, state, etc.)
* @returns The authorization URL
* @param options - returnPathname, screenHint, custom state, redirectUri, etc.
*/
async getAuthorizationUrl(
async createAuthorization(
options: GetAuthorizationUrlOptions = {},
): Promise<string> {
// Build the combined state parameter (matches authkit-nextjs format)
const internalState = options.returnPathname
? btoa(JSON.stringify({ returnPathname: options.returnPathname }))
.replace(/\+/g, '-')
.replace(/\//g, '_')
: null;
// If both internal and custom state, combine as internal.custom
// Otherwise use whichever is provided
const state =
internalState && options.state
? `${internalState}.${options.state}`
: internalState || options.state || undefined;
return this.client.userManagement.getAuthorizationUrl({
provider: 'authkit',
redirectUri: options.redirectUri ?? this.config.redirectUri,
screenHint: options.screenHint,
organizationId: options.organizationId,
loginHint: options.loginHint,
prompt: options.prompt,
clientId: this.config.clientId,
state,
): Promise<GeneratedAuthorizationUrl> {
return generateAuthorizationUrl({
client: this.client,
config: this.config,
encryption: this.encryption,
options,
});

@@ -160,8 +154,8 @@ }

/**
* Convenience method: Get sign-in URL.
* Convenience method: Create sign-in authorization URL.
*/
async getSignInUrl(
async createSignIn(
options: Omit<GetAuthorizationUrlOptions, 'screenHint'> = {},
): Promise<string> {
return this.getAuthorizationUrl({
): Promise<GeneratedAuthorizationUrl> {
return this.createAuthorization({
...options,

@@ -173,8 +167,8 @@ screenHint: 'sign-in',

/**
* Convenience method: Get sign-up URL.
* Convenience method: Create sign-up authorization URL.
*/
async getSignUpUrl(
async createSignUp(
options: Omit<GetAuthorizationUrlOptions, 'screenHint'> = {},
): Promise<string> {
return this.getAuthorizationUrl({
): Promise<GeneratedAuthorizationUrl> {
return this.createAuthorization({
...options,

@@ -181,0 +175,0 @@ screenHint: 'sign-up',

@@ -0,1 +1,6 @@

import sessionEncryption from '../core/encryption/ironWebcryptoEncryption.js';
import {
OAuthStateMismatchError,
PKCECookieMissingError,
} from '../core/errors.js';
import { AuthService } from './AuthService.js';

@@ -6,4 +11,4 @@

apiKey: 'test-api-key',
redirectUri: 'http://localhost:3000/callback',
cookiePassword: 'test-password-that-is-32-chars-long!!',
redirectUri: 'https://app.example.com/callback',
cookiePassword: 'this-is-a-test-password-that-is-32-characters-long!',
cookieName: 'wos-session',

@@ -27,28 +32,83 @@ };

const mockStorage = {
getSession: async () => 'encrypted-session-data',
saveSession: async () => ({ response: 'updated-response' }),
clearSession: async () => ({
response: 'cleared-response',
headers: { 'Set-Cookie': 'wos-session=; Path=/; Max-Age=0' },
}),
};
const mockClient = {
userManagement: {
getJwksUrl: () => 'https://api.workos.com/sso/jwks/test-client-id',
getAuthorizationUrl: ({ screenHint }: any) =>
`https://api.workos.com/sso/authorize?screen_hint=${screenHint || ''}`,
authenticateWithCode: async ({ code }: any) => ({
accessToken: `access-${code}`,
refreshToken: `refresh-${code}`,
user: mockUser,
impersonator: undefined,
/**
* Storage mock that remembers the last value passed to setCookie so
* handleCallback's getCookie read sees what sign-in wrote. Exposes the most
* recent options per cookie name for path/scope assertions.
*/
function makeStorage(initialSession: string | null = 'encrypted-session-data') {
const cookies = new Map<string, string>();
const lastSetOptions = new Map<string, any>();
const lastClearOptions = new Map<string, any>();
return {
cookies,
lastSetOptions,
lastClearOptions,
getSession: async () => initialSession,
getCookie: async (_req: any, name: string) => cookies.get(name) ?? null,
setCookie: async (_res: any, name: string, value: string, options: any) => {
cookies.set(name, value);
lastSetOptions.set(name, options);
return {
headers: {
'Set-Cookie': `${name}=${value}; Path=${options.path}; Max-Age=${options.maxAge}`,
},
};
},
clearCookie: async (_res: any, name: string, options: any) => {
lastClearOptions.set(name, options);
cookies.delete(name);
return {
headers: {
'Set-Cookie': `${name}=; Path=${options.path}; Max-Age=0`,
},
};
},
saveSession: async () => ({
response: 'updated-response',
headers: { 'Set-Cookie': 'wos-session=encrypted; Path=/; Max-Age=3600' },
}),
getLogoutUrl: ({ sessionId }: any) =>
`https://api.workos.com/sso/logout?session_id=${sessionId}`,
},
};
clearSession: async () => ({
response: 'cleared-response',
headers: { 'Set-Cookie': 'wos-session=; Path=/; Max-Age=0' },
}),
};
}
const mockEncryption = {
const testVerifier = 'test-verifier-abcdefghijklmnopqrstuvwxyz1234567890';
function makeClient(capture?: { authCall?: Record<string, unknown> }) {
return {
userManagement: {
getJwksUrl: () => 'https://api.workos.com/sso/jwks/test-client-id',
getAuthorizationUrl: (opts: any) => {
const params = new URLSearchParams({
state: opts.state ?? '',
screen_hint: opts.screenHint ?? '',
});
return `https://api.workos.com/sso/authorize?${params.toString()}`;
},
authenticateWithCode: async (opts: any) => {
if (capture) capture.authCall = opts;
return {
accessToken: `access-${opts.code}`,
refreshToken: `refresh-${opts.code}`,
user: mockUser,
impersonator: undefined,
};
},
getLogoutUrl: ({ sessionId }: any) =>
`https://api.workos.com/sso/logout?session_id=${sessionId}`,
},
pkce: {
generate: async () => ({
codeVerifier: testVerifier,
codeChallenge: 'test-challenge',
codeChallengeMethod: 'S256',
}),
},
};
}
// For withAuth / getSession tests — returns a decrypted Session-shaped blob.
const mockEncryptionSessionShape = {
sealData: async () => 'encrypted-session-data',

@@ -65,9 +125,11 @@ unsealData: async () => ({

let service: AuthService<any, any>;
let storage: ReturnType<typeof makeStorage>;
beforeEach(() => {
storage = makeStorage();
service = new AuthService(
mockConfig as any,
mockStorage as any,
mockClient as any,
mockEncryption as any,
storage as any,
makeClient() as any,
mockEncryptionSessionShape as any,
);

@@ -84,11 +146,8 @@ });

it('returns null user when no session exists', async () => {
const emptyStorage = {
...mockStorage,
getSession: async () => null,
};
const emptyStorage = makeStorage(null);
const testService = new AuthService(
mockConfig as any,
emptyStorage as any,
mockClient as any,
mockEncryption as any,
makeClient() as any,
mockEncryptionSessionShape as any,
);

@@ -111,4 +170,4 @@

mockConfig as any,
mockStorage as any,
mockClient as any,
storage as any,
makeClient() as any,
failingEncryption as any,

@@ -132,11 +191,8 @@ );

it('returns null when no session exists', async () => {
const emptyStorage = {
...mockStorage,
getSession: async () => null,
};
const emptyStorage = makeStorage(null);
const testService = new AuthService(
mockConfig as any,
emptyStorage as any,
mockClient as any,
mockEncryption as any,
makeClient() as any,
mockEncryptionSessionShape as any,
);

@@ -176,26 +232,89 @@

describe('getAuthorizationUrl()', () => {
it('delegates to operations', async () => {
const result = await service.getAuthorizationUrl();
describe('createAuthorization()', () => {
it('returns url and writes the verifier cookie via storage.setCookie', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
expect(result).toContain('authorize');
const result = await realService.createAuthorization('res');
expect(result.url).toContain('authorize');
expect(realStorage.cookies.get('wos-auth-verifier')).toBeTruthy();
expect(result.headers?.['Set-Cookie']).toContain('wos-auth-verifier=');
expect(realStorage.lastSetOptions.get('wos-auth-verifier')?.path).toBe(
'/',
);
});
});
describe('getSignInUrl()', () => {
it('returns sign-in URL', async () => {
const result = await service.getSignInUrl();
describe('createSignIn()', () => {
it('returns sign-in URL and writes the verifier cookie', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
expect(result).toContain('screen_hint=sign-in');
const result = await realService.createSignIn('res');
expect(result.url).toContain('screen_hint=sign-in');
expect(realStorage.cookies.get('wos-auth-verifier')).toBeTruthy();
});
});
describe('getSignUpUrl()', () => {
it('returns sign-up URL', async () => {
const result = await service.getSignUpUrl();
describe('createSignUp()', () => {
it('returns sign-up URL and writes the verifier cookie', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
expect(result).toContain('screen_hint=sign-up');
const result = await realService.createSignUp('res');
expect(result.url).toContain('screen_hint=sign-up');
expect(realStorage.cookies.get('wos-auth-verifier')).toBeTruthy();
});
});
describe('clearPendingVerifier()', () => {
it('emits a delete cookie with Path=/', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.clearPendingVerifier('res');
expect(realStorage.lastClearOptions.get('wos-auth-verifier')?.path).toBe(
'/',
);
});
it('accepts undefined response for headers-only adapters', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
const result = await realService.clearPendingVerifier(undefined);
expect(result.headers?.['Set-Cookie']).toContain('wos-auth-verifier=');
});
});
describe('getWorkOS()', () => {

@@ -205,3 +324,4 @@ it('returns WorkOS client', () => {

expect(result).toBe(mockClient);
expect(result).toBeDefined();
expect(typeof (result as any).userManagement.getJwksUrl).toBe('function');
});

@@ -211,66 +331,463 @@ });

describe('handleCallback()', () => {
it('authenticates and creates session', async () => {
const result = await service.handleCallback('request', 'response', {
code: 'auth-code-123',
it('round-trips through createSignIn → handleCallback', async () => {
const capture: { authCall?: Record<string, unknown> } = {};
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient(capture) as any,
sessionEncryption,
);
await realService.createAuthorization('res', {
returnPathname: '/dashboard',
state: 'my.custom.state',
});
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
expect(result.authResponse.accessToken).toBe('access-auth-code-123');
expect(result.returnPathname).toBe('/');
expect(result.response).toBe('updated-response');
const result = await realService.handleCallback('req', 'res', {
code: 'auth-code-xyz',
state: sealedState,
});
expect(result.authResponse.accessToken).toBe('access-auth-code-xyz');
expect(result.returnPathname).toBe('/dashboard');
expect(result.state).toBe('my.custom.state');
expect(capture.authCall?.codeVerifier).toBe(testVerifier);
});
it('decodes returnPathname from state', async () => {
const state = btoa(JSON.stringify({ returnPathname: '/dashboard' }));
it('returns both session and verifier-delete Set-Cookie as a string[]', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
const result = await service.handleCallback('request', 'response', {
code: 'auth-code-123',
state,
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
const result = await realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
});
expect(result.returnPathname).toBe('/dashboard');
const setCookie = result.headers?.['Set-Cookie'];
expect(Array.isArray(setCookie)).toBe(true);
expect(setCookie).toHaveLength(2);
expect(
(setCookie as string[]).some(c => c.startsWith('wos-session=')),
).toBe(true);
expect(
(setCookie as string[]).some(c => c.startsWith('wos-auth-verifier=')),
).toBe(true);
expect(
(setCookie as string[]).find(c => c.startsWith('wos-auth-verifier=')),
).toContain('Max-Age=0');
});
it('treats invalid state as custom state', async () => {
const result = await service.handleCallback('request', 'response', {
code: 'auth-code-123',
state: 'invalid-state',
it('merges lowercase set-cookie headers into an array (case-insensitive)', async () => {
// Regression: adapters that normalize through Headers objects emit
// lowercase `set-cookie`.
const realStorage = makeStorage();
const lowerStorage = {
...realStorage,
setCookie: async (
_res: any,
name: string,
value: string,
options: any,
) => {
realStorage.cookies.set(name, value);
realStorage.lastSetOptions.set(name, options);
return {
headers: {
'set-cookie': `${name}=${value}; Path=${options.path}; Max-Age=${options.maxAge}`,
},
};
},
clearCookie: async (_res: any, name: string, options: any) => {
realStorage.lastClearOptions.set(name, options);
realStorage.cookies.delete(name);
return {
headers: {
'set-cookie': `${name}=; Path=${options.path}; Max-Age=0`,
},
};
},
saveSession: async () => ({
response: 'updated-response',
headers: {
'set-cookie': 'wos-session=encrypted; Path=/; Max-Age=3600',
},
}),
};
const realService = new AuthService(
mockConfig as any,
lowerStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
const result = await realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
});
const setCookie = result.headers?.['set-cookie'];
expect(Array.isArray(setCookie)).toBe(true);
expect(setCookie).toHaveLength(2);
expect(
(setCookie as string[]).some(c => c.startsWith('wos-session=')),
).toBe(true);
expect(
(setCookie as string[]).some(c => c.startsWith('wos-auth-verifier=')),
).toBe(true);
});
it('emits the verifier-delete cookie with Path=/', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
await realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
});
expect(realStorage.lastClearOptions.get('wos-auth-verifier')?.path).toBe(
'/',
);
});
it('throws OAuthStateMismatchError when storage cookie differs from url state', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
// Tamper the stored cookie after sign-in
realStorage.cookies.set(
'wos-auth-verifier',
sealedState.slice(0, -2) + 'XX',
);
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow(OAuthStateMismatchError);
});
it('throws PKCECookieMissingError when storage has no verifier cookie', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
realStorage.cookies.delete('wos-auth-verifier');
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow(PKCECookieMissingError);
});
it('throws OAuthStateMismatchError when state is undefined', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: undefined,
}),
).rejects.toThrow(OAuthStateMismatchError);
});
it('defaults returnPathname to "/" when the sealed state omits it', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
const result = await realService.handleCallback('req', 'res', {
code: 'c',
state: sealedState,
});
expect(result.returnPathname).toBe('/');
expect(result.state).toBe('invalid-state');
});
it('parses new format with internal.userState', async () => {
// Create URL-safe base64 internal state
const internal = btoa(JSON.stringify({ returnPathname: '/profile' }))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
const state = `${internal}.my-custom-state`;
it('best-effort clears the verifier cookie on OAuthStateMismatchError', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
const result = await service.handleCallback('request', 'response', {
code: 'auth-code-123',
state,
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
realStorage.cookies.set(
'wos-auth-verifier',
sealedState.slice(0, -2) + 'XX',
);
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow(OAuthStateMismatchError);
expect(realStorage.lastClearOptions.get('wos-auth-verifier')?.path).toBe(
'/',
);
});
it('emits a scheme-agnostic (secure=false, sameSite=lax) delete on pre-unseal failure', async () => {
// Covers the end-to-end case that motivates the schemeAgnostic flag:
// sign-in used an http:// redirectUri override (secure=false), then
// the callback hits a pre-unseal error (state mismatch) — we don't
// know the override at that point, so the fallback delete must drop
// Secure so the browser accepts the Set-Cookie over http://.
const realStorage = makeStorage();
const realService = new AuthService(
{ ...mockConfig, cookieSameSite: 'lax' } as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res', {
redirectUri: 'http://localhost:3000/callback',
});
// Confirm the original set was secure=false (the scenario we're
// proving we can still clean up).
expect(realStorage.lastSetOptions.get('wos-auth-verifier')?.secure).toBe(
false,
);
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
realStorage.cookies.set(
'wos-auth-verifier',
sealedState.slice(0, -2) + 'XX',
);
expect(result.returnPathname).toBe('/profile');
expect(result.state).toBe('my-custom-state');
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow(OAuthStateMismatchError);
const clearOpts = realStorage.lastClearOptions.get('wos-auth-verifier');
expect(clearOpts?.secure).toBe(false);
expect(clearOpts?.sameSite).toBe('lax');
expect(clearOpts?.path).toBe('/');
});
it('handles user state with dots', async () => {
const internal = btoa(JSON.stringify({ returnPathname: '/dashboard' }))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
const state = `${internal}.user.state.with.dots`;
it('keeps Secure on scheme-agnostic delete when sameSite=none (Secure is required)', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
{ ...mockConfig, cookieSameSite: 'none' } as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
const result = await service.handleCallback('request', 'response', {
code: 'auth-code-123',
state,
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
realStorage.cookies.set(
'wos-auth-verifier',
sealedState.slice(0, -2) + 'XX',
);
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow(OAuthStateMismatchError);
const clearOpts = realStorage.lastClearOptions.get('wos-auth-verifier');
expect(clearOpts?.secure).toBe(true);
expect(clearOpts?.sameSite).toBe('none');
});
it('best-effort clears the verifier cookie on PKCECookieMissingError', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
realStorage.cookies.delete('wos-auth-verifier');
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow(PKCECookieMissingError);
expect(realStorage.lastClearOptions.get('wos-auth-verifier')?.path).toBe(
'/',
);
});
it('best-effort clears the verifier cookie when authenticateWithCode throws', async () => {
const realStorage = makeStorage();
const throwingClient = makeClient();
throwingClient.userManagement.authenticateWithCode = async () => {
throw new Error('WorkOS exchange failed');
};
const realService = new AuthService(
mockConfig as any,
realStorage as any,
throwingClient as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow('WorkOS exchange failed');
expect(realStorage.lastClearOptions.get('wos-auth-verifier')?.path).toBe(
'/',
);
});
it('best-effort clears the verifier cookie when saveSession throws', async () => {
const realStorage = makeStorage();
realStorage.saveSession = async () => {
throw new Error('storage write failed');
};
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow('storage write failed');
expect(realStorage.lastClearOptions.get('wos-auth-verifier')?.path).toBe(
'/',
);
});
it('swallows clearCookie errors so the original failure propagates', async () => {
const realStorage = makeStorage();
const throwingClient = makeClient();
throwingClient.userManagement.authenticateWithCode = async () => {
throw new Error('original exchange failure');
};
realStorage.clearCookie = async () => {
throw new Error('clearCookie blew up');
};
const realService = new AuthService(
mockConfig as any,
realStorage as any,
throwingClient as any,
sessionEncryption,
);
await realService.createAuthorization('res');
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
await expect(
realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
}),
).rejects.toThrow('original exchange failure');
});
it('round-trips redirectUri override through the sealed state into the clear cookie', async () => {
const realStorage = makeStorage();
const realService = new AuthService(
mockConfig as any,
realStorage as any,
makeClient() as any,
sessionEncryption,
);
// Override with an http:// URL so the `secure` attribute differs from
// the https:// default — proves handleCallback used the override.
await realService.createAuthorization('res', {
redirectUri: 'http://localhost:3000/callback',
});
const sealedState = realStorage.cookies.get('wos-auth-verifier')!;
expect(result.returnPathname).toBe('/dashboard');
expect(result.state).toBe('user.state.with.dots');
expect(realStorage.lastSetOptions.get('wos-auth-verifier')?.secure).toBe(
false,
);
await realService.handleCallback('req', 'res', {
code: 'code',
state: sealedState,
});
expect(
realStorage.lastClearOptions.get('wos-auth-verifier')?.secure,
).toBe(false);
});
});
});
import type { WorkOS } from '@workos-inc/node';
import { AuthKitCore } from '../core/AuthKitCore.js';
import type { AuthKitConfig } from '../core/config/types.js';
import {
getPKCECookieOptions,
PKCE_COOKIE_NAME,
} from '../core/pkce/cookieOptions.js';
import { AuthOperations } from '../operations/AuthOperations.js';
import type {
AuthResult,
CreateAuthorizationResult,
CustomClaims,

@@ -16,2 +21,35 @@ GetAuthorizationUrlOptions,

/**
* Merge two `HeadersBag` values. `Set-Cookie` matching is case-insensitive;
* existing key casing is preserved. Multiple `Set-Cookie` values are
* concatenated into a `string[]`. Other keys are shallow-merged (second wins).
*/
function mergeHeaderBags(
a: HeadersBag | undefined,
b: HeadersBag | undefined,
): HeadersBag | undefined {
if (!a) return b;
if (!b) return a;
const merged: HeadersBag = { ...a };
let setCookieKey = Object.keys(merged).find(
k => k.toLowerCase() === 'set-cookie',
);
for (const [key, value] of Object.entries(b)) {
if (key.toLowerCase() !== 'set-cookie') {
merged[key] = value;
continue;
}
if (!setCookieKey) {
merged[key] = value;
setCookieKey = key;
continue;
}
const left = merged[setCookieKey]!;
const leftArr = Array.isArray(left) ? left : [left];
const rightArr = Array.isArray(value) ? value : [value];
merged[setCookieKey] = [...leftArr, ...rightArr];
}
return merged;
}
/**
* Framework-agnostic authentication service.

@@ -27,3 +65,3 @@ *

* - `handleCallback()` - Process OAuth callback
* - `signOut()`, `getSignInUrl()`, etc. - Delegate to AuthOperations
* - `signOut()`, `createSignIn()`, etc. - Delegate to AuthOperations
*

@@ -49,3 +87,3 @@ * **Used by:** @workos/authkit-tanstack-react-start

this.core = new AuthKitCore(config, client, encryption);
this.operations = new AuthOperations(this.core, client, config);
this.operations = new AuthOperations(this.core, client, config, encryption);
}

@@ -185,27 +223,74 @@

/**
* Get authorization URL - delegates to AuthOperations.
* Create an authorization URL and write the PKCE verifier cookie.
*
* @param response - Framework-specific response object (may be undefined —
* adapters that mutate responses in-place will get back a mutated copy).
* @param options - screenHint, returnPathname, custom state, redirectUri, etc.
* @returns The URL to redirect the browser to, plus storage's
* `{ response?, headers? }` carrying the verifier `Set-Cookie`.
*/
async getAuthorizationUrl(options: GetAuthorizationUrlOptions = {}) {
return this.operations.getAuthorizationUrl(options);
async createAuthorization(
response: TResponse | undefined,
options: GetAuthorizationUrlOptions = {},
): Promise<CreateAuthorizationResult<TResponse>> {
const { url, sealedState, cookieOptions } =
await this.operations.createAuthorization(options);
const write = await this.storage.setCookie(
response,
PKCE_COOKIE_NAME,
sealedState,
cookieOptions,
);
return { url, ...write };
}
/**
* Convenience: Get sign-in URL.
* Convenience: Create sign-in URL and write the PKCE verifier cookie.
*/
async getSignInUrl(
async createSignIn(
response: TResponse | undefined,
options: Omit<GetAuthorizationUrlOptions, 'screenHint'> = {},
) {
return this.operations.getSignInUrl(options);
): Promise<CreateAuthorizationResult<TResponse>> {
return this.createAuthorization(response, {
...options,
screenHint: 'sign-in',
});
}
/**
* Convenience: Get sign-up URL.
* Convenience: Create sign-up URL and write the PKCE verifier cookie.
*/
async getSignUpUrl(
async createSignUp(
response: TResponse | undefined,
options: Omit<GetAuthorizationUrlOptions, 'screenHint'> = {},
) {
return this.operations.getSignUpUrl(options);
): Promise<CreateAuthorizationResult<TResponse>> {
return this.createAuthorization(response, {
...options,
screenHint: 'sign-up',
});
}
/**
* Emit a `Set-Cookie` header that clears the PKCE verifier cookie.
*
* Use on any exit path where a sign-in was started (verifier cookie
* written) but `handleCallback` will not run to clear it — OAuth error
* responses, missing `code`, early bail-outs.
*
* Pass `options.redirectUri` on requests that used a per-request
* `redirectUri` override at sign-in time, so the delete cookie's computed
* attributes (notably `secure`) match what was originally set.
*/
async clearPendingVerifier(
response: TResponse | undefined,
options?: Pick<GetAuthorizationUrlOptions, 'redirectUri'>,
): Promise<{ response?: TResponse; headers?: HeadersBag }> {
return this.storage.clearCookie(
response,
PKCE_COOKIE_NAME,
getPKCECookieOptions(this.config, options?.redirectUri),
);
}
/**
* Get the WorkOS client instance.

@@ -220,74 +305,114 @@ * Useful for direct API calls not covered by AuthKit.

* Handle OAuth callback.
* This creates a new session after successful authentication.
*
* @param request - Framework-specific request (not currently used)
* @param response - Framework-specific response
* @param options - OAuth callback options (code, state)
* @returns Updated response, return pathname, and auth response
* Reads the verifier cookie via storage, verifies it against the URL
* `state`, exchanges the code, saves the session, and — on success —
* also emits a verifier-delete `Set-Cookie` so `HeadersBag['Set-Cookie']`
* carries both entries as a `string[]`. Adapters MUST append each
* `Set-Cookie` as its own header (never comma-join).
*
* Error-path cleanup: if any step after the cookie read throws
* (state mismatch, tampered seal, code-exchange failure, session-save
* failure), `handleCallback` attempts a best-effort `clearCookie` on the
* response before rethrowing so the verifier does not linger for its full
* 600s TTL. Response-mutating adapters get the delete `Set-Cookie`
* applied in place; headers-only adapters should still call
* `clearPendingVerifier` in their catch block to capture the headers bag.
*/
async handleCallback(
_request: TRequest,
request: TRequest,
response: TResponse,
options: { code: string; state?: string },
options: {
code: string;
state: string | undefined;
},
) {
// Authenticate with WorkOS using the OAuth code
const authResponse = await this.client.userManagement.authenticateWithCode({
code: options.code,
clientId: this.config.clientId,
});
const cookieValue = await this.storage.getCookie(request, PKCE_COOKIE_NAME);
// Create and save the new session
const session: Session = {
accessToken: authResponse.accessToken,
refreshToken: authResponse.refreshToken,
user: authResponse.user,
impersonator: authResponse.impersonator,
};
let unsealed;
try {
unsealed = await this.core.verifyCallbackState({
stateFromUrl: options.state,
cookieValue: cookieValue ?? undefined,
});
} catch (err) {
// Pre-unseal failure — we don't know the per-request redirectUri
// override, so emit a scheme-agnostic delete: same (name, domain,
// path) tuple as the original set (what the browser uses to match
// for replacement), with `Secure` dropped in the `sameSite: 'lax'`
// case so the Set-Cookie is accepted over http:// callbacks too.
// The `sameSite: 'none'` case already forces Secure on both set and
// clear, so there's no scheme-mismatch risk there.
await this.bestEffortClearVerifier(response, undefined, {
schemeAgnostic: true,
});
throw err;
}
const encryptedSession = await this.core.encryptSession(session);
const { response: updatedResponse, headers } = await this.saveSession(
response,
encryptedSession,
);
const { codeVerifier, returnPathname, customState, redirectUri } = unsealed;
const clearOptions = getPKCECookieOptions(this.config, redirectUri);
// Parse state: format is `{internal}.{userState}` or legacy `{base64JSON}`
let returnPathname = '/';
let customState: string | undefined;
try {
const authResponse =
await this.client.userManagement.authenticateWithCode({
code: options.code,
clientId: this.config.clientId,
codeVerifier,
});
if (options.state) {
if (options.state.includes('.')) {
const [internal, ...rest] = options.state.split('.');
customState = rest.join('.'); // Rejoin in case userState contains dots
try {
// Reverse URL-safe base64 encoding and decode
const decoded = (internal ?? '')
.replace(/-/g, '+')
.replace(/_/g, '/');
const parsed = JSON.parse(atob(decoded));
returnPathname = parsed.returnPathname || '/';
} catch {
// Malformed internal state, use default
}
} else {
try {
const parsed = JSON.parse(atob(options.state));
if (parsed.returnPathname) {
returnPathname = parsed.returnPathname;
} else {
customState = options.state;
}
} catch {
customState = options.state;
}
}
const session: Session = {
accessToken: authResponse.accessToken,
refreshToken: authResponse.refreshToken,
user: authResponse.user,
impersonator: authResponse.impersonator,
};
const encryptedSession = await this.core.encryptSession(session);
const save = await this.storage.saveSession(response, encryptedSession);
const clear = await this.storage.clearCookie(
save.response ?? response,
PKCE_COOKIE_NAME,
clearOptions,
);
return {
response: clear.response ?? save.response,
headers: mergeHeaderBags(save.headers, clear.headers),
returnPathname: returnPathname ?? '/',
state: customState,
authResponse,
};
} catch (err) {
await this.bestEffortClearVerifier(response, redirectUri);
throw err;
}
}
return {
response: updatedResponse,
headers,
returnPathname,
state: customState,
authResponse,
};
/**
* Best-effort verifier cleanup on a `handleCallback` error path.
*
* Mutates the response in place for response-mutating adapters.
* Swallows storage errors — cleanup must never mask the original failure.
* The original error is always rethrown by the caller.
*
* `schemeAgnostic` (pre-unseal failures only): drops the `Secure` attribute
* on the delete when `sameSite === 'lax'` so the browser accepts the
* `Set-Cookie` over http:// dev callbacks. Cookie replacement matches on
* (name, domain, path), not `Secure`, so the tuple-matched original cookie
* is still cleared regardless of its original `Secure` flag.
*/
private async bestEffortClearVerifier(
response: TResponse | undefined,
redirectUri: string | undefined,
{ schemeAgnostic = false }: { schemeAgnostic?: boolean } = {},
): Promise<void> {
const options = getPKCECookieOptions(this.config, redirectUri);
if (schemeAgnostic && options.sameSite === 'lax') {
options.secure = false;
}
try {
await this.storage.clearCookie(response, PKCE_COOKIE_NAME, options);
} catch {
// Swallow: cleanup is opportunistic; callers get the original error.
}
}
}

@@ -43,5 +43,6 @@ import { createAuthService } from './factory.js';

expect(typeof service.signOut).toBe('function');
expect(typeof service.getAuthorizationUrl).toBe('function');
expect(typeof service.getSignInUrl).toBe('function');
expect(typeof service.getSignUpUrl).toBe('function');
expect(typeof service.createAuthorization).toBe('function');
expect(typeof service.createSignIn).toBe('function');
expect(typeof service.createSignUp).toBe('function');
expect(typeof service.clearPendingVerifier).toBe('function');
expect(typeof service.getWorkOS).toBe('function');

@@ -48,0 +49,0 @@ expect(typeof service.handleCallback).toBe('function');

@@ -75,5 +75,8 @@ import type { WorkOS } from '@workos-inc/node';

getService().refreshSession(session, organizationId),
getAuthorizationUrl: opts => getService().getAuthorizationUrl(opts),
getSignInUrl: opts => getService().getSignInUrl(opts),
getSignUpUrl: opts => getService().getSignUpUrl(opts),
createAuthorization: (response, opts) =>
getService().createAuthorization(response, opts),
createSignIn: (response, opts) => getService().createSignIn(response, opts),
createSignUp: (response, opts) => getService().createSignUp(response, opts),
clearPendingVerifier: (response, opts) =>
getService().clearPendingVerifier(response, opts),
getWorkOS: () => getService().getWorkOS(),

@@ -80,0 +83,0 @@ handleCallback: (request, response, opts) =>

@@ -22,1 +22,23 @@ /**

}
/**
* Constant-time byte-array equality.
*
* Used for comparing security-sensitive values (OAuth state, tokens, MACs)
* where early-return on mismatch would leak timing information about how
* many leading bytes matched.
*
* Runtime-portable: no `node:crypto`, no `Buffer` — works in Node, browsers,
* and edge runtimes. Callers produce `Uint8Array`s via `TextEncoder`.
*
* Contract:
* - Returns false immediately if lengths differ (length is not secret).
* - Once past the length gate, inspects EVERY byte regardless of mismatches.
* - No branches whose count depends on byte values.
*/
export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) diff |= a[i]! ^ b[i]!;
return diff === 0;
}