fix(shared-auth): send the Boot error body on a 403 refusal [JDWLABS-499] - #224
Merged
Conversation
CustomAccessDeniedHandler's sendError forwards to /error with the same verified token that got the caller past authentication, so that dispatch succeeds and BasicErrorController answers with a real application/json body instead of the empty one this library previously wrote for both 401 and 403. WriteForbidden now emits that body: a fixed-order timestamp/status/error/path object with no message key, since server.error.include-message is never rather than merely unset. WriteUnauthorized is untouched — the 401 forward is refused before BasicErrorController runs, so it stays genuinely empty. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FvYBM6o7wARm2v9jmDp1yY
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
authhttp.WriteForbidden) previously wrote the status and theAccess-Denied-Reasonheader with no body, mirroring what was assumed for both 401 and 403. Measured against a bootedusersrole(FilterChainContractParityTests.theAccessDeniedHandlerAnswersWithTheContainerErrorBody), the 403 is not empty: the caller's verified token also authenticates the internal forward to/error, soBasicErrorControllerruns and answers with Boot's standard error JSON.WriteForbiddennow emits that body:Content-Type: application/jsonand a fixed-ordertimestamp/status/error/pathobject, withmessageabsent entirely (server.error.include-messageisnever).timestampuses millisecond precision with an explicit numeric offset, matching Boot's Jackson output.WriteUnauthorized(401) is untouched — that forward is refused beforeBasicErrorControllerever runs, so it stays genuinely empty.libs/backend/shared/auth/README.md's error-body section is corrected: the "this library currently reproduces only the 401 half" gap note is removed now that both halves are reproduced.Test plan
assertContainerErrorBodywritten first against the old empty-bodyWriteForbidden, confirmed red, then made green by the implementation.go test ./... -race -count=1— all packages pass, includingauthhttp.Example(unchanged, still compiles).go vet ./...— clean.golangci-lint run ./...— no issues found.npx nx run-many -t lint test -p backend-shared-auth— both targets pass.Exact body emitted (measured with a throwaway probe against
WriteForbidden, not committed){"timestamp":"2026-09-06T04:31:08.376+00:00","status":403,"error":"Forbidden","path":"/api/profiles/7"}Content-Type: application/json,Access-Denied-Reason: Not Authorized.Tests added/changed
authhttp.assertContainerErrorBody— new field-by-field fixture comparing the Go 403 body against the shapeFilterChainContractParityTests.theAccessDeniedHandlerAnswersWithTheContainerErrorBodymeasured on the JVM (status/error by value, timestamp/path by shape).TestWriteForbiddenMatchesTheAccessDeniedHandler— updated to assert the new body instead of an empty one.TestAuthorizeWritesTheAccessDeniedShapeOnDenial— updated the same way for theAuthorizehelper's 403 path.assertRefusalShape— narrowed in its doc comment to the 401 shape, which is unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_01FvYBM6o7wARm2v9jmDp1yY