# M7 OAuth 2.0 and OpenID Connect Provider Profile

<!-- m7-identity-discovery:begin -->
## Connect to M7 Identity

Issuer: <https://sso.user.m7.org>

Discovery JSON: <https://sso.user.m7.org/.well-known/openid-configuration>

- [Discovery and signing keys](https://m7.org/docs/api/sso.user.m7.org/discovery-and-keys.md)
- [Integration quickstart](https://m7.org/docs/api/sso.user.m7.org/quickstart.md)
- [Provider profile](https://m7.org/docs/api/sso.user.m7.org/provider-profile.md)
- [M7 Identity SDK and current downloads](https://m7.org/docs/sdk/m7-identity/)

Live discovery supplies endpoint locations and advertised capabilities. Use the
provider profile for the integration contract and current testing status.
<!-- m7-identity-discovery:end -->

Status: Release-candidate public contract\
Profile version: `M7-OIDC-1.0-RC6`\
Last reviewed: 2026-09-20

This profile defines the interoperable OAuth 2.0 and OpenID Connect behavior
that an external M7 client can rely on. It consolidates the choices M7 makes
where the underlying standards permit several behaviors. The endpoint guides
linked from this page remain the detailed request and response references.

This is a release-candidate profile, not an OpenID certification claim. The
independent-library, RFC 8705 mTLS, and scoped JAR interoperability campaigns
have passed; the official OpenID conformance-suite run remains pending.

The server supports all 15 asymmetric signing profiles below. Shared issuance and personal
Device Authorization/strict-refresh flows passed live algorithm tests; normal
browser sign-in and refresh were confirmed by the operator. After deploying
the updated Identity SDK, the operator also confirmed Ed25519 browser sign-in
and refresh. This does not claim a separate authorization-code campaign for
every algorithm or ML-DSA browser-cookie compatibility.

HS256/384/512 client-secret signing additionally passed client credentials,
device/strict-refresh and browser login/profile/refresh. See [HMAC setup and
acceptance boundaries](./registration.md#hmac-token-signing). Both live discovery
documents advertise these three algorithms alongside the 15 asymmetric
profiles, for 18 total.

HMAC ID tokens are restricted to confidential clients. The User.M7/SSO update
rejecting `token_endpoint_auth_method=none` with HMAC is implemented and
locally tested; production deployment verification remains pending. The
earlier public-device HMAC test is historical signing evidence, not an
eligible configuration under this policy. See the setup guide above for
errors, existing-client repair and the retained shared-storage boundary.

At 2026-09-15 21:49:24 UTC, both live discovery documents returned identical
metadata with all 18 ID-token algorithms. Provider JWKS covered all 15
asymmetric profiles and contained no symmetric material. HMAC discovery is
deployed and advertised. Consumers MUST use the live advertisement when
selecting automatically; see [HMAC and discovery](https://m7.org/docs/api/sso.user.m7.org/discovery-and-keys.md#hmac-and-discovery).

All ten authorization response modes and JARM RS256/RS512 signing are deployed
and advertised, verified from both discovery documents at 2026-09-15 06:24 UTC.
Direct authorization, PAR and cancellation passed live for each mode,
including both flexible Web Message presentations. These checks do not extend
the certification or iframe account-reuse claims below. Separately, the
2026-09-16 encrypted-response campaign passed inline-key RS256 login, encrypted
`form_post.jwt`, UserInfo, strict refresh and cancellation. Both live metadata
documents advertised RSA-OAEP-256/A256GCM at 2026-09-16 03:08:43 UTC. See
[response encryption](https://m7.org/docs/api/sso.user.m7.org/response-encryption.md) for receiving requirements.

The key words **MUST**, **MUST NOT**, **SHOULD**, and **MAY** describe the M7
integration contract. They do not broaden an application's registered grants,
scopes, audiences, or ownership.

## Profile identity and discovery

| Item | Contract |
| --- | --- |
| Authorization-server issuer | `https://sso.user.m7.org` |
| OpenID discovery | `https://sso.user.m7.org/.well-known/openid-configuration` |
| OAuth authorization-server metadata | `https://sso.user.m7.org/.well-known/oauth-authorization-server` |
| Subject type | `public` |
| Authorization response | `code`; query, form POST, fragment, signed JARM and Web Message modes |
| Profile version | `M7-OIDC-1.0-RC6` |

Clients MUST use discovery for endpoint locations and advertised capability
sets. This profile defines behavior and constraints that cannot be represented
completely in discovery metadata. Automatic negotiation MUST treat an absent
advertisement as unavailable. Explicitly configured HMAC clients use the
separate shared-secret contract linked above. Account-level two-factor settings have no discovery flag;
their hosted behavior is described separately below.

M7 publishes asymmetric signing keys at the discovered `jwks_uri`. For those profiles, clients MUST select a
key by the signed JWT's exact `kid` and supported `alg`, verify the signature,
and then validate the token-class claims. They MUST NOT trust a decoded JWT
without verification.

### Issuer rules by token class

| Token or response | Issuer rule |
| --- | --- |
| New ID token | Exact discovery issuer, `https://sso.user.m7.org` |
| Migrating ID token | The documented transitional allowlist may also contain legacy `id.m7.org` |
| Signed UserInfo response | Exact discovery issuer |
| Signed JARM authorization response | Exact discovery issuer; audience is the public OAuth client ID |
| Web Message response | `iss` equals the exact discovery issuer; browser sender origin and window must also match |
| Newly issued M7 access token | Canonical issuer `https://sso.user.m7.org`; legacy `id.m7.org` is an explicit migration allowance only |

The legacy ID-token issuer is a migration compatibility rule, not a suffix or
host-matching rule. New integrations SHOULD use the canonical discovery issuer
and MUST reject unrelated issuer values.

## Supported integration shapes

| Need | Supported profile |
| --- | --- |
| Browser, web, SPA, or native user sign-in | Authorization code with PKCE `S256` |
| CLI, TV, or input-constrained user sign-in | OAuth Device Authorization Grant |
| Service acting as itself | `client_credentials` with a confidential client |
| Service acting for a user at one connected provider | RFC 8693 Token Exchange with confidential caller and live approval |
| Long-lived user authorization | Rotating refresh-token lineage when `refresh_token` is registered and granted |
| Back-channel authorization request | Pushed Authorization Requests (PAR) |
| Signed authorization request | Per-client JAR by value or registered HTTPS URI at `/authorize` |

M7 does not support implicit or hybrid response types. A client MUST use
`response_type=code`. PKCE is required for authorization-code flow, and the
only accepted method is exact `S256`; `plain` is not supported.

The application MUST create high-entropy, single-use `state` and SHOULD create
a single-use OpenID Connect `nonce`. It MUST validate callback state and the
requested nonce in the ID token before establishing an application session.

## Authorization response delivery

The supported response modes are `query`, `form_post`, `fragment`,
`query.jwt`, `form_post.jwt`, `fragment.jwt`, `jwt`, `web_message.opener`,
`web_message.parent`, and `web_message`. A client MAY register several through
the M7 `response_modes` extension, but MUST select one permitted mode per
request. Omission selects `query`; it does not select the first allowed mode.
An unsupported or disallowed value fails without a fallback transport.
PAR and JAR MUST retain the selected mode in their bound transaction.

Signed JARM MUST use the client's `authorization_signed_response_alg`, exactly
RS256 or RS512 with RS256 as the default. The client MUST validate signature,
provider issuer, client-ID audience, expiry and single-use state before acting
on the enclosed code or error. Signing-only JARM is readable. Registered
[response encryption](https://m7.org/docs/api/sso.user.m7.org/response-encryption.md) instead requires a compact JWE;
the client MUST decrypt and then perform the same inner signature and claim
checks. The inline-key RS256 encrypted browser path passed live acceptance on
2026-09-16; see the encryption guide for its coverage limits.
Its signing policy is independent of ID tokens, UserInfo, JAR and client
authentication. `jwt` uses query delivery for the supported code response.

Web Message clients MUST validate the provider origin, exact popup/frame
window, message issuer and state, and accept only one result. Explicit modes
use only the named opener or parent. Generic mode prefers the opener, then
uses the parent only when an opener is absent. Invalid or closed receivers
MUST NOT trigger a fallback transport.

Iframe use additionally requires an explicit provider framing allowance for
the embedding origin and compatible client CSP/browser policies. Client
registration alone does not authorize framing. Current live acceptance covers
fresh password sign-in and cancellation from approved iframe origins; it does
not establish remembered-session reuse or iframe MFA, email, federation or
consent-denial coverage. See [response modes](https://m7.org/docs/api/sso.user.m7.org/response-modes.md) for the
complete delivery table, registration, SDK and browser requirements.

## Consumer email sign-in

M7 supports a magic link and a one-time code in one email for an existing
consumer account with a unique active verified address. Both share one-use
proof and require the browser that requested the email. Link landing displays
the requested address and requires explicit confirmation. Applications MUST
use the normal hosted authorization-code flow and callback contract. The
account's independent Email sign-in two-factor setting applies to both
methods. See [email sign-in](https://m7.org/docs/api/sso.user.m7.org/email-sign-in.md).

## Consumer two-factor authentication

Consumer accounts MAY enable independent TOTP checks for regular local
password login, email sign-in, local-password Device Code approval, and
remembered-account or existing-session authorization. Account/session intervals are Every time,
15 minutes, one hour, or eight hours. A completed proof is reusable only within
the appropriate account, browser, authenticator, and authorization context.

Fresh federated authentication follows the provider's policy; later reuse of
the remembered M7 account follows the account/session setting. Refresh-token
requests and machine credentials do not prompt for these codes. Applications
MUST continue using M7's hosted authorization pages, preserve the normal OAuth
transaction, and handle a due `prompt=none` check as `login_required` with
`error_reason=account_two_factor_required`.

Recovery codes are single-use. M7's hosted recovery option offers full
authenticator unlink by default and always unlinks on use of the final code.
The settings page also supports TOTP, recovery-code, and eligible linked-account
unlink. These account features do not add a discovery flag or a new `amr`/`acr`
assurance guarantee. See [two-factor authentication and recovery](https://m7.org/docs/api/sso.user.m7.org/two-factor-authentication.md).

## Client authentication

Each client has one registered token-endpoint authentication method. The same
method applies wherever that method is advertised for the invoked endpoint.

| Method | Token | PAR | Device authorization | Introspection | Revocation |
| --- | ---: | ---: | ---: | ---: | ---: |
| `none` | Yes | Yes | Yes | No | Yes |
| `client_secret_basic` | Yes | Yes | Yes | Yes | Yes |
| `client_secret_post` | Yes | Yes | Yes | Yes | Yes |
| `client_secret_jwt` | Yes | Yes | Yes | Yes | Yes |
| `private_key_jwt` | Yes | Yes | Yes | Yes | Yes |
| `tls_client_auth` | Yes | Yes | Yes | Yes | Yes |
| `self_signed_tls_client_auth` | Yes | Yes | Yes | Yes | Yes |

`none` identifies a public client with its exact `client_id`; it is not
anonymous access. A public client cannot use `client_credentials` or
introspection.

For `client_secret_basic`, the client identifier and secret are
application/x-www-form-urlencoded before Basic construction, as required by
the OAuth client-password contract. Clients SHOULD use a standards-compliant
library rather than constructing the header ad hoc.

For `client_secret_jwt` and `private_key_jwt`:

- `iss` and `sub` MUST both equal the exact `client_id`;
- `aud` MUST contain either the canonical issuer
  `https://sso.user.m7.org` or the exact invoked endpoint URL;
- `iat` and unexpired `exp` MUST be current;
- `jti` MUST be nonempty and unique; and
- a replayed, stale, incorrectly signed, or otherwise invalid assertion is
  rejected as `invalid_client`.

| Assertion method | Algorithms |
| --- | --- |
| `client_secret_jwt` | `HS256`, `HS384`, `HS512` |
| `private_key_jwt` | `RS256`, `RS384`, `RS512`, `ES256` |

### Mutual-TLS client authentication

M7 supports both RFC 8705 authentication methods. A client using
`tls_client_auth` presents a certificate that chains to an M7-trusted client
CA and matches exactly one registered subject-DN or SAN selector. A client
using `self_signed_tls_client_auth` presents the exact self-signed certificate
represented by one key in its registered inline `jwks` or HTTPS `jwks_uri`.

The canonical issuer remains `https://sso.user.m7.org`. Clients discover and
use the dedicated `mtls_endpoint_aliases` when presenting a client certificate:

| Operation | mTLS endpoint alias |
| --- | --- |
| Token | `https://mtls-sso.user.m7.org/token` |
| PAR | `https://mtls-sso.user.m7.org/par` |
| Device authorization | `https://mtls-sso.user.m7.org/device_authorization` |
| Introspection | `https://mtls-sso.user.m7.org/introspect` |
| Revocation | `https://mtls-sso.user.m7.org/revoke` |
| UserInfo | `https://mtls-sso.user.m7.org/userinfo` |

The ordinary authorization endpoint, discovery documents, registration
endpoint, and provider JWKS remain on the canonical SSO origin.

For either mTLS authentication method, the registration value
`tls_client_certificate_bound_access_tokens=true` requires certificate-bound
access and refresh tokens. M7 records the presented leaf certificate's
SHA-256 thumbprint as `cnf."x5t#S256"`. Token refresh MUST present the same
certificate, and a protected-resource request MUST present the exact bound
certificate over mutual TLS; the token cannot be downgraded to bearer use.
When a token carries both DPoP and certificate confirmation members, both
sender constraints apply.

## Provider-signed responses

| Response | Formats and algorithms |
| --- | --- |
| ID token | Signed JWT using a configured asymmetric profile or, for confidential clients, HS256/384/512 with a recoverable client secret; registration default is `RS256` |
| UserInfo | JSON by default, or signed JWT using registered `RS256` or `RS512` |
| Provider JWKS | `RSA`, `EC`, `OKP`, and `AKP` signing keys selected by exact `kid`, matching `alg`, and algorithm-appropriate key parameters |

For a signed UserInfo response, the client MUST validate the JWS, canonical
issuer, its own `client_id` as audience, time claims, and exact `sub` equality
with the already validated ID token.

### Signing algorithm selection

| Family | ID-token signing algorithms | Public JWK type |
| --- | --- | --- |
| RSA PKCS#1 v1.5 | `RS256`, `RS384`, `RS512` | `RSA` |
| RSA-PSS | `PS256`, `PS384`, `PS512` | `RSA` |
| ECDSA | `ES256`, `ES384`, `ES512`, `ES256K` | `EC` |
| Edwards | `Ed25519`, `Ed448` | `OKP` |
| ML-DSA | `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87` | `AKP` |
| HMAC (confidential clients) | `HS256`, `HS384`, `HS512` | None: shared client secret, never published |

Clients select `id_token_signed_response_alg` during registration; omission
continues to default to `RS256`. The protected `alg` MUST match the configured
algorithm and the selected verification key. HMAC requires the original client
secret and authoritative issued-token/state checks for API credentials. There is no automatic downgrade or
fallback to a different signing profile. UserInfo, client-authentication
assertions, JAR and DPoP retain their separate algorithm lists.

A third-party OIDC/JOSE client MAY use a supported profile when its verifier
supports that exact algorithm and key type. The generic `EdDSA` name is not an
alias for M7's `Ed25519` or `Ed448` values. Support for those explicit names and
for `AKP`/ML-DSA must be checked in the consumer's library.

Current-source M7 Identity SDK asymmetric verification supports the 15 public-key profiles through its
existing PHP paths and optional `m7crypto` backend, with an explicit allowlist
and compatible public-key resolution. Missing native support MUST fail rather
than bypass signature verification. Published SDK ZIPs remain at their existing
release capabilities until a new artifact includes the change. Local HMAC
verification additionally uses explicit `hmac_secret` and m7crypto; the Web
SDK session path uses authenticated introspection.

ML-DSA works in the tested device/native paths, but the current Web SDK's
full-token cookies cannot generally accommodate it. Clients using that browser
session integration MUST select a compact supported profile until storage and
transport are revised and tested. See [post-quantum storage limits](https://m7.org/docs/api/sso.user.m7.org/discovery-and-keys.md#post-quantum-tokens-and-browser-storage)
and [signing profiles and key formats](https://m7.org/docs/api/sso.user.m7.org/discovery-and-keys.md#signing-profiles).

## Authorization requests, PAR, and JAR

An authorization request MUST use an exact registered HTTPS redirect URI,
`response_type=code`, PKCE `S256`, and a nonempty allowed resource-audience
set. M7 performs exact redirect matching; it does not support wildcard redirect
URIs, HTTP loopback exceptions, or custom-scheme exceptions in the current
profile.

PAR uses `POST` with an application/x-www-form-urlencoded body and the
client's registered authentication method. Successful PAR returns exactly the
standard top-level values below with HTTP `201 Created`:

```json
{
  "request_uri": "urn:ietf:params:oauth:request_uri:OPAQUE_VALUE",
  "expires_in": 600
}
```

The browser request then carries the issued `request_uri`; it MUST NOT override
the stored authorization parameters.

### Signed Request Objects (JAR)

A client with an enabled JAR Request Policy MAY send a signed Request Object to
`/authorize`, either by value in `request` or through an exactly registered
HTTPS `request_uri`. The outer request MUST include `client_id` and exactly
one delivery parameter. JAR uses a dedicated client key source: exactly one
inline public JWKS or HTTPS JWKS URI, with one selected signing algorithm from
`RS256`, `RS384`, `RS512`, and `ES256`. It does not change the client's
registered token-endpoint authentication method or require every authorization
to use JAR.

The signed object MUST contain matching `iss` and `client_id`, an `aud` naming
the canonical authorization server, an integer `exp`, a fresh nonempty `jti`,
and the authorization parameters required for code flow. The signed inner
parameters are authoritative; outer authorization fields cannot override them.
PKCE, exact redirect matching, scopes, consent, authentication freshness, and
sender-constraint policies continue to apply.

The JWT `aud` identifies M7 as the recipient of the Request Object. It MUST NOT
be used to select a resource audience. This JAR profile uses the client's
complete registered resource-audience set; a separately signed resource subset
is not supported.

A customer-hosted object is fetched only from its exact registered public
HTTPS URI, without redirects. After verification, M7 preserves the accepted
parameters through login and consent; later content changes cannot alter that
transaction. Replay, invalid signatures, invalid claims, unregistered URIs,
and failed retrieval are rejected.

Discovery describes these capabilities through `request_parameter_supported`,
`request_uri_parameter_supported`, `request_object_signing_alg_values_supported`,
and `require_request_uri_registration`. See [the discovery metadata
contract](./discovery-and-keys.md#supported-protocol-metadata) for their values
and [JAR integration](https://m7.org/docs/api/sso.user.m7.org/authorization-requests.md#signed-request-objects-jar)
for the request and hosting rules. JAR at `/par`, JAR metadata through Dynamic
Registration and encrypted Request Objects remain outside this JAR profile.
Signed JARM is supported separately for authorization responses.

## Resource audiences and scopes

An audience identifies an intended resource. It is not proof that a client
owns a domain or resource. Requested resource audiences must be on the client's
current administrative allowlist. M7 additionally appends the issuer-selected
receiving app's OAuth UUID to `aud`. Each resource server MUST still require its
own exact resource audience and scopes. Merely adding an app UUID to requested
audiences does not select or change the issuer-owned `m7.target_client_id`.

Audience identifiers are exact, case-sensitive strings. They may be URLs or
opaque identifiers. A grant may request a nonempty subset of its configured
audiences. Where omission is permitted, omission selects the client's complete
configured set. Refresh cannot add or replace a lineage's audience set.

`client_id` identifies the client that requested issuance. Ordinary app-bound
issuance also stamps that app in `m7.target_client_id`. For Blog → Files exchange,
`client_id` and `act.sub` identify Blog, `m7.target_client_id` identifies Files,
and `sub` remains the user. The entire `m7` object is reserved for issuer-owned
claims; configured or requested claim overrides MUST NOT replace it. A receiving
app restriction MUST use the validated nested target claim, never a fallback to
`client_id` or an arbitrary UUID in `aud`. ID-token `aud`/`azp` and UserInfo's
existing `m7` identity projection retain their separate contracts. See
[access-token claim semantics](https://m7.org/docs/api/sso.user.m7.org/token-formats.md#requesting-client-and-receiving-application).

The standard SSO scope catalogue is:

| Scope | Meaning |
| --- | --- |
| `openid` | Enables OpenID Connect, ID-token issuance, and UserInfo `sub`; required for UserInfo |
| `profile` | Standard profile claims and the compatible `m7` profile projection |
| `email` | Primary active email and verification state when available |
| `groups` | Tenant-member organization and group projection |
| `offline_access` | Requests durable offline use; does not add UserInfo claims by itself |

Applications MUST request only an allowed subset. Consent and provider policy
may narrow the result. Application-specific resource scopes may exist beyond
the SSO catalogue.

## Token issuance and refresh

The token endpoint accepts only `POST` with an
application/x-www-form-urlencoded body. Successful token requests return HTTP
`200` and `application/json`.

Authorization codes are opaque, confidential, and single use. Redemption MUST
use the exact redirect URI and PKCE verifier bound to the authorization
transaction. Device codes are also confidential and single use after approved
pickup.

`client_credentials` represents the OAuth application itself: its `sub` and
`client_id` identify the application. It does not impersonate a user and does
not return a refresh token.

Refresh belongs to one authenticated client and one immutable resource-
audience lineage. The normal profile rotates the refresh package. A client MUST
persist the complete successful replacement before discarding its preceding
package. A revoked, expired, malformed, foreign, or otherwise invalid refresh
credential returns HTTP `400` with `error=invalid_grant`.

M7 refresh packages may contain `binding_chain` and `binding_link`. The default
advisory binding policy permits a standard refresh request to omit these M7
extensions. Clients using a binding-required profile MUST retain and return
both values; legacy personal-lineage signing also requires its binding package.
The optional M7 acknowledgement refresh mode and `/token/ack` are extensions to
the base profile; ordinary integrations SHOULD omit `refresh_mode`.

### Token Exchange (RFC 8693)

The token endpoint supports the canonical
`urn:ietf:params:oauth:grant-type:token-exchange` grant. A confidential requesting
client MUST have that grant registered and present its original, active local
M7 user access token. One active connection selects one provider in the same
identity realm. Provider policy, exchange approval, both registrations, source
authority, audiences and scope ceilings MUST permit the request. Same-site
automatic approval compares registered owners: a personal owner or organization,
never the human managers of two different organizations.

An access-only request returns an access token, even when the source permits
offline access. Omitted scope excludes `offline_access`. Explicit authorized
`offline_access`, with the caller's refresh grant enabled, returns access and
refresh credentials and their complete native binding package. The supported
requested token type is access token; requesting the refresh-token type is not
the bundle selector. No ID token is issued.

Exchange MUST preserve the subject and required sender binding. Delegated
refresh MUST preserve caller, actor, receiving app, audience and absolute offline
expiry, and recheck live connection permissions. Withdrawing approval blocks
exchange and delegated refresh without revoking the original login by itself.
External, machine, refresh-token and ID-token subjects, public clients,
actor-token input, HMAC output and further exchange of a delegated token are
unsupported. Impersonation, multiple targets and chained exchange remain deferred.

Both discovery URLs advertised the grant with identical metadata on 2026-09-20
at 11:51:14 UTC. Consumer RS256 issuance, access-only/offline selection, delegated
strict refresh and permission withdrawal passed live acceptance that day. Tenant
and other signing/binding combinations are not established by that live run.
See [request/response details](https://m7.org/docs/api/sso.user.m7.org/token.md#token-exchange-rfc-8693),
[registration policy](https://m7.org/docs/api/sso.user.m7.org/registration.md#connection-configuration-m7-extensions)
and the [current-source SDK guide](https://m7.org/docs/sdk/m7-identity/cli/token-exchange).

## DPoP sender constraint

M7 advertises RFC 9449 DPoP with exactly `ES256`. Dynamic Registration's
standard `dpop_bound_access_tokens=true` selects required DPoP. Omission or
`false` leaves DPoP optional, allowing an otherwise eligible request to opt in
with a valid proof. Internal M7 root-session proofing is a separate mechanism
and is not altered by a customer's DPoP registration setting.

A DPoP proof MUST:

- be a signed `dpop+jwt` using `ES256` and a public JWK;
- bind the exact HTTP method and normalized target URI;
- contain a current `iat` and a fresh, single-use `jti`; and
- contain the exact access-token `ath` for protected-resource requests.

Authorization-code binding may use `dpop_jkt` in the authorization request or
PAR. PAR also accepts a DPoP proof; when both are present, their key
thumbprints MUST match. The token request MUST use the same key when the code
is bound.

A bound token response uses `token_type=DPoP` and carries `cnf.jkt` in the
access and refresh tokens. A protected-resource request then uses
`Authorization: DPoP ACCESS_TOKEN` plus a distinct fresh `DPoP` proof. An
unbound token uses `token_type=Bearer` and MUST NOT be upgraded to the DPoP
authorization scheme.

At the token endpoint, a missing or invalid proof required by client or grant
policy returns HTTP `400` with `error=invalid_dpop_proof` before a code or
device credential is consumed. At a protected resource, a missing, malformed,
stale, replayed, wrong-method, wrong-URI, or wrong-`ath` proof is rejected with
HTTP `401`, `error=invalid_dpop_proof`, and `WWW-Authenticate: DPoP`
advertising `algs="ES256"`. Scheme or key-binding confusion is rejected as
`invalid_token` with the same DPoP challenge.

M7 does not currently issue `DPoP-Nonce`.

## UserInfo and protected resources

UserInfo accepts `GET` or form-encoded `POST`. A standard request authenticates
only with the access token; M7 derives the owning client from the validated
token. An optional legacy `client_id` request value is accepted only as an
additional exact-match assertion and never overrides the token.

UserInfo requires an active access token with `openid`. It does not require a
dedicated UserInfo audience, but it rechecks the signed resource audiences and
scopes against current client policy. Only the UUID matching the verified
`m7.target_client_id` is treated as issuer-added audience metadata; other entries
remain subject to the registered resource allowlist. A resource API
still MUST enforce its own exact audience, applicable caller/target policy, scope, principal, active
state, and sender constraint.

JSON UserInfo success uses HTTP `200` and `application/json`; registered signed
UserInfo uses HTTP `200` and `application/jwt`. Clients MUST compare UserInfo
`sub` exactly with the validated ID-token `sub`.

## Introspection, revocation, and end session

Introspection is restricted to confidential clients. An authenticated client
may inspect only a token issued to that exact `client_id`. Success returns HTTP
`200` with `active` and, only when active and authorized, the available token
metadata. Clients and resource servers MUST treat `active=false` as unusable.
Active introspection includes the available `m7` and `act` claims. A delegated
token still belongs to its requesting `client_id` for this authentication check;
the target stamp does not let the receiving app introspect another client's token.

Public and confidential clients may revoke their own credentials. A valid
revocation request returns HTTP `200`, `application/json`, and exactly `{}`
whether the submitted token was active, malformed, expired, already revoked,
or owned by another client. This opaque response prevents token-existence and
ownership probing; a foreign token is not revoked.

The end-session endpoint accepts `GET` or `POST`. A validated
`post_logout_redirect_uri` must exactly match the client's registered list.
Without front-channel work, M7 returns to an approved URI with HTTP `303`.
With front-channel work, it first serves a bounded iframe-delivery page and then
continues to the validated destination. Invalid return destinations never become
redirect targets.

### Logout notifications

Both discovery documents advertise front/back-channel logout and session support
as true, verified on 2026-09-20 at 02:58:25 UTC. Explicit end-session and account
chooser logout revoke the selected root family before notifying registered
participating applications. Account session management sends back-channel only;
the target browser need not be present. Ordinary switches and replacement do
not dispatch notifications.

Participation begins at successful OpenID authorization-code pickup. ID tokens
expose the stable root `sid`, preserved through refresh. M7 MUST include `sid`
in both channels, and `iss` in front-channel requests, even when the corresponding
registration requirement flag is false or absent. Clients MUST NOT interpret
those flags as send/omit switches.

Back-channel delivery posts a signed `logout+jwt` artifact using the client's
current ID-token signing and encryption policy. Receivers MUST validate the
envelope and session association before acting. Delivery acknowledgement does
not establish application-session invalidation. The current Web/PHP SDK back
receiver is receipt-only; see [logout notifications](https://m7.org/docs/api/sso.user.m7.org/logout-notifications.md)
for exact claims, trigger scope, browser limits, retries and SDK boundaries.

## Dynamic Client Registration

Dynamic Client Registration uses authenticated `POST /register` with a JSON
object and returns HTTP `201` on success. Defaults are:

| Metadata | Default |
| --- | --- |
| `application_type` | `web` |
| `response_types` | `["code"]` |
| `grant_types` | `["authorization_code"]` |
| `token_endpoint_auth_method` | `client_secret_basic` |
| `id_token_signed_response_alg` | `RS256` |
| UserInfo response | JSON |
| `dpop_bound_access_tokens` | `false` |

Public clients MUST explicitly request `token_endpoint_auth_method=none`.
Secret-based registration returns the plaintext secret once; clients MUST
store it securely because it is not later retrievable.

An mTLS registration selects either `tls_client_auth` or
`self_signed_tls_client_auth`. CA-issued authentication requires exactly one
of `tls_client_auth_subject_dn`, `tls_client_auth_san_dns`,
`tls_client_auth_san_uri`, `tls_client_auth_san_ip`, or
`tls_client_auth_san_email`. Self-signed authentication requires exactly one
of inline `jwks` or HTTPS `jwks_uri`, and the selected JWK must represent the
presented self-signed certificate. The optional
`tls_client_certificate_bound_access_tokens` boolean defaults to `false` and
is valid only with an mTLS authentication method.

Registration creates a protocol-valid client but deliberately does not grant a
resource audience. An administrator must configure at least one exact
`allowed_audiences` value before token issuance. M7 does not currently expose
public resource-authority packs or permit a client to grant itself an arbitrary
audience.

## Error and rate-limit contract

Protocol errors use an OAuth JSON object with `error` and, where safe,
`error_description`. Clients MUST branch on the stable `error` value rather
than prose and MUST safely tolerate unknown error values. Browser
authorization errors return only to an already validated redirect URI and
preserve the transaction's exact `state`.

Rate-limited protocol requests return HTTP `429`,
`error=too_many_requests`, and a `Retry-After` header describing the current
cooldown. A client MUST stop and wait; it MUST NOT rapidly retry. A request
during an active rolling penalty may restart one configured quiet period from
the latest attempt, but it does not add a second penalty duration to the
existing expiry. Defensive thresholds are intentionally not part of the
public profile.

## Support boundaries and remaining verification

| Feature | Status in this profile |
| --- | --- |
| OpenID certification | Not claimed; official conformance-suite execution remains pending |
| JAR at `/par`, JAR Dynamic Registration metadata, and encrypted Request Objects | Unsupported; signed JAR is enabled per client at `/authorize` |
| Encrypted JARM authorization responses | RSA-OAEP-256 / A256GCM deployed; form_post.jwt live success/cancellation passed; all four modes have local coverage |
| Signed resource-audience subsets in JAR | Deferred; JAR uses the complete registered resource set |
| JWE-encrypted ID tokens or UserInfo | RSA-OAEP-256 / A256GCM deployed; inline-key RS256 PHP login, UserInfo and strict refresh passed live |
| OAuth Token Exchange | Supported scoped local-user, single-target profile; consumer RS256 exchange/refresh accepted live; external subjects out of scope; impersonation, multiple targets and chains deferred |
| DPoP nonce | Not issued |
| Implicit or hybrid flow | Unsupported |
| PKCE `plain` | Unsupported |
| Public-client introspection | Unsupported |
| Automatic DCR audience or authority packs | Deferred; administrative audience assignment remains required |

## Executable profile assertions

These stable assertion IDs let conformance tests and release evidence point to
the public rule without exposing private fixtures or operations.

| Assertion | Required result |
| --- | --- |
| `M7P-DISC-001` | Both discovery URLs return the canonical issuer and identical advertised capability sets |
| `M7P-AUTH-001` | Authorization code with exact redirect, state, nonce, and PKCE `S256` succeeds; wrong state, nonce, audience, or verifier fails closed |
| `M7P-RESP-001` | All ten registered response modes deliver success and safe errors; omitted request mode selects query; unknown/disallowed modes fail without fallback; PAR/JAR retain the selected mode |
| `M7P-JARM-001` | Four JARM modes carry one signed response with exact RS256/RS512 policy, issuer, client audience, expiry and state; tampered, unsigned, mixed or mismatched responses fail before code exchange |
| `M7P-WMSG-001` | Explicit opener/parent and generic receiver selection enforce exact window, origin, issuer and state, one-use receipt, and receiver cleanup; missing/closed receivers fail without fallback |
| `M7P-PAR-001` | Successful PAR returns HTTP 201 with top-level string `request_uri` and numeric `expires_in` |
| `M7P-JAR-001` | An enabled client can complete signed direct and registered-URI authorization; signatures, client/issuer/audience/time binding, replay rejection, URI restrictions, immutable continuation, and ordinary authorization/PAR regressions enforce this profile |
| `M7P-CLIENT-001` | Every advertised client-authentication method and algorithm reaches grant processing; invalid or replayed credentials return `invalid_client` |
| `M7P-TOKEN-001` | Successful grants return the registered signing profile and correct principal, audience, scope, and client binding |
| `M7P-TARGET-001` | App-bound issuance stamps the issuer-owned nested target and appends its UUID to the resource audience set; caller overrides cannot replace it; refresh preserves it |
| `M7P-EXCHANGE-001` | An approved one-target exchange preserves the user, identifies caller and actor, selects the provider target, respects offline selection and scope/TTL ceilings, and fails exchange/refresh after approval withdrawal |
| `M7P-REFRESH-001` | Valid refresh rotates successfully; revoked or invalid refresh returns HTTP 400 `invalid_grant` |
| `M7P-DPOP-001` | Bound issuance, refresh, and resource use preserve one exact `cnf.jkt`; downgrade and proof negatives fail with the documented 401 challenge |
| `M7P-USERINFO-001` | JSON and signed UserInfo validate and bind `sub` exactly without requiring a request `client_id` |
| `M7P-LIFE-001` | Introspection, opaque revocation, post-revocation inactive state, and end-session redirect controls match this profile |
| `M7P-LOGOUT-001` | Explicit logout revokes before notification; switches send none; registered participants receive stable `sid`; API root revocation sends back-channel only; receiver acceptance is distinct from local invalidation |
| `M7P-DCR-001` | Registration defaults are authoritative, unsupported metadata fails without a client, and no resource audience is self-granted |
| `M7P-RATE-001` | Limiting returns 429 plus accurate `Retry-After`; cooldown recovery requires no protocol-bound administrative token |
| `M7P-MTLS-001` | Both advertised RFC 8705 authentication methods work through the discovered aliases; bound issuance and refresh preserve one exact `cnf."x5t#S256"`; UserInfo and resource use require that certificate; introspection and revocation accept the registered mTLS method; and missing, mismatched, untrusted, expired, or revoked certificates fail closed |

## Detailed references

- [Provider capability card](https://m7.org/docs/api/sso.user.m7.org/provider-capabilities.md)
- [Discovery and signing keys](https://m7.org/docs/api/sso.user.m7.org/discovery-and-keys.md)
- [Authorization requests, PAR, and JAR](https://m7.org/docs/api/sso.user.m7.org/authorization-requests.md)
- [Authorization response modes](https://m7.org/docs/api/sso.user.m7.org/response-modes.md)
- [Token endpoint and grants](https://m7.org/docs/api/sso.user.m7.org/token.md)
- [Device authorization](https://m7.org/docs/api/sso.user.m7.org/device-authorization.md)
- [UserInfo and scopes](https://m7.org/docs/api/sso.user.m7.org/userinfo.md)
- [Dynamic Client Registration](https://m7.org/docs/api/sso.user.m7.org/registration.md)
- [Token lifecycle and logout](https://m7.org/docs/api/sso.user.m7.org/lifecycle.md)
- [Logout notifications](https://m7.org/docs/api/sso.user.m7.org/logout-notifications.md)
- [Authorization callback outcomes](https://m7.org/docs/api/sso.user.m7.org/authorization-callback-outcomes.md)
- [M7 token formats](https://m7.org/docs/api/sso.user.m7.org/token-formats.md)
