Repository navigation
fix(accounts): the refresh cookie crosses the REST transcoder as Set-Cookie - #1150
Merged
Merged
Conversation
…Cookie Since the Connect authentication completions lift the refresh token into the cookie themselves (cookieTheRefreshToken, #1019) and clear it from the body, the REST surface's cookie came back through grpc-gateway as header metadata, which the default matcher writes as Grpc-Metadata-Set-Cookie. A browser stores no cookie from that header and the REST middleware had no body token left to lift, so every browser sign-in lost its session on the next page load and was sent back to sign-in — and the refresh credential travelled in a header page script can read. restOutgoingHeaderMatcher keeps Set-Cookie's name across the transcoder; every other header keeps grpc-gateway's default prefix. Tests drive the transcoder's own response forwarding with and without the matcher, and hold the CUSTOM wiring line in the generated rest_gen.go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1150: the transcoder test fed hand-built metadata, so the joined path was still untested — the gap #1019 shipped through. The chain test now drives REST -> refreshTokenCookie -> grpc-gateway (the matchers rest_gen.go wires) -> gRPC over h2c -> a Connect AuthService using the production lift and cookie readers, for authenticate, refresh and logout: exactly one codefly_rt Set-Cookie, no token in any body, nothing prefixed, and the cookie a browser sends back is what the handler receives. Without the matcher it fails with the cell's symptom. The wiring check parses rest_gen.go like the incoming matcher's lockstep test (a commented-out option no longer passes), and the matcher no longer sits between refreshCookie and its doc comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Independent security review (Opus 5.5): 0 blockers, 1 major, 3 minors. Disposition at the new head:
|
antoinetoussaint-byte
enabled auto-merge
October 18, 2026 17:45
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.
What broke
A browser sign-in through the REST surface completes (
POST /v1/auth/authenticateanswers 200 with an access token), then the next page load'sPOST /v1/auth/refreshanswers 400refresh_token: must be at least 1 charactersand the browser is sent back to sign-in. Observed on a deployed cell running v0.0.101; reproduced in the browser step by step:refreshTokenof length 0 in its body;grpc-metadata-set-cookieand noSet-Cookie;codefly_rt, and refresh has nothing to send.Cause
Since #1019 the Connect authentication completions lift the refresh token into the cookie (
cookieTheRefreshToken) and clear it from the body. The REST mux transcodes to that Connect surface over gRPC, so the handler'sSet-Cookiearrives as header metadata and grpc-gateway's default matcher writes it asGrpc-Metadata-Set-Cookie. The REST middleware (refreshTokenCookie) finds an empty body token and sets nothing. Two consequences:Grpc-Metadata-Set-Cookieis not a forbidden response header, sofetchexposes it — which is what the HttpOnly cookie exists to prevent.Each half was tested alone (the REST middleware against a fake body, the Connect lift against a header), never through the transcoder.
Fix (at the hop that renamed the header)
restOutgoingHeaderMatcher(rest_extras.go):set-cookiemetadata →Set-Cookie; every other key keeps grpc-gateway's defaultGrpc-Metadata-prefix.rest_gen.go: one CUSTOM line wiring it (runtime.WithOutgoingHeaderMatcher), marked preserve when regenerating like the incoming matcher above it.Tests
TestTheRefreshCookieCrossesTheRESTTranscoderAsSetCookie— drives grpc-gateway's ownForwardResponseMessagewith the matcher: exactly oneSet-Cookieequal to the refresh cookie, no prefixed copy, other metadata unchanged.TestTheDefaultTranscoderPrefixesTheCookie— the same forward without the matcher reproduces the bug (noSet-Cookie, a prefixed header).TestTheRESTMuxIsBuiltWithTheOutgoingHeaderMatcher— holds the generated file's wiring line.go test ./pkg/adapters/green,go vetandgofmtclean.Reused: the existing
refreshCookiebuilder and the incoming-matcher CUSTOM pattern. Classification: fix. Docs: none describe the transcoder's header mapping.🤖 Generated with Claude Code