--- name: identityserver-dcr description: "Configuring Dynamic Client Registration (DCR) in Duende IdentityServer: endpoint setup, authorization policies, custom validation with DynamicClientRegistrationValidator, software statement validation, IClientConfigurationStore, and separate DCR hosting." invocable: false --- # Dynamic Client Registration (DCR) ## When to Use This Skill - Setting up Dynamic Client Registration (DCR) at `/connect/dcr` - Securing the DCR endpoint with authorization policies - Customizing DCR validation with `DynamicClientRegistrationValidator` - Implementing software statement validation - Persisting dynamically registered clients with `IClientConfigurationStore` - Hosting DCR in a separate application from IdentityServer ## Core Principles - DCR requires the `Duende.IdentityServer.Configuration` NuGet package - Requires **Business Edition** or higher license - Always secure the `/connect/dcr` endpoint with an authorization policy — never expose it unauthenticated - Enforce PKCE and restrict allowed grant types in the DCR validator - Use persistent stores (database) for dynamically registered clients in production Docs: https://docs.duendesoftware.com/identityserver/configuration/dcr ## Overview Dynamic Client Registration allows clients to register themselves at the `/connect/dcr` endpoint per RFC 7591. This feature requires the **Business Edition** or higher and has been available since version 6.3. DCR uses a separate NuGet package and can be hosted in the same application as IdentityServer or in a separate host. ### Setup ```bash dotnet add package Duende.IdentityServer.Configuration ``` ```csharp // Program.cs builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes); builder.Services.AddIdentityServerConfiguration(); var app = builder.Build(); app.UseIdentityServer(); app.UseAuthorization(); app.MapDynamicClientRegistration(); app.Run(); ``` `MapDynamicClientRegistration()` is an endpoint-routing extension from the **`Duende.IdentityServer.Configuration`** package (separate from `Duende.IdentityServer`). Call it where you configure the pipeline/endpoint routing — in the quickstart/template hosts this is the `ConfigurePipeline()` method (`HostingExtensions.cs`), alongside `UseIdentityServer()`. `AddIdentityServerConfiguration()` registers the DCR services; `MapDynamicClientRegistration()` maps the `/connect/dcr` endpoint. Both are required. ### Securing the DCR Endpoint Apply standard ASP.NET Core authorization policies to the DCR endpoint: ```csharp // Using JWT bearer for the DCR endpoint builder.Services.AddAuthentication() .AddJwtBearer("dcr", options => { options.Authority = "https://identity.example.com"; options.Audience = "IdentityServer.Configuration"; options.TokenValidationParameters.ValidTypes = ["at+jwt"]; }); builder.Services.AddAuthorization(options => { options.AddPolicy("dcr", policy => { policy.AddAuthenticationSchemes("dcr"); policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "IdentityServer.Configuration"); }); }); app.MapDynamicClientRegistration() .RequireAuthorization("dcr"); ``` ### DCR Request and Response **Registration request:** ``` POST /connect/dcr HTTP/1.1 Content-Type: application/json Authorization: Bearer { "client_name": "My Dynamic App", "redirect_uris": ["https://app.example.com/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "client_secret_basic" } ``` **Registration response:** ```json { "client_id": "generated-client-id", "client_secret": "generated-secret", "client_name": "My Dynamic App", "redirect_uris": ["https://app.example.com/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "registration_client_uri": "https://identity.example.com/connect/dcr?client_id=generated-client-id", "registration_access_token": "..." } ``` ### Customizing DCR Validation Extend `DynamicClientRegistrationValidator` to add custom validation logic: ```csharp public class CustomDcrValidator : DynamicClientRegistrationValidator { protected override Task ValidateGrantTypesAsync( DynamicClientRegistrationContext context) { // Only allow authorization_code var grantTypes = context.Request.GrantTypes; if (grantTypes.Any(gt => gt != "authorization_code")) { context.SetError("Grant type not allowed"); return Task.CompletedTask; } return base.ValidateGrantTypesAsync(context); } protected override Task ValidateRedirectUrisAsync( DynamicClientRegistrationContext context) { // Enforce HTTPS redirect URIs var uris = context.Request.RedirectUris; if (uris.Any(u => !u.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) { context.SetError("Redirect URIs must use HTTPS"); return Task.CompletedTask; } return base.ValidateRedirectUrisAsync(context); } protected override Task SetClientDefaultsAsync( DynamicClientRegistrationContext context) { // Set defaults for dynamically registered clients var client = context.Client; client.RequirePkce = true; client.AllowOfflineAccess = false; client.AccessTokenLifetime = 300; // 5 minutes return base.SetClientDefaultsAsync(context); } } ``` Register: ```csharp builder.Services.AddIdentityServerConfiguration() .AddDynamicClientRegistrationValidator(); ``` ### DynamicClientRegistrationContext The context object passed to validation methods contains: | Property | Purpose | | --------- | ----------------------------------------------------- | | `Client` | The IdentityServer `Client` being built | | `Request` | The raw DCR request | | `Caller` | The `ClaimsPrincipal` of the authenticated DCR caller | | `Items` | Dictionary for passing data between validation steps | ### Software Statements Software statements are signed JWTs that contain pre-approved client metadata. Validate them by overriding `ValidateSoftwareStatementAsync`: ```csharp public class SoftwareStatementDcrValidator : DynamicClientRegistrationValidator { protected override async Task ValidateSoftwareStatementAsync( DynamicClientRegistrationContext context) { var softwareStatement = context.Request.SoftwareStatement; if (string.IsNullOrEmpty(softwareStatement)) { context.SetError("Software statement required"); return; } var handler = new JsonWebTokenHandler(); var validationResult = await handler.ValidateTokenAsync( softwareStatement, new TokenValidationParameters { ValidIssuer = "https://trusted-authority.example.com", IssuerSigningKeys = await GetTrustedKeysAsync(), ValidateLifetime = true }); if (!validationResult.IsValid) { context.SetError("Invalid software statement"); return; } // Apply claims from software statement to the client var claims = validationResult.ClaimsIdentity; context.Client.ClientName = claims.FindFirst("software_name")?.Value; await base.ValidateSoftwareStatementAsync(context); } } ``` ### Other DCR Extensibility Points | Interface | Purpose | | --------------------------------------------- | ---------------------------------------- | | `IDynamicClientRegistrationRequestProcessor` | Process the DCR request (extend default) | | `IDynamicClientRegistrationResponseGenerator` | Customize the DCR response | ### Client Configuration Store DCR needs a persistent store for dynamically registered clients. Use the Entity Framework implementation: ```bash dotnet add package Duende.IdentityServer.Configuration.EntityFramework ``` ```csharp builder.Services.AddIdentityServerConfiguration() .AddClientConfigurationStore(); ``` Or implement `IClientConfigurationStore` for a custom backing store: ```csharp public class CustomClientConfigurationStore : IClientConfigurationStore { public async Task AddAsync(Client client) { // Persist the dynamically registered client } public async Task FindByClientIdAsync(string clientId) { // Retrieve a dynamically registered client } public async Task UpdateAsync(Client client) { // Update client configuration } public async Task DeleteAsync(string clientId) { // Remove a dynamically registered client } } ``` ### Separate DCR Host DCR can be hosted in a separate application from IdentityServer: ```csharp // Separate DCR host — Program.cs builder.Services.AddIdentityServerConfiguration(options => { options.IdentityServerBaseUrl = "https://identity.example.com"; }); builder.Services.AddAuthentication() .AddJwtBearer("dcr", options => { options.Authority = "https://identity.example.com"; options.Audience = "IdentityServer.Configuration"; }); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapDynamicClientRegistration().RequireAuthorization("dcr"); app.Run(); ``` ## Common Anti-Patterns - **Exposing the DCR endpoint without authentication** — Always secure `/connect/dcr` with an authorization policy. - **Allowing dynamically registered clients to use any grant type** — Restrict allowed grant types and enforce PKCE in the DCR validator. - **Using in-memory stores for DCR clients in production** — Use persistent stores (database) for production deployments. ## Common Pitfalls 1. **Business Edition requirement**: `AddIdentityServerConfiguration()` requires a Business Edition or higher license. Community Edition does not support DCR. 2. **Client secrets**: Dynamically registered clients receive generated secrets. Ensure your `IClientConfigurationStore` stores these securely (hashed, not plaintext). 3. **Software statement trust**: Software statements must be validated against a trusted signing key. Do not accept software statements signed by unknown issuers. 4. **Separate host connectivity**: When hosting DCR separately, it must be able to communicate with IdentityServer's data stores. Ensure the `IClientConfigurationStore` is backed by the same database that IdentityServer reads from (or uses a shared data layer). ## Related Skills - `identityserver-configuration` — IdentityServer host configuration, client types, grant types, secret management, and resource configuration - `identityserver-saml` — SAML 2.0 Identity Provider (the other advanced IdentityServer feature) - `identityserver-stores` — Persistent store patterns (useful for custom `IClientConfigurationStore`) - `aspnetcore-authorization` — Authorization policies for securing the DCR endpoint - `identity-security-hardening` — Security hardening including HTTPS enforcement