Skip to content

Align OpenIddict with Duende IdentityServer - #2549

Closed
pieroviano wants to merge 122 commits into
openiddict:devfrom
pieroviano:dev
Closed

pieroviano wants to merge 122 commits into
openiddict:devfrom
pieroviano:dev

Conversation

@pieroviano

@pieroviano pieroviano commented Sep 15, 2026 •

Copy link
Copy Markdown

Duende IdentityServer parity

This PR adds the remaining Duende IdentityServer parity features to the OpenIddict fork. New features are opt-in, except the default-behaviour changes listed below.

Features

Area What's added
Server-side sessions sid in tokens, sliding and absolute expiration, revocation that cascades to tokens and authorizations, session queries by subject, client or sid, admin API list/get/terminate
OIDC logout (OP) Back-Channel Logout 1.0 (logout_token sent through a pluggable sender), Front-Channel Logout 1.0 (iframe URL helpers), Session Management 1.0 (check_session_iframe, session_state)
OIDC logout (RP) Client-stack back-channel and front-channel logout receivers, logout_token validation shared with the BFF, session_state and check_session_iframe exposed to apps
Dynamic Client Registration RFC 7591 registration and RFC 7592 read/update/delete on the server, software statements, initial access tokens, client-side RegisterAsync and management helpers
CIBA Ping and push delivery modes on server and client, signed authentication requests, new OpenIddict.Server.SystemNetHttp notification sender
JARM jwt, query.jwt, fragment.jwt and form_post.jwt response modes, signing and optional encryption, discovery metadata, client-side validation
JAR by reference Fetching an external request_uri, with SSRF protections and a per-client allow-list
Multi-issuer hosting Issuer resolved per request, with per-issuer discovery and token validation
FAPI 2.0 Server and client Security Profile presets
Earlier review follow-ups DPoP nonce retry with a fresh client assertion; DPoP in the userinfo challenge; distributed BFF caches; encrypted BFF logout tokens; JWT introspection algorithm restriction; DPoP replay handling on EF6 and MongoDB
SAML IdP Encrypted assertions, HTTP-Artifact binding, AuthnRequest replay cache, Single Logout linked to OIDC end-session
SAML SP New OpenIddict.Client.Saml package with .AspNetCore and .Owin hosts, dynamic SAML providers, SAML signature helpers shared with the IdP
Admin UI New OpenIddict.Server.AspNetCore.AdminUI Blazor library (applications, scopes, authorizations, tokens, keys, sessions), --admin-ui template switch

Behaviour changes

  • SAML IdP
    • The AuthnRequest replay cache is on by default and fails closed. A browser refresh or back now returns 400, and load-balanced setups need a shared IDistributedCache.
    • With a custom SP store, metadata always sets WantAuthnRequestsSigned to true.
    • Single Logout matches NameID and SessionIndex strictly.
    • The OpenIddictServerSamlService constructor has two more parameters.
  • Sessions
    • Sign-in on an expired or revoked session is rejected.
    • The issuer is checked before a session is revoked.
    • frontchannel_logout_uri must share an origin with a registered redirect URI.
  • Client logout
    • Logout tokens require exp (can be turned off).
    • A future iat is only accepted within clock skew.
    • Misconfigured logout endpoints fail at startup (ID0760/ID0766).
    • The BFF rejects replayed logout tokens even when distributed caching is off.
  • CIBA
    • A client's registered delivery mode is enforced.
    • Push clients can't poll the token endpoint.
    • client_notification_token must follow the b64token syntax.
    • transaction_failed is no longer mapped to server_error.
  • JARM: in require mode, discovery lists only the JWT response modes.
  • DCR: unsafe URI schemes and out-of-policy metadata are rejected, and tokens are read only from the Authorization header.
  • FAPI follow-ups
    • With DPoP on, userinfo returns two WWW-Authenticate headers.
    • EF6 checks ReferenceId uniqueness inside a serializable transaction.
    • New startup error ID0990 when no signing key matches the allowed introspection algorithms.
    • MongoDB index names now start with openiddict_.
  • Dependencies: OpenIddict.Server.SystemNetHttp does not depend on Polly.

Tests

Target Passed Failed
net10.0 6200 0
net48 5968 0
  • All 38 test projects were run for each framework they target.
  • templates/verify.sh passed: 4 template builds and 18 smoke checks.
  • The solution builds, except OpenIddict.Sandbox.Maui.Client, which needs the maui-tizen workload.

