M7 consumer identity federation protocol reference

This reference defines the protocol contract for using an M7 consumer account as an OpenID Connect identity provider in another identity system. It is for protocol-level federation, identity brokers, and custom integrations.

For ordinary application sign-in, use the standard M7 SDK. The SDK owns the normal application session and callback integration. Do not implement this federation profile merely to replace the standard SDK.

Profile summary

The M7 consumer-federation profile uses:

  • OpenID Connect authorization code flow;
  • top-level browser redirects and query response mode for the integration in this guide;
  • mandatory PKCE using S256;
  • a high-entropy, single-use state value;
  • a high-entropy, single-use OpenID Connect nonce;
  • the openid profile email scope set for the current federation adapter;
  • an ID token as the signed identity assertion;
  • UserInfo as the current scoped profile projection; and
  • the ID-token sub as the stable external identity key.

The relying system must complete ID-token and UserInfo validation before it creates, links, or signs in a local account. It must never use email, username, or another presentation claim as the federation key.

Discovery and endpoints

This reference uses the query integration throughout. Other clients may select the additional response modes, subject to their registration, callback-validation and browser requirements. The iframe password-login tests do not establish iframe federation or consent coverage.

Retrieve current metadata before configuring an integration:

GET https://sso.user.m7.org/.well-known/openid-configuration

The current production endpoints are:

Purpose Endpoint
Authorization-server issuer https://sso.user.m7.org
Authorization GET https://sso.user.m7.org/authorize
Pushed authorization request (optional) POST https://sso.user.m7.org/par
Token POST https://sso.user.m7.org/token
UserInfo GET or POST https://sso.user.m7.org/userinfo
Dynamic client registration POST https://sso.user.m7.org/register
Signing-key set GET https://sso.user.m7.org/jwks.json

Do not apply one issuer rule to every token class. Discovery returns the canonical authorization-server issuer https://sso.user.m7.org. During the current ID-token migration, validate an exact issuer allowlist containing https://sso.user.m7.org and legacy id.m7.org; current access tokens retain iss: "id.m7.org".

Client registration

Use a dedicated OAuth client for each federation integration. Do not reuse a client that already serves an unrelated application sign-in path. Independent registration permits safe redirect, secret, policy, and lifecycle changes without breaking the other application.

Create and manage the client in the M7 account application, or use POST /register when an authorized provisioning tool needs dynamic registration. Dynamic registration requires an active M7 user bearer token; it is not an anonymous client-enrollment endpoint.

The server-side federation profile uses this registration contract:

Metadata Value or rule
application_type web
client_name A user-facing name that identifies the relying system
redirect_uris One or more exact HTTPS callback URIs; no wildcards
response_types code
grant_types authorization_code
scope Permit at least openid profile email for the current profile
token_endpoint_auth_method client_secret_basic for the recommended confidential-server profile
initiate_login_uri Optional trusted HTTPS login-start URI used for PAR re-entry

M7 returns the client_id and, for a secret-based method, the plaintext client_secret once. The identifier is public. The secret is a credential: store it in a server-side secret manager and never send it to a browser, mobile client, front-channel authorization URL, log, or source repository.

Public clients can register token_endpoint_auth_method=none and still must use PKCE. A public client has no secret and sends its client_id in the token request. A confidential identity broker should use a server-side method, normally client_secret_basic or private_key_jwt.

At present, M7's every-request consumer-federation consent policy is enabled for clients specifically designated for that profile. Registering a generic OAuth client does not by itself designate it as the consumer-federation client. Confirm that profile assignment as part of federation onboarding.

Client authentication

The token and PAR endpoints authenticate the client with its registered token_endpoint_auth_method. A request must not silently fall back to a different method.

Method Request contract
none Send client_id in the form body. For public clients only.
client_secret_basic Send Authorization: Basic base64(client_id:client_secret).
client_secret_post Send client_id and client_secret in the form body.
client_secret_jwt Send client_id, the JWT-bearer assertion type, and an HMAC-signed client_assertion.
private_key_jwt Send the same assertion fields with an assertion signed by a registered private key.

For JWT client assertions, iss and sub must equal the client_id; aud must equal the canonical authorization-server issuer or the exact endpoint receiving the assertion; and iat, exp, and a unique jti are required. An assertion sent to /par may therefore use https://sso.user.m7.org or the exact PAR endpoint as aud; the same rule applies at /token.

Authorization-code flow

Transaction material

Before redirecting the browser, the relying system must generate and retain one transaction record containing:

Value Requirement
state High-entropy, unpredictable, bound to the initiating browser session, expiring, and single-use
nonce High-entropy, unpredictable, expiring, single-use, and later matched exactly against the ID token
code_verifier 43–128 RFC 3986 unreserved characters, retained only by the relying system
code_challenge Base64url-encoded SHA-256 of the exact verifier, without padding
redirect_uri Exact registered callback URI used for both authorization and token exchange
requested scopes The exact scope set expected by the callback and claim validator

