Skip to content

Add REST API OAuth resource scopes#2336

Merged
ejsmith merged 24 commits into
mainfrom
feature/oauth-rest-resource-scopes
Jun 29, 2026
Merged

Add REST API OAuth resource scopes#2336
ejsmith merged 24 commits into
mainfrom
feature/oauth-rest-resource-scopes

Conversation

@ejsmith

@ejsmith ejsmith commented Jun 27, 2026

Copy link
Copy Markdown
Member

Closes #2332.

Summary

  • Add distinct OAuth protected resources for /mcp and /api/v2, with resource-specific metadata, bearer challenges, and strict token/resource validation.
  • Allow OAuth clients to request reduced consent scopes and explicitly selected organizations from the OAuth consent flow.
  • Require offline_access for MCP consent/grants so MCP clients receive refreshable authorization.
  • Store OAuth grants in a dedicated OAuth token index, look up access and refresh tokens by SHA-256 hash, and keep raw bearer credentials out of token document ids and persisted token fields.
  • Add user-facing OAuth grant management with a shared data-table layout so users can review connected applications and revoke client access.
  • Add GitHub Copilot CLI setup instructions to the Account AI Tools page.
  • Keep legacy API keys on the existing token path while moving new OAuth/MCP grants to OAuth-specific storage.
  • Merge the latest main branch changes into this PR branch.

Compatibility

This intentionally tightens the brand-new OAuth/MCP behavior: OAuth clients now need an explicit supported resource, at least one supported resource scope, selected organizations, and MCP grants require offline_access. New OAuth bearer tokens are stored and looked up by hash in the OAuth token index, so earlier experimental OAuth grants need to re-authorize. Non-OAuth API keys and normal user auth paths are preserved.

Validation

  • dotnet build --no-restore
  • dotnet test --no-build -- --filter-class Exceptionless.Tests.Controllers.OAuthControllerTests
  • dotnet test --no-build -- --filter-class Exceptionless.Tests.Controllers.ExceptionlessMcpToolsTests
  • dotnet test --no-build -- --filter-class Exceptionless.Tests.Controllers.OpenApiControllerTests
  • dotnet test --no-build -- --filter-class Exceptionless.Tests.Repositories.OAuthTokenRepositoryTests
  • dotnet test --no-build -- --filter-class Exceptionless.Tests.Serializer.Models.OAuthTokenSerializerTests
  • dotnet test --no-build -- --filter-class Exceptionless.Tests.Repositories.TokenRepositoryTests
  • dotnet test --no-build -- --filter-class Exceptionless.Tests.Serializer.Models.TokenSerializerTests
  • dotnet test -- --filter-class Exceptionless.Tests.Controllers.OAuthControllerTests
  • npm run lint from src/Exceptionless.Web/ClientApp
  • npm run check from src/Exceptionless.Web/ClientApp
  • git diff --check

@ejsmith

ejsmith commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

I reviewed the OAuth/resource-scope implementation at 1438ac476. I found one correctness/security issue that I think should be fixed before this ships, plus a few hardening items.

Required

  • OAuth org access is snapshotted and can outlive org membership. Authorization stores selected org ids, token creation persists them, refresh preserves them, and request authentication trusts the token org ids without intersecting them with the current user's OrganizationIds. If a user grants OAuth access to an org and is later removed from that org, the OAuth client can keep accessing that org until the access token expires and can continue refreshing for the refresh-token lifetime.

    Suggested fix: during OAuth bearer authentication and refresh, intersect token.OAuthOrganizationIds with the current user's org ids. If the intersection is empty, reject/revoke the token. Refreshed tokens should only carry the current intersection. If feasible, also trim or revoke OAuth tokens when org membership changes.

Hardening / follow-up

  • The consent screen preselects every organization and does not show the registered app/client name. That makes it too easy to grant all-org access to a dcr_... client without a clear identity. I would default to no org selection or the current org only, and show a validated client display name from the registered OAuthApplication.
  • The anonymous revoke endpoint revokes any supplied OAuth token string without checking client_id ownership. A leaked bearer can already be abused, but this allows unauthenticated token DoS and is weaker than expected revocation semantics. I would accept/validate client_id while keeping unknown-token responses non-revealing.
  • Refresh-token replay currently invalidates only the reused old token. Consider adding a grant/session id and revoking the active token family on replay to better contain stolen refresh tokens.

The rest of the core flow looked sound in static review: PKCE S256 enforcement, redirect URI matching including loopback dynamic ports, resource binding, public-client-only DCR, scope-based MCP authorization, REST read/write policies, and metadata-fetch SSRF controls all look directionally correct.

@ejsmith ejsmith marked this pull request as ready for review June 28, 2026 23:30
@ejsmith

ejsmith commented Jun 28, 2026

Copy link
Copy Markdown
Member Author

/preview

@github-actions github-actions Bot added the dev-preview Deploy this pull request to the shared dev environment. label Jun 28, 2026
@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown

Preview deployed

@github-actions

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Exceptionless.AppHost 39% 40% 134
Exceptionless.Core 71% 63% 8585
Exceptionless.Insulation 24% 23% 203
Exceptionless.Web 75% 64% 4907
Summary 71% (16315 / 23055) 62% (8318 / 13310) 13829

ejsmith commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

I re-reviewed the current head 3622ad8aaa94 after the OAuth/resource-scope follow-up work.

No blocking issues found in this pass. I specifically rechecked the earlier organization-scope concern, OAuth access/refresh token storage and revocation behavior, resource-bound bearer authentication, REST/MCP scope policy wiring, OAuth application cache invalidation, and the Svelte consent/grant-management UI.

Current live checks on this head are green. GitHub still reports the PR merge state as BLOCKED, which appears to be branch-protection/review state rather than a failing check.

@ejsmith ejsmith merged commit a58ac55 into main Jun 29, 2026
9 checks passed
@ejsmith ejsmith deleted the feature/oauth-rest-resource-scopes branch June 29, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-preview Deploy this pull request to the shared dev environment.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow OAuth clients to access selected REST API endpoints

1 participant