Skip to content
rw3iss Auth

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 credential
const 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-revoke

How 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:

Terminal window
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 / -nest
import { ServiceTokenManager } from '@rw3iss/auth-server-ts';
// PHP / Laravel — rw3iss/auth-server-laravel
$token = VenService::token(); // cached, auto-renews
Http::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