Skip to content
Back to skills

Identityserver Dcr

ASecurity

Configuring Dynamic Client Registration (DCR) in Duende IdentityServer: endpoint setup, authorization policies, custom validation with DynamicClientRegistrationValidator, software statement validation, IClientConfigurationStore, and separate DCR hosting.

  • 9 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 16, 2026
ai-agentsrustbashapidatabasesecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 16, 2026

npx -y skills add DuendeSoftware/duende-skills --skill identityserver-dcr --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Identityserver Dcr?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Identityserver Dcr
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/duendesoftware-identityserver-dcr/badge)](https://www.skillsdirectory.com/skills/duendesoftware-identityserver-dcr)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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 <access_token>

{
    "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<CustomDcrValidator>();
```

### 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<Client?> 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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.