TL;DR
No. The overlap is too small and the fundamental models are too different.
You'd override 80%+ of the behavior, which isn't "extending" — it's replacing
while pretending to extend.
Fundamental Mismatches
1. Request Model
Duende's endpoint parses JSON directly into DynamicClientRegistrationRequest
with individual fields (grant_types, redirect_uris, scope, etc.). UDAP's
request is a software_statement JWT that must be decoded, signature-verified
against an X.509 certificate, and chain-validated before you even get to those
fields. You'd have to either:
- Override the endpoint to do JWT parsing before the validator sees it, then map
claims into Duende's request model
- Or override
ValidateSoftwareStatementAsync to do ALL the heavy lifting, then
back-populate context.Request
Either way, you're fighting the data flow.
2. Authentication Model
Duende's context requires a ClaimsPrincipal caller — the authenticated entity
making the request. UDAP registration is anonymous HTTP; the certificate chain
in the JWT IS the authentication. You'd need to pass a dummy/synthetic
principal.
3. Client Lifecycle
Duende only creates new clients (HTTP 201). UDAP has three outcomes:
| Outcome |
HTTP Status |
| New registration |
201 Created |
| Upsert (same cert re-registers) |
200 OK |
Cancellation (grant_types: []) |
200 OK |
The processor and response generator have no concept of upsert or cancellation.
You'd override ProcessAsync entirely to add these, and override the response
generator for status code selection.
4. Secret Model
Duende generates random hashed secrets (CryptoRandom → SHA256). UDAP stores
certificate metadata as secrets:
UDAP_SAN_URI_ISS_NAME — the SAN URI from the cert
UDAP_COMMUNITY — resolved community ID
- Both expire when the certificate expires
You'd override AddClientSecret and GenerateSecret completely.
5. Trust Infrastructure
UDAP needs community trust anchors, intermediate certificates, and
TrustChainValidator — none of which exist in Duende's DCR. These would need
to be injected into your subclass and called during validation.
Override Analysis
| Duende Virtual Method |
Override? |
Reason |
ValidateSoftwareStatementAsync |
Yes, completely |
Becomes the entire UDAP JWT + cert chain validation |
SetGrantTypesAsync |
Yes |
Cancel registration semantics, different error handling |
SetRedirectUrisAsync |
Partially |
Similar logic, but UDAP-specific error messages |
SetScopesAsync |
Yes |
Wildcard expansion, aggregation |
SetDefaultScopes |
Yes |
UDAP server settings for default scopes |
SetSecretsAsync |
Yes, completely |
JWK-based → X.509-based secrets |
SetClientNameAsync |
Minimal |
Similar |
SetLogoutParametersAsync |
No-op |
UDAP doesn't use these |
SetMaxAgeAsync |
No-op |
UDAP doesn't use this |
SetUserInterfaceProperties |
Partially |
Logo validation is much stricter |
SetPublicClientProperties |
No-op |
N/A for UDAP |
SetAccessTokenProperties |
No-op |
N/A |
SetIdTokenProperties |
No-op |
N/A |
SetServerSideSessionProperties |
No-op |
N/A |
Processor ProcessAsync |
Yes, completely |
Upsert, cancel, UDAP secrets |
Processor AddClientId |
Partially |
Similar |
Processor GenerateSecret |
Yes, completely |
Certificate-based, not random |
| Response generator (all 5 methods) |
Yes |
Status code selection, UDAP response format |
Every single method is either overridden or no-op'd. At that point you're
not extending — you're wearing a Duende costume.
Architecture Comparison
Both pipelines already follow the same pattern:
Duende: Endpoint → Context → Validator → Processor → ResponseGenerator
UDAP: Endpoint → Context → Validator → Processor → (inline response)
The architectural alignment is already there. The class hierarchy doesn't need
to match.
UDAP vs RFC 7591: Why They're Fundamentally Different
| Aspect |
RFC 7591 (Duende) |
UDAP DCR |
| Authentication |
API key/secret or OAuth token |
X.509 certificates (PKI-based) |
| Software Statement |
Optional, can contain metadata |
Mandatory, contains ALL metadata, signed with cert |
| Metadata Location |
Top-level JSON fields |
MUST be inside JWT (software_statement) |
| Issuer Validation |
Not defined |
MUST match cert SAN URI |
| Replay Protection |
Optional |
JTI mandatory (when configured) |
| Token Lifetime |
Not constrained |
Max 5 minutes (exp - iat) |
| Trust Model |
Single explicit trust |
Community-based PKI with trust anchors |
| Secret Storage |
Long-lived hashed secrets |
Certificate metadata, auto-expiring |
| Audience Validation |
Optional |
Mandatory, must match endpoint URL |
| Cancellation |
Not defined |
Empty grant_types array |
| Re-registration |
Not defined |
Upsert with same cert |
| Status Codes |
201 create, 400 error |
201 create, 200 update/cancel, 400 error |
| Scopes |
Static validation |
Wildcard expansion and aggregation |
| Community ID |
N/A |
Resolved from cert chain, stored as secret |
| Subject Claim |
Not required |
Must exist AND must equal issuer |
UDAP DCR is not an extension of RFC 7591 — it's a PKI-based alternative
that happens to share some concepts. The overlap is roughly 20-30%.
When Extending WOULD Make Sense
- If Duende adds UDAP-specific hooks (unlikely)
- If UDAP DCR evolves closer to standard RFC 7591 (unlikely — PKI-first is the
whole point)
- If Duende adds features UDAP clients would actually use (DPoP, new grant
types) — but inheriting behavior you don't understand is a liability
Recommendation
Keep the current separate architecture. The refactoring already achieved the
valuable goal: clean separation of validation from client creation, following
the same patterns Duende uses. The codebase is understandable to anyone familiar
with Duende's DCR, without the fragility of subclassing it.
If You Want to Reduce Maintenance Burden
- Share error constants — Duende's
DynamicClientRegistrationErrors uses
the same RFC 7591 error codes (invalid_client_metadata,
invalid_software_statement, etc.)
- Consider
IClientConfigurationStore for persistence, if your
IUdapClientRegistrationStore with upsert/cancel isn't too specialized
- Keep architectural alignment — same patterns make it easy to reason about
both systems side-by-side
TL;DR
No. The overlap is too small and the fundamental models are too different.
You'd override 80%+ of the behavior, which isn't "extending" — it's replacing
while pretending to extend.
Fundamental Mismatches
1. Request Model
Duende's endpoint parses JSON directly into
DynamicClientRegistrationRequestwith individual fields (
grant_types,redirect_uris,scope, etc.). UDAP'srequest is a
software_statementJWT that must be decoded, signature-verifiedagainst an X.509 certificate, and chain-validated before you even get to those
fields. You'd have to either:
claims into Duende's request model
ValidateSoftwareStatementAsyncto do ALL the heavy lifting, thenback-populate
context.RequestEither way, you're fighting the data flow.
2. Authentication Model
Duende's context requires a
ClaimsPrincipal caller— the authenticated entitymaking the request. UDAP registration is anonymous HTTP; the certificate chain
in the JWT IS the authentication. You'd need to pass a dummy/synthetic
principal.
3. Client Lifecycle
Duende only creates new clients (HTTP 201). UDAP has three outcomes:
201 Created200 OKgrant_types: [])200 OKThe processor and response generator have no concept of upsert or cancellation.
You'd override
ProcessAsyncentirely to add these, and override the responsegenerator for status code selection.
4. Secret Model
Duende generates random hashed secrets (
CryptoRandom→ SHA256). UDAP storescertificate metadata as secrets:
UDAP_SAN_URI_ISS_NAME— the SAN URI from the certUDAP_COMMUNITY— resolved community IDYou'd override
AddClientSecretandGenerateSecretcompletely.5. Trust Infrastructure
UDAP needs community trust anchors, intermediate certificates, and
TrustChainValidator— none of which exist in Duende's DCR. These would needto be injected into your subclass and called during validation.
Override Analysis
ValidateSoftwareStatementAsyncSetGrantTypesAsyncSetRedirectUrisAsyncSetScopesAsyncSetDefaultScopesSetSecretsAsyncSetClientNameAsyncSetLogoutParametersAsyncSetMaxAgeAsyncSetUserInterfacePropertiesSetPublicClientPropertiesSetAccessTokenPropertiesSetIdTokenPropertiesSetServerSideSessionPropertiesProcessAsyncAddClientIdGenerateSecretEvery single method is either overridden or no-op'd. At that point you're
not extending — you're wearing a Duende costume.
Architecture Comparison
Both pipelines already follow the same pattern:
Duende: Endpoint → Context → Validator → Processor → ResponseGenerator
UDAP: Endpoint → Context → Validator → Processor → (inline response)
The architectural alignment is already there. The class hierarchy doesn't need
to match.
UDAP vs RFC 7591: Why They're Fundamentally Different
software_statement)exp - iat)grant_typesarray201create,400error201create,200update/cancel,400errorUDAP DCR is not an extension of RFC 7591 — it's a PKI-based alternative
that happens to share some concepts. The overlap is roughly 20-30%.
When Extending WOULD Make Sense
whole point)
types) — but inheriting behavior you don't understand is a liability
Recommendation
Keep the current separate architecture. The refactoring already achieved the
valuable goal: clean separation of validation from client creation, following
the same patterns Duende uses. The codebase is understandable to anyone familiar
with Duende's DCR, without the fragility of subclassing it.
If You Want to Reduce Maintenance Burden
DynamicClientRegistrationErrorsusesthe same RFC 7591 error codes (
invalid_client_metadata,invalid_software_statement, etc.)IClientConfigurationStorefor persistence, if yourIUdapClientRegistrationStorewith upsert/cancel isn't too specializedboth systems side-by-side