Do not store the client secret, authorization code, or token package in the browser transaction. Do not place the verifier or nonce in logs or analytics.

Authorization request

Send a top-level browser GET to /authorize with:

Parameter Requirement
client_id Registered federation client identifier
redirect_uri Exact registered HTTPS callback URI
response_type code
scope openid profile email for the current federation profile
state The transaction's exact opaque state
nonce The transaction's exact OpenID Connect nonce
code_challenge Challenge derived from the retained verifier
code_challenge_method Exact, case-sensitive value S256

Do not put a client secret in this request. Do not embed the M7 authorization page in an iframe. M7 owns sign-in, account selection, and consent interaction.

An integration may instead authenticate to /par, push the same request parameters, and, after the HTTP 201 Created response, immediately navigate the browser to /authorize with the returned request_uri. A PAR URI is opaque, short-lived, and single-use. Do not add or override authorization fields in the browser request. Start a new PAR transaction after expiry.

Callback parameters

M7 returns to the exact registered callback using a top-level browser GET with query parameters. Require exactly one terminal shape.

Success:

Parameter Meaning
code Confidential, short-lived, single-use authorization code
state Original opaque state, returned unchanged

Error:

Parameter Meaning
error Stable OAuth error category
error_reason Stable M7 machine-readable reason when supplied
error_description Optional safe, non-normative prose; never branch on it
trace_id Optional opaque support-correlation identifier
state Original state when M7 has a trusted callback transaction

Before interpreting either shape, compare state with the stored transaction using an exact, timing-safe comparison and atomically consume the transaction. Reject missing, mismatched, array-shaped, duplicate, or conflicting protected fields. Never exchange a code after a callback error or a failed state check.

An expired PAR request is not a callback result: the expired record can no longer supply trusted callback state. Begin a new authorization request from the registered initiate_login_uri or the M7 re-entry page.

Token exchange

Exchange the code with a server-to-server form request:

POST https://sso.user.m7.org/token
Content-Type: application/x-www-form-urlencoded
Form field Requirement
grant_type authorization_code
code Exact code received at the callback
redirect_uri Exact URI used in the authorization request
code_verifier Original, unmodified PKCE verifier
client_id Required in the body for none; may accompany other methods when permitted

Authenticate using the client's registered method. The code, redirect URI, client, and verifier are one binding; any mismatch must fail closed.

A successful response for openid contains an access_token, id_token, token_type, granted scope, and expiry data. token_type=Bearer identifies an unbound access token; token_type=DPoP identifies a token bound through cnf.jkt. It can also contain refresh or binding material under client and service policy. A federation proof normally does not request offline access: validate the ID token, retrieve the current UserInfo projection, establish the local session, and discard provider tokens that the integration does not need.

State and nonce requirements

state and nonce have different security purposes and neither replaces the other:

  • state binds the browser callback to the relying system's transaction and prevents login CSRF and response injection.
  • nonce binds the ID token to that same authorization attempt and prevents reuse of an identity assertion in another browser transaction.

Both values must be generated with a cryptographically secure random source, stored under an expiry, accepted once, and compared exactly. Do not derive one from the other. Delete the transaction after success, terminal error, or expiry.

Scopes and returned identity data

The current M7 consumer-federation adapter requests exactly:

openid profile email
Scope Federation use
openid Enables ID-token issuance. Required for this profile.
profile Permits standard profile and M7 consumer profile projection through UserInfo.
email Permits the active primary email and email_verified when available.
groups Tenant-member group data; it does not expose consumer-account groups and is not part of this profile.
offline_access Requests durable refresh behavior; normally unnecessary for a one-time federation proof.

Request only data the relying system needs. An allowed scope is not proof that every corresponding claim exists. Email and profile fields remain optional; sub is the only stable federation identity key.

For a client designated for M7 consumer federation, every new interactive authorization request shows the M7 consent screen, including when the user already has an eligible M7 session. M7 does not currently persist a Connected Apps grant, so a prior approval never suppresses the next screen.

The approval is short-lived, single-use, and bound to the exact client, redirect URI, scopes, state, PKCE challenge, and nonce. It cannot authorize a different request. prompt=none cannot complete this interaction and returns consent_required; begin a fresh interactive request if the user can approve.

This is a temporary federation policy until Connected Apps grant storage is available. Integrations must not infer permanent approval or attempt to cache, replay, or bypass M7's consent decision.

ID-token claims and validation

An M7 ID token is a signed identity assertion for the relying client. It is not an API access token. Decode it only as part of a complete verification procedure.

The current consumer identity profile requires:

Claim Validation
iss Exact current allowlist: https://sso.user.m7.org or legacy id.m7.org; reject all other values
aud Contains the relying OAuth client_id
azp When present, or when aud has multiple values, exactly equals the client_id
sub Stable dashed UUID for the M7 consumer account
nonce Exact value stored for the authorization transaction
typ Exact logical token type id
rlm Exact realm consumer
pty Exact principal type user
iat Valid issued-at time; reject an implausible future value
nbf Not later than the current time plus the validator's small allowed skew
exp Later than the current time
preferred_username or name Optional presentation value; never an identity key

