You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(auth): OAuth error codes, statuses and challenges follow the RFCs - #522
Auth's OAuth errors drifted from RFC 6749, RFC 6750 and RFC 9126 in small ways that clients and the conformance suite trip on: state= sent back to clients that sent none (oauth4webapi rejects it), a Basic challenge for clients that never used Basic, and codes that don't say what went wrong.
Now auth answers each case the standard way:
Codes.unsupported_response_type for anything but response_type=code; unauthorized_client for a client that must push its requests; insufficient_scope (403, with its Bearer challenge naming openid) for a UserInfo token without openid; invalid_request for a missing code, refresh_token or response_type.
Challenges and statuses.invalid_client is 401 with a Basic challenge only when the client sent an Authorization header, otherwise 400. Nothing answers 401 without a challenge (a page refusing the browser's session is now 403). UserInfo without any token gets a bare Bearer challenge.
state is optional end to end (nullable column, migration 0049), so none goes back when none was sent, and an oversized one fails before any redirect carries it.
Pushed requests. A request_uri is read before every other parameter, so max_age=garbage beside it no longer fails the claim, and a push must name client_id even with Basic. Both SDKs now always send client_id.
An error before the redirect URI is verified already shows auth's error page since #518, so this no longer moves /oauth/authorize and /oidc/logout into the pages.
Tests cover each case (oidc/tests/errors.rs, plus updated expectations elsewhere), and the OIDC reference, troubleshooting page and architecture doc describe the behaviour.
Written by Claude Opus 5.5 in Claude Code (via T3 Code)
Summary
This change aligns OAuth 2.0 and OIDC error codes, HTTP status codes, WWW-Authenticate challenges, and state handling with RFC 6749, RFC 6750, and RFC 9126. It also updates PAR request handling, token response headers, and the related SDKs, database schema, tests, and documentation.
Test results and conformance suite results are not available from the supplied evidence.
Protocol and error model
crates/common/src/protocol/oauth.rs: ErrorCode adds UnauthorizedClient (unauthorized_client), InsufficientScope (insufficient_scope), and UnsupportedResponseType (unsupported_response_type).
apps/auth-service/src/oidc/error.rs:
OAuthError::Protocol stores an optional credential challenge.
A new Challenge type represents Basic and Bearer challenges. It supplies the WWW-Authenticate value and the response status.
Basic challenges return 401. A Bearer challenge uses the status from BearerChallenge, and falls back to 401 if that status is invalid.
OAuthError::challenged attaches a challenge only to protocol rejections. Internal errors do not receive one.
into_response emits the stored challenge header.
The code-based challenge selection is removed. Callers now assign challenges.
Unchallenged InvalidToken and InsufficientScope responses return 403.
InvalidClient and UnauthorizedClient use the default 400 status instead of 401.
apps/auth-service/src/oidc/inline.rs: InlineResponse.state is optional. It is omitted from the JSON when absent.
Client authentication and redirects
apps/auth-service/src/oidc/mod.rs:
Client authentication separates requests with an Authorization header from requests without one. Failures with a header receive a Basic challenge. Failures without a header have no challenge.
Authentication logic moves into a helper that receives the optional header.
ClientRedirect accepts an optional state. It appends state only when present. iss is still appended.
Authorization endpoint (/oauth/authorize)
apps/auth-service/src/oidc/authorize/mod.rs:
The handler accepts raw query or form bytes and parses them in two stages.
First, it reads request_uri and its optional client_id. If request_uri is present, it claims the pushed request and does not parse other parameters.
Otherwise, it deserializes and validates the full browser request.
Malformed raw input or parameters return invalid_request.
A missing state is rejected before the client redirect is built.
A client that requires pushed requests receives unauthorized_client instead of invalid_request.
start_authorization returns the redirect URL directly. resume remains a separate redirect handler.
Client redirects pass state and the stored client_state as optional values.
AuthorizationParameters.state changes from String to Option<String>.
The 512-byte limit applies only when state is present. Oversized values still return InvalidRequest.
state() becomes pub(super).
An empty response_type returns InvalidRequest.
An unsupported non-empty response_type returns UnsupportedResponseType instead of InvalidRequest.
apps/auth-service/src/oidc/authorize/consent.rs: decide passes request.client_state.as_deref() to client_redirect.
Pushed authorization requests (PAR)
apps/auth-service/src/oidc/authorize/pushed.rs:
push_authorization returns invalid_request when client_id is absent. This check runs before client authentication.
When client_id is present, it always passes client_id to client authentication. This includes requests that use Basic credentials.
Token endpoint and UserInfo
apps/auth-service/src/oidc/token.rs:
OidcApplication::token returns a Pragma: no-cache header with the token JSON. Its signature changes to return ([(HeaderName, &'static str); 1], Json<TokenResponse>) in the success case.
Missing code and missing refresh_token return invalid_request instead of invalid_grant.
userinfo_response attaches Bearer challenges to missing, malformed, or unverifiable access tokens. These return invalid_token.
A token without the openid scope returns insufficient_scope. The challenge names the required scope.
Database
crates/database/migrations/0049_optional_state.sql: removes the NOT NULL constraint from authorization_requests.client_state.
crates/database/src/repositories/oidc/mod.rs: InsertAuthorizationRequest::client_state and StoredAuthorizationRequest::client_state change from String to Option<String>. Their documentation describes state as optional.
crates/database/src/repositories/oidc/tests.rs: the PAR test sets client_state to Some("state").
crates/database/.sqlx/*.json: three SQLx metadata files update nullability for redirect_uri and one other column. The metadata matches the schema change.
Client SDKs
crates/auth-sdk/src/client.rs: Client::post adds client_id to every form. This includes confidential-client requests that use HTTP Basic. Previously only public-client forms included it.
packages/auth/src/client.ts: Client.#post adds client_id to the form for public and confidential clients. Public-client form behavior does not change.
packages/auth/src/protocol.ts: ErrorCode adds "unauthorized_client", "unsupported_response_type", and "insufficient_scope". The open-ended string branch is unchanged.
packages/auth/test/client.test.ts: the confidential-client refresh test expects client_id: 'notes-web' in the token request form. The public-client test title changes to "with its ID alone."
Web and UI
crates/ui/src/oidc.rs: the InvalidRequest failure message no longer names a sign-in request. UnauthorizedClient, InsufficientScope, and UnsupportedResponseType use the generic request-failure message.
apps/auth-service/src/web/auth/login/tests.rs: the expired or invalid sign-in message assertion changes from "This sign-in request is invalid or expired." to "This request is invalid or expired."
Tests
apps/auth-service/src/oidc/tests/errors.rs (new, included through tests/mod.rs): six ignored, database-dependent async tests. They cover:
unsupported and missing response types
optional and oversized state
pushed-request parameter handling
token endpoint authentication and malformed grants
UserInfo scope enforcement
apps/auth-service/src/oidc/tests/authorization.rs: two consent-flow tests expect FORBIDDEN instead of UNAUTHORIZED.
apps/auth-service/src/oidc/tests/protocol.rs: a tokenless UserInfo request expects 401 with WWW-Authenticate: Bearer. The previous challenge included error="invalid_token".
apps/auth-service/src/oidc/tests/pushed.rs:
An unregistered client's push request expects 400 with invalid_client.
An unclaimable request_uri checks the rendered error page for either of two messages.
A plain authorization request from a client that requires PAR expects unauthorized_client.
apps/auth-service/src/oidc/tests/tokens.rs: an unauthenticated authorization-code exchange expects BAD_REQUEST. The test notes that RFC 6749 §5.2 does not require a challenge here.
apps/auth-service/src/clients/tests/registry.rs: the authenticates helper parses the OAuth error response from the token endpoint. A client counts as authenticated unless its error code is InvalidClient. The previous check rejected only HTTP 401. The tests also use Axum's bounded to_bytes reader.
Dependencies
apps/auth-service/Cargo.toml: serde_urlencoded moves from [dev-dependencies] to [dependencies].
Documentation
docs/dev/architecture/oidc.md: the architecture table documents oidc/error.rs. The authorization flow section describes the two-stage PAR path and the bounded state. It also covers unsupported_response_type and unauthorized_client.
docs/user/apps/troubleshooting.md:
Redirect-URI errors include iss before state when present.
A separate state limit of 512 bytes is documented.
Guidance covers missing client_id in pushed requests, missing code or refresh_token, and HTTP Basic responses (400 and 401).
docs/user/reference/oidc.md: the PAR section requires client_id even with HTTP Basic. It also adds an Errors section. The section covers error codes, local versus redirected errors, iss, conditional state, the 512-byte limit, endpoint-specific invalid_client responses, UserInfo bearer errors, and no-cache token headers.
OAuth error codes now include unauthorized_client, insufficient_scope, and unsupported_response_type. Protocol errors can carry Basic or Bearer challenges, and authentication errors use a Basic challenge when the request includes an Authorization header. Related status expectations and UI messages are updated.
Authorization parsing and optional state apps/auth-service/Cargo.toml, apps/auth-service/src/oidc/authorize/*, apps/auth-service/src/oidc/inline.rs, apps/auth-service/src/oidc/mod.rs, crates/database/migrations/0049_optional_state.sql, crates/database/src/repositories/oidc/*, crates/database/.sqlx/*, crates/auth-sdk/src/client.rs, packages/auth/src/client.ts, packages/auth/test/client.test.ts, apps/auth-service/src/oidc/tests/errors.rs, apps/auth-service/src/oidc/tests/pushed.rs, docs/dev/architecture/oidc.md, docs/user/apps/troubleshooting.md, docs/user/reference/oidc.md
Authorization reads request_uri and optional client_id before parsing other parameters. The flow supports missing state, limits supplied state to 512 bytes, and requires client_id for pushed requests. SDK clients include client_id in confidential-client forms.
Token and UserInfo responses apps/auth-service/src/oidc/token.rs, apps/auth-service/src/oidc/tests/*, docs/user/reference/oidc.md
Token responses add Pragma: no-cache, and missing grant parameters return invalid_request. UserInfo applies Bearer challenges to missing or rejected tokens and returns insufficient_scope when a token lacks openid.
sequenceDiagram
participant authorize
participant start_authorization
participant AuthorizationRequest
authorize->>start_authorization: raw form request
start_authorization->>AuthorizationRequest: read PushedReference
AuthorizationRequest-->>start_authorization: client_id and request_uri
alt request_uri is present
start_authorization->>start_authorization: claim pushed request with client_id
else request_uri is absent
start_authorization->>AuthorizationRequest: deserialize full authorization parameters
end
start_authorization-->>authorize: authorization URL
Loading
Merge Risk: Moderate · up to 74b59
The OAuth error and state changes look consistent with their stated intent, but the required conformance CI job fails at this head. Confirm whether the mismatches are only the expected logout outcomes and update the expected-results list, or fix the cause, before merging.
Pre-merge checks: 3 · 1
Failed checks: 1 inconclusive
Check name
Status
Explanation
Resolution
Linked Issues check
Issue #275 has coding objectives, and the current PR addresses most of them. Evidence is the PR summary, the head doc at docs/user/reference/oidc.md, and the attested stack summaries. Stack status: PR…
Provide the OP basic conformance suite result for the head commit 74b5994, or confirm that the conformance check is not required for this assessment. Provide a review of PR #521 (head b7aecc2…
Passed checks: 3 passed
Check name
Status
Explanation
Description Check
Check skipped - CodeRabbit’s high-level summary is enabled.
Title check
The title names the fix and its scope: OAuth error codes, HTTP statuses, and challenges aligned with the RFCs. The changeset covers all three: new ErrorCode variants (unauthorized_client, insufficient…
Out of Scope Changes check
Each change in #522 maps to an issue #275 objective or to documentation for that objective:
- Error code additions in crates/common and packages/auth/src/protocol.ts: directly required. - Migration 0…
Full details: Title check
Explanation
The title names the fix and its scope: OAuth error codes, HTTP statuses, and challenges aligned with the RFCs. The changeset covers all three: new ErrorCode variants (unauthorized_client, insufficient_scope, unsupported_response_type), status changes to 401/403/400 handling, and WWW-Authenticate challenge logic in oidc/error.rs. The title does not mention the optional state, PAR handling, or Pragma header, but it does not need to cover every detail. It is a single, specific sentence that a teammate scanning history would understand.
Full details: Out of Scope Changes check
Explanation
Each change in #522 maps to an issue #275 objective or to documentation for that objective:
Error code additions in crates/common and packages/auth/src/protocol.ts: directly required.
Migration 0049, .sqlx metadata, and database repository Option<String> changes for client_state: required for optional state.
oidc/error.rs, oidc/mod.rs, oidc/inline.rs, and token.rs: required for challenges, statuses, Pragma, and optional state.
SDK changes (crates/auth-sdk/src/client.rs, packages/auth/src/client.ts) that always send client_id: required by the PAR client_id objective. Their test updates (packages/auth/test/client.test.ts) support the same change.
authorize/mod.rs, parameters.rs, pushed.rs, consent.rs: required for parse order, optional state, and unauthorized_client.
Test updates in oidc/tests/authorization.rs, protocol.rs, pushed.rs, tokens.rs, registry.rs: expectations that change with the new statuses and codes.
web/auth/login/tests.rs and crates/ui/src/oidc.rs message text: the invalid_request message is changed to a generic form. This follows the change to InvalidRequest handling. It is a small user-visible text change. It is connected to the objective, not unrelated.
docs/dev/architecture/oidc.md, docs/user/apps/troubleshooting.md, docs/user/reference/oidc.md: documentation of the changed behaviour.
No change was found that lacks a connection to the issue objectives.
Full details: Linked Issues check
Explanation
Issue #275 has coding objectives, and the current PR addresses most of them. Evidence is the PR summary, the head doc at docs/user/reference/oidc.md, and the attested stack summaries. Stack status: PR #522 (current) is reviewed. PR #521 (base of this PR, the merge base is its head) is uncertain. PR #523 (downstream) is uncertain. Objectives by status:
[#275] Add and use unsupported_response_type, unauthorized_client, insufficient_scope in ErrorCode: Addressed by #522 (crates/common/src/protocol/oauth.rs; authorize/parameters.rs; authorize/mod.rs; token.rs for insufficient_scope 403).
[#275] invalid_request for missing code, refresh_token: Addressed by #522 (token.rs).
[#275] state optional end to end (nullable column, migration 0049, repositories/oidc, inline.rs, mod.rs): Addressed by #522.
[#275] Oversized state checked before redirect: Addressed by #522 (parameters.rs, authorize/mod.rs; 512-byte limit documented in docs/user/reference/oidc.md).
[#275] Basic challenge only when Basic was used; 401 responses carry a challenge: Addressed by #522 (oidc/error.rs, oidc/mod.rs). Unchallenged rejections are 400 or 403 by design, as documented.
[#275] Pragma: no-cache on token responses: Addressed by #522 (token.rs).
[#275] PAR path chosen before other fields parsed; client_id required in PAR even with Basic: Addressed by #522 (authorize/mod.rs, pushed.rs, crates/auth-sdk/src/client.rs, packages/auth/src/client.ts).
[#275] Errors before redirect verification render the HTML error page: Addressed by existing behaviour. The reference doc at head states that until the client and redirect URI are checked, errors appear on the auth error page. The PR cites #518 as the source. This PR does not change that path.
[#275] Test per case: Addressed in part by #522 (oidc/tests/errors.rs, plus updated expectations). The six new tests are #[ignore]d database-dependent tests. Their pass status is not shown.
[#275] Conformance suite OP basic plan passes these checks: Uncertain. The PR states it does not report a conformance result. No evidence is available.
Stack uncertainty: #521 and #523 have no current review evidence. This PR is stated to be stacked on #521. No objective was shown to depend on #521 code, but this cannot be excluded from the available evidence.
Resolution
Provide the OP basic conformance suite result for the head commit 74b5994, or confirm that the conformance check is not required for this assessment. Provide a review of PR #521 (head b7aecc2) that confirms no required objective depends on code outside this PR. Provide a test run result for oidc/tests/errors.rs, or confirm that the ignored database tests were run.
Finishing Touches
Generate unit tests (beta)
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
A rabbit reads the query’s trail, And skips the fields beyond the veil. If state is gone, no blank string grows, A challenge comes when auth header shows. Then tokens rest beneath no-cache, While errors find their proper place.
Recent review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 905e865e-bf79-4e2f-90c9-3e704efd7ee8
Commits
Reviewing files that changed from the base of the PR and between b7aecc2 and 74b5994.
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
- unsupported_response_type, unauthorized_client and insufficient_scope join
the shared codes, and auth uses them: another response_type, a client that
must push sending a request through the browser, and a UserInfo token
without openid, at 403 with its Bearer challenge.
- A missing code or refresh_token is invalid_request.
- state is optional end to end: no empty state goes back to a client that
sent none, and an oversized one fails before any error carries it.
- invalid_client challenges Basic, at 401, only when the client sent an
Authorization header, and is 400 otherwise; nothing is 401 without a
challenge. UserInfo challenges a missing token without an error code.
- Token responses carry Pragma: no-cache.
- A pushed request's request_uri is read before every other parameter, and
a push must name its client_id even with Basic credentials. Both SDKs now
always send client_id.
Closes#275
PiquelChips
changed the title
fix(auth): OAuth errors follow the RFCs, and unverified requests get a page instead of raw JSON
fix(auth): OAuth error codes, statuses and challenges follow the RFCs
Nov 13, 2026
PiquelChips
added
bug
Something isn't working
apps/auth-service
Origin Auth at auth.sourceorigin.dev, its settings and developer console, and the operator CLI
standards
Open standards compliance (OIDC, OAuth, RFCs)
labels
Nov 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apps/auth-serviceOrigin Auth at auth.sourceorigin.dev, its settings and developer console, and the operator CLIbugSomething isn't workingschema-changeAny changes to the database schemastandardsOpen standards compliance (OIDC, OAuth, RFCs)
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Auth's OAuth errors drifted from RFC 6749, RFC 6750 and RFC 9126 in small ways that clients and the conformance suite trip on:
state=sent back to clients that sent none (oauth4webapi rejects it), a Basic challenge for clients that never used Basic, and codes that don't say what went wrong.Now auth answers each case the standard way:
unsupported_response_typefor anything butresponse_type=code;unauthorized_clientfor a client that must push its requests;insufficient_scope(403, with its Bearer challenge namingopenid) for a UserInfo token withoutopenid;invalid_requestfor a missingcode,refresh_tokenorresponse_type.invalid_clientis 401 with a Basic challenge only when the client sent anAuthorizationheader, otherwise 400. Nothing answers 401 without a challenge (a page refusing the browser's session is now 403). UserInfo without any token gets a bareBearerchallenge.stateis optional end to end (nullable column, migration 0049), so none goes back when none was sent, and an oversized one fails before any redirect carries it.Pragma: no-cachebesideCache-Control: no-store.request_uriis read before every other parameter, somax_age=garbagebeside it no longer fails the claim, and a push must nameclient_ideven with Basic. Both SDKs now always sendclient_id.An error before the redirect URI is verified already shows auth's error page since #518, so this no longer moves
/oauth/authorizeand/oidc/logoutinto the pages.Tests cover each case (
oidc/tests/errors.rs, plus updated expectations elsewhere), and the OIDC reference, troubleshooting page and architecture doc describe the behaviour.Stacked on #521.
Closes #275
Note
Written by Claude Opus 5.5 in Claude Code (via T3 Code)
Summary
This change aligns OAuth 2.0 and OIDC error codes, HTTP status codes,
WWW-Authenticatechallenges, andstatehandling with RFC 6749, RFC 6750, and RFC 9126. It also updates PAR request handling, token response headers, and the related SDKs, database schema, tests, and documentation.Test results and conformance suite results are not available from the supplied evidence.
Protocol and error model
crates/common/src/protocol/oauth.rs:ErrorCodeaddsUnauthorizedClient(unauthorized_client),InsufficientScope(insufficient_scope), andUnsupportedResponseType(unsupported_response_type).apps/auth-service/src/oidc/error.rs:OAuthError::Protocolstores an optional credential challenge.Challengetype represents Basic and Bearer challenges. It supplies theWWW-Authenticatevalue and the response status.BearerChallenge, and falls back to 401 if that status is invalid.OAuthError::challengedattaches a challenge only to protocol rejections. Internal errors do not receive one.into_responseemits the stored challenge header.InvalidTokenandInsufficientScoperesponses return 403.InvalidClientandUnauthorizedClientuse the default 400 status instead of 401.apps/auth-service/src/oidc/inline.rs:InlineResponse.stateis optional. It is omitted from the JSON when absent.Client authentication and redirects
apps/auth-service/src/oidc/mod.rs:Authorizationheader from requests without one. Failures with a header receive a Basic challenge. Failures without a header have no challenge.ClientRedirectaccepts an optionalstate. It appendsstateonly when present.issis still appended.Authorization endpoint (
/oauth/authorize)apps/auth-service/src/oidc/authorize/mod.rs:request_uriand its optionalclient_id. Ifrequest_uriis present, it claims the pushed request and does not parse other parameters.invalid_request.stateis rejected before the client redirect is built.unauthorized_clientinstead ofinvalid_request.start_authorizationreturns the redirect URL directly.resumeremains a separate redirect handler.stateand the storedclient_stateas optional values.apps/auth-service/src/oidc/authorize/parameters.rs:AuthorizationParameters.statechanges fromStringtoOption<String>.stateis present. Oversized values still returnInvalidRequest.state()becomespub(super).response_typereturnsInvalidRequest.response_typereturnsUnsupportedResponseTypeinstead ofInvalidRequest.apps/auth-service/src/oidc/authorize/consent.rs:decidepassesrequest.client_state.as_deref()toclient_redirect.Pushed authorization requests (PAR)
apps/auth-service/src/oidc/authorize/pushed.rs:push_authorizationreturnsinvalid_requestwhenclient_idis absent. This check runs before client authentication.client_idis present, it always passesclient_idto client authentication. This includes requests that use Basic credentials.Token endpoint and UserInfo
apps/auth-service/src/oidc/token.rs:OidcApplication::tokenreturns aPragma: no-cacheheader with the token JSON. Its signature changes to return([(HeaderName, &'static str); 1], Json<TokenResponse>)in the success case.codeand missingrefresh_tokenreturninvalid_requestinstead ofinvalid_grant.userinfo_responseattaches Bearer challenges to missing, malformed, or unverifiable access tokens. These returninvalid_token.openidscope returnsinsufficient_scope. The challenge names the required scope.Database
crates/database/migrations/0049_optional_state.sql: removes theNOT NULLconstraint fromauthorization_requests.client_state.crates/database/src/repositories/oidc/mod.rs:InsertAuthorizationRequest::client_stateandStoredAuthorizationRequest::client_statechange fromStringtoOption<String>. Their documentation describesstateas optional.crates/database/src/repositories/oidc/tests.rs: the PAR test setsclient_statetoSome("state").crates/database/.sqlx/*.json: three SQLx metadata files update nullability forredirect_uriand one other column. The metadata matches the schema change.Client SDKs
crates/auth-sdk/src/client.rs:Client::postaddsclient_idto every form. This includes confidential-client requests that use HTTP Basic. Previously only public-client forms included it.packages/auth/src/client.ts:Client.#postaddsclient_idto the form for public and confidential clients. Public-client form behavior does not change.packages/auth/src/protocol.ts:ErrorCodeadds"unauthorized_client","unsupported_response_type", and"insufficient_scope". The open-ended string branch is unchanged.packages/auth/test/client.test.ts: the confidential-client refresh test expectsclient_id: 'notes-web'in the token request form. The public-client test title changes to "with its ID alone."Web and UI
crates/ui/src/oidc.rs: theInvalidRequestfailure message no longer names a sign-in request.UnauthorizedClient,InsufficientScope, andUnsupportedResponseTypeuse the generic request-failure message.apps/auth-service/src/web/auth/login/tests.rs: the expired or invalid sign-in message assertion changes from "This sign-in request is invalid or expired." to "This request is invalid or expired."Tests
apps/auth-service/src/oidc/tests/errors.rs(new, included throughtests/mod.rs): six ignored, database-dependent async tests. They cover:stateapps/auth-service/src/oidc/tests/authorization.rs: two consent-flow tests expectFORBIDDENinstead ofUNAUTHORIZED.apps/auth-service/src/oidc/tests/protocol.rs: a tokenless UserInfo request expects401withWWW-Authenticate: Bearer. The previous challenge includederror="invalid_token".apps/auth-service/src/oidc/tests/pushed.rs:400withinvalid_client.request_urichecks the rendered error page for either of two messages.unauthorized_client.apps/auth-service/src/oidc/tests/tokens.rs: an unauthenticated authorization-code exchange expectsBAD_REQUEST. The test notes that RFC 6749 §5.2 does not require a challenge here.apps/auth-service/src/clients/tests/registry.rs: theauthenticateshelper parses the OAuth error response from the token endpoint. A client counts as authenticated unless its error code isInvalidClient. The previous check rejected only HTTP 401. The tests also use Axum's boundedto_bytesreader.Dependencies
apps/auth-service/Cargo.toml:serde_urlencodedmoves from[dev-dependencies]to[dependencies].Documentation
docs/dev/architecture/oidc.md: the architecture table documentsoidc/error.rs. The authorization flow section describes the two-stage PAR path and the boundedstate. It also coversunsupported_response_typeandunauthorized_client.docs/user/apps/troubleshooting.md:issbeforestatewhen present.statelimit of 512 bytes is documented.client_idin pushed requests, missingcodeorrefresh_token, and HTTP Basic responses (400and401).docs/user/reference/oidc.md: the PAR section requiresclient_ideven with HTTP Basic. It also adds an Errors section. The section covers error codes, local versus redirected errors,iss, conditionalstate, the 512-byte limit, endpoint-specificinvalid_clientresponses, UserInfo bearer errors, and no-cache token headers.