Services (m2m)
Services are machine credentials — backend systems that call the
auth platform (or each other) without a human user. They are a
separate registry from applications: apps issue user tokens for
people; services mint service tokens
(token_type: "service") via the OAuth2 client_credentials grant.
Reference: ServicesModule ·
M2MClientRecord ·
Service tokens (Laravel)
All management routes are system_admin ONLY — these credentials sit below every other admin tier.
Create a service credential
import { createAuthClient } from '@rw3iss/auth-client';
const client = createAuthClient({ apiBaseUrl: 'https://auth.ryanweiss.net/api/v1', appCode: 'auth-client-demo',});await client.ready();await client.auth.login({ email: 'admin@ryanweiss.net', password: '…' });
const { client: svc, client_secret } = await client.services.create({ client_id: 'billing-svc', name: 'Billing backend', description: 'Issues invoices; needs publish scope on gsku', scopes: ['gsku:publish'],});
// ⚠ client_secret is visible exactly ONCE — store it in your secret// manager now. The server keeps only a bcrypt hash.console.log(svc.id, client_secret);Manage credentials
const services = await client.services.list(); // every non-revoked credentialconst one = await client.services.get(svc.id);
// Rotation = create a replacement, deploy it, revoke the old one:const next = await client.services.create({ client_id: 'billing-svc-2', name: 'Billing backend', scopes: ['gsku:publish'] });await client.services.revoke(svc.id); // soft-revokeHow services authenticate (the grant itself)
The client_credentials grant is deliberately not in this browser
SDK — it requires the secret, which never belongs in a browser. Your
backend exchanges it at POST /oauth/token and receives a service
token whose scopes claim downstream services verify locally:
curl -X POST https://auth.ryanweiss.net/api/v1/oauth/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=client_credentials&client_id=billing-svc&client_secret=…'Use the backend SDKs, which wrap this with caching + auto-renewal:
// Node — @rw3iss/auth-server-ts / -nestimport { ServiceTokenManager } from '@rw3iss/auth-server-ts';// PHP / Laravel — rw3iss/auth-server-laravel$token = VenService::token(); // cached, auto-renewsHttp::withToken($token)->post('https://other-svc/api/…');The receiving service validates the token locally (shared
JWT_ACCESS_SECRET, no network hop) and checks the scope — e.g. the
Nest adapter’s guards, or Laravel’s RequireServiceToken middleware.
How services relate to applications
Nothing structural ties an m2m client to an app row — a service
credential authorizes platform-internal calls, not user logins. The
typical pairing: an application (say claimleo) has a backend that
holds a service credential (claimleo-svc) for server-to-server work
like publishing data or calling another service’s admin API, while its
human users authenticate through the app registry as usual.
See also
- Applications — the user-facing registry.
- auth-server-nest → m2m — registering per-service permission catalogs.