Harden the invite exchange trust boundary - #55
Merged
Conversation
System.Security.Claims is already a global using; the local directive raised an IDE0005 warning on build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AuthProxy validated the signed invite JWT at the magic-link entry (Phase 1) but forwarded the token from the HTTP-only .cratis-invite cookie verbatim as a Bearer token to Invite.ExchangeUrl at Phase 2 without re-checking it, and never bound the invitation to its intended recipient. Both let an authenticated caller redeem an invitation they should not. - Re-validate the token (RSA signature, issuer, audience, lifetime) at the Phase-2 exchange forward and refuse to forward when it fails, so AuthProxy is the authoritative invite validator across both phases. HTTP-only only blocks browser JS - a logged-in caller can still craft the cookie value. - Bind the invite to its recipient: when the token carries Invite.EmailClaim (default 'email'), require the authenticating account's provider-verified email to match, respecting email_verified, and reject on mismatch with the new invitation-email-mismatch page. - Forward the verified email (and its verification status) in the exchange body so the backend can apply its own defense-in-depth check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the Phase-2 trust boundary with a real InviteTokenValidator and signed tokens: a validly-signed cookie forwards, while forged, unsigned, expired, wrong-issuer and wrong-audience tokens are rejected. Cover email binding: a matching verified email forwards and is included in the exchange body, while a different account's email or an unverified email is rejected, and an invite that targets no email still forwards. Add an end-to-end scenario for a forged cookie presented after a normal login. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Describe the Phase-2 re-validation, the Invite.EmailClaim binding, and the new invitation-email-mismatch page across the well-known pages reference and the two invitation lobby flow guides. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
Makes AuthProxy the authoritative invite-token validator across both phases of the invite flow and binds an invitation to its intended recipient. Previously the signed invite token was validated only at the magic-link entry (Phase 1) and then forwarded verbatim from the HTTP-only
.cratis-invitecookie to the exchange endpoint (Phase 2) without re-validation, and nothing bound the invited email to the authenticating account — so a logged-in caller could present a self-crafted token, or accept an invitation with a different account, and be provisioned as the invited user.Added
Invite.EmailClaimconfiguration (defaultemail) naming the invite-token claim that carries the email an invitation was issued for. Set it to an empty string to disable email-binding enforcement.invitation-email-mismatch.htmlwell-known page, served (HTTP 403) when the signed-in account's verified email does not match the invited email.Changed
Security
email_verified) and reject on mismatch, so an invitation can no longer be redeemed with a different account.