Known gaps

  • No conformance or interop runs yet: FAPI 2.0, logout, CIBA, SAML.
  • No EF Core migration or MongoDB index for the new session columns.
  • Back-channel logout and CIBA notifications are sent during the request, with no retry queue.
  • CIBA has no built-in RP notification endpoint and no expired_token push.
  • DCR doesn't support jwks_uri, client_secret_jwt, or initial access tokens from external issuers.
  • SAML SP has no Single Logout, artifact binding or redirect-binding ACS. SAML IdP can't import SP metadata.
  • Multi-issuer doesn't work with remote validation, CIBA, or mTLS endpoint aliases.
  • The intermediate DCR commits 8e01c78c…966d50eb don't compile on their own; only the final state builds.

Security notes

  • Replay caches built on IDistributedCache aren't atomic across instances.
  • The SAML replay cache fails closed, so flooding it blocks logins, and there is no rate limiting.
  • On net48, the SSRF check for request objects runs at DNS lookup, so DNS rebinding is still possible.
  • DCR's default allow-lists are broad; client_credentials can be registered by default.
  • Front-channel logout with FrontchannelLogoutSessionRequired=false allows logout CSRF.
  • With EnableIdentityTokenHintSessionResolution on, a leaked ID token can end a session.
  • IdP-initiated SAML SSO (opt-in) is open to login CSRF.
  • The JARM client accepts any algorithm the server advertises when none is registered.

…r and client stacks

Server: opt-in request object support (EnableRequestObjectSupport) for the authorization
and pushed authorization endpoints, request objects validated using the client JSON Web Key
Set (optionally encrypted), RFC 9101 merge semantics, global and per-client requirements
(RequireSignedRequestObjects, ft:jar) and discovery metadata.

Client: request objects sent when required by the server or enabled in the registration
(UseSignedRequestObjects), signed with the registration's asymmetric key.
…mode) to the server stack

- New backchannel authentication endpoint (SetBackchannelAuthenticationEndpointUris,
  AllowClientInitiatedBackchannelAuthenticationFlow, ASP.NET Core/OWIN pass-through).
- auth_req_id issued as an inactive reference token, redeemed with the
  urn:openid:params:grant-type:ciba grant (authorization_pending, access_denied, expired_token).
- OpenIddictServerService to list, approve and reject pending requests.
- Generic polling interval (interval parameter + slow_down) for the device code and CIBA grants.
- Discovery metadata and configuration validation.
…ient stack

- OpenIddictClientService.ChallengeUsingBackchannelAsync/AuthenticateWithBackchannelAsync.
- Backchannel authentication request pipeline (client authentication negotiation, client
  assertions, mTLS certificates, System.Net.Http integration, response validation).
- CIBA grant support at the token endpoint (auth_req_id, identity token required).
- Discovery extraction of the backchannel authentication metadata.
- Only enforce the pushed authorization requests requirement for interactive flows.
- New Key entity: IOpenIddictKeyStore/IOpenIddictKeyManager, EF Core, EF6 and MongoDB stores.
- OpenIddictServerKeyRing creates, announces, rotates and retires RSA keys; credentials are resolved per transaction.
- IOpenIddictServerKeyProtector, with an ASP.NET Core Data Protection implementation.
- Local validation follows key rotation; opt-in Quartz key pruning.
- Validate DPoP proofs at the token, PAR and userinfo endpoints (typ, alg, public jwk, htm/htu, iat, ath)
- Replay protection via redeemed token entries when token storage is enabled
- Optional server nonces (use_dpop_nonce + DPoP-Nonce header)
- Bind access tokens and public-client refresh tokens with cnf.jkt; token_type=DPoP
- dpop_jkt at authorization/PAR, enforced at code redemption
- Global RequireDPoP option, per-client ft:dpop requirement, discovery metadata
- DPoP scheme and WWW-Authenticate algs in ASP.NET Core and OWIN hosts
- Accept access tokens sent using the DPoP scheme (ASP.NET Core and OWIN hosts)
- Validate DPoP proofs (htm/htu/iat/ath, public jwk) and cnf.jkt binding for local and introspected tokens
- Reject DPoP-bound tokens sent as bearer tokens and unbound tokens sent with the DPoP scheme
- Replay protection via IDistributedCache when registered
- WWW-Authenticate DPoP challenges with algs
- EnableDPoPTokenBinding(); per-registration DPoPSigningCredentials (ephemeral P-256 by default)
- Negotiate DPoP from dpop_signing_alg_values_supported; send proofs to token, PAR and userinfo (DPoP scheme + ath)
- Retry once on use_dpop_nonce and cache server nonces per authority
- OpenIddictClientService.CreateDPoPProofAsync/GetDPoPJsonWebKeyThumbprint for resource calls
- System.Net.Http handlers attach DPoP header and extract DPoP-Nonce
- Progress row, commits and deviations
- Gap matrix, implemented phases, test counts and remaining work
- Validate proof signatures using a minimal JWK (kty/crv/x/y or kty/e/n) so x5c can't substitute the verification key
- Reject algorithms incompatible with the key type or curve
- Add helper tests for key substitution, curve mismatch, symmetric keys and invalid iat
- Certificate-based binding takes precedence at the token endpoint, so a DPoP-bound code could not be redeemed
- Add client test covering both cases
- Deviations: PAR/mTLS code binding rule, assertion reuse on nonce retry, proof key hardening
- Client test count
- EnableJsonWebTokenIntrospectionResponses(): signed token-introspection+jwt (iss, aud, iat, token_introspection)
- Optional RSA-OAEP/A256CBC-HS512 encryption with the client's JWKS enc key (non-degraded mode)
- ASP.NET Core/OWIN: Accept header detection and application/token-introspection+jwt responses
- Discovery: introspection_signing/encryption alg metadata
- Integration tests
- OpenIddictClientRegistration.RequireJsonWebTokenIntrospectionResponses
- System.Net.Http: Accept header and token extraction
- ValidateIntrospectionResponseToken: typ/iss/signature/aud checks, optional decryption, token_introspection extraction
- Unit tests
- RequireJsonWebTokenIntrospectionResponses() option (introspection mode)
- System.Net.Http: Accept header and token extraction
- ValidateIntrospectionResponseToken: typ/iss/signature/aud checks, optional decryption, token_introspection extraction
- Unit tests
- RFC 9701 requires an authenticated caller; public clients get plain JSON
- Integration test
- Encrypt only when the application sets intr_rsp:enc_alg (RSA-OAEP), as RFC 9701 section 6 requires
- Support intr_rsp:enc_enc (A128CBC-HS256 by default, or A256CBC-HS512) and publish both in discovery
- Throw (ID0552/ID0553) on unsupported settings or a missing RSA enc key instead of sending plaintext
- Trim whitespace around JWT introspection response bodies in the client and validation stacks
- Add tests for opt-in, defaults, bad settings, tokens signed with local keys and unsigned JWEs
- Encryption deviation now describes the intr_rsp:enc_alg / intr_rsp:enc_enc opt-in
- List the review fixes commit
- Add IOpenIddictClientRegistrationProvider (static options provider registered by default, AddRegistrationProvider)
- Extract registration initialization/validation from OpenIddictClientConfiguration
- OpenIddictClientService resolves static and dynamic registrations; dynamic ones are validated (declared redirect URIs, no static id collision) and cached by id (DynamicRegistrationCacheLifetime)
- Handlers default to the unique registration through the service
- ASP.NET Core: decorate IAuthenticationSchemeProvider to resolve dynamic provider names as forwarded schemes
- OWIN: resolve dynamic provider names for forwarded challenges, sign-outs and authentication
- WebIntegration: make ConfigureProvider/ValidateProvider browsable for dynamic registrations
- Client: provider resolution, initialization, validation, caching and handler fallbacks
- ASP.NET Core/OWIN: dynamic provider names as forwarded schemes/types, challenge and callback
- Progress row and deviations table
- Gap matrix, implemented phases and remaining work updated
- Reuse a cached dynamic registration only if issuer, provider name and client id are unchanged
- Check the cache before creating configuration managers/DPoP keys
- Purge expired cache entries on insert
- Tests for changed and unchanged cached registrations; plan updated
- Cookie OnValidatePrincipal access token refresh (keyed single-flight, result retention, invalid_grant sign-out)
- User/client access token DelegatingHandler and YARP transform (DPoP-aware)
- MapOpenIddictBffEndpoints: login, logout (sid check), user (X-CSRF), callbacks, back-channel logout
- Back-channel logout token validation + IOpenIddictClientAspNetCoreBffSessionStore / in-memory store
- Store backchannel_access_token_type in ASP.NET Core and OWIN client hosts
- Tests for refresh, CSRF, endpoints, logout, handlers and proxy
Map the SOAP artifact resolution endpoint in the ASP.NET Core and OWIN hosts, redirect with SAMLart for HTTP-Artifact assertion consumer services and consume request states before returning a response.
- Enable request replay protection by default and add DisableRequestReplayProtection()
- Consume request states only once the response was created and stored
- Encrypt stored artifact messages with a key derived from the artifact handle
- Size-limit the private in-memory replay cache and artifact store
- Reject AuthnRequests combining AssertionConsumerServiceIndex and ProtocolBinding
- Expose ArtifactResolutionResult.ErrorDescription and cover rejection paths in tests
- Reject messages whose replay entry cannot be stored (full private cache or discarding distributed cache)
- Never evict private in-memory replay entries before they expire
- Use per-key striped locks in the replay cache and artifact store
- Bind legacy (version 2) request states to a single use instead of rejecting them
- Accept AssertionConsumerServiceIndex with a ProtocolBinding matching the indexed endpoint
- Remove the redundant EnableRequestReplayProtection() builder method
- shared/OpenIddict.Extensions/Saml/OpenIddictSamlHelpers.cs: encoding, secure XML loading, RSA-SHA2 redirect/enveloped signatures (element-level, nested-signature safe), auto-post page
- OpenIddict.Server.Saml keeps only IdP-specific wrappers (redirect query parsing, single-root-signature rule)
… and decryptor

