Authorize an inbound machine at a PHP service
Token/PHP 0.1.4 supports two online operations. introspect() asks an
authorization server for standard RFC 7662 token activity. M7-specific
introspectAcl() asks SSO for the receiving application's current
principal-list decision on an inbound machine token. The latter returns a
receiver-list principal. Ordinary-machine checks can enroll that machine;
personal admin owner proxies require two existing memberships as described
below. The live Token/PHP 0.1.4
ZIP and its integrity sidecars were verified on 2026-09-30; its clean-extraction
conformance suite exercises this ACL operation.
Set up the receiving application
Bind the receiving OAuth application to an active personal or organization principal list through API User. Choose the initial Active, Pending, or Disabled membership status, optional rejection switches, and ordered app/org ACL rules. App rules match the issuing machine's registered app; org rules match its current owning organization. The first matching rule selects the policy status, otherwise the initial status applies. The list's consumer or tenant human pool is fixed; machine principals can be members of either kind of list. List membership is separate from organization Management membership.
Register the exact resource audience accepted by the receiving application.
The machine app obtains a client-credentials access token with that audience
and sends it to the receiving resource. SSO requires at least one value in the
token's signed aud to match one of the authenticated receiver's current
allowed_audiences rows. The receiver's public Client ID need not appear in
the token's aud. The receiver authenticates to SSO with its own confidential
client credentials; callers cannot select another receiver's policy by a form
field. Resolve m7_acl_introspection_endpoint from trusted SSO discovery, or
the top-level mTLS ACL extension for certificate client authentication.
Check the request
$report = $identity->introspectAcl($inboundToken, [
'auth' => [
'method' => 'client_secret_basic',
'client_id' => $receiverClientId,
'client_secret' => $receiverClientSecret,
],
]);
if (!$report->allowed()) {
throw new RuntimeException('Inbound machine access denied');
}
$principal = $report->principal(); // receiver list: id, status, list
$confirmation = $report->confirmation(); // cnf when sender-bound
The SDK sends the raw inbound token to SSO's POST /introspect/acl as the
token form field and returns an AclIntrospectionReport. ok() means a
valid M7 ACL response was received; tokenActive() reports verified activity;
allowed() is true only for an active token and active effective ACL status.
Pending, disabled, expired, unbound, malformed, and failed requests deny.
For an ordinary machine, principal() identifies the machine in the receiving
list, independently
of any m7.principal claim in the inbound token. An active result carries the
current stored role and data even if the receiving application's two
token-enrichment switches are off. Pending or denied results omit both. The
list's selectable roles and default govern assignment separately; removing a
choice does not erase an existing member's stored role from this live result.
An inbound token's own role and data, if enriched at issuance, remain a snapshot.
On a verified qualifying ordinary-machine check, SSO can create the stable
principal and list membership. A rule's optional token_expires_after_seconds proposes a sliding
deadline for principal_lists.expires, bounded by the inbound token's signed
expiry. Later qualifying checks extend the stored expiry only when the new
deadline is later; a disabled member is not reactivated. This is shared list
state and can be seen by other applications using the list. The decision's
cache deadline is also token-bounded. Neither deadline changes or revokes a
token already issued. Call the ACL endpoint on every protected request when
policy removal must take effect immediately.
If confirmation() contains jkt, validate the DPoP proof on the inbound
resource request: signature, method, URL, access-token hash, freshness, and
replay. If it contains x5t#S256, compare the certificate presented to the
resource's trusted TLS boundary. Check both when both are present, and reject
unsupported confirmation shapes. SSO cannot verify this proof because it does
not receive the resource request. The resource also enforces its own route,
scope, object ownership, and business permissions after the ACL decision.
Manage the attached list members
The prepared Token/PHP 0.1.5-rc.1 candidate adds
M7IdentitySDK::upsertAclMember(principalId, changes, options) and
AclMembershipReport. The immutable published 0.1.4 ZIP omits this operation;
use the candidate support status and
verify the deployed membership endpoint contract
before integration.
Attach the receiver to a principal list and enable Allow application to manage members on its registration. The receiver's confidential credentials select that attached list; the SDK accepts neither a caller-supplied list ID nor an access token for this operation. Keep the secret in trusted backend configuration.
$report = $identity->upsertAclMember($knownGlobalPrincipalId, [
'status' => 'active',
'role' => 'member',
], [
'auth' => [
'method' => 'client_secret_basic',
'client_id' => $receiverClientId,
'client_secret' => $receiverClientSecret,
],
]);
if (!$report->ok()) {
throw new RuntimeException($report->reason());
}
$membership = $report->membership();
$list = $report->list();
Supply an existing global user or machine principal UUID and a nonempty change
map containing only status, role, expires, and data. New membership
requires status active, pending or disabled. Omitted fields preserve
existing values; null explicitly clears expiry or data. Expiry uses UTC
Y-m-d H:i:s, and data is an array/object map or null. A role must match the
list's permitted role policy. Approving a pending member preserves its existing
expiry unless you explicitly change it. Membership association IDs remain
stable and updates affect every application sharing the list.
The SDK sends a bounded JSON POST to
https://sso.user.m7.org/acl/members/upsert; certificate client authentication
uses the mtls-sso.user.m7.org alias. Basic, Post and the two existing mTLS
methods are supported; server-side JWT assertion methods require a different
HTTP client. A custom endpoint is trusted deployment configuration and must
be HTTPS. Invalid UUIDs, changes or authentication fail before sending.
ok() means HTTP 200 and a structurally valid membership bound to the exact
principal and returned list. Inspect membership() and list() only after
success; they are null on failure. reason(), httpStatus() and response()
retain failure information. Do not log credentials or membership data.
Transport errors, excessive bodies, malformed JSON and foreign memberships
fail closed. The method does not retry, acquire tokens or persist local state.
An active membership still passes through receiver deny rules, organization policy, expiry and owner-proxy gates. Keep checking inbound sender proof and resource permissions for every protected request.
Personal admin owner proxies
A signed boolean machine_owner_proxy: true marks a personal admin owner
proxy. SSO verifies the exact token, current scope/audience policy and active
personal admin registration with the client_credentials grant. It resolves
the current registered owner account and requires both the proxy app and owner
user to have existing active, unexpired memberships in the same receiving
consumer list. Both principal records and the owner account must also be active.
This path never enrolls either actor or extends a lease.
An allowed response keeps the standard principal shape but returns the
owner user's global principal ID, role, and data. The proxy membership only
gates access; its role contributes no permission. The additional binding is
available through the existing report accessors:
$principal = $report->principal();
$proxy = $report->value('owner_proxy');
$traceId = $report->value('trace_id');
| Binding field | Meaning |
|---|---|
client_id |
Proxy app's public OAuth Client ID. |
principal_id |
Proxy app's global list-principal ID. |
owner_uid |
Current registered owner's consumer-account UID. |
owner_name |
Owner account name, or null. |
The SDK validates the base ACL response shape and retains the binding; it
does not validate the additional owner binding or rewrite the token's machine
subject. Direct consumers must interpret and validate the binding before
assigning the effective user. Match its client_id to the validated token's
machine identity (id, sub, and client_id). Use this binding only for a
verified signed boolean machine_owner_proxy: true; reject missing,
malformed, or unexpected bindings and require non-nil UUIDs for the proxy
principal and owner UID. If the token carries a signed m7.principal.id, match that
ID to owner_proxy.principal_id; the returned principal.id belongs to the
user. Updated shared M7Roster performs these checks and supplies kind=user,
id=principal.id, and rid=owner_proxy.owner_uid to the receiving app.
A failed membership gate returns an active-token authorization denial with
allowed: false, policy_status: null, and no principal or owner binding.
An optional trace_id is a support reference. Token/profile rejection returns
token_active: false. Ordinary-machine responses omit owner_proxy.
The owner-proxy decision expiry is bounded by token expiry and both membership
leases; its membership status/expiry fields describe the owner membership.
This response handling uses the existing Token/PHP 0.1.4 report accessors; server acceptance follows SSO's current owner-proxy ACL contract. The resource still validates inbound sender proof and its own permissions. Current M7Roster refuses sender-bound tokens until inbound proof enforcement is connected.
Serverless entry for a consumer service
M7 can provide identity, list enrollment, policy decisions, and a shared expiring membership record, reducing account and principal administration a consumer service must build. The receiving service still handles trusted credential configuration, inbound sender proof, request authorization, resource state, and its own permission model. Mail currently has an isolated ACL test route; that route does not replace Mail's internal authorization or establish universal production enforcement.
See API User principal lists for list and app configuration and SSO authorization for the endpoint response and failure contract.