fix(auth): verify sealed-session access tokens - #33
Conversation
constructAction deserialized the verified action request into the generic
EventSchema envelope ({object, id, event, data, created_at}). The real
Actions wire format is a flat context object discriminated by 'object'
(authentication_action_context / user_registration_action_context), so
parsing it into EventSchema silently dropped every useful field (user,
user_data, ip_address, device_fingerprint, issuer, ...) with no error.
Replace the EventSchema return with a typed ActionContext
(+ ActionUserData), mirroring workos-node's ActionContext and reusing the
generated User, Organization, OrganizationMembership, and Invitation models
for nested objects. Dispatch on 'object' to read the type-specific fields.
Tests now exercise the real wire format for both context types; the prior
test used a fabricated event envelope that never matched what WorkOS sends.
signResponse emitted a base64 {payload, sig} shape that no other SDK
produces. The real Actions response wire format (workos-node + python,
ruby, php, dotnet, kotlin) is {object, payload, signature}, where
signature = HMAC-SHA256(secret, "<timestamp>.<JSON(payload)>") and
payload = {timestamp, verdict, error_message?}.
Switch ActionSignedResponse to {object, payload, signature} and build
the response body by hand so the signed payload bytes are byte-identical
to the transmitted payload bytes (JSONEncoder reorders nested keys
non-deterministically, so re-serialization would break the signature).
Callers send bodyData as the response body. error_message is included
only on a Deny verdict with a non-empty message, matching workos-node.
A compromised cookie password must not make attacker-controlled JWT claims authoritative. Add an opt-in verified path while retaining source compatibility, and reject non-expiring legacy tokens to close the reported forgery path. Slow salted key derivation raises the cost of offline password guessing without forcing existing sessions to log out during the upgrade. Addresses VULN-1614.
The workflow started from a branch whose earlier changes were already squash-merged. Include current main so the security PR has a focused diff and validates against the current generated SDK surface.
|
| let key = | ||
| versioned ? try deriveKey(password, salt: Data(raw.prefix(16))) : deriveKey(password) |
There was a problem hiding this comment.
Any attacker-supplied wos2. cookie that base64-decodes to more than 44 bytes reaches the 600,000-iteration PBKDF before authentication fails. Session cookies are processed on each request, and this synchronous derivation runs before authenticateVerified() reaches its first await. Repeated forged cookies can therefore consume roughly 100–300 ms of CPU each, starving the cooperative executor or exhausting server capacity. Avoid performing this expensive password derivation independently for every untrusted request—for example, prederive or cache server-side key material and use a cheap authenticated per-cookie derivation while bounding concurrent work.
How this was verified: A syntactically valid unauthenticated cookie reaches the synchronous 600,000-iteration PBKDF before AES-GCM authentication or JWT verification occurs.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/WorkOS/Helpers/SessionSealing.swift
Line: 68-69
Comment:
**Forged Cookies Amplify CPU**
Any attacker-supplied `wos2.` cookie that base64-decodes to more than 44 bytes reaches the 600,000-iteration PBKDF before authentication fails. Session cookies are processed on each request, and this synchronous derivation runs before `authenticateVerified()` reaches its first `await`. Repeated forged cookies can therefore consume roughly 100–300 ms of CPU each, starving the cooperative executor or exhausting server capacity. Avoid performing this expensive password derivation independently for every untrusted request—for example, prederive or cache server-side key material and use a cheap authenticated per-cookie derivation while bounding concurrent work.
**How this was verified:** A syntactically valid unauthenticated cookie reaches the synchronous 600,000-iteration PBKDF before AES-GCM authentication or JWT verification occurs.
**Knowledge Base Used:**
- [Authentication and sessions](https://app.greptile.com/workos/-/custom-context/knowledge-base/workos/workos-ios/-/docs/authentication-and-sessions.md)
- [Security and data protection](https://app.greptile.com/workos/-/custom-context/knowledge-base/workos/workos-ios/-/docs/security-and-data-protection.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Description
Addresses VULN-1614.
authenticateVerified()and verified logout helpers that validate RS256 access tokens against the client's JWKS, requiresub/exp, bind cookie user IDs to the signed subject, and omit unsigned impersonator data.wos2.seals using PBKDF2-HMAC-SHA256 (600,000 iterations, 16-byte salt). Continue reading legacy SHA-256/hex-key seals and upgrade them on reseal.Compatibility and migration
Migrate to
try client.loadVerifiedSession(sessionData:cookiePassword:), thenawait session.authenticateVerified()andawait session.getVerifiedLogoutUrl()(or the client-bound static verified authentication helper).Deprecated authentication remains signature-unverified for source compatibility; mandatory expiration alone does not prevent forgeries with a future
exp. Seal/refresh operations continue accepting legacy short passwords to avoid forced logouts, but verified authentication rejects them. Use a cryptographically random password of at least 32 characters. Cookie profile fields other than the matched user ID are not signed claims.PBKDF2 adds CPU cost (roughly 100–300 ms depending on device); seal/unseal should run off the UI thread. Verified authentication fetches JWKS on every call without caching and fails closed on endpoint/verification errors. New seals cannot be read by older SDK versions.
Validation
./script/cipasses on currentmain: strict Swift formatting, build, and all 385 tests in 36 suites. No warnings originate in changed source files; existing generated-model deprecations and expected legacy-API test warnings remain. All five changed files are hand-maintained@oagen-ignore-filefiles.Documentation
No WorkOS API-reference changes. Migration, trust boundaries, password guidance, and PBKDF2/network costs are documented in the helper API comments above.