Skip to content
rw3iss Auth

Service tokens (m2m)

Machine credentials for one backend calling another (e.g. ClaimLeo → GlobalSKU “publish item”). This is not end-user identity — no human, no session, no shadow user (that’s the SSO bridge). It mirrors the Nest adapter’s ServiceAuthClient / ServiceOnlyGuard.

Backed by the auth-server’s OAuth2 client_credentials grant: a system_admin registers an m2m client once (POST /admin/m2m-clients — the secret is shown once); the calling service exchanges it for a short-lived service token (token_type: "service", scopes claim, 15-minute lifetime); the receiving service validates locally (shared HS256 secret) and enforces scopes.

Outbound — mint + attach

AUTH_M2M_CLIENT_ID=claimleo-svc
AUTH_M2M_CLIENT_SECRET=<from registration>
AUTH_M2M_SCOPES="gsku:publish" # optional; empty = full client scope set
use rw3iss\AuthServer\Laravel\Facades\VenService;
use Illuminate\Support\Facades\Http;
// Sugar — registered Http macro:
Http::withVenServiceToken()->post('https://globalsku.app/api/…', $payload);
// Or explicitly:
Http::withToken(VenService::token())->post($url, $payload);
// After a downstream 401 (early revocation), force a re-mint:
VenService::fresh();

Tokens are cached (vauth:svc_token:{client_id}) until expires_in − AUTH_M2M_TTL_MARGIN (default 60 s) — a busy app mints at most once per expiry window.

Inbound — machine-only routes

Route::post('/partner/publish', PublishController::class)
->middleware('vauth.service_only:gsku:publish');
  • Validates the bearer locally — no network hop.
  • Requires token_type: "service"; a valid user token gets 403 SERVICE_REQUIRED.
  • Every middleware parameter is a required scope (ALL must be present; colons inside scope codes are fine).
  • On success the ServicePrincipal is available:
$svc = $request->attributes->get('ven_service');
$svc->id; // client_id
$svc->serviceName; // "ClaimLeo backend"
$svc->hasScope('gsku:publish');

Failure envelopes

StatuscodeWhen
401UNAUTHENTICATEDmissing / invalid / expired bearer
403SERVICE_REQUIREDa valid user token on a machine-only route
403MISSING_SCOPEservice token lacking a required scope (details.required)

Notes

  • Scopes are resource:action strings — same convention as user permissions, but they live on the m2m client, not on roles.
  • Rotation: register a replacement client, switch env, soft-revoke the old one; outstanding tokens die within their 15-minute lifetime.
  • Full guide with registration walkthrough: docs/SERVICE_TOKENS.md.