JWT templates
A JWT template defines the custom claims Torii puts into a user’s access
token. You author it as a free-form JSON document in the dashboard
(Project → JWT Templates); string values may contain {{ user.* }}
shortcodes that resolve against the signed-in user when a token is minted.
{ "role": "{{ user.public_metadata.role || \"member\" }}", "email": "{{ user.email }}", "plan": "{{ user.public_metadata.plan }}"}At mint time that becomes, for a given user:
{ "role": "admin", "email": "ada@example.com", "plan": "pro" }The standard claims Torii always stamps (iss, sub, pid, iat, exp, and
when present locale, org_id, org_role, act) are added alongside your
template’s claims, so you only author the custom part.
The default template and the session token
Section titled “The default template and the session token”One template per environment can be marked Default (the star in the
dashboard list). Its claims are applied to the session token, the token the
SDK holds and getToken() returns. With no default template, session tokens
carry only the standard claims.
const { getToken } = useAuth();const token = await getToken(); // session token: standard claims + the default templateChanging which template is the default takes effect on the user’s next token refresh; it doesn’t rewrite tokens already issued.
Shortcodes
Section titled “Shortcodes”A shortcode resolves a single user field. Use it as a whole value (the resolved value keeps its JSON type) or embedded in a string (interpolated as text):
{ "uid": "{{ user.id }}", // whole value "greeting": "Hi {{ user.first_name }}", // embedded "team": "{{ user.public_metadata.team || \"none\" }}" // with a default}| Shortcode | Resolves to |
|---|---|
{{ user.id }} | User id (same as sub) |
{{ user.email }} | Primary email address |
{{ user.email_verified }} | true / false |
{{ user.name }} | Full name |
{{ user.first_name }} / {{ user.last_name }} | Name parts |
{{ user.public_metadata }} | The whole public bag (object) |
{{ user.public_metadata.<key> }} | A key inside the public bag |
{{ user.unsafe_metadata }} / {{ user.unsafe_metadata.<key> }} | The unsafe bag |
|| "fallback"supplies a value when the shortcode resolves to nothing. Without a fallback, a whole-value shortcode that resolves to nothing omits that claim; an embedded one resolves to an empty string.privateMetadatais never available as a shortcode: it must not reach a token. See user metadata.
Named templates and getToken({ template })
Section titled “Named templates and getToken({ template })”Beyond the default, you can create additional named templates for tokens you
hand to third-party services (Hasura, Supabase, …). Mint one on demand with
getToken({ template }):
const { getToken } = useAuth();
// Mints a token from the template named "hasura" for THIS user.const hasuraToken = await getToken({ template: 'hasura' });
await fetch('https://my-hasura/v1/graphql', { headers: { Authorization: `Bearer ${hasuraToken}` },});A named-template token is returned to you for the call; it never replaces the
session token. getToken({ template }) caches each minted token in memory and
reuses it until ~30s before it expires (its lifetime is the template’s, see
below), then mints a fresh one. It resolves null if no template with that name
exists (or the session has ended).
Example “hasura” template projecting the Hasura namespaced claim:
{ "https://hasura.io/jwt/claims": { "x-hasura-default-role": "{{ user.public_metadata.role || \"user\" }}", "x-hasura-user-id": "{{ user.id }}" }}Token lifetime
Section titled “Token lifetime”A template may set its own lifetime (seconds). A token minted from it expires after that lifetime instead of the environment default, useful for short-lived third-party tokens.
Validation, reserved claims, and size
Section titled “Validation, reserved claims, and size”Templates are validated when you save them (and via the dashboard’s Preview, which renders against a sample user):
- Reserved claims are rejected. You can’t set the standard claims Torii
manages:
iss,sub,aud,exp,nbf,iat,jti,pid,act,locale,org_id,org_role. - Unknown shortcodes are rejected, so a typo can’t silently ship an empty claim.
- Size budget (~1.2 KB). The worst-case size of the custom claims is checked at save time and rejected if it could overflow the token’s budget. The JWT-eligible metadata bags are capped on write (512 bytes each), so selecting them can’t overflow at mint time.
Server-minted tokens
Section titled “Server-minted tokens”If you mint tokens on your backend (not via the SDK), the named-template endpoint is bearer-authenticated:
POST /_torii/auth/session/token?template=<name>Authorization: Bearer <the user's session access token>→ 200 { "accessToken": "<jwt>", "expiresAt": "<iso8601>" }→ 404 { "code": "jwt_template_not_found" }