Validation order:

  1. Parse the protected header and require the client's configured ID-token algorithm, also present in live discovery and the verifier's allowlist, plus a usable kid. Do not accept an algorithm supplied only by the token.
  2. Read the conventional keys array from /jwks.json and select the one exact member whose kid and alg match the protected header.
  3. Require the algorithm-specific key type and parameters, then validate the JWS signature before trusting any payload claim. See signing profiles for the available algorithms, advertisement status and SDK compatibility.
  4. Validate issuer, audience, authorized party, logical token type, realm, principal type, time claims, and the exact transaction nonce.
  5. Use the validated sub as the external account identifier.

Cache public keys by kid according to their HTTP caching metadata and refresh the JWKS once for an unknown kid before failing. Do not pin one signing key indefinitely. Do not accept an access token, tenant token, refresh token, or decoded but unverified JWT as an M7 consumer identity assertion.

UserInfo request and validation

After validating the ID token, call UserInfo with the access token. M7 derives the owning OAuth client from the validated token:

GET https://sso.user.m7.org/userinfo
Authorization: Bearer ACCESS_TOKEN

An optional client_id is accepted only as a legacy exact-match assertion. POST is also supported; use it when an M7 fingerprint binding is required so the fingerprint does not enter a URL. The example is for token_type=Bearer. For token_type=DPoP, use Authorization: DPoP and one fresh ath-bound proof for UserInfo. M7 creates any required internal downstream attestations; clients do not send them. See the UserInfo contract.

The relying system must:

  1. require a successful JSON response;
  2. require a nonempty sub;
  3. compare UserInfo sub exactly with the already validated ID-token sub;
  4. reject a mismatch rather than linking either identity;
  5. consume only claims authorized by the granted scopes; and
  6. ignore unknown m7 extension fields unless its own contract defines them.

Use preferred_username, name, picture, locale, email, and email_verified only as scoped profile attributes. Email can be absent and can change. A relying system that needs a verified email must require email_verified=true and still key the account by sub.

An invalid, expired, revoked, incorrectly bound, or unsupported token returns HTTP 401 with invalid_token. A valid token belonging to a different client_id returns HTTP 403 with insufficient_scope.

Identity linking boundary

Store the federation link as at least the provider identity plus the validated M7 consumer sub. Keep the relying tenant or realm in the local uniqueness boundary when one consumer is allowed to join more than one independent tenant. Do not automatically merge accounts based on matching email, username, display name, or avatar.

Require an authenticated, explicit linking ceremony before attaching a new M7 identity to an existing local account. Protect link, unlink, and recovery operations against account takeover independently of the ordinary login flow.

Errors

Authorization callback errors and token endpoint errors are different transactions and must remain separate.

Surface Representative errors Required handling
Authorization callback access_denied, login_required, consent_required, interaction_required, temporarily_unavailable, server_error Validate state, do not call /token, and follow the stable error and error_reason contract.
Local M7 authorization page Invalid client, redirect, PAR, or untrusted request state Do not invent a callback from untrusted input; start a new request after correcting configuration or expiry.
Token endpoint invalid_request, invalid_client, invalid_grant, unauthorized_client, invalid_scope, unsupported_grant_type, invalid_dpop_proof, server_error Do not establish a session; classify by error, not prose, and restart authorization when the code or transaction is no longer usable.
UserInfo invalid_token, insufficient_scope, server_error Reject the identity proof; do not fall back to unvalidated ID-token presentation claims.

Treat unknown error and reason values as observable terminal failure, never as success. error_description is safe human-readable context and is not a stable programming interface. Retain an opaque trace_id for support correlation, but never include credentials, raw callback URLs, token contents, or internal provider diagnostics in a support record.

See Authorization callback outcomes for the complete callback error/reason matrix, retryability, and PAR re-entry contract.

Security requirements

  • Use HTTPS for every registered URI and protocol endpoint.
  • Use a dedicated client and exact redirect-URI matching; do not use wildcards.
  • Keep confidential-client authentication and token exchange on a trusted server.
  • Generate fresh state, nonce, PKCE verifier, and PKCE challenge for every authorization attempt.
  • Bind and expire the complete transaction, then consume it atomically.
  • Validate callback state before success or error handling.
  • Validate the ID-token signature and all required consumer-profile claims.
  • Require ID-token and UserInfo subject equality before account mapping.
  • Use sub, never email or username, as the durable external identity.
  • Apply least-privilege scopes and do not request offline_access without a durable refresh-token use case.
  • Never log authorization codes, state, nonce, PKCE material, client secrets, access tokens, ID tokens, refresh material, DPoP proofs, or raw callback URLs.
  • Rate-limit login starts and callbacks, reject replay, and make local session creation idempotent.
  • Rotate a disclosed client secret and invalidate affected local transactions.
  • Keep provider authentication separate from local authorization: a valid M7 identity does not by itself grant tenant membership, roles, or resources.