Your values
Open the connector in Upsolve (Project settings → API connectors). It shows three values, which you use as constants:
The issuer also publishes an OpenID configuration at
<issuer>/.well-known/openid-configuration, which auth providers read to accept tokens from another issuer.
There is no secret. Upsolve keeps the private key; the JWKS URL publishes only public keys.
The token
It travels inAuthorization: Bearer <token> unless the connector names another header and prefix.
The connector can add up to ten claims of your choosing: a property of the user or their organization (the
properties you send to embed-token), or a fixed text such as role: "authenticated". Upsolve sets iss aud sub org act iat exp nbf jti itself.
The header carries alg: "ES256", typ: "JWT" and a kid naming the key.
Verification rules
Verify every request that carries an Upsolve token:- Signature: ES256 only. Find the key by the header’s
kidin the JWKS, which you cache and fetch again when akidis not in it (Upsolve rotates keys; an old key stays published for 24 hours). Reject a token without akid. issequals your issuer exactly.audequals your audience exactly.expis in the future. Allow at most 60 seconds of clock skew.
sub (and their organization by org, when present) and authorize the request exactly as if that user had signed in. Answer 401 for a missing or invalid token, and for a valid token naming nobody you know. Answer 403 for a user who may not do what was asked.
Keep your existing authentication beside it: requests without an Upsolve token work as before. Use act.source if you want to limit what Upsolve-made calls may do, for example no writes from tile.
Snippets
Each one verifies all four rules. Replace the values with yours.Using your auth provider
Supabase
- In the Supabase Dashboard, open Authentication → Sign In / Providers → Third-Party Auth.
- Click Add provider, choose Custom OIDC and enter your issuer. Supabase reads the keys through the OpenID configuration.
- On the connector, click Supabase compatible. It adds the claim
role: "authenticated", without which requests run asanon. - In your RLS policies, read the user as
auth.jwt()->>'sub'.auth.uid()castssubto a UUID, so it only works when theexternalIdyou send to Upsolve is the user’s Supabase id. - If you also use Supabase Auth, add a restrictive policy that checks
auth.jwt()->>'iss', so ids from the two issuers cannot collide.
Auth0
Auth0 issues your users’ tokens; Upsolve’s tokens are checked by your API next to them. If your API usesexpress-oauth2-jwt-bearer, add a second auth({ issuerBaseURL: <issuer>, audience: <audience>, tokenSigningAlg: "ES256" }) and accept a request that either one accepts. With any other middleware, add Upsolve as a second trusted issuer the same way, or use a snippet above.
Clerk
Clerk verifies the sessions it issues, not Upsolve’s tokens. Keep Clerk’s middleware for your app’s own requests, and put a snippet above in front of it for requests that carry an Upsolve token. Look the user up bysub, the externalId you send to Upsolve (usually the Clerk user id).
Check it
- On the connector, click Copy a test token. You get a ready
curlline with a token that lasts five minutes (act.sourceistest). Run it against your local server and confirm it answers200as that user. - Change the audience you expect, or wait five minutes, and confirm the same call answers
401. - Deploy, then run Check in Upsolve. It calls the endpoint you pick three times: with a valid token (it expects
2xx), with a token for another audience, and with an expired token (both properly signed; it expects401or403). Each failure comes with what to fix. Upsolve never shows your API’s response bodies.
Keys and rotation
Each Upsolve project has one signing key at a time. Rotate signing key on the connector makes a new key sign every token from then on; the old key stays in the JWKS for 24 hours, so a token it signed a moment ago still verifies, and is then removed. A verifier that fetches the JWKS again on an unknownkid needs no change.