Conversation
Every sign-in now generates a PKCE verifier, seals it alongside
returnPathname + customState + a nonce into a single blob, and uses that
blob as BOTH the OAuth `state` query param AND the value of a short-lived
`wos-auth-verifier` cookie. On callback, the adapter passes the cookie
value; the library byte-compares it against the URL state before
decrypting and then threads `codeVerifier` into `authenticateWithCode`.
A leaked state value on its own can no longer be used to complete a
session hijack.
Breaking changes (minor bump, pre-1.0):
- `getAuthorizationUrl` / `getSignInUrl` / `getSignUpUrl` now return
`{ url, sealedState, cookieOptions }` instead of a bare URL string.
- `handleCallback` requires a new `cookieValue: string | undefined` field.
- `SessionEncryption.unsealData` interface gains optional `ttl`.
- The `{internal}.{userState}` split-state format is removed; customState
now lives inside the sealed blob and round-trips byte-identically.
New module: `src/core/pkce/` — constants, state schema, seal/unseal,
cookie options, and orchestrator. New errors: `OAuthStateMismatchError`,
`PKCECookieMissingError`. New AuthService delegators:
`getPKCECookieOptions`, `buildPKCEDeleteCookieHeader`.
Coverage lifted to 90/82/85/90 (lines/branches/functions/statements) with
new tests across pkce.spec, state.spec, cookieOptions.spec, and extended
ironWebcryptoEncryption.spec + AuthService.spec + AuthOperations.spec.
- `getPKCECookieOptions`: collapse redundant three-way ternary. Since `configuredSameSite ?? 'lax'` already defaults non-'none' values to 'lax', the explicit `strict → lax` branch was dead. Replaced with a single `none ? 'none' : 'lax'` check and an inline comment documenting the `strict → lax` downgrade intent. - `AuthService.getPKCECookieOptions`: strip JSDoc paragraph that restated what the method name and one-line delegator body already convey. No behavior change. 194/194 tests pass.
…-level TTL Two defense-in-depth fixes for the PKCE verifier cookie: 1. Scope `wos-auth-verifier` cookie `Path` to the redirect URI's pathname instead of `/`. Multiple AuthKit apps on the same host under different subpaths no longer collide on a shared cookie slot — starting sign-in in one app no longer overwrites another's in-flight verifier. Falls back to `/` if the redirect URI is missing or invalid. 2. Embed `issuedAt` in the sealed PKCE state payload and enforce age in `unsealState` independently of the `SessionEncryption` adapter's `ttl` handling. Custom encryptors may legally ignore the optional `ttl` option; the payload-level check is the authoritative 600s guard. No breaking changes to the public surface: `PKCECookieOptions.path` is widened from the literal `'/'` to `string`; `PKCEState` gains `issuedAt` (consumers destructure the fields they need).
Strict `issuedAt > Date.now()` rejection broke multi-node deployments
where sign-in on node A and callback on node B could see node B's clock
slightly behind, producing a negative `ageMs` and a spurious
`SessionEncryptionError('PKCE state expired')`.
Allow up to 60s of negative skew — matches iron-webcrypto's default
`timestampSkewSec`. Payloads meaningfully in the future (> 60s) still
fail closed.
There was a problem hiding this comment.
Pull request overview
This PR hardens the OAuth flow by binding the OAuth state parameter to a short-lived PKCE verifier cookie, so callbacks must present both the URL state and the matching cookie before the code exchange proceeds.
Changes:
- Introduces PKCE state sealing/unsealing with TTL enforcement and new typed errors for state mismatch / missing cookie.
- Updates URL-generation APIs to return
{ url, sealedState, cookieOptions }and updates callback handling to requirecookieValue. - Adds PKCE cookie helpers (options + delete header builder) and updates public exports + docs/tests accordingly.
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/service/factory.ts | Exposes new PKCE cookie helper methods on the service factory output. |
| src/service/factory.spec.ts | Verifies the factory output includes the new helper functions. |
| src/service/AuthService.ts | Adopts the new URL-generation result shape and enforces cookie-bound callback verification. |
| src/service/AuthService.spec.ts | Updates tests for the new URL result shape + cookie-bound callback flow and errors. |
| src/operations/AuthOperations.ts | Switches authorization URL generation to PKCE-backed generateAuthorizationUrl and returns the new triple shape. |
| src/operations/AuthOperations.spec.ts | Adds assertions for sealed state, cookie options, and PKCE params in the generated URL. |
| src/index.ts | Exports new PKCE helpers/constants and new error types as part of the public API. |
| src/core/session/types.ts | Extends SessionEncryption to support optional ttl and adds PKCE-related public types. |
| src/core/pkce/state.ts | Adds sealed PKCE state format with runtime validation + TTL enforcement. |
| src/core/pkce/state.spec.ts | Adds coverage for state round-tripping, tamper detection, schema validation, and TTL behavior. |
| src/core/pkce/pkce.spec.ts | Adds end-to-end PKCE round-trip coverage through generateAuthorizationUrl and verifyCallbackState. |
| src/core/pkce/index.ts | Creates a PKCE submodule export surface for internal/public consumption. |
| src/core/pkce/generateAuthorizationUrl.ts | Implements PKCE URL generation returning { url, sealedState, cookieOptions }. |
| src/core/pkce/cookieOptions.ts | Implements PKCE cookie option derivation + Set-Cookie serialization helper. |
| src/core/pkce/cookieOptions.spec.ts | Adds unit tests for cookie option derivation and Set-Cookie serialization. |
| src/core/pkce/constants.ts | Introduces PKCE cookie name and TTL constants. |
| src/core/errors.ts | Adds OAuthStateMismatchError and PKCECookieMissingError. |
| src/core/errors.spec.ts | Adds tests validating new error types and inheritance. |
| src/core/encryption/ironWebcryptoEncryption.ts | Adds TTL support to unsealData (and already supports TTL in sealing). |
| src/core/encryption/ironWebcryptoEncryption.spec.ts | Adds tests validating TTL enforcement behavior. |
| src/core/AuthKitCore.ts | Adds verifyCallbackState that checks state vs cookie before decrypting/unsealing. |
| src/core/AuthKitCore.spec.ts | Adds tests for verifyCallbackState error cases and refresh behaviors. |
| pnpm-lock.yaml | Locks the new valibot dependency. |
| package.json | Bumps version to 0.4.0 and adds valibot. |
| README.md | Documents the new PKCE cookie-bound flow, updated APIs, and adapter responsibilities. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…w intent - Use crypto.timingSafeEqual for state-vs-cookie compare to avoid timing side-channels (with length gate). - Document cookieOptions `secure` forcing when sameSite=none. - Expand PKCE_CLOCK_SKEW_MS docs: asymmetric by design (negative skew for multi-node drift, strict upper bound keeps payload check authoritative). - Fix misleading state.spec test comment: assert the real 601s boundary, not a fictional 660s grace window.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Generalize SessionStorage with getCookie/setCookie/clearCookie primitives so
the PKCE verifier cookie flows through the same abstraction as the session
cookie. AuthService now writes and reads the verifier internally — callers
no longer see sealed blobs or cookie options.
Hard rename to match library precedent:
- getAuthorizationUrl/getSignInUrl/getSignUpUrl → createAuthorization/
createSignIn/createSignUp. Each takes `response` and returns
{ url, response?, headers? }.
- handleCallback drops `cookieValue`; reads via storage.getCookie.
- buildPKCEDeleteCookieHeader/getPKCECookieOptions removed; replaced by
auth.clearPendingVerifier(response, { redirectUri? }).
Seal per-call `redirectUri` override into PKCE state so handleCallback can
emit a verifier-delete Set-Cookie with the matching Path — no orphan
cookies under overrides.
handleCallback success now returns headers['Set-Cookie'] as string[] (session
cookie + verifier delete). Adapters must append each entry as its own HTTP
header; document HeadersBag invariant accordingly.
Remove from public API: PKCE_COOKIE_NAME, getPKCECookieOptions,
serializePKCESetCookie, PKCECookieOptions, buildPKCEDeleteCookieHeader.
Bump to 0.4.0. MIGRATION.md and README.md rewritten for the collapsed API.
Tests: 207 pass (+4 new — clearPendingVerifier paths, multi-Set-Cookie
assertion, getCookie/setCookie/clearCookie primitives, redirectUri
round-trip through sealed state).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 25 changed files in this pull request and generated 10 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 25 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…erifier Two regressions caught on third-party review of the 0.4.0 storage-owned cookie API. Both break real adapters even though in-repo tests passed: - `mergeHeaderBags` matched the literal key `Set-Cookie`, so adapters that normalize through `Headers` objects (lowercase `set-cookie`) dropped one cookie on callback — either the session cookie or the verifier delete. Now matches case-insensitively and reuses the existing key's casing. - `clearPendingVerifier(response: TResponse, ...)` required a concrete response even though `SessionStorage.clearCookie` accepts `undefined` for headers-only emission. The documented error-path cleanup was unreachable for headers-only adapters. Widened to `TResponse | undefined`. Also fix a rename leftover: `GetAuthorizationUrlOptions` JSDoc referenced the old `getAuthorizationUrl` name. +2 regression tests (lowercase Set-Cookie merge, undefined clearPendingVerifier).
…fy mergeHeaderBags
Docs:
- MIGRATION.md was documenting a transitional API (`{ url, sealedState,
cookieOptions }`, `buildPKCEDeleteCookieHeader`, `cookieValue` on
`handleCallback`, `serializePKCESetCookie`, `decodeState`) that never
shipped. Rewritten against real v0.3.4: `getSignInUrl(): Promise<string>`,
`handleCallback(req, res, { code, state? })`, plaintext `{internal}.{userState}`
state, abstract `getSession` on `CookieSessionStorage`. Deleted the
"Removed public exports" and `decodeState` sections — nothing to migrate
from. Added header-bag casing note covering the eb39cd7 invariant.
- README: removed bogus `parseCookieHeader` import (not exported); inline
cookie parsing in the storage example. Middleware refresh path now
appends each `Set-Cookie` entry individually — `headers.set('Set-Cookie',
string[])` would comma-join, exactly the failure mode we warn against.
Noted `clearPendingVerifier` accepts `undefined` for headers-only adapters.
Code cleanup (simplify pass on eb39cd7):
- `mergeHeaderBags`: flattened — dropped dead `left === undefined` branch,
early-continue for non-cookie and no-existing-key cases. JSDoc rationale
trimmed to one line (reasoning lives in commit history).
- Test: dropped `Object.keys().find()` theater — the mock authored lowercase
`set-cookie`, assert on `headers['set-cookie']` directly. Comment trimmed.
- Token refresh: rewrote the "Token Expiry Buffer: 60 seconds default" line that didn't match reality. Actual behavior: validateAndRefresh refreshes when verifyToken fails (expired/invalid). isTokenExpiring is a standalone helper, not wired into the refresh path. Now documented as such. - Direct Access example: was broken — passed getConfigurationProvider() to constructors that need AuthKitConfig, and referenced undefined `encryption`. Swapped to getConfig() + the exported sessionEncryption default. - Set-Cookie casing: handleCallback output passes through mergeHeaderBags, which preserves the adapter's key casing. Callers using a literal `headers['Set-Cookie']` with lowercase-emitting adapters miss both cookies. Updated both README and MIGRATION to check both casings. (The README middleware example uses the bundled CookieSessionStorage which always emits capital-S, so it's unchanged.) - Default cookie name: README said `wos_session`; actual config default (via ConfigurationProvider) is `wos-session`. The CookieSessionStorage fallback at :14 still says `wos_session` — that's a codebase bug to fix separately; users on the normal createAuthService path hit the hyphen.
Previous attempt used `getConfig()` (takes a key, returns one value) and `getWorkOS(config)` (takes no args). Use `getConfigurationProvider().getConfig()` for the full AuthKitConfig and call `getWorkOS()` zero-arg.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…nProvider Both sites documented `wos-session` as the default, but the CookieSessionStorage constructor fell back to `wos_session` (underscore) — divergent from the `wos-session` (hyphen) default in ConfigurationProvider. Users on the normal createAuthService path see hyphen; direct CookieSessionStorage instantiation with an unresolved config saw underscore. Not user-visible in practice — the provider always resolves cookieName before CookieSessionStorage sees the config — but the inconsistency is real and the underscore default contradicts the docs.
- errors.spec.ts (21 → 8 tests): was testing Node's built-in Error behavior 5 different ways for each subclass. Consolidated subclass name/inheritance checks into one `it.each` table; kept one base-class test covering message/cause/data and two TokenRefreshError tests for the actual custom fields. - pkce.spec.ts (8 → 3 tests): integration tests overlapped with state.spec.ts (tamper, TTL, customState variants) and AuthKitCore.spec.ts (missing state/cookie). Kept the three behaviors that only emerge at the integration seam: full round-trip, empty-string cookie falsy, concurrent sign-in cross-flow rejection. - state.spec.ts: three customState preservation tests (dots, JSON-like, 2KB) collapsed into one `it.each` — all testing the same opacity property. 209 → 191 tests, -303 spec LOC. No coverage loss.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The 2-line constants.ts file was the only constants module in the codebase — elsewhere, defaults live inline next to the code that uses them (CookieSessionStorage defaults, ConfigurationProvider defaults, token expiry buffer). Move PKCE_COOKIE_NAME and PKCE_COOKIE_MAX_AGE into cookieOptions.ts where they're most tightly coupled to behavior, and document the 600s rationale — 10-minute convention matching Arctic, openid-client, Clerk, Okta. state.ts imports from cookieOptions.ts now.
…age context to PKCE expiry errors - mergeHeaderBags: if `b` contained both `Set-Cookie` and `set-cookie` keys, the pre-loop scan computed the merge target once from `a`, so the second casing from `b` would be inserted as a separate key instead of merged. Pathological (no real adapter emits both), but a real gap. Now captures the first set-cookie key we see and reuses it. - PKCE state expired: the docstring claims callers differentiate via the `cause` chain, but the expiry throw had no cause. Attach age/maxAge/skew context so production clock-skew issues are diagnosable via `cause` without leaking the sealed blob.
Greptile SummaryThis PR introduces PKCE state binding and storage-owned verifier cookie management for the OAuth sign-in flow. The sealed state blob doubles as both the OAuth Confidence Score: 4/5Safe to merge after addressing the Two P2 findings:
Important Files Changed
Sequence DiagramsequenceDiagram
participant C as Client/Browser
participant A as Adapter
participant S as AuthService
participant Op as AuthOperations
participant Enc as Encryption
participant WOS as WorkOS API
Note over C,WOS: Sign-in initiation
C->>A: GET /sign-in
A->>S: createSignIn(response, options)
S->>Op: createAuthorization(options)
Op->>WOS: pkce.generate()
WOS-->>Op: { codeVerifier, codeChallenge }
Op->>Enc: sealState({ nonce, codeVerifier, issuedAt, ... })
Enc-->>Op: sealedState
Op-->>S: { url, sealedState, cookieOptions }
S->>S: storage.setCookie(response, wos-auth-verifier, sealedState)
S-->>A: { url, response?, headers? }
A->>C: Redirect to url + Set-Cookie: wos-auth-verifier=sealedState
Note over C,WOS: OAuth callback
C->>A: GET /callback?code=X&state=sealedState
A->>S: handleCallback(request, response, { code, state })
S->>S: storage.getCookie(request, wos-auth-verifier) cookieValue
S->>S: verifyCallbackState({ stateFromUrl, cookieValue })
Note right of S: constantTimeEqual(urlBytes, cookieBytes)
S->>Enc: unsealState(cookieValue)
Note right of Enc: schema validate + issuedAt TTL check
Enc-->>S: { codeVerifier, returnPathname, ... }
S->>WOS: authenticateWithCode({ code, codeVerifier })
WOS-->>S: { accessToken, refreshToken, user }
S->>Enc: encryptSession(session)
S->>S: storage.saveSession(response, encryptedSession)
S->>S: storage.clearCookie(response, wos-auth-verifier)
S-->>A: { response?, headers: { Set-Cookie: [sessionCookie, verifierDelete] }, returnPathname }
Reviews (8): Last reviewed commit: "feat(pkce)!: reject oversized sealed sta..." | Re-trigger Greptile |
…irectUri plumbing
Path-scoped verifier cookies (Path=<redirectUri.pathname>) are invisible
in Chrome DevTools' Application panel from any page outside the scoped
path, which makes PKCE wiring look broken even when it works end-to-end.
Drop the scoping: the cookie is HttpOnly with a 10-minute TTL, so the
cost of sending it on unrelated same-origin requests is negligible vs.
the debugging pain.
Removes the dead plumbing that existed solely to recover the per-call
path at callback time:
- StateSchema no longer stamps `redirectUri` into the sealed blob
- generateAuthorizationUrl stops including it when sealing
- AuthService.handleCallback drops the `sealedRedirectUri` destructure
- AuthService.clearPendingVerifier loses its `{ redirectUri? }` option
BREAKING CHANGE: `clearPendingVerifier(response, opts)` is now
`clearPendingVerifier(response)`. Callers passing `{ redirectUri }`
must drop the argument. Sealed-blob schema stays `optional`-tolerant
of an unused `redirectUri` field so in-flight cookies from prior
builds continue to round-trip during deploy.
de13e36 added `timingSafeEqual` from `node:crypto` and `Buffer.from` to `verifyCallbackState`, which broke client bundles in consumers (Vite externalizes `node:crypto` for the browser; `timingSafeEqual` has no browser equivalent, causing Rollup to fail on the missing export). Replace with a runtime-portable `constantTimeEqual` helper in `src/utils.ts` (XOR-accumulate loop) and `TextEncoder` for byte conversion. AuthKitCore now truly matches its "pure business logic, no framework-specific code" docstring — works in Node, browsers, and edge runtimes.
Adapters that want the PKCE-delete cookie's attributes to match a per-request redirectUri (e.g. middleware-scoped overrides) need to pass the same override used at sign-in time. Forward it to `getPKCECookieOptions`, which already accepts it. Additive-only change: existing single-arg callers are unaffected.
- Replace the 3x-repeated `GetAuthorizationUrlResult & { response?,
headers? }` intersection with a named `CreateAuthorizationResult<TResponse>`.
- Delete orphan `src/core/pkce/index.ts` — nothing imports from it and
its contents are documented as internal.
- Drop a comment in `cookieOptions.ts` that restated the adjacent JSDoc.
- Document URL-decode requirement on SessionStorage.getCookie (addresses greptile P2): custom encryption backends emitting seals with chars encodeURIComponent escapes (+/=) would silently break state comparison. - Correct MIGRATION.md §7 (greptile P1): PKCE cookie path is always '/', not redirect-URI-scoped. Multi-app isolation requires cookieDomain.
Two issues flagged during code review: 1. handleCallback only cleared the verifier cookie on the success path — failed callbacks (state mismatch, tampered seal, exchange failure, save failure) left the verifier live for the full 600s TTL. Now best-effort clears on all post-cookie-read exits; cleanup errors are swallowed so the original failure propagates. 2. The per-request redirectUri override passed to createAuthorization was threaded into creation but not into the handleCallback success-path delete — so cookie attributes (notably secure) could mismatch between set and clear. Now persisted into the sealed state and recovered on unseal so the delete matches the original set. Adds six tests covering the new error-path cleanup, clear-swallowing, and the override round-trip (using an http:// override so the secure attribute flip is observable).
Codex flagged a residual gap: pre-unseal errors (state mismatch, tampered seal) can't recover the per-request redirectUri override, so the fallback delete used config-default `secure=true`. On an http:// override + http:// callback, the browser may reject the Set-Cookie and the verifier would linger. Cookie replacement matches on (name, domain, path), not Secure — so dropping Secure on the fallback clear is safe. Only do it for sameSite=lax; sameSite=none requires Secure and forces it on both set and clear already. Adds two tests proving the scheme-agnostic behavior for lax and that sameSite=none still keeps Secure.
PR review flagged a silent-failure regression: the sealed PKCE state is stored as the wos-auth-verifier cookie, so a few KB of caller-supplied `options.state` can push the cookie past the per-cookie browser limit (~4 KB). Browsers drop it silently and the next callback always fails with PKCECookieMissingError — with no signal at sign-in time. Pre-0.4.0, `state` was URL-only and callers could carry much more. Guard in two layers: 1. Cheap early check: `options.state` > 2048 UTF-8 bytes throws PKCEPayloadTooLargeError before any crypto work runs. 2. Authoritative check: serialized Set-Cookie header > 3800 bytes throws the same error. Catches combinations the input-only check can't — returnPathname + state near the limit, or a long cookieDomain eating the budget. Extracts `serializeCookie` from CookieSessionStorage into a shared helper so the guard measures the exact bytes the adapter will emit. Exports `PKCEPayloadTooLargeError` from the package root. Six boundary tests cover: at-limit pass, just-over reject, multibyte UTF-8 counted in bytes not chars, returnPathname overflow, cookieDomain attribute pressure, and error-message content. Docs: MIGRATION.md §5 documents the 2KB state cap and the regression from URL-only state; JSDoc on `AuthUrlOptions.state` mirrors it.
Summary
Introduces two tightly-coupled breaking changes against the OAuth sign-in flow:
stateparameter is now a sealed blob that is byte-compared against anHttpOnlywos-auth-verifiercookie before the code is exchanged. Prevents a stolenstatefrom being replayed from a different browser / context.SessionStorageprimitives (getCookie/setCookie/clearCookie). Adapters no longer serialize, read, or delete the cookie manually; the library owns the lifecycle end-to-end.See
MIGRATION.mdfor the full migration guide.API changes
New methods (replace the old URL builders)
getAuthorizationUrl(...)createAuthorization(response, ...)getSignInUrl(...)createSignIn(response, ...)getSignUpUrl(...)createSignUp(response, ...)All three take a
responseargument and return{ url, response?, headers? }. Storage either mutatesresponse(adapters that overrideapplyHeaders) or emits headers via theheadersbag — callers forward whichever is populated.handleCallback— simpler signatureBefore:
After:
cookieValueis gone —handleCallbackreads the verifier viastorage.getCookie(request, 'wos-auth-verifier').On success,
result.headerscarries aSet-Cookieentry as astring[]with two entries: the session cookie and the verifier-delete cookie. Adapters MUST.appendeach entry individually — never comma-join into a single header.The header-bag key is case-insensitive:
mergeHeaderBagspreserves the adapter's casing, so checkheaders['Set-Cookie'] ?? headers['set-cookie'].New:
clearPendingVerifierUsed on error paths where
handleCallbackdoesn't run (OAuth error responses, missingcode, early bail-outs). AcceptsTResponse | undefined— headers-only adapters can passundefined.New typed errors
OAuthStateMismatchError— URLstatedoesn't match the cookiePKCECookieMissingError— verifier cookie absent on callbackSessionEncryptionError)Cookie details
HttpOnly,SameSite=Lax(downgraded fromStrictso the cookie actually rides the cross-site OAuth redirect back)Secureinferred fromredirectUriprotocol (forced on whenSameSite=None)Max-Age=600Path=/so the cookie is visible in Chrome DevTools' Application panel from any page (path-scoped cookies are invisible from pages outside the scope, which made PKCE wiring look broken even when working end-to-end).HttpOnly+ 10-minute TTL keeps exposure bounded.issuedAt;unsealStateenforces the 600s TTL independently of theSessionEncryptionadapter'sttlhandling, with 60s bounded clock-skew tolerance for multi-node deploys.Removed
getAuthorizationUrl/getSignInUrl/getSignUpUrl(replaced — see table above)decodeState—stateis no longer a cleartext return-pathname carrier; usehandleCallback's returnedreturnPathnameinsteadserializePKCESetCookie/buildPKCEDeleteCookieHeader/getPKCECookieOptions/PKCECookieOptions/PKCE_COOKIE_NAMEfrom the public surface — storage owns all of this internallyAlso in this PR
BREAKING CHANGE: every adapter consumer must update. See `MIGRATION.md`.