Skip to content

fix(oauth): backfill ept:pushed_authorization on pre-existing clients (ID2183)#169

Merged
windischb merged 1 commit into
developfrom
fix/par-permission-backfill
Jul 22, 2026
Merged

fix(oauth): backfill ept:pushed_authorization on pre-existing clients (ID2183)#169
windischb merged 1 commit into
developfrom
fix/par-permission-backfill

Conversation

@windischb

Copy link
Copy Markdown
Contributor

What

Fixes the upgrade/backfill bug reported in Atlas modgud/engineering/par-endpoint-permission-backfill-missing (verified via the DevOrbs incident 2026-07-22).

Since Modgud advertises the RFC-9126 /connect/par endpoint, current .NET OIDC clients call it by default (PushedAuthorizationBehavior.UseIfAvailable). BuildClientPermissions() grants ept:pushed_authorization as a baseline, but OAuthAdminService only recomputes permissions when a grant/scope change (or an unrelated re-save) is sent. Clients persisted before that baseline kept their old permission list and failed the PAR challenge with unauthorized_client / OpenIddict ID2183 until an admin re-saved them.

Fix

PushedAuthorizationPermissionBackfill — a one-time, idempotent boot migration (same cold-start-walks-every-realm shape as LegacyAdminGroupRenameBootstrap):

  • Enters each active realm's tenant context and appends a SetPermissions event adding the missing ept:pushed_authorization to every non-deleted client that lacks it.
  • Add-only: grants, scopes, response types, other endpoints are carried through unchanged.
  • The ft:par requirement (RequirePushedAuthorizationRequests) lives in the separate Requirements list and is never touched — this grants may use PAR, never must.
  • The projection carries the authoritative permission set, so only streams that actually need it are aggregated + appended; a repeat boot finds none → no new events.
  • Direct event-stream append (no Wolverine bus), so tenant DBs never need the durable-inbox tables.

Acceptance criteria → tests

Modgud.Api.Tests/Authorization/PushedAuthorizationPermissionBackfillTests.cs:

  1. A stripped legacy client gets ept:pushed_authorization added, exactly (previous perms + PAR), with ft:par and all requirements byte-identical; one append happened; a second boot appends nothing (idempotent).
  2. A client that already has the permission is left untouched (no new event).
  3. A stripped legacy client is refused by /connect/par (ID2183) before the backfill and accepted (201) after — no manual re-save.

Criterion "no cross-tenant write" is structurally guaranteed by the per-realm TenantContext.Enter pattern (same as the tested group-rename bootstrap).

Verification

  • 3 new backfill tests green; existing PAR + group-rename suites green (no regression); backend build green.
  • Not a config toggle — PAR stays enabled; this is purely additive to existing clients.

🤖 Generated with Claude Code

Since Modgud advertises the RFC-9126 /connect/par endpoint, current .NET
OIDC clients call it by default (PushedAuthorizationBehavior.UseIfAvailable).
BuildClientPermissions() grants ept:pushed_authorization as a baseline, but
OAuthAdminService only recalculates permissions when a grant/scope change (or
an unrelated re-save) comes through — so clients persisted before the baseline
kept their old permission list and failed the PAR challenge with
unauthorized_client / OpenIddict ID2183 until an admin re-saved them.

Add PushedAuthorizationPermissionBackfill, a one-time idempotent boot
migration (same cold-start-walks-every-realm pattern as
LegacyAdminGroupRenameBootstrap): for every active realm it enters the tenant
context and appends a SetPermissions event adding the missing PAR endpoint
permission to each non-deleted client that lacks it. Add-only — grants,
scopes, response types and other endpoints are carried through unchanged, and
the separate ft:par requirement (RequirePushedAuthorizationRequests) is never
touched, so this grants "may use PAR", never "must". The projection carries
the authoritative permission set, so only streams that actually need it are
aggregated + appended; a repeat boot finds none and appends nothing.

Tests (Modgud.Api.Tests/Authorization/PushedAuthorizationPermissionBackfillTests):
adds the permission while preserving the rest + ft:par and is idempotent
(one append, second boot none); a client that already has it is untouched;
a stripped legacy client is refused by /connect/par (ID2183) before the
backfill and accepted (201) after — no manual re-save required.

Ref: Atlas modgud/engineering/par-endpoint-permission-backfill-missing
(DevOrbs incident 2026-07-22).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@windischb
windischb merged commit 52af948 into develop Jul 22, 2026
8 checks passed
@windischb
windischb deleted the fix/par-permission-backfill branch July 22, 2026 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant