Service tokens (machine / kiosk)
Service tokens are admin-minted JWTs for clients that cannot complete an interactive login: TV displays, kiosks, cron jobs, integration scripts.
They reuse the same ES256 keys and JWKS as human access tokens, carry the existing RBAC of one application (aud + permissions), and can be revoked via RFC 7662 introspection.
| Issuer | https://admin.fixweb.cloud |
| JWKS | https://admin.fixweb.cloud/.well-known/jwks.json |
| Introspect | https://admin.fixweb.cloud/introspect |
| Admin console | https://admin-admin.fixweb.cloud |
When to use (and when not)
Use a service token when there is no human at the keyboard and the client must call an API as that application.
Do not use one for a browser SPA that can run authorization code + PKCE — human tokens remain the right path (refresh, per-user grants, logout).
Create (tenant admin)
- Open Apps → select the application → Service tokens → Create.
- Pick a name, TTL (default 365 days; optional « no expiration »), and
permissions from the app's catalogue. app:{id}:access is always included.
- Copy the JWT once — it is never shown again and never stored server-side.
API equivalent:
POST /api/admin/service-tokens
Authorization: Bearer <admin access token>
Content-Type: application/json
{
"name": "lobby-tv",
"application_id": "jarvis",
"permissions": ["app:jarvis:access"],
"expires_in_days": 30
}
expires_in_days: null mints a token without exp. Prefer a finite TTL.
Token shape
{
"iss": "https://admin.fixweb.cloud",
"aud": "<application_id>",
"sub": "svc:<token-id>",
"jti": "<token-id>",
"mode": "access",
"type": "service",
"iat": 1710000000,
"exp": 1741536000,
"properties": {
"tokenID": "<token-id>",
"name": "lobby-tv",
"application_id": "<application_id>",
"permissions": ["app:jarvis:access"],
"apps": ["<application_id>"]
}
}
iss is always the IdP's public issuer (ISSUER), never the admin console host.
Verify in your backend
- Fetch JWKS from
https://admin.fixweb.cloud/.well-known/jwks.json. - Verify signature (ES256),
iss,aud(= your app id),type === "service". - Read
properties.permissions.
Checking aud is mandatory: a service token minted for another app on the same IdP must not authorize against your permission names.
See Integration for library samples (adapt type / aud).
Introspection (revocation)
POST /introspect (application/x-www-form-urlencoded, field token).
Authenticate with either:
- a confidential OAuth client (
client_id+client_secret— see console
OAuth clients), or
- auto-introspection: present the service token alone (no client secret).
Response when active:
{
"active": true,
"sub": "svc:…",
"aud": "jarvis",
"exp": 1741536000,
"jti": "…",
"application_id": "jarvis",
"name": "lobby-tv",
"permissions": ["app:jarvis:access"]
}
Otherwise { "active": false } with no reason (RFC 7662). Cache for ~60 s so revocation is effective within a minute without hammering the IdP.
Revoke from the console or:
POST /api/admin/service-tokens/:id/revoke
Authorization: Bearer <admin access token>
Kiosk / URL pattern
Example: https://leaderboard.example.com/?token=<JWT> → front reads ?token= and calls the API with Authorization: Bearer <JWT>.
Precautions:
- One token per device — so you can revoke a single kiosk.
- Short TTL when the token lives in a URL.
- Strip the query after read (
history.replaceState) — query strings land
in browser history, CDN logs, and the Referer header.