# 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

```php
$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.

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

```php
$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](https://m7.org/docs/api/sso.user.m7.org/authorization.md#personal-admin-owner-proxies).
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](https://m7.org/docs/api/api.user.m7.org/principal-lists.md)
for list and app configuration and [SSO authorization](https://m7.org/docs/api/sso.user.m7.org/authorization.md#inbound-machine-principal-list-acl)
for the endpoint response and failure contract.
