workos-auth/core: Replace Clerk with WorkOS AuthKit#51
Conversation
…auth. Swap server hooks, auth helpers, OAuth callback/sign-out routes, and sign-in/up redirect flows so dashboard login works end-to-end without Clerk.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Greptile SummaryThis PR replaces Clerk with WorkOS AuthKit for server-side authentication, swapping
Confidence Score: 4/5The auth migration is structurally correct and the JWKS URL matches WorkOS documentation; no user data or session tokens are exposed by the changes. The core auth flow — callback, session cookies, bearer verification, and protected-path guarding — is wired correctly. The configureAuthKit empty-string fallbacks mean a misconfigured deployment will produce a crypto failure on the first authenticated request rather than a clear startup error, which could be hard to diagnose. The authKitHandle() factory pattern also creates a new closure per request rather than reusing a module-level instance. src/hooks.server.ts — the configureAuthKit call and the authKitHandle() instantiation pattern both warrant a second look before merging. Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser
participant SvelteKit as SvelteKit (hooks.server.ts)
participant AuthKitHandle as authKitHandle
participant Auth as auth.ts
participant WorkOS as WorkOS AuthKit
participant Route as Route Handler
Note over Browser,Route: Web session flow
Browser->>SvelteKit: GET /runs (unauthenticated)
SvelteKit->>AuthKitHandle: "authKitHandle()({event, resolve})"
AuthKitHandle->>Auth: resolve(authEvent) guardProtectedPath
Auth-->>SvelteKit: "redirect 303 /sign-in?redirect_url=/runs"
SvelteKit-->>Browser: 303 /sign-in
Browser->>SvelteKit: "GET /sign-in?redirect_url=/runs"
SvelteKit->>Auth: "authKit.getSignInUrl({returnTo: '/runs'})"
Auth->>WorkOS: build redirect URL
WorkOS-->>SvelteKit: signInUrl
SvelteKit-->>Browser: render page (JS redirects to WorkOS)
Browser->>WorkOS: authenticate
WorkOS-->>Browser: "redirect to /callback?code=..."
Browser->>SvelteKit: "GET /callback?code=..."
SvelteKit->>WorkOS: authKit.handleCallback() exchanges code
WorkOS-->>SvelteKit: session + access token
SvelteKit-->>Browser: set session cookie, redirect to /runs
Note over Browser,Route: API/SDK bearer token flow
Browser->>SvelteKit: GET /api/runs (Bearer token)
SvelteKit->>AuthKitHandle: "authKitHandle()({event, resolve})"
AuthKitHandle->>Auth: authenticateBearer(event)
Auth->>WorkOS: "jwtVerify(token, JWKS at /sso/jwks/{clientId})"
WorkOS-->>Auth: payload.sub
Auth-->>SvelteKit: "locals.bearerUserId = sub"
SvelteKit->>Route: resolve guarded route handler
Route-->>Browser: 200 OK
Note over Browser,Route: Sign-out flow
Browser->>SvelteKit: POST /sign-out (form submit)
SvelteKit->>WorkOS: authKit.signOut(event)
WorkOS-->>SvelteKit: clear cookie + redirect URL
SvelteKit-->>Browser: redirect to WorkOS sign-out
|
| configureAuthKit({ | ||
| clientId: env.WORKOS_CLIENT_ID ?? '', | ||
| apiKey: env.WORKOS_API_KEY ?? '', | ||
| redirectUri: env.WORKOS_REDIRECT_URI ?? '', | ||
| cookiePassword: env.WORKOS_COOKIE_PASSWORD ?? '' | ||
| }); |
There was a problem hiding this comment.
All four
configureAuthKit parameters fall back to an empty string when the corresponding env var is absent. A missing cookiePassword in particular will cause iron-webcrypto to throw a crypto error at request time (it requires a password of at least 32 characters), making the failure happen silently on the first authenticated request rather than at startup. Asserting these values are present at boot makes misconfiguration immediately visible.
| configureAuthKit({ | |
| clientId: env.WORKOS_CLIENT_ID ?? '', | |
| apiKey: env.WORKOS_API_KEY ?? '', | |
| redirectUri: env.WORKOS_REDIRECT_URI ?? '', | |
| cookiePassword: env.WORKOS_COOKIE_PASSWORD ?? '' | |
| }); | |
| const missingVars = ['WORKOS_CLIENT_ID', 'WORKOS_API_KEY', 'WORKOS_REDIRECT_URI', 'WORKOS_COOKIE_PASSWORD'].filter( | |
| (key) => !env[key as keyof typeof env] | |
| ); | |
| if (missingVars.length > 0) { | |
| throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`); | |
| } | |
| configureAuthKit({ | |
| clientId: env.WORKOS_CLIENT_ID!, | |
| apiKey: env.WORKOS_API_KEY!, | |
| redirectUri: env.WORKOS_REDIRECT_URI!, | |
| cookiePassword: env.WORKOS_COOKIE_PASSWORD! | |
| }); |
| }); | ||
| </script> | ||
|
|
||
| <svelte:head><title>Sign in | FlightLog</title></svelte:head> |
There was a problem hiding this comment.
The redirect to the WorkOS sign-in URL is wired only through
onMount, so a user with JavaScript disabled will land on a loading spinner that never resolves. Adding a <noscript> meta-refresh provides a functional fallback without changing the JS path.
| <svelte:head><title>Sign in | FlightLog</title></svelte:head> | |
| <svelte:head> | |
| <title>Sign in | FlightLog</title> | |
| <noscript><meta http-equiv="refresh" content="0; url={data.signInUrl}" /></noscript> | |
| </svelte:head> |
| }); | ||
| </script> | ||
|
|
||
| <svelte:head><title>Sign up | FlightLog</title></svelte:head> |
There was a problem hiding this comment.
Same JS-only redirect issue as
sign-in/+page.svelte — without a <noscript> fallback, users without JavaScript see an infinite loading state.
| <svelte:head><title>Sign up | FlightLog</title></svelte:head> | |
| <svelte:head> | |
| <title>Sign up | FlightLog</title> | |
| <noscript><meta http-equiv="refresh" content="0; url={data.signUpUrl}" /></noscript> | |
| </svelte:head> |

Stack Context
This 3-PR stack migrates FlightLog from Clerk to WorkOS AuthKit while preserving cookie sessions for the dashboard and Bearer tokens for the SDK/API.
Stack:
workos-auth/core→workos-auth/ui-shell→workos-auth/docs-testsWhy?
Clerk is being replaced with WorkOS AuthKit. This PR lands the server-side auth plumbing so login, callback, sign-out, and protected routes work without Clerk before any UI polish or docs updates land in the follow-ups.
What?
@workos/authkit-sveltekit+josefor Clerk depsauthKitHandlewith bearer verification and protected-path guards/callback) and POST sign-out routesTest plan
bun run checkbun run lint(src/)bunx vitest run src/lib/server/auth.test.ts src/lib/auth-redirect.test.ts/runs→ sign in → land on/runs→ sign out