fix(auth): redirect to login from authorize whatever the Accept header - #59
Open
ExtraToast wants to merge 1 commit into
Open
ExtraToast wants to merge 1 commit into
ExtraToast wants to merge 1 commit into
Conversation
The authorization-server entry point redirected only requests whose Accept matched text/html, so a request arriving with Accept: application/json was answered with a bodyless 401 instead of the login page. Outline sign-in on mobile hit exactly that after /auth/oidc redirected to the authorize endpoint. The authorize endpoint now has its own path-scoped entry point. /api/userinfo keeps answering 401 for a JSON request, which a test now pins.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Outline sign-in from a phone showed a bare 401 where the login page belongs.
notes.jorisjonkers.dev/auth/oidcredirects to/api/oauth2/authorize, and that endpoint's entry point redirects to/loginonly when the request'sAcceptmatchestext/html. Anything else falls through to a bodyless 401.From the Traefik access log, 2026-09-18 — same URL, same client, three times:
The query strings are byte-identical, so only a header can explain the difference. Confirmed against production and reproduced locally:
Accepttext/html/login/login*/*/login/loginapplication/json/loginOutline registered its service worker one second before the first 401, which is the likeliest re-issuer of that navigation as a non-HTML fetch; an installed PWA shell produces the same shape. The initiator does not need naming to fix this — the authorize endpoint is only ever a browser destination, so no
Acceptvalue should decide whether the user sees a login page.This is the third instance of the same mechanism on this chain: JWKS answered 401 to
Accept: application/json(#50), and the discovery document escaped only because its filter short-circuits before authorization.Fix
/api/oauth2/authorizegets its own path-scopedLoginUrlAuthenticationEntryPoint, registered before thetext/htmlmedia-type mapping. Every other endpoint on the chain keeps the behaviour it has:/api/userinfostill answers 401 to a JSON request, which a new test pins so a later broadening cannot turn an API endpoint into a redirect.Validation
@ParameterizedTestcases coverapplication/json,text/html,*/*andapplication/json, text/plain, */*. Theapplication/jsoncase returns 401 on the unfixed chain — run before the change, not inferred./api/userinfowithAccept: application/jsonstill returns 401../gradlew :api:integrationTest— 212 tests, 0 failures.:api:test— 175, 0 failures.detektMain,detektTest,detektIntegrationTestclean.param(): MockMvc putsparam()values in the query string and the parameter map, and the authorization endpoint rejects the duplicatedresponse_typewithinvalid_request. A pre-encodedredirect_uriis double-encoded for the same reason, and+never decodes to a space — each of those returns a 400 that looks like the endpoint refusing the request. Written up in the test.Risk
session-loginattempts that followed were 400invalid-credentials, not 401 — wrong credentials on that device, then a first-try success on desktop.findCredentialsByUsernameis an exact match with no email fallback and no case folding, so autofilling an email address into that field fails with no hint. Out of scope here; worth its own change.