Skip to content

feat: support keyless PKCE public clients - #59

Open
nicknisi wants to merge 6 commits into
mainfrom
riker/96-let-workos-authkit-session-run-as-a-keyl
Open

nicknisi wants to merge 6 commits into
mainfrom
riker/96-let-workos-authkit-session-run-as-a-keyl

Conversation

@nicknisi

@nicknisi nicknisi commented Oct 7, 2026

Copy link
Copy Markdown
Member

Let @workos/authkit-session run as a keyless PKCE public client, bringing the experience shipped in workos/authkit-react-router#96 (workos/authkit-react-router#96, "feat: support keyless PKCE public clients", merged Oct 6) to authkit-session. A server that only signs users in should work with WORKOS_CLIENT_ID, WORKOS_REDIRECT_URI and WORKOS_COOKIE_PASSWORD and no WORKOS_API_KEY.

Read #96's diff first (gh pr diff 96 --repo workos/authkit-react-router) and mirror its design and rigor:

  • AuthKitConfig becomes a discriminated union: a confidential config (apiKey: string) and a public config (no apiKey). It stays non-breaking: existing users with WORKOS_API_KEY via env or configure() keep working unchanged, and env-only configuration works for both modes.
  • Required keys: apiKey comes out of the requiredKeys in src/core/config/ConfigurationProvider.ts. Construct the client as new WorkOS({ apiKey, clientId, ...options }) in src/core/client/workos.ts, so workos-node 8.x's keyless mode applies (authenticateWithCode with a codeVerifier and authenticateWithRefreshToken omit client_secret when there's no key). Check the installed @workos-inc/node peer range supports this and raise the minimum if needed.
  • Clear errors for key-only features. Anything that needs a key must fail with a clear, actionable error naming WORKOS_API_KEY, not an opaque 401. Audit every WorkOS call (getWorkOS consumers, AuthService/AuthOperations, featureFlags.createRuntimeClient, any organizations or userManagement admin calls) and list which need a key.
  • README: a public-client (keyless) section saying when to use it and what's unavailable without a key.
  • Unit tests:
    • config validation in both modes
    • client construction with and without a key
    • code exchange and refresh sending no client_secret in public mode (assert the request bodies)
    • the clear error for key-only features
    • type-level tests that the union narrows (e.g. expectTypeOf, or @ts-expect-error cases)

Then prove it end to end: authkit-session is the core used by authkit-tanstack-start, so use that package's example app (/Users/nicknisi/Developer/authkit-tanstack-start/example) as the harness. Copy it to a scratch directory outside both repos and point it at this branch's packed build (pnpm pack, then a file: or overrides dependency in the copy only). Run it with no WORKOS_API_KEY, and show that GET on the sign-in route redirects to the AuthKit authorization URL with code_challenge and code_challenge_method=S256. Leave the dev server running and give Nick the URL and exact steps to sign in and see the session load and a refresh succeed. If a refresh can be shown without his login (e.g. a debug log line in the demo copy only), add it to the demo copy, not the library.

Riker's check pnpm install --frozen-lockfile >/dev/null && pnpm typecheck && pnpm lint && pnpm format:check && pnpm test && pnpm build passed.

Opened as a draft by Riker (job 96).

Model AuthKitConfig as a union of a confidential config (apiKey) and a
public config (no apiKey), drop apiKey from the required keys while still
reading it from env, and build the WorkOS client with
new WorkOS({ apiKey, clientId, ...options }) so the SDK runs in public-client
mode when no key is configured.
@nicknisi
nicknisi marked this pull request as ready for review October 7, 2026 17:22
@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Critical risk] Removes API key requirement from authentication flow.

The latest changes appear safe to merge, with no new actionable issues found.

What we checked:

  • Wrong verifier passes the test: The test hashes the verifier sent in the code exchange and compares it with the original challenge. A mismatch now fails the assertion.

Summary

This PR adds keyless public-client configuration while keeping API-key configuration available. The latest changes strengthen the callback and management-error tests.

  • The callback test checks that the submitted PKCE verifier matches the sign-in challenge.
  • Management-call tests check the SDK's error details and confirm no request was sent.
  • nicknisi intentionally kept the SDK's error message; the README and getWorkOS() docs explain how to set WORKOS_API_KEY.
Diagram
sequenceDiagram
  participant Browser
  participant AuthKit
  participant WorkOS
  Browser->>AuthKit: Start sign-in
  AuthKit-->>Browser: Authorization URL and sealed PKCE cookie
  Browser->>WorkOS: Sign in with code challenge
  WorkOS-->>Browser: Redirect with code and state
  Browser->>AuthKit: Callback with PKCE cookie
  AuthKit->>WorkOS: Exchange code with verifier, without client secret
  WorkOS-->>AuthKit: Access and refresh tokens
  AuthKit-->>Browser: Session cookie
  AuthKit->>WorkOS: Refresh with client ID and refresh token
Loading

Reviews (2) · Last reviewed commit: "test: check PKCE verifier against challe..." · Reviewed by Greptile

Comment thread src/service/publicClient.spec.ts
Comment thread src/service/publicClient.spec.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant