Skip to content

OIDC resource server: scheme, configuration and Protected Resource Metadata (1/7) - #891

Merged
KrzysztofPajak merged 17 commits into
developfrom
feature/oidc-resource-server
Oct 11, 2026
Merged

KrzysztofPajak merged 17 commits into
developfrom
feature/oidc-resource-server

Conversation

@KrzysztofPajak

Copy link
Copy Markdown
Member

Type: feature

Issue

GrandNode can only authenticate Web API calls with its own JWT (API users with a shared secret). Integrations, applications and MCP-class clients increasingly expect OAuth 2.1: tokens issued by the organisation's identity provider, audience-bound to the resource, discoverable through Protected Resource Metadata (RFC 9728). There was no way to accept such tokens or to let a plugin protect its own endpoints with them.

This is the first of a stack of PRs (each targets the previous one; review in order):

  1. OIDC resource server: scheme, configuration, metadata (this PR)
  2. Token to customer mapping and work-context resolver
  3. Web API accepts provider tokens next to the existing JWT
  4. Guide and test environment (Keycloak, Zitadel)
  5. Browser sign-in: core (resolver login mode, /register external mode)
  6. Browser sign-in: Authentication.OpenIdConnect plugin
  7. Browser sign-in: guide

Solution

GrandNode becomes an OAuth 2.1 resource server only (it never issues tokens), configured in the appsettings section OidcResourceServer, without provider-specific code:

  • Oidc JwtBearer authentication scheme: JWKS signature, issuer, lifetime, and audience = canonical resource URI (RFC 8707) built from the store URL, never from the Host header.
  • Protected resources come from configuration (Resources) and from plugins (IOidcProtectedResourceProvider) through one registry; each has a path prefix, audiences, supported and required scopes, authorized parties and an optional admin-panel requirement.
  • RFC 9728 metadata at /.well-known/oauth-protected-resource[/{path}]; 401/403 answers carry WWW-Authenticate with resource_metadata, error and scope, plus a ProblemDetails body.
  • The configuration is validated at startup; when the feature is disabled the scheme still exists with an empty configuration, so nothing fails open or crashes.

Breaking changes

None. The feature is off unless OidcResourceServer:Enabled is true.

Testing

  1. dotnet test src/Tests/Grand.Web.Common.Tests - all pass.
  2. With the feature off, run the app: nothing changes, /.well-known/oauth-protected-resource returns 404.
  3. End-to-end steps with Keycloak and Zitadel are in the guide added in PR 4 (docs/oidc-resource-server.md, sections 6, 7 and 10).

… conditions, scheme always registered

The derived audience and resource_metadata URL come from the store resolved for the request, never from the Host header. Resources with account conditions are refused until those checks exist. With the resource server disabled the Oidc scheme is still registered with no issuer and no keys, so endpoints naming it answer 401. authorization_servers publishes the Authority as configured, an http Authority requires RequireHttpsMetadata=false, garbage tokens log at Information, and the startup skips an uninstalled database.
Copilot AI balanced review requested due to automatic review settings October 11, 2026 05:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcPipelineTests.cs Fixed
Comment thread src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcTestHost.cs Fixed
Comment thread src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcTestHost.cs Fixed
Comment thread src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcTestHost.cs Fixed
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcProtectedResourceRegistry.cs Dismissed
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcChallengeWriter.cs Fixed

@KrzysztofPajak KrzysztofPajak left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the OIDC resource server. The ones I'd address before merge: store resolution for the audience check (OidcCanonicalUri), guest-customer creation on the anonymous metadata endpoints, and the RFC 9728 metadata URL shape. The rest are robustness and nits.

Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcJwtBearerEvents.cs
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcCanonicalUri.cs Outdated
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcProtectedResourceMetadata.cs Outdated
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcProtectedResourceMetadata.cs Outdated
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcJwtBearerEvents.cs
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcAuthenticationBuilderExtensions.cs Outdated
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcProtectedResourceRegistry.cs Outdated
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcProtectedResourceMetadata.cs Outdated
Comment thread src/Web/Grand.Web.Common/Security/Oidc/OidcCanonicalUri.cs Outdated
Comment thread src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcChallengeWriterTests.cs Dismissed
Comment thread src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcPipelineTests.cs Dismissed
@KrzysztofPajak

Copy link
Copy Markdown
Member Author

Review fixes:

@KrzysztofPajak
KrzysztofPajak merged commit 92c6853 into develop Oct 11, 2026
6 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the feature/oidc-resource-server branch October 11, 2026 09:35
KrzysztofPajak added a commit that referenced this pull request Oct 11, 2026
KrzysztofPajak added a commit that referenced this pull request Oct 11, 2026
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.

2 participants