Skip to content

feat(oauth): access-denied page instead of redirect on gate failure#67

Merged
BK1031 merged 2 commits into
mainfrom
bk1031/feat-access-denied-page
Jun 3, 2026
Merged

feat(oauth): access-denied page instead of redirect on gate failure#67
BK1031 merged 2 commits into
mainfrom
bk1031/feat-access-denied-page

Conversation

@BK1031
Copy link
Copy Markdown
Contributor

@BK1031 BK1031 commented Jun 2, 2026

When a user fails an application's required-group access gate, show a clear in-app Access denied page instead of immediately redirecting them back to the client with ?error=access_denied.

Screenshot

Access denied page

Backend

  • ValidateAuthorize now runs CheckAccessGate (using the entity_id the SPA already sends) and returns 403 {error: access_denied, app_name} when the user doesn't qualify. So the denial is determined on landing, before the consent screen renders.
  • Fails closed: a non-denial gate error (core unreachable) returns 502, consistent with the rest of the flow.
  • The gate is still enforced at the authorize POST and token exchange too (defense in depth) — this just surfaces it earlier for UX.

Frontend (AuthorizePage)

  • Renders a dedicated "You don't have access to {app}" page for the 403 access_denied validate response — the denied user never sees a consent screen.
  • Safety net: the approve POST also catches 403 access_denied (membership can change between landing and approving) and shows the same page instead of redirecting.
  • Other errors still redirect to the client per spec; genuine server errors still show the generic "Can't authorize" page.

Notes

  • User-initiated Cancel still redirects with access_denied (the client should know the user declined) — only gate denials show the in-app page.
  • Verified: oauth go build/vet/gofmt clean; web tsc/eslint/build clean.

BK1031 added 2 commits June 2, 2026 16:55
…ailure

When a user fails an application's required-group gate, show a clear
in-app error page rather than bouncing them back to the client with
?error=access_denied.

- ValidateAuthorize runs CheckAccessGate (entity_id from the session) and
  returns 403 access_denied + app_name, so denial shows on landing — before
  the consent screen
- AuthorizePage renders a dedicated Access denied page for that response,
  and also catches a 403 access_denied on the approve POST as a safety net
  (membership can change between landing and approving) instead of redirecting
Match the consent screen's layout: app icon (gradient tile or icon URL),
app name, and a 'Back to <app>' action. Backend 403 now also returns
app_icon_url so the validate-step denial can render the icon. Extract a
shared AppAvatar used by both the consent and denied views.
@BK1031 BK1031 merged commit 434bc25 into main Jun 3, 2026
12 checks passed
@BK1031 BK1031 deleted the bk1031/feat-access-denied-page branch June 3, 2026 01:11
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