Status: shipped 2026-08-04.
Signet's session-protected GET {base_path}/token endpoint mints EdDSA JWTs with the key published at {base_path}/jwks. With no query parameter it preserves the better-auth JWT-plugin shape: the public user object plus server-owned registered claims and a 15-minute lifetime.
Named templates are an opt-in Signet extension for relying parties that need a smaller or different claim set:
[jwt]
default_expires_in = 900
[[jwt.templates]]
name = "supabase"
audience = "authenticated"
expires_in = 3600
[jwt.templates.claims]
role = "authenticated"
email = "{{user.email}}"
session_id = "{{session.id}}"
app_metadata = { provider = "signet", profile = "{{user.publicMetadata}}" }Request it with the same live session cookie used by the rest of the auth API:
const response = await fetch("https://auth.example.com/api/auth/token?template=supabase", {
credentials: "include",
})
const { token } = await response.json()An unknown template returns HTTP 400 JWT_TEMPLATE_NOT_FOUND; a caller without a live session still receives 401 first. Template names are 1–64 ASCII letters/digits plus ., _, or -, must start with a letter/digit, and are unique. At most 32 templates may be configured. Each lifetime is 60–86,400 seconds and each configured claim tree is limited to 8 KiB and eight nesting levels.
Claim ownership
A named template is an allow-list. It starts empty, resolves only the configured claims, and then Signet adds these registered claims:
| Claim | Named-template value |
|---|---|
iss | the instance's full auth issuer (server.base_url + server.base_path) |
sub | authenticated user id |
iat | current Unix time |
exp | iat + expires_in |
aud | template audience, or server.base_url when omitted |
Putting iss, sub, iat, exp, or aud inside claims stops boot. audience is the only declarative registered-claim override. Signet owns the issuer/subject/time facts so metadata cannot turn a token into another principal, issuer, or lifetime.
Claim values may be strings, booleans, numbers, arrays, objects, or null. A string which is exactly one placeholder is replaced while preserving its JSON type; interpolation inside a larger string is rejected. If an allowed optional field is absent, its value is JSON null.
Available user placeholders:
{{user.id}} {{user.email}} {{user.name}} {{user.image}}
{{user.emailVerified}} {{user.createdAt}} {{user.updatedAt}}
{{user.role}} {{user.banned}} {{user.banReason}} {{user.banExpires}}
{{user.username}} {{user.displayUsername}} {{user.isAnonymous}}
{{user.publicMetadata}} {{user.unsafeMetadata}}Available session placeholders:
{{session.id}} {{session.userId}} {{session.expiresAt}}
{{session.createdAt}} {{session.updatedAt}} {{session.ipAddress}}
{{session.userAgent}} {{session.activeOrganizationId}} {{session.impersonatedBy}}There is deliberately no {{user.privateMetadata}} or {{session.token}}. Both are rejected at boot rather than silently rendered. Templates can copy public/unsafe metadata, so authorization policy should prefer operator-owned static claims or data from a trusted application database; do not treat user-writable unsafe metadata as an entitlement.
Relying-party boundaries
The template changes claims; it does not configure the relying party. Verify the compact token's signature against {base_path}/jwks, require EdDSA, bind the expected iss and aud, validate exp/iat, and make authorization decisions from an explicit claim contract. Key rotation and cache behavior remain relying-party operational concerns.
The supabase example is a Supabase-style role/audience shape, not a promise that a hosted Supabase project accepts Signet as a first-class provider. Supabase's current third-party-auth catalog is provider-specific even though its verifier requires asymmetric JWTs, OIDC discovery, and a kid; confirm the current project integration before deployment. Signet emits asymmetric EdDSA tokens with a kid and matching discovery issuer, but claim shape alone cannot enroll an issuer.
Firebase custom tokens are not supported by this feature. Firebase requires RS256 with a Google service-account issuer and subject, a fixed Google audience, a uid, and at most a one-hour life. Signet deliberately signs EdDSA as its own issuer and will not let a template impersonate a Google service account. Use the Firebase Admin SDK/service-account signer when Firebase custom tokens are required.
Primary references retrieved 2026-08-04:
- Supabase third-party auth overview:
https://supabase.com/docs/guides/auth/third-party/overview - Firebase custom-token contract:
https://firebase.google.com/docs/auth/admin/create-custom-tokens