Browser sign-in adds a Matrix account by sending the user to their homeserver’s own sign-in page in a browser. It is for homeservers that hand authentication to an OAuth 2.0 authorization server, such as Matrix Authentication Service, and may offer no password login at all. Unlike QR login it needs no second Matrix client. The design is ADR 0117.
This page is for client authors using the API and for operators diagnosing a failed sign-in.
POST /v1/accounts/login/browser with the Matrix user ID.
Axon finds the homeserver from the user ID’s server name and asks its authorization server for a link and a short code.
An account being reactivated keeps the homeserver URL it was stored with, as it does for password login.GET /v1/accounts/login/browser/{flow_id}.
When the stage is waiting_for_authorization, the response carries verification_uri and user_code.finalizing, then done with the new account_id.DELETE /v1/accounts/login/browser/{flow_id} cancels a flow.
A flow lasts thirty minutes at most, and less if the authorization server sets a shorter expiry. Only one sign-in flow of any kind, browser or QR, can be open for a Matrix user at a time.
waiting_for_authorization.
Do not cache them.verified is false.
Lead the user straight on to recovery-key import or interactive verification, or encrypted history will not decrypt.error_code as a generic failure.
The set may grow.cancel_reason replaced means a newer browser sign-in for the same user took over; say so rather than “cancelled”.
Treat any other cancel_reason like an ordinary cancellation.A failed flow has stage failed and one of these in error_code.
| Code | What happened | What to tell the user |
|---|---|---|
unsupported |
The homeserver has no OAuth authorization server, or it does not offer the device authorization grant. | This homeserver does not support browser sign-in. Try a password or QR login. |
authorization_denied |
The user declined at the authorization server. | Sign-in was declined. Start again if that was a mistake. |
authorization_expired |
Nobody approved before the code or the flow expired. | The sign-in link expired. Start again. |
user_mismatch |
The browser was signed in as a different Matrix user from the one entered. | You approved as a different account. Sign out in the browser, or fix the user ID. |
conflict |
The account became active, or began to be deleted, while the user was approving. | This account was signed in another way. Nothing more is needed. |
timeout |
The authorization server or homeserver did not answer one request in time. | The homeserver is not responding. Try again shortly. |
upstream |
The homeserver could not be found, or it or its authorization server answered in a way Axon could not use. The server log says which. | Sign-in failed at the homeserver. Try again, and contact the operator if it lasts. |
internal |
A local failure: storage, configuration, or the SDK. | Sign-in failed on this Axon server. Contact its operator. |
Creating a flow can also be refused outright:
| Status | Meaning |
|---|---|
400 |
The user ID is not a valid Matrix user ID. |
409 |
The account is already signed in, is being deleted or is still shutting down, or another sign-in flow is open for it. |
429 |
Too many sign-in flows are open or recently finished. Retry shortly. |
A new browser sign-in replaces an open browser sign-in for the same user when that one is at least ten seconds old and has not been approved.
That is how a user gets past a flow whose tab was closed or whose create response was lost.
The replaced flow ends cancelled with cancel_reason replaced, and its link and code stop working; tell its user that a newer sign-in took over.
A flow that is younger, already approved and finishing, or a QR login still answers 409.
If the old flow’s token request is out when the new one arrives, Axon waits for that answer first (up to 15 s); if it carries the approval, the old flow finishes and the new request gets 409.
A request that would be refused anyway (the account is already signed in, for example) is refused without touching the open flow.
The server logs Matrix OAuth login flow replaced by a new one for the same user with the old flow_id.
A flow ID from QR login is 404 on these routes, and a browser flow ID is 404 on the QR routes.
Every line a browser sign-in logs sits in a span named matrix_oauth_browser_sign_in that carries the flow_id and the expected_user_id, so searching the log for the user’s Matrix ID finds the whole attempt.
The default log level is enough.
RUST_LOG=axon_sync=debug adds the homeserver and authorization-server hosts that discovery found, and each request to slow polling down.
A successful sign-in logs, in order:
Matrix OAuth login flow createdMatrix OAuth browser sign-in is waiting for the user to approve, with the link’s host, whether the link carries the code, the polling interval, and the time allowedMatrix OAuth device authorization approved, with the number of polls and how long the user tookMatrix OAuth login finalized and supervised, with verified=falseMatrix OAuth login flow completedA failure logs a warning with a phase and an error_class, then Matrix OAuth login flow failed with the error_code the client saw.
The last line reached tells you where it stopped:
| Symptom | Look for |
|---|---|
Fails at once with upstream, no “waiting” line |
phase="homeserver_discovery": the error field names the server and the step that refused. Otherwise phase="oauth_discovery", "oauth_registration" or "device_authorization". |
Fails at once with unsupported |
reason="no_endpoint" or reason="grant_type_not_listed": the authorization server does not offer the device grant. phase="oauth_discovery" with error_class="unsupported" means there is no authorization server at all. |
Matrix OAuth device authorization was refused |
oauth_error. invalid_scope means the server rejects the scopes Axon asks for. unauthorized_client means the registered client may not use the device grant. |
| User says they approved, but the flow ends as expired | The “waiting” line shows the time allowed, and the expiry line shows polls and waited_secs. No “approved” line means the server never reported approval, usually because the user approved a different code or session. |
| “Approved” is logged, then the flow fails | phase="e2ee_initialization" with timeout: the homeserver did not answer the encryption setup requests that follow a new session within a minute. phase="identity_validation" for a wrong user, phase="device_validation" for a homeserver that did not bind the token to the requested device, phase="session_validation" for a missing refresh token. |
| User was asked to type a code they could not find | link_carries_code=false on the “waiting” line: the server sent a bare link, and the client must show the code. |
Flow stays in waiting_for_authorization with repeated retries |
Matrix OAuth token poll did not complete; will retry or token endpoint is unavailable: the authorization server is unreachable. Axon backs off (up to 30 s between polls, longer if the server sends Retry-After) and ends the flow as upstream after two minutes of failures in a row, logging token endpoint kept failing; giving up with unreachable_secs. |
A new sign-in gets 409 though the open one was not approved |
Matrix OAuth login flow kept a token request open; not replaced: the old flow’s token request was still unanswered after 15 s. replaced Matrix OAuth login flow did not stop in time: the old flow was replaced but had not let go of the user yet. Retry shortly. |
The log never contains the link, the user code, the device code, or any token.
oauth_error is only ever one of the standard OAuth error codes, other, or none; text chosen by the remote server is not logged.
Browser sign-in has no settings of its own.
It uses the Matrix OAuth client settings under [sync.matrix_oauth]:
request_timeout_secs bounds each request Axon sends to the authorization server, including the device authorization request and every token poll.static_registrations supplies a client ID for an authorization server that does not allow dynamic registration.
That client must be allowed the device authorization grant.For it to work, the homeserver must advertise an authorization server whose metadata lists a device_authorization_endpoint and the urn:ietf:params:oauth:grant-type:device_code grant type.
In Matrix Authentication Service that is the oauth.device_code_grant_enabled setting.