Marker-cookie pattern per docs/conventions/per-app-edge-identity-validation.md:
- New BbEdgeIdentityMiddleware compares `_bb_edge_sub` cookie to
`X-Auth-Request-User` header on every authenticated request. On mismatch,
flushes the Django session and replaces request.user with AnonymousUser
so DRF returns 401 / browser navigations land at the bridge handoff
redirect. Lazy-populates the cookie on legacy sessions; passes through
for anonymous requests and bearer-token-only callers.
- Trusted-JWT view sets `_bb_edge_sub` on the redirect response when
X-Auth-Request-User is present (single session-mint choke-point — the
Bucket-4 entry-point is the only path that creates Plane sessions in
this deployment).
- SignOutAuthEndpoint reads optional BB_LOGOUT_REDIRECT_URL env. When set,
the SPA's /auth/sign-out/ form-POST is 302'd to the platform bridge's
synced-logout endpoint (clears edge `_bb_oauth2` + back-channels Zitadel
end_session). Without this, the user's Zitadel session at the edge
outlives the Plane logout and silently re-logs them in via bridge handoff
→ trusted sign-in. Vanilla regression-safe: env unset → upstream behavior.
Net surface vs upstream-clean: 1 new middleware file, 1 line in MIDDLEWARE,
~20 lines added to trusted.py and signout.py. No new dependencies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>