Authorization callback outcomes

M7 completes a browser authorization request by navigating the top-level browser to the client's exact registered redirect_uri. The callback uses an ordinary GET, and the authorization response is carried in the URI query. M7 does not use a form-body callback or return tokens to the callback.

This guide defines every supported callback outcome, which conditions remain on M7, and how a client or SDK must handle each result.

Process every callback defensively

For each authorization attempt, retain a high-entropy, single-use state with the browser transaction. On return:

  1. Accept the callback only at the exact registered endpoint and process the query response.
  2. Require a non-empty returned state and compare it with the expected value before acting on either success or error. Use a constant-time comparison when your platform provides one.
  3. Require exactly one terminal shape: a non-empty code or a non-empty error, never both and never neither.
  4. Reject duplicate, conflicting, array-shaped, or otherwise malformed protected fields.
  5. On success, exchange the code only from the application's trusted token pickup component using the original redirect URI and PKCE verifier.
  6. On error, do not call /token. Classify the result using error and error_reason, then expire the authorization transaction.
  7. Treat an unknown error or error_reason as a safe terminal failure. Do not convert an unfamiliar value into success.

A state mismatch takes precedence over every application-level outcome. For example, a callback that says signup_cancelled but carries the wrong state is an invalid callback, not a trusted cancellation.

Callback fields

Field When present Stability and client use
code Successful authorization only Opaque, confidential, short-lived, and single-use. Send it only to /token; do not inspect it.
state Every successful callback and every error for which the original state was available Compare with the expected single-use state before any outcome handling. Do not use it as application data.
error Authorization error only Stable OAuth error class. Clients may branch on documented values and must safely retain unknown values.
error_reason Normally present on an authorization error Stable M7 machine-readable reason. Clients may branch on documented values and must safely retain unknown values.
error_description Optional on an error Safe human-readable explanation. Clients must not parse it or depend on exact wording.
trace_id Optional on a logged error Opaque support correlation value. It carries no authority or diagnostic meaning; retain it only for support correlation.

During migration, some cancellation responses retain legacy discriminator text such as login_cancelled or signup_cancelled in error_description. Integrations must use error_reason; description text is not a protocol field.

Success

Existing-session, account-chooser, password-login, and federated-login success all use the same callback shape:

https://app.example.com/oauth/callback?code=AUTHORIZATION_CODE&state=STATE

Signup or account activation does not issue a separate kind of authorization success. After the preserved request resumes and sign-in completes, the client receives the same code and state callback.

The authorization code is reserved for approximately ten minutes. It is not an access token and must never be used as an API bearer credential. Validate state first, then perform the server-side authorization-code exchange.

Callback errors and required actions

The table lists stable client-visible fields. error_description remains safe human-facing text and may vary unless an exact value is stated. “Start again” means create a fresh state, PKCE verifier, challenge, and authorization request; do not replay the old callback.

Situation error error_reason Retryability and client or SDK action
User leaves password login or the remembered-account chooser access_denied login_cancelled Terminal for this attempt. Return to the application; start a new interactive request only if the user chooses.
User cancels hosted signup access_denied signup_cancelled Terminal for this attempt. Offer sign-in or a fresh authorization request.
Public signup is disabled for the application access_denied public_signup_disabled Do not repeat signup automatically. Offer an allowed sign-in path or contact the application owner.
User cancels the pending-account ceremony access_denied account_pending_cancelled Terminal for this attempt. Preserve no authorization code or pickup state.
Registration requires administrator approval access_denied registration_pending_approval Do not loop signup. Tell the user approval is pending and begin a new request after approval.
User cancels account recovery access_denied account_recovery_cancelled Terminal for this attempt. Return safely or start a new flow at the user's request.
User cancels password recovery access_denied forgot_password_cancelled Terminal for this attempt. Return safely or start a new flow at the user's request.
User cancels username recovery access_denied forgot_username_cancelled Terminal for this attempt. Return safely or start a new flow at the user's request.
GitHub, Discord, or X reports user cancellation or denial access_denied federated_login_cancelled Terminal for this attempt. Offer another provider or a fresh sign-in request.
An unexpected provider, persistence, policy, configuration, or processing failure occurs after callback trust server_error unspecified_error_try_again Retryable only through a fresh authorization request, normally after a delay. Preserve trace_id for support. Do not infer the internal cause.
prompt=none cannot use an eligible current session login_required session_required Start a new interactive authorization without prompt=none when interaction is allowed.
Authorization completion code fails validation invalid_request authorization_code_validation_failed Start the authorization request again. Do not call /token.
Application admission policy denies the account access_denied application_access_denied Do not retry automatically. Access or application policy must change first.
Signup is not configured correctly for the application invalid_request signup_configuration_error Application owner must correct signup configuration before retrying.
Application policy disables federated sign-in access_denied federated_login_disabled Use an allowed sign-in method; do not retry the disabled provider path.
Trusted federated handoff is expired or invalid invalid_request federated_login_session_invalid Start authorization again. Never replay the old provider handoff.
Client requests anything other than authorization code response type unsupported_response_type response_type_not_supported Correct the request to response_type=code.
Required state is missing invalid_request state_required Correct the client and start again. No state can be echoed in this callback.
Requested prompt value is unsupported invalid_request prompt_not_supported Correct or omit prompt, then start again.
max_age is malformed invalid_request invalid_max_age Send a non-negative integer or omit the field, then start again.
login_hint is malformed invalid_request invalid_login_hint Correct or omit the hint, then start again.

An error callback never contains an authorization code. A client must not begin token exchange for any error, malformed callback, missing state, or state mismatch.

Opaque server failures and trace IDs

M7 deliberately hides internal system and provider failure labels. A safely returnable unexpected failure has this public shape:

https://app.example.com/oauth/callback?
  error=server_error&
  error_reason=unspecified_error_try_again&
  error_description=An%20unspecified%20error%20occurred.%20Please%20try%20again.&
  trace_id=OPAQUE_TRACE_ID&
  state=STATE

The example is split across lines for readability; the browser receives one URI. The trace ID lets M7 operators correlate the public failure with a private log event. It must not reveal or be treated as an internal error code. Internal failure reasons, exception details, provider responses, configuration data, and credentials are never part of the public callback contract.

Outcomes that remain on M7

M7 redirects only after it trusts the client and exact registered callback. Some failures therefore remain within the hosted flow:

Condition M7 behavior Client implication
Missing, unknown, or invalid client_id Local OAuth-style failure No client registration is trusted, so M7 does not redirect.
Missing, malformed, non-HTTPS, or unregistered redirect_uri Local OAuth-style failure Never construct a return target from the rejected value.
Browser DPoP proof is invalid Local safe retry response with a trace ID M7's hosted browser code may restart the proof ceremony; this is not a client callback.
Authorization challenge is expired, reused, or invalid Local safe retry response with a trace ID M7's hosted browser code may restart the challenge; this is not a client callback.
Hosted login, signup, consent, credentials, or required form input is invalid Local safe form or JSON error The user can correct the input without terminating authorization.
Authorization request hydration fails before trusted callback state is recovered Local safe start-again result Begin a new authorization request; M7 does not redirect using untrusted input.
Upstream provider state is missing, invalid, or expired M7 provider terminal or retry page Provider input alone cannot establish a relying-party callback.
Provider proof, token exchange, or profile lookup fails while trusted provider state remains M7 retry page with a safe application-return option Raw provider diagnostics remain private. A chosen return uses the generic server-error callback.
/token rejects a code, client, PKCE verifier, or grant Server-to-server JSON error This is a token-endpoint outcome, not an authorization callback.

M7-local JSON fields are implementation details for M7's hosted pages unless a separate public endpoint guide explicitly defines them. Client applications must not call hosted form-processing routes as APIs.

PAR expiry and re-entry

An expired pushed authorization request no longer provides trustworthy callback state. M7 therefore does not send PAR expiry to the expired request's redirect_uri.

The hosted flow uses a local result with:

  • error=invalid_request;
  • error_reason=request_uri_expired;
  • outcome=authorization_reentry_required;
  • an opaque trace_id;
  • next_method=GET; and
  • a trusted next_uri.

If the OAuth client has a registered initiate_login_uri, M7 uses that URI to start a new authorization request. Otherwise it uses M7's /par-expired page. The re-entry URI is not an authorization callback, and it receives no code, token, PKCE verifier, or expired state.

Upstream provider callbacks are separate

GitHub, Discord, and X return to M7-specific provider callbacks. M7 validates and consumes provider state before deciding whether it has a trusted relying application callback.

  • An explicit provider cancellation becomes access_denied with error_reason=federated_login_cancelled when trusted state is available.
  • An unexpected provider failure becomes the opaque public server-error shape only when trusted state is available.
  • Without trusted provider state, M7 remains on a local retry or terminal page.

Provider access tokens, response bodies, profiles, proof material, and internal failure labels are never forwarded to the relying application callback.

Callback URI handling

M7 preserves a registered callback's existing non-reserved query parameters, appends callback fields once using RFC 3986 encoding, and inserts them before an existing fragment:

https://app.example.com/oauth/callback?source=m7&code=AUTHORIZATION_CODE&state=STATE#complete

State and code are opaque. After query decoding, the returned state must equal the original value exactly, including Unicode and reserved characters.

A registered URI must not already contain any reserved callback field: code, state, error, error_reason, error_description, or trace_id. M7 rejects such a conflict rather than overwriting or duplicating it.

Keep callback and token errors separate

An authorization callback reports the outcome of browser authorization. The token endpoint reports the outcome of a later server-to-server grant request. Both layers can use OAuth labels, but they are different transactions.

  • A callback error means no token exchange should begin.
  • A successful callback supplies an authorization code, not a token package.
  • /token errors such as invalid_grant or invalid_client must be handled by the token-pickup component and must not be reinterpreted as callback errors.
  • Access tokens, ID tokens, refresh tokens, binding values, and activation secrets are never fields of the browser callback.

Logging and data handling

Never log or place in analytics, telemetry, support tickets, screenshots, or persistent browser storage:

  • the raw callback URI;
  • authorization codes or state values;
  • access, ID, or refresh tokens;
  • PKCE verifiers;
  • DPoP proofs, challenges, or keys;
  • client secrets or assertions;
  • provider credentials, responses, or profiles; or
  • internal exception or system-error details.

Log only a safe event classification and the opaque trace ID needed for correlation. When displaying an error to a user, escape all callback values and show the trace ID as a support reference, not as diagnostic detail.