JWT authentication is experimental. The config option is prefixed with
experimental_ and the API may change in future versions.Why JWTs
JWTs are short-lived, asymmetrically-signed tokens (RS256 or ES256) issued by your backend. Compared to a long-lived API key, they let you:- Cut latency and backend code. Clients hit Rhinestone directly with a bounded access token instead of round-tripping through your server for every SDK call. For sponsored intents, a separate extension token binds your approval to the exact payload — so you stay in control per intent without proxying the submission.
- Bound the blast radius of a leaked credential. Access tokens expire on a TTL you choose. An API key stays valid until you notice and rotate it.
- Rotate signing keys without downtime. Register a new
kid, start minting tokens with it, and tokens signed under the oldkidkeep verifying until they expire. No coordinated client deploy, no revocation race.
Dashboard setup
Before you can issue JWTs, you need to register a signing key with Rhinestone. See JWT keys for how to generate or upload a key in the Dashboard. The Integrator ID you set there becomes theiss claim, and the Key ID becomes the kid header — both referenced in Configuration below.
Configuration
Signing a JWT requires five values. Three come from the Dashboard; the other two are free-form labels you pick yourself.integratorId, keyId, and projectId are verified server-side. The access token’s (iss, kid) must resolve to a registered key, and the token’s sub must equal the project that key was registered against. Mismatches produce a 401 or 403 at verification time — see Troubleshooting.
appId is a free-form environment/app label — typically prod, staging, etc. It isn’t registered anywhere. The only server-side check is that the access token’s app_id matches the accompanying intent-extension token’s app_id, so a sponsorship approval issued for one deployment can’t be spent by another. Use it to correlate logs or split rate limits per deployment; if you don’t need that, use the same value everywhere.
keyId and appId are independent axes. keyId rotates on key rotation (same deployment, new signing key). appId rotates on environment changes (same key, new deployment). You can reuse one signing key across multiple environments, or rotate keys within a single environment.SDK usage
There are two integration patterns depending on where the signing key lives.- Client-server
- Same-host
When the SDK runs on the client (browser, mobile) and a separate backend holds the private key, fetch tokens from your backend over HTTP:Your backend is responsible for issuing the two token types. See Sponsorship signing server for a drop-in implementation.
Sponsorship signing server
For the client-server pattern, your backend needs two endpoints: one that issues short-lived access tokens, and one that signs an intent extension token for each sponsored intent. The SDK ships ready-made handlers for both.Web standard handlers
The Web Standard handlers accept aRequest and return a Response. They work with Next.js App Router, Hono, SvelteKit, Remix, Deno, Bun, and Cloudflare Workers:
Express
For Express, usecreateExpressRouter. It mounts GET /access-token and POST /extension-token:
Custom sponsorship policy
By default the signer sponsors any intent your users submit. To restrict this, pass ashouldSponsor filter. Each predicate can be sync or async, and omitted predicates default to true. Filters are AND-composed — the intent must pass all of them to be signed:
shouldSponsor config can be passed to the handler factories (createAccessTokenHandler, createExtensionTokenHandler, createExpressRouter).
Denied requests throw a SponsorshipDeniedError, which the handlers surface as a 403. When calling the signer directly, the error is instanceof-checkable:
Troubleshooting
Auth failures surface as HTTP 4xx responses with amessage field. The common ones:
If you’re hitting a 401 seconds after registering or rotating a key, the verification cache may briefly hold the old state — wait a minute and retry, or register under a fresh
kid.