A Clerk login can look fine in the browser and still fail on your server with a 401.
The frontend has a user. getToken() returns something. The request includes:
Authorization: Bearer <token>
Then FastAPI, Express, or your API route rejects it.
Before rewriting your auth code, check this:
CLERK_PUBLISHABLE_KEY
CLERK_SECRET_KEY
CLERK_JWT_ISSUER
They need to come from the same Clerk instance.
The bug
This can happen when you have multiple Clerk projects open:
Frontend:
CLERK_PUBLISHABLE_KEY = pk_test_from_project_A
Backend:
CLERK_SECRET_KEY = sk_test_from_project_B
CLERK_JWT_ISSUER = https://project-c.clerk.accounts.dev
Each value can look valid by itself.
But the token was minted by one Clerk instance, and your backend is trying to verify it against another one. That is enough for server-side verification to fail.
You might see:
JWT issuer mismatch
invalid issuer
JWKS key not found
invalid signature
401 unauthorized
Different library, same root issue.
The quick fix
Open one Clerk dashboard project and copy all auth values again from the same place.
Then restart both servers.
# frontend
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
# backend
CLERK_SECRET_KEY=sk_test_...
CLERK_JWT_ISSUER=https://same-instance.clerk.accounts.dev
Do not only refresh the browser. Dev servers often keep old env values until the process restarts.
Why agents miss this
An AI coding agent can write the shape of a Clerk integration:
add middleware
read bearer token
verify JWT
sync user
protect route
But it will not know you pasted keys from two Clerk projects unless it checks the running path.
This is why I like running a small auth workflow before wiring the app.
Receipt
I ran FetchSandbox's Clerk user_signup workflow as the receipt layer:
Workflow: clerk/user_signup
Result: passed
Steps: 4/4
Duration: 77.44 ms
Verified webhooks: user.created, user.updated
Timeline: https://fetchsandbox.com/runs/7aa06cff84?flow=run_b8399f77-352c-4fe4-ad50-24e3060f8d8d
That workflow proves the user lifecycle surface:
POST /users
GET /users/{id}
PATCH /users/{id}
verify user.created + user.updated
It does not replace your real Clerk project. It gives your agent or teammate a runnable auth flow before they touch app code.
Takeaway
If Clerk works in the browser but your server returns 401, check the boring thing first:
publishable key
secret key
issuer URL
Same instance. Same environment. Then restart.
FetchSandbox MCP setup if you want the agent to run the workflow first:
{
"mcpServers": {
"fetchsandbox": {
"command": "npx",
"args": ["-y", "fetchsandbox-mcp"]
}
}
}
Top comments (0)