Directions
1Create the application in the Clerk dashboard
Sign up at clerk.com and create an application. Pick your sign-in methods now — email code and one or two OAuth providers (Google, GitHub) covers most products; every method you add is another button on the sign-in card, so fewer is cleaner.
Clerk creates a development instance immediately with keys that work from localhost. The production instance comes later and is tied to your real domain — don't ship dev keys.
2Copy the publishable and secret keys
From API Keys in the dashboard, copy the publishable key (pk_...) and the secret key (sk_...). The publishable key is safe to embed in frontend code — it only identifies your instance. The secret key is server-only: in a Worker it belongs in a secret (wrangler secret put CLERK_SECRET_KEY), never in wrangler.toml or the repo.
Frontend (public): PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
Worker (secret): wrangler secret put CLERK_SECRET_KEY
3Mount the sign-in component on the frontend
Load ClerkJS on the pages that need auth and mount the prebuilt components — SignIn on your sign-in page, UserButton in the header. On a static or Astro site this is one island, not an app-wide rewrite: the rest of the site stays plain HTML.
After sign-in, Clerk keeps a session cookie and exposes getToken(), which returns a short-lived JWT. That token — sent as an Authorization: Bearer header — is how your frontend proves identity to your API.
4Verify the JWT in your Worker
In the Worker, verify the token on every protected request using Clerk's backend SDK or plain JWKS verification against your instance's public keys. Verification is a signature check plus expiry and issuer claims — no network call to Clerk on the hot path once the JWKS is cached.
Two mistakes cause the classic "signed in but still 401": verifying against the wrong instance (dev token, prod keys), and configuring a proxy URL the backend doesn't share. Verify with the plain instance issuer unless you know you need the proxy.
const { userId } = await verifyToken(token, {
secretKey: env.CLERK_SECRET_KEY,
});
if (!userId) return new Response("unauthorized", { status: 401 }); 5Create the production instance on your domain
In the Clerk dashboard create the production instance and add your domain. Clerk gives you a handful of DNS records — CNAMEs for the frontend API and account portal subdomains — to add at your DNS host. On Cloudflare, set them to DNS only (grey cloud).
Swap the frontend to the pk_live_ key, put the sk_live_ key in the Worker secret, and test the whole loop on the real domain: sign in, call the API, sign out. OAuth providers also need their production redirect URLs whitelisted in each provider's console.
Common issues & fixes
Signed in on the frontend but the API returns 401.
The token and the verifier disagree about the instance. Confirm the frontend pk_ and Worker sk_ are from the same instance (both dev or both live), and remove any proxyUrl option the backend doesn't also use.
Clerk DNS records won't verify.
The CNAMEs are proxied. Set them to DNS only (grey cloud) — Clerk needs to issue certificates on those hostnames and the proxy blocks the challenge.
OAuth sign-in loops back to the sign-in page.
The provider's redirect URI doesn't match the production Clerk domain. Add the exact callback URL from the Clerk dashboard to the provider's OAuth app settings.
Every request hits Clerk's API and adds latency.
You're calling the sessions endpoint instead of verifying the JWT locally. Use networkless verification with the JWKS — it's a local crypto check after the first fetch.
Users vanish when they change their email.
Don't key your database on email — emails change and can be reused. Key on the Clerk userId, store email as a plain column.
Honesty note: articles are drafted with AI assistance, then a human
checks each one against the vendor's current setup flow before it
publishes — nothing goes live unreviewed. Vendor interfaces still
change; if a step looks different, the underlying record is what
matters.