Scoped Tokens
Issue short-lived JWTs with explicit permissions in one namespace.
A scoped token is a JWT that gives its holder selected permissions in one namespace. Issue it on your backend with your Rivet token, using the RivetKit client or the HTTP API, then return only the scoped token to the application.
Issue a token
RivetKit
Create a client on your backend with your Rivet token in the endpoint:
// Runs on your backend. The endpoint carries your Rivet token:
// https://my-namespace:<rivet-token>@rivet.example.com
const client = createClient<typeof registry>({
endpoint: process.env.RIVET_ENDPOINT ?? "http://localhost:6420",
});
For one Actor, call issueToken on its handle. Without permissions, the token can only send traffic to that Actor:
// Defaults to gateway read access, scoped to this Actor's ID.
const handle = client.user.getOrCreate(["user-123"]);
const { token, issuedAt, expiresAt } = await handle.issueToken({
subject: "user-123",
expiresIn: 900,
});
Pass permissions to replace the default. The handle accepts actor_gateway and actor permissions and scopes each grant to the Actor’s ID:
// Replaces the default. Every grant is scoped to this Actor's ID.
await handle.issueToken({
subject: "user-123",
expiresIn: 900,
permissions: {
actor_gateway: ["read"],
actor: ["read", "delete"],
},
});
For namespace-wide grants, such as creating Actors, call client.auth.issueToken with an explicit grant list:
// Namespace-wide grants are never added automatically.
await client.auth.issueToken({
subject: "user-123",
expiresIn: 900,
grants: [{ resource: "actor", target: "any", operations: ["create"] }],
});
expiresIn is in seconds. issuedAt and expiresAt are Unix timestamps in milliseconds.
HTTP API
Call POST /auth/tokens with your Rivet token. This grants traffic to every Actor in the production namespace; use {"id":"<actor-id>"} as the target for one Actor.
export RIVET_API=https://rivet.example.com
curl -X POST "$RIVET_API/auth/tokens" \
-H "Authorization: Bearer $RIVET_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"namespace": "production",
"subject": "user-123",
"duration": 900,
"grants": [
{"resource":"actor_gateway","target":"any","operations":["read"]}
]
}'
The response contains token, issued_ts and expires_ts. Timestamps are Unix milliseconds; duration is seconds. See Create token.
Return only the scoped token to the client. Never send your Rivet token to a browser.
Use the token
RivetKit
Give the browser client a getToken function that asks your backend for a token. The client calls it on first use and again when the token expires.
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";
const user = actor({
state: { name: "" },
actions: {
getName: (c) => c.state.name,
},
});
const registry = setup({ use: { user } });
// Runs in the browser. The endpoint carries only the namespace; the token comes from your backend.
const client = createClient<typeof registry>({
endpoint: "https://my-namespace@rivet.example.com",
// Called on first use and again when the token expires.
getToken: async () => {
const response = await fetch("/api/rivet-token", { method: "POST" });
const { token } = (await response.json()) as { token: string };
return token;
},
});
const name = await client.user.get(["user-123"]).getName();
console.log(name);
HTTP API
Send the token as a bearer token. This request reaches the Actor’s request handler:
curl "$RIVET_API/gateway/$ACTOR_ID/request/" \
-H "Authorization: Bearer $SCOPED_TOKEN"
A gateway grant does not allow Actor creation, listing, node registration or pool management.
Grants
Each grant names a resource, a target, and operations drawn from create, read, update, delete, and list. A target is "any" or an object such as {"id":"<actor-id>"}. Replace the placeholder with the resolved Actor ID.
| Resource | Purpose |
|---|---|
actor | Create, resolve, list, or manage Actors. |
actor_gateway | Reach an Actor’s actions, events, or HTTP handlers. |
node | Register or inspect nodes. |
pool | Read or manage node pool configuration. |
Actor management and gateway access require separate grants. Permission to reach one Actor does not allow the holder to create other Actors. Namespace administration and token issuance require your Rivet token.
Inspect and renew
curl "$RIVET_API/auth/tokens/inspect" \
-H "Authorization: Bearer $SCOPED_TOKEN"
The response contains the namespace slug, grants, subject, and issue and expiry timestamps. Renew expiring credentials through your authenticated backend. Do not give clients your Rivet token to renew tokens themselves.
Signing keys
The control plane signs tokens with keys it rotates automatically every 7 days. A token lasts 1 hour by default and at most 24 hours.
To revoke every outstanding token at once, rotate the signing keys immediately. Run these on any control plane instance; they read the instance’s configuration:
rivet-engine depot jwt show
rivet-engine depot jwt emergency-rotate \
--request-id <new-uuid> \
--expected-coordinator-epoch <epoch-from-show> \
--expected-ring-version <version-from-show>
Every instance stops accepting the old tokens within 5 minutes. If the command’s reply is lost, run show again and retry with the same request ID.