Authorization
Peak Gateway uses two authorization models: OAuth2 scopes for API clients (machine-to-machine) and Firebase roles for portal users (human users). Gateway tenant targeting is organization/location based.
OAuth2 Scopes (API Clients)
API clients authenticate via OAuth2 client_credentials flow with the auth service, then use Bearer tokens with scoped access.
| Scope | Description | Endpoints |
|---|---|---|
txn:process | Process scoped payment transactions on API-client surfaces | Card-not-present transaction APIs on online-txn; processing endpoints that explicitly use OAuth scope checks |
txn:sync | Sync terminal-approved card-present transactions and refunds | POST /api/v1/transactions/sync, POST /api/v1/transactions/sync/refund on card-present |
session:create | Create online checkout sessions and hosted payment forms | POST /api/v1/checkout/sessions, POST /api/v1/hosted/* on online-txn |
merchant:activate | Activate payment processors for locations | POST /api/v1/locations/{id}/activate-transit on management |
provision:request | Request and manage merchant onboarding jobs | /api/v1/provisioning/jobs/* on merchant-onboarding |
organizations:read | Read organization and org-scoped location records | GET /api/v1/organizations/{organizationId}/locations/* on management |
organizations:write | Create organizations and create, provision, or update organization-bound locations | POST /api/v1/organizations, POST/PUT /api/v1/organizations/{organizationId}/locations/* on management |
batch:manage | Manage settlement batches (retry stuck, force-close) | POST /api/v1/settlements/{id}/retry, POST /api/v1/settlements/{id}/force-close on processing |
products:read / products:write | Read / manage storefront products, prices, and promotion codes | /api/v1/products/*, /api/v1/prices/*, /api/v1/promotion-codes/* on online-txn |
coupons:read / coupons:write | Read / manage storefront coupons | /api/v1/coupons/* on online-txn |
customer:read / customer:write | Read / manage stored storefront customers | /api/v1/customers/* on online-txn |
shipping:read / shipping:write | Read / manage storefront shipping configuration | /api/v1/shipping/config on online-txn |
admin:* | Wildcard admin access, grants all scopes | All endpoints across all services |
admin:* controls scopes, not portal roles. Organization targeting is independent of scopes:
organization_idsbinds an OAuth client to specific organizations- an empty
organization_idsset is treated as a platform-admin/global client and is not available through customer-managed OAuth client APIs
Token claims use snake_case (organization_ids), while management API payloads and portal DTOs use camelCase (organizationIds) for the same fields.
An OAuth client with admin:* and organization_ids=["org_123"] can access organization-scoped management endpoints for org_123 only. Customer-managed OAuth clients cannot request admin:*, organizations:write, platform-admin ownership, global access, or unbound all-organization access.
Management authorizes OAuth tenant access from signed token claims only. It does
not call auth during request authorization to recover stored client
organization bindings. After changing a client's organization bindings, mint a
new access token before calling management; already-issued tokens without the
required organization_ids / organizationAccess claims are rejected until
they expire or are replaced.
Typical Client Configurations
| Client Type | Scopes |
|---|---|
| POS Terminal sync client | txn:sync |
| E-Commerce Integration | session:create, txn:process |
| Device Management System | provision:request |
| Management/support onboarding client | organizations:read, organizations:write |
| Admin Dashboard | admin:* |
Customer-Managed OAuth Clients
Merchant portal users can manage their own machine clients through the customer OAuth client surface:
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/customer-oauth-clients | List customer-owned clients for the caller's organizations |
POST | /api/v1/customer-oauth-clients | Create an organization-bound customer client |
GET | /api/v1/customer-oauth-clients/{clientId} | Get an owned client |
PUT | /api/v1/customer-oauth-clients/{clientId}/scopes | Update allowed scopes |
POST | /api/v1/customer-oauth-clients/{clientId}/secret:rotate | Rotate the client secret |
DELETE | /api/v1/customer-oauth-clients/{clientId} | Revoke the client |
Customer-managed clients are persisted in the canonical oauth_clients registry with owner_type=CUSTOMER. Their organization_ids are auto-bound to the caller's allowed organization set or to a requested subset. Reads, updates, rotations, and revocations require both owner_type=CUSTOMER and organization ownership.
Customer-created clients can request only customer-safe scopes: txn:process, txn:sync, session:create, batch:manage, gift_cards:read, gift_cards:write, and organizations:read.
This surface is separate from:
- platform-admin OAuth client management at
/api/v1/oauth-clients, which can manage all registry rows - auth-service admin management at
/api/v1/clients customer_identities, which links end-customer identity-provider subjects and does not store Gateway OAuth client credentials
Registering a Platform OAuth Client
- Admin Portal → OAuth Clients → Register
- Assign scopes matching the client's use case
- Bind the client to organization IDs when it should be restricted to specific customer organizations
- Note the
client_idandclient_secret - Client calls
POST /auth/oauth2/tokenwithgrant_type=client_credentialson the public gateway domain
Portal Roles (Firebase Auth)
Portal users authenticate via Firebase Auth (Google SSO or SAML). Roles are hierarchical, higher ranks include all lower rank permissions.
| Role | Rank | Description |
|---|---|---|
super_admin | 0 | Full access including SAML SSO configuration and user deletion |
admin | 1 | Full operational access across organizations, locations, users, transactions, and settlements |
location_admin | 2 | Location-scoped operator who manages assigned organization/location settings, users, transactions, and subscriptions |
location_user | 3 | Location-scoped portal user with read-heavy access to assigned organization/location data |
readonly | 4 | Read-only access to assigned organization/location data |
Token model
Firebase custom claims carry direct grants only (anchors) — never an expanded, pre-computed access list:
role: platform tier (super_admin,admin), if anyorganizationAccess: explicit per-organization role entrieslocationAccess: explicit per-location role entriesagentAccess: explicit per-agent (ISO) role entries — deliberately compact; an agent's descendant subtree is never embedded in the token
Every wider scope — an organization's full location set, an agent's descendant subtree — is expanded server-side from these anchors on each request, by consulting the database. A token never carries a derived or promoted grant.
A user can hold at most 25 direct locationAccess entries. This cap keeps
the worst-case claims payload comfortably under Firebase's 1000-byte
custom-claim limit; a user needing broader reach takes an explicit
organization-level grant instead, which is a single claim entry regardless
of how many locations the organization has. Requests that would exceed the
cap are rejected at write time.
Revocation is bounded at the check interval (60 seconds by default):
whenever a grant is revoked, downgraded, or a user is deactivated, the
service rewrites custom claims and calls Firebase's refresh-token
revocation for that user. Token signatures are verified locally on every
request; the remote revocation lookup (tokensValidAfterTime + disabled
flag) runs at most once per user per interval and is cached in between —
so a revoked or disabled principal keeps working for at most one interval,
the same bound as the server-side grant and subtree caches, without a
Firebase round-trip on every request.
Authorization Annotations
Services enforce authorization via two primary authorization patterns:
@RequireScope(...): Used on controllers that serve API clients, including online-txn payment/session APIs, merchant-onboarding provisioning APIs, processing OAuth exception paths, and card-present terminal sync.@RequireManagementPermission(...): Used on management controllers for portal users and backed byManagementAuthorizer@RequireRole(...): Used for portal/location-user service surfaces, including card-present sale/auth and role-protected processing operations.
OAuth clients are not promoted into Firebase-style portal roles. Merchant targeting comes from OAuth token merchant claims, while management permissions remain role-based unless an endpoint explicitly opts into OAuth scope checks.
The scope checks live in libs/security, while management permission enforcement lives under services/management/.../security.
Endpoint Authorization Matrix
Auth Service
| Method | Path | Auth |
|---|---|---|
GET | /health | Public |
POST | /api/v1/clients | Admin (super_admin, admin) |
GET | /api/v1/clients | Admin (super_admin, admin) |
POST | /api/v1/clients/{clientId}/secret:rotate | Admin (super_admin, admin) |
DELETE | /api/v1/clients/{clientId} | Admin (super_admin, admin) |
GET | /api/v1/me | Authenticated |
Processing Service
Most processing controllers are role-protected internal/portal surfaces. OAuth-exposed processing paths are listed only when the controller declares @RequireScope(...).
| Method | Path | Auth |
|---|---|---|
POST | /api/v1/gift-cards/* | gift_cards:write / gift_cards:read by operation |
GET | /api/v1/gift-cards/* | gift_cards:read |
Card-Present Service
| Method | Path | Auth |
|---|---|---|
POST | /api/v1/transactions/sale | location_user or higher |
POST | /api/v1/transactions/auth | location_user or higher |
POST | /api/v1/transactions/sync | txn:sync |
POST | /api/v1/transactions/sync/refund | txn:sync |
Management Service
Most management endpoints remain portal-role-based. The OAuth-exposed paths are the explicit exceptions below.
| Method | Path | Min Role |
|---|---|---|
POST | /api/v1/locations | admin |
GET | /api/v1/locations | readonly |
PUT | /api/v1/locations/{id} | location_admin |
POST | /api/v1/saml-providers | super_admin |
GET | /api/v1/audit-log | admin |
GET | /api/v1/customer-oauth-clients | organization_admin/location_admin/readonly scoped to caller organizations |
POST | /api/v1/customer-oauth-clients | organization_admin/location_admin scoped to caller organizations |
Management Service (OAuth exceptions)
| Method | Path | Scope |
|---|---|---|
POST | /api/v1/organizations | organizations:write, admin:* |
POST | /api/v1/locations/{locationId}/activate-transit | merchant:activate |
POST | /api/v1/organizations/{organizationId}/locations | organizations:write or admin:* |
GET | /api/v1/organizations/{organizationId}/locations/{locationId} | organizations:read or admin:* |
PUT | /api/v1/organizations/{organizationId}/locations/{locationId} | organizations:write or admin:* |
GET | /api/v1/organizations/{organizationId}/locations/by-external-id?externalId=... | organizations:read or admin:* |
POST | /api/v1/organizations/{organizationId}/locations/online/provision | organizations:write or admin:* |
GET | /api/v1/event-subscriptions | admin:* |
GET | /api/v1/event-subscriptions/{subscriptionId} | admin:* |
POST | /api/v1/event-subscriptions | admin:* |
PUT | /api/v1/event-subscriptions/{subscriptionId} | admin:* |
DELETE | /api/v1/event-subscriptions/{subscriptionId} | admin:* |
Online-Txn Service
| Method | Path | Auth |
|---|---|---|
POST | /api/v1/checkout/sessions | session:create |
POST | /api/v1/checkout/sessions/{id}/pay | Public |
POST | /api/v1/tokens | session:create |
POST | /api/v1/api-keys | Merchant auth |
GET | /api/v1/webhooks/events | Public |
GET/POST/PATCH | /api/v1/products/*, /api/v1/prices/*, /api/v1/promotion-codes/* | products:read / products:write |
GET/POST/PATCH | /api/v1/coupons/* | coupons:read / coupons:write |
GET/PUT | /api/v1/customers/* | customer:read / customer:write |
GET/PUT/DELETE | /api/v1/shipping/config | shipping:read / shipping:write |
Merchant-Onboarding Service
| Method | Path | Scope |
|---|---|---|
POST | /api/v1/provisioning/jobs | provision:request |
GET | /api/v1/provisioning/jobs/{id} | provision:request |