# Optional application logout hooks

The live [Web/PHP 0.1.6 release](https://m7.org/docs/sdk/m7-identity/README.md#m7-identity-sdk-downloads) exposes
two optional mount points for applications that want local enforcement of
provider logout notifications. Applications may leave them unconfigured and rely
on their existing authority introspection/refresh-family revocation behaviour.
There is no SQL requirement or SDK-provided store, queue or scheduler.

## Configure application code

Set `M7_LOGOUT_HOOKS_FILE` to an absolute readable PHP file outside the public
document root and the installed SDK package. It returns an array of callables:

```php
<?php
require '/etc/my-app/bootstrap.php';
return [
    'ingest' => fn(array $event): bool => appLogout()->acceptVerifiedLogout($event),
    'is_revoked' => function (array $context): bool {
        if ($context['token_response'] && $context['id_token'] !== null) {
            appSessions()->associateTokenResponse($context);
        }
        return appLogout()->isSessionRevoked($context);
    },
];
```

The example services belong to your application and must be implemented.
Callbacks must return without printing, setting response headers or terminating
the request. Never log credential context.
Either callback can be omitted. An empty/unset setting disables both; a
configured invalid file or callback fails with HTTP 503
`logout_hooks_unavailable` when reached. Configuration loads once per request.

## Ingest verified notifications

`ingest` receives the trusted configured `issuer` and `client_id`, verified
`iat`, `exp`, `jti`, and the verified `sid` and/or `sub` when present. It
receives no raw logout JWT or browser credentials. The existing signature,
issuer/audience, time, event, nonce and encryption checks run first.

Return exactly `true` after durable invalidation or durable queue acceptance,
including an already-applied duplicate. Only then does the receiver send
HTTP 200 `ok`. A throw, `false` or another return type gives HTTP 503
`logout_ingestion_failed` with `retryable=true`. Provider retry policy is
separate. Application exception details are excluded from SDK logs/responses.

Without `ingest`, the receiver still acknowledges validated receipt only.
Durable queue acceptance can leave an asynchronous enforcement delay unless
your checker also sees queued invalidations immediately.

## Check session revocation

`is_revoked` receives these fields:

| Field | Meaning |
| --- | --- |
| `issuer`, `client_id` | Trusted deployment scope. |
| `stage` | Operation, such as `session_use`, `refresh`, `ack`, or `token_response`. |
| `access_token`, `refresh_token` | Selected credential inputs, strings or `null`. |
| `id_token`, `id_token_nonce` | Token-response ID token and expected login nonce, strings or `null`. |
| `token_response` | Whether this context comes from a successful server-to-server token response. |

Return exactly `false` to allow normal processing or `true` to stop it with
HTTP 401 `session_revoked`. Current-cookie operations clear SDK active/pending
cookies; pending ACK clears pending cookies. A supplied/candidate credential
does not clear a different current browser package. Throws/non-boolean results
give HTTP 503 `logout_check_failed`, retaining cookies for retry.

The checker runs before cookie/supplied-token session resolution, refresh,
pending-package use/installation, ACK, active-cookie installation and UserInfo.
It rechecks session resolution and refresh/ACK/UserInfo results after outbound
work. Checks may repeat within a request. The SDK does not recall already-sent
responses or make application storage and SDK requests atomic.

Your application must establish the session association. Credential strings
can be unvalidated browser input; the SDK supplies no decoded identity claim
as a trusted lookup key. At `token_response`, validate ID-token issuer,
client audience, nonce and signing/encryption policy before mapping credential
digests to the root `sid`/subject. Preserve root identity across refresh and
associate pending successors before ACK. Decide how unknown/legacy credentials
are handled; a fresh sign-in may be required. The `token_response` marker does
not itself assert ID-token claim validation. Existing signing/encryption
policies are unchanged.

## Independent protected endpoints

Host-owned pages/APIs and independent application sessions need their own
integration. After loading the SDK `shared.php`, an endpoint can call:

```php
m7_logout_require_session('my_api', ['access_token' => $accessToken]);
// Continue normal signature, proof, expiry and permission checks.
```

The helper returns when disabled or allowed, otherwise throws
`M7LogoutHookException`. If your framework owns exception handling, catch it
and deny access using `httpStatus()`/`response()`. Its optional third argument
`all` or `pending` clears SDK cookies on revocation; the default `none` suits
independent API credentials. This does not destroy a host PHP session or
replace endpoint authorization. Pure signature/expiry validation without a
revocation integration retains an access-token window until expiry.

## Application storage duties

Scope writes/lookups by issuer and client; match `sid`/`sub` consistently.
Handle duplicates idempotently and delayed/out-of-order events without
revoking unrelated/new roots. Define the subject-only policy explicitly.
Make accepted writes visible to every worker/node. Checking must not consume
an invalidation needed by later requests. Keep records through the affected
credential, pending-package and application-session lifetimes; the logout
JWT's short expiry is not sufficient retention. Cleanup is application-owned.

The SDK's front-channel receiver retains its existing browser-cookie cleanup;
these hooks do not add verified front-channel ingestion or stored `sid`
matching. Register and test receivers using the
[provider logout contract](https://m7.org/docs/api/sso.user.m7.org/logout-notifications.md).