- Resources ID0880-ID0907, ID2440-ID2459, ID6680-ID6684
…n and metadata services

- Resources ID0908-ID0909
… provider

- Round trips (both bindings), signed/unsigned combinations, wrapping, DTD, InResponseTo/relay state/expiry, clock skew, audience, destination, recipient, replay, error status, encrypted assertions, metadata import (HTTPS/file/signed), dynamic registrations and caching
- Authentication handler: challenges (HTTP-Redirect/HTTP-POST), assertion consumer service (sign-in or pass-through), metadata endpoint
- Protected request state in SameSite=None correlation cookies keyed by a random relay state
- Scheme provider decorator exposing static and dynamic registration provider names as forwarded schemes
- Resources ID0910-ID0911; TestServer tests against the OpenIddict SAML identity provider
…on and response validation

- Cache all registration lookups (including negative results) with a bounded cache; fresh provider results replace cached registrations; add ClearCache
- Reject UNC and dynamic file metadata addresses; add AllowedDynamicMetadataHosts
- Honor metadata validUntil/cacheDuration and never fall back to expired metadata
- Require HTTPS single sign-on endpoints unless AllowInsecureIdentityProviderEndpoints
- Reject disabled request signing when AuthnRequestsSigned is advertised
- Ignore bearer confirmations containing NotBefore; keep IdP status text out of error descriptions
…ponses

- ASP.NET Core scheme provider uses the cached SAML registration lookups
- OWIN challenges ignore provider names owned by other authentication middleware, use cached lookups and are skipped when headers were already sent
- Send X-Content-Type-Options: nosniff with ACS error responses
- OpenIddictServerAspNetCoreAdminOperations: describe entries, filtered paging, cascading authorization revocation, key revocation with key ring invalidation, public JWKS writer
- Admin API endpoints use the shared operations (behaviour unchanged)
- MapOpenIddictAdminUI(policy, prefix) + AddOpenIddictAdminUI(options): static SSR Razor components over the untyped managers
- Applications (search, paging, create, edit, secret set/generate/remove, delete), scopes CRUD, authorizations and tokens (filters, details, revoke), keys (list, revoke)
- Shared admin operations reused; stored secrets, token payloads and key material never rendered; private JWK parameters stripped
- Antiforgery validated on every post, security headers (CSP, no-store, DENY), embedded stylesheet
- Resource strings ID0680-ID0683, ID2340-ID2343, ID6480-ID6484
- TestServer tests: policy enforcement, antiforgery, create/edit/secret/delete, scopes, authorization/token/key revocation
- Optional AdminUI parameter (dotnet new --admin-ui) referencing OpenIddict.Server.AspNetCore.AdminUI and mapping it under /admin with the admin policy
- Treat identifiers the store can't convert (e.g. GUID keys) as unknown entities instead of failing the request
- Bound the in-memory application search and always surface an exact client_id match
- Detect endpoints that make the admin UI routes ambiguous (e.g. the admin API mapped with the same prefix)
- Indicate when an authorization has more tokens than displayed
- Hide the Razor page components from IntelliSense (not a supported API surface)
- Add tests for secret generation, JWKS replacement/removal, key ring invalidation and real EF Core stores
…pplications

- Render the original redirect URI strings so saving the form keeps exact-match registrations intact
- Keep stored application, client and consent types not known by the admin UI
- Filter the token list by authorization and link to it from the authorization details
Track SAML service provider sessions as server-side sessions sharing the login identifier, validate signed LogoutRequest/LogoutResponse messages (HTTP-Redirect and HTTP-POST), propagate logouts to the session participants (front-channel redirect chain, SOAP back-channel, OpenID Connect end session integration), publish SingleLogoutService metadata and add the single logout endpoint to the ASP.NET Core and OWIN hosts.
…e actual session status after admin UI termination failures
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