Skip to content

Harden the invite exchange trust boundary - #55

Merged
woksin merged 4 commits into
mainfrom
fix/harden-invite-exchange-trust-boundary
Jul 14, 2026
Merged

Harden the invite exchange trust boundary#55
woksin merged 4 commits into
mainfrom
fix/harden-invite-exchange-trust-boundary

Conversation

@woksin

@woksin woksin commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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-invite cookie 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.EmailClaim configuration (default email) 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.html well-known page, served (HTTP 403) when the signed-in account's verified email does not match the invited email.

Changed

  • The Phase-2 invite exchange request now forwards the authenticating account's provider-verified email and its verification status, so the backend can apply its own defense-in-depth check of invited-email vs. authenticated-email at accept time.

Security

  • Re-validate the invite token (RSA signature, issuer, audience, and lifetime) at the Phase-2 exchange forward and refuse to forward when it fails. HTTP-only cookies only block browser JavaScript, so an authenticated caller could otherwise hand a self-crafted token to the exchange endpoint.
  • Bind an invitation to its recipient: when the invite token targets a specific email, require the authenticating account's verified email to match (respecting email_verified) and reject on mismatch, so an invitation can no longer be redeemed with a different account.

woksin and others added 4 commits July 14, 2026 10:04
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>
@woksin woksin added the minor label Jul 14, 2026
@woksin
woksin merged commit b9a9ce4 into main Jul 14, 2026
3 checks passed
@woksin
woksin deleted the fix/harden-invite-exchange-trust-boundary branch July 14, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant