# Discovery and signing keys

<!-- 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 -->

## Discovery

- [OpenID Connect discovery JSON](https://sso.user.m7.org/.well-known/openid-configuration)
- [OAuth authorization server metadata](https://sso.user.m7.org/.well-known/oauth-authorization-server)
- [Public signing keys (JWKS)](https://sso.user.m7.org/jwks.json)


M7 publishes OpenID Connect and OAuth authorization-server metadata at both:

```text
GET https://sso.user.m7.org/.well-known/openid-configuration
GET https://sso.user.m7.org/.well-known/oauth-authorization-server
```

Both endpoints return the same JSON metadata. Use discovery to obtain the
issuer and endpoint URLs rather than duplicating them in application code.

The metadata advertises:

- `issuer`
- `authorization_endpoint`
- `pushed_authorization_request_endpoint`
- `registration_endpoint`
- `token_endpoint`
- `end_session_endpoint`
- `userinfo_endpoint`
- `m7_orgs_endpoint` (M7 extension for consumer organizations)
- `revocation_endpoint`
- `introspection_endpoint`
- `m7_acl_introspection_endpoint` (M7 receiver-list machine ACL decision)
- `device_authorization_endpoint`
- `jwks_uri`
- supported response types, grants, PKCE methods, client-authentication
  methods, claims, and scopes

Use `GET` or `HEAD`; other methods are rejected.

`m7_acl_introspection_endpoint` is the existing M7 extension for authenticated
receivers to request an inbound machine-token policy decision at
`https://sso.user.m7.org/introspect/acl`. It is distinct from the standard
`introspection_endpoint`. mTLS discovery also advertises the top-level
`m7_mtls_acl_introspection_endpoint` at
`https://mtls-sso.user.m7.org/introspect/acl`; use it when the receiver
authenticates with its certificate. See the
[inbound ACL contract](https://m7.org/docs/api/sso.user.m7.org/authorization.md#inbound-machine-principal-list-acl).

## Supported protocol metadata

The supported metadata contract is:

| Metadata item | Values |
| --- | --- |
| `response_types_supported` | `code` |
| `response_modes_supported` | `query`, `form_post`, `fragment`, `query.jwt`, `form_post.jwt`, `fragment.jwt`, `jwt`, `web_message.opener`, `web_message.parent`, `web_message` |
| `authorization_signing_alg_values_supported` | `RS256`, `RS512` for signed JARM |
| `subject_types_supported` | `public` |
| `frontchannel_logout_supported` | `true` |
| `frontchannel_logout_session_supported` | `true` |
| `backchannel_logout_supported` | `true` |
| `backchannel_logout_session_supported` | `true` |
| `id_token_signing_alg_values_supported` | `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, `ES512`, `ES256K`, `Ed25519`, `Ed448`, `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87`, `HS256`, `HS384`, `HS512` (18-profile source update; rollout status below) |
| `userinfo_signing_alg_values_supported` | `RS256`, `RS512` |
| `request_parameter_supported` | `true` for the JAR-enabled discovery release |
| `request_uri_parameter_supported` | `true` for the JAR-enabled discovery release |
| `request_object_signing_alg_values_supported` | `RS256`, `RS384`, `RS512`, `ES256` |
| `require_request_uri_registration` | `true` |
| `grant_types_supported` | `authorization_code`, `refresh_token`, `client_credentials`, `urn:ietf:params:oauth:grant-type:device_code`, `urn:ietf:params:oauth:grant-type:token-exchange` |
| `code_challenge_methods_supported` | `S256` |
| `token_endpoint_auth_methods_supported` | `none`, `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, `private_key_jwt`, `tls_client_auth`, `self_signed_tls_client_auth` |
| `token_endpoint_auth_signing_alg_values_supported` | `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256` |
| `introspection_endpoint_auth_methods_supported` | `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, `private_key_jwt`, `tls_client_auth`, `self_signed_tls_client_auth` |
| `introspection_endpoint_auth_signing_alg_values_supported` | `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256` |
| `revocation_endpoint_auth_methods_supported` | `none`, `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, `private_key_jwt`, `tls_client_auth`, `self_signed_tls_client_auth` |
| `revocation_endpoint_auth_signing_alg_values_supported` | `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256` |
| `scopes_supported` | `openid`, `profile`, `email`, `groups`, `orgs`, `offline_access` |

`m7_orgs_endpoint` is `https://sso.user.m7.org/userinfo/orgs`. It is a custom
M7 discovery field; generic OIDC clients can ignore it. When mTLS aliases are
enabled, `m7_mtls_orgs_endpoint` advertises the certificate-bearing alias.
It is placed at the metadata root, outside `mtls_endpoint_aliases`.
See [consumer organizations](https://m7.org/docs/api/sso.user.m7.org/userinfo-orgs.md) and the metadata example below.

Both live metadata documents returned identical JSON advertising the canonical
Token Exchange grant and `https://sso.user.m7.org/token` on 2026-09-20 at
11:51:14 UTC. Advertisement means the server supports the
[documented exchange profile](https://m7.org/docs/api/sso.user.m7.org/token.md#token-exchange-rfc-8693); the requesting
client still needs its grant, an approved connection and provider policy.
Connection fields are [M7 registration extensions](https://m7.org/docs/api/sso.user.m7.org/registration.md#connection-configuration-m7-extensions),
not new standard discovery permissions. The existing UserInfo `m7` claim
advertisement is not a negotiation flag for access-token `m7.target_client_id`.

Both live metadata documents returned identical JSON with all four logout flags
true on 2026-09-20 at 02:58:25 UTC. M7 sends a stable root `sid` in both channels
and `iss` in the front channel, regardless of the client's session-required flags.
These advertisements describe provider delivery; they do not guarantee browser
cookie clearing or SDK application-session invalidation. See
[logout notifications](https://m7.org/docs/api/sso.user.m7.org/logout-notifications.md).

Discovery also publishes `tls_client_certificate_bound_access_tokens: true`
and this RFC 8705 endpoint-alias object:

```json
{
  "mtls_endpoint_aliases": {
    "token_endpoint": "https://mtls-sso.user.m7.org/token",
    "pushed_authorization_request_endpoint": "https://mtls-sso.user.m7.org/par",
    "device_authorization_endpoint": "https://mtls-sso.user.m7.org/device_authorization",
    "introspection_endpoint": "https://mtls-sso.user.m7.org/introspect",
    "revocation_endpoint": "https://mtls-sso.user.m7.org/revoke",
    "userinfo_endpoint": "https://mtls-sso.user.m7.org/userinfo"
  }
}
```

Use these aliases only for requests that present an mTLS client certificate.
They do not change the issuer: ID tokens and signed UserInfo still require the
canonical discovery issuer. Authorization, discovery, registration, and JWKS
remain on `https://sso.user.m7.org`.

The discovery document is the source of truth for endpoint URLs and supported
feature lists. A client still needs to be configured and authorized for every
grant, scope, authentication method, audience, and redirect URI it uses.

The advertised standard ID-token claims are `iss`, `sub`, `aud`, `exp`, `iat`,
`nbf`, `jti`, `azp`, `nonce`, and `auth_time`. Discovery also lists the
supported profile, email, and `m7` extension claims described in
[UserInfo and scopes](https://m7.org/docs/api/sso.user.m7.org/userinfo.md). `nonce` is present when the authorization
request supplied one, and `auth_time` is present when the request or registered
client policy requires it.

JAR supports signed Request Objects by value in `request` and by an exactly
registered customer-hosted HTTPS `request_uri` at `/authorize`. Each client
must first enable its dedicated JAR Request Policy and configure an allowed
signing algorithm and public key source. `require_request_uri_registration`
requires prior exact URI registration in that policy; it does not imply that
Dynamic Registration accepts `request_uris`.

Check the live document before enabling automatic feature selection: a server
still returning `false` for the Request Object flags has not enabled the JAR
discovery advertisement. This does not disable ordinary PAR. Its M7-issued
`urn:ietf:params:oauth:request_uri:` remains distinct from a customer-hosted
URI. JAR at `/par`, encrypted Request Objects, and JAR Dynamic Registration
metadata are not supported. See [Signed Request Objects
(JAR)](./authorization-requests.md#signed-request-objects-jar).

Both live discovery documents returned the ten response modes and the two
JARM signing algorithms at 2026-09-15 06:24 UTC. The default mode is `query`;
form POST, fragment, signed JARM and Web Message are also supported. See
[response modes](https://m7.org/docs/api/sso.user.m7.org/response-modes.md) for delivery and prerequisites, and
[callback outcomes](https://m7.org/docs/api/sso.user.m7.org/authorization-callback-outcomes.md) for result handling.
Both live discovery documents advertise `id_token_encryption_alg_values_supported`,
`authorization_encryption_alg_values_supported` and
`userinfo_encryption_alg_values_supported` as `["RSA-OAEP-256"]`, with each
corresponding `*_encryption_enc_values_supported` set to `["A256GCM"]`.
Rechecked at 2026-09-16 03:08:43 UTC: both documents are identical and advertise
these six values. The inline-key RS256 browser campaign passed encrypted
ID-token/JARM/UserInfo, strict refresh and cancellation on 2026-09-16.
JARM signing algorithms are independent of encryption and the ID-token list.
See [response encryption](https://m7.org/docs/api/sso.user.m7.org/response-encryption.md).

M7 accepts only the exact PKCE method `S256`. Authorization requests with a
missing method, `plain`, different capitalization, or an unknown method are
rejected before an authorization code can be issued.

## HMAC and discovery

Both live discovery documents advertise HS256, HS384 and HS512 in
`id_token_signing_alg_values_supported`, alongside the 15 asymmetric profiles.
Both discovery URLs use this same handler. HMAC requires a recoverable client
secret and has no public PEM/JWKS key; see [HMAC setup](https://m7.org/docs/api/sso.user.m7.org/registration.md#hmac-token-signing).
UserInfo and JARM remain RS256/RS512, and client-authentication, JAR and DPoP
algorithm lists retain their separate contracts.

Discovery lists provider capabilities, not every permitted combination of
client settings. Public clients using `none` must select an asymmetric
algorithm. The User.M7/SSO eligibility guard is locally tested and awaits
production verification; it does not remove HMAC from discovery or change
the public-key inventory. See the setup guide for the exact boundary.

The source update passed local discovery, registration, JWKS, UserInfo,
JAR and mTLS contracts. At 2026-09-15 21:49:24 UTC, both public discovery URLs
returned HTTP 200 with identical metadata and the full 18-algorithm ID-token
list. Public JWKS covered the 15 asymmetric profiles and contained no symmetric
material. HMAC discovery is deployed and advertised. Automatic selection must
follow the live list. Discovery responses may be cached for 300 seconds.

## Signing profiles

The complete signing set contains 18 profiles: the 15 asymmetric profiles
with public provider keys below, plus three HMAC profiles using the client's
shared secret. Choose from the live advertisement and your client's supported
algorithms; HMAC uses the shared-secret contract above.

| Algorithms | JWK type | Public key parameters |
| --- | --- | --- |
| `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512` | `RSA` | `n`, `e`; RSA-PSS uses the selected hash for MGF1 and a salt the size of that hash |
| `ES256` | `EC` | `crv=P-256`, `x`, `y` |
| `ES384` | `EC` | `crv=P-384`, `x`, `y` |
| `ES512` | `EC` | `crv=P-521`, `x`, `y` |
| `ES256K` | `EC` | `crv=secp256k1`, `x`, `y` |
| `Ed25519` | `OKP` | `crv=Ed25519`, `x` |
| `Ed448` | `OKP` | `crv=Ed448`, `x` |
| `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87` | `AKP` | `alg` selects the parameter set; `pub` is the base64url public key |
| `HS256`, `HS384`, `HS512` | No public JWK | Original client secret; never published in JWKS |

The exact algorithm identifiers and key types are registered in the
[IANA JOSE registry](https://www.iana.org/assignments/jose/jose.xhtml).
Edwards uses the explicit `Ed25519` or `Ed448` algorithm name, not `EdDSA`.
ML-DSA consumers need JOSE support for `AKP` and the selected ML-DSA parameter
set; generic OAuth support alone does not establish that capability.

Use a JOSE-aware verifier for the selected profile: ECDSA JWS signatures use
fixed-width `R || S`, rather than an ASN.1 DER signature. Configure a local
algorithm allowlist, require the client's selected ID-token algorithm, and
bind the matching key type and curve/parameter set. Do not widen the allowlist
from an untrusted token header or fall back to another algorithm on failure.

The current-source M7 Identity SDK token verifier supports the 15 asymmetric
profiles with an explicit local allowlist and a compatible key resolver.
Existing RSA and ES256/P-256 JWK paths retain PHP OpenSSL; additional profiles
use the optional `m7crypto` extension (`M7\Crypto\Key`) and its OpenSSL backend.
They require a resolved SPKI public PEM through the existing provided-PEM or
trusted exact-kid `x5u` path; RSA-PSS also accepts the existing RSA JWK key type.
This change does not add EC/OKP/AKP JWKS decoders or expand signed UserInfo.
An absent module or unavailable algorithm fails explicitly; installing it does
not widen the configured allowlist. Existing published SDK ZIPs are unchanged;
deploy the updated source or a later artifact that explicitly includes it.

For asymmetric profiles, provider keys sign ID tokens and shared-key access/refresh packages. A client
configured for personal refresh certificates instead receives access/refresh
tokens signed by its lineage's key, reused across refresh. Those personal
keys are deliberately absent from provider JWKS and are resolved by the
exact-kid PEM endpoint below. ID tokens continue to use published provider
keys for asymmetric profiles; HMAC ID tokens use the client secret instead.
Treat refresh tokens as opaque; this key description does not authorize
applications to depend on their internal claims.

Token size depends on both the algorithm and claims. The live sample's
ML-DSA-87 ID token was 7,069 bytes; consumers must account for the selected
profile in token storage and transport limits.

## Post-quantum tokens and browser storage

ML-DSA signing and native SDK verification are supported in the tested device
token-pickup/refresh and machine-token paths. Those JSON token responses do not
store the relying client's tokens in browser cookies. Use updated SDK source,
the compatible `m7crypto` backend and an explicit algorithm allowlist; the
grant alone does not establish verifier or transport compatibility.

The current M7 Web/PHP SDK stores each complete access and refresh JWT in a
single HttpOnly cookie. **Do not select ML-DSA for that browser session path
until its token-storage integration is revised and tested.**
[Chromium limits each cookie name plus value to 4,096 bytes](https://chromium.googlesource.com/chromium/src/net/%2B/refs/heads/main/cookies/parsed_cookie.h).
The encoded signatures alone occupy:

| Algorithm | Raw signature bytes | Unpadded base64url characters |
| --- | ---: | ---: |
| `ML-DSA-44` | 2,420 | 3,227 |
| `ML-DSA-65` | 3,309 | 4,412 |
| `ML-DSA-87` | 4,627 | 6,170 |

ML-DSA-65/87 exceed that cookie limit before JWT headers and claims are added.
ML-DSA-44 can exceed it with normal claim growth; a small sample fitting does
not establish general compatibility. PHP accepting `setcookie()` or a larger
application input limit does not prove the browser retained the cookie.
Aggregate Cookie/Authorization headers and proxy, server and storage limits
also need explicit budgets.

This is a restriction of the current Web SDK token-cookie design, not a ban
on post-quantum web authentication. A different integration can keep tokens
server-side behind a bounded session reference, subject to its full security,
refresh and sender-constraint contract. M7's Web SDK redesign and native crypto
distribution remain separate follow-ups. Ed25519 browser sign-in and refresh
have operator-confirmed production acceptance with the updated SDK source.

## Signing-key set

M7 exposes this discovery key endpoint:

```text
GET https://sso.user.m7.org/jwks.json
```

This is a conventional RFC 7517 JSON Web Key Set. It returns a `keys` array
containing the currently published M7 provider-signing keys:

```json
{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS512",
      "kid": "SIGNING_KEY_UUID",
      "n": "BASE64URL_RSA_MODULUS",
      "e": "AQAB",
      "x5u": "https://sso.user.m7.org/cert/kid/SIGNING_KEY_UUID?response=pem"
    }
  ]
}
```

Select the key whose exact `kid` equals the signed JWT's protected-header
`kid`, require `use=sig`, and require `alg` to match both the protected header
and the configured algorithm. Validate the algorithm's key type and public
parameters from the table above before verifying the signature.
The endpoint validates the upstream provider inventory and fails closed
instead of publishing unknown fields, private material, duplicate IDs,
unsupported algorithms, malformed key material, or untrusted certificate
locations.

Use `GET` or `HEAD` for `/jwks.json`. A successful response uses
`Content-Type: application/jwk-set+json` and is publicly cacheable for 300
seconds. An unavailable or invalid provider key set returns `server_error`
with HTTP 502; do not use a stale unknown key as a fallback.

### PEM lookup

Each published `x5u` points to the matching public certificate or public key:

```text
GET https://sso.user.m7.org/cert/kid/SIGNING_KEY_UUID?response=pem
```

`kid` must be a dashed UUID. Provider keys are selected from the current JWKS;
personal-lineage keys may be retrieved by exact `kid` without appearing there.
The optional `response` parameter may be omitted or must equal `pem`; other formats are not
supported. Use `GET` or `HEAD`. A successful response uses
`Content-Type: application/x-pem-file` and is publicly cacheable for 300
seconds.

For provider keys, M7 verifies that the PEM key type and algorithm-specific
public parameters match the selected JWKS member. Personal-lineage PEMs are
validated as supported public signing keys and remain outside provider JWKS.
An invalid key identifier or format returns HTTP 400, an unknown `kid` returns
HTTP 404, and an unavailable or invalid PEM fails closed with HTTP 502.
OIDC libraries supporting the selected algorithm can validate provider keys
from their JWK parameters; fetch the fixed SSO PEM URL when the validator
needs PEM input. Never follow an arbitrary token-supplied certificate URL.

## Validate tokens before use

For an ID token or a resource-server JWT, validate at least:

1. the JWS signature using the key selected by `kid`;
2. the token type and intended audience for your client or resource;
3. expiry and other time-based claims; and
4. `nonce` for an ID token obtained from a browser authorization that supplied
   a nonce.

### Issuer values by token class

Use an issuer policy for the token class being validated:

| Purpose | Value |
| --- | --- |
| Authorization-server issuer in discovery | `https://sso.user.m7.org` |
| Current ID-token migration allowlist | `https://sso.user.m7.org` or legacy `id.m7.org` |
| Newly issued access-token `iss` value | `https://sso.user.m7.org` |
| Legacy access-token migration allowance | `id.m7.org`, only when explicitly included in trusted validation policy |
| Public signing-key origin | `https://sso.user.m7.org/jwks.json` |

ID-token issuers are an exact transitional allowlist, not an arbitrary host or
suffix match. Reject every other issuer. New relying-party integrations should
retain the canonical discovery issuer and the documented legacy value only
while this migration contract remains active.

Configure new access-token integrations for `https://sso.user.m7.org`.
If accepting legacy tokens during migration, allow `id.m7.org` explicitly;
never derive trusted issuers from the submitted token. After selecting the
public key by `kid`, validate the token class's issuer, signature, audience,
authorized party or client, type, time claims, principal profile, and OIDC
nonce where applicable. Do not trust a decoded JWT merely because it has
expected-looking claims.

## Caching and key rotation

Respect HTTP caching headers on the JWKS and PEM responses. Cache keys by
`kid` and tolerate a new `kid` at any time. For provider keys, refresh the JWKS
once for an unknown `kid`, and fail validation if the key remains absent. Do not pin a single
signing key indefinitely and do not continue using a removed key beyond an
explicitly safe cache policy. Personal-lineage access tokens use the fixed
exact-kid PEM lookup; their absence from provider JWKS is expected and is not
a reason to select a different provider key.

## Organization endpoint metadata

Both metadata URLs use the same handler. The ordinary field and `orgs` scope
are present in current source; the mTLS field is enabled by the existing
`M7_OAUTH_MTLS_ENABLED` rollout gate. The following is a relevant excerpt,
not an exhaustive metadata document:

```json
{
  "issuer": "https://sso.user.m7.org",
  "userinfo_endpoint": "https://sso.user.m7.org/userinfo",
  "m7_orgs_endpoint": "https://sso.user.m7.org/userinfo/orgs",
  "scopes_supported": ["openid", "profile", "email", "groups", "orgs", "offline_access"],
  "tls_client_certificate_bound_access_tokens": true,
  "m7_mtls_orgs_endpoint": "https://mtls-sso.user.m7.org/userinfo/orgs",
  "mtls_endpoint_aliases": {
    "token_endpoint": "https://mtls-sso.user.m7.org/token",
    "pushed_authorization_request_endpoint": "https://mtls-sso.user.m7.org/par",
    "device_authorization_endpoint": "https://mtls-sso.user.m7.org/device_authorization",
    "introspection_endpoint": "https://mtls-sso.user.m7.org/introspect",
    "revocation_endpoint": "https://mtls-sso.user.m7.org/revoke",
    "userinfo_endpoint": "https://mtls-sso.user.m7.org/userinfo"
  }
}
```

`m7_mtls_orgs_endpoint` is a **top-level M7 extension**, not a standard alias
member. The canonical issuer stays unchanged. Generic OIDC clients may ignore
the M7 fields; org-aware clients use trusted exact-issuer metadata and the correct
certificate-bearing endpoint. Nested roles/tags/pub add no new discovery flags
or standard `claims_supported` entries. No SSO email-list endpoint is advertised.

At 2026-09-27 02:11 UTC both public metadata URLs returned HTTP 200 and identical
JSON containing these fields; advertisement is verified separately from live
consumer-token acceptance of the richer response. See the
[normative org profile](https://m7.org/docs/api/sso.user.m7.org/provider-profile.md#consumer-organization-discovery),
[endpoint contract](https://m7.org/docs/api/sso.user.m7.org/userinfo-orgs.md), and
[SDK endpoint resolution](https://m7.org/docs/sdk/m7-identity/cli/organization-access.md#trusted-endpoint-and-proof-creation).
