Table of Contents

Registrations that cannot work

New in 0.117.0, Duende's configuration validators. A client registration is checked where it is read — every time, because a store may be a database that answers differently tomorrow — and one that cannot work reads as no client: the token endpoint answers invalid_client, the authorize endpoint shows its error page. The reason goes to the log and to InvalidClientConfigurationEvent (an error event; EventsOptions.RaiseErrorEvents). The code on this page is included from samples/Server.Hosting, which registers a client its own rule refuses, and a test asks for a token as it.

The default rules

DefaultClientConfigurationValidator refuses

  • an OpenID Connect client with no grant type;
  • a token lifetime of zero or less, or a negative refresh lifetime;
  • an interactive grant and no redirect URI;
  • a CORS origin that is not an origin (https://app.example.com, no path, no trailing slash);
  • a redirect URI whose scheme ValidationOptions.InvalidRedirectUriPrefixes refuses (ValidationOptions);
  • a secret required but none configured, or client credentials without a secret required.

The messages are Duende's. With automatic key management, ClientConfigurationValidator also refuses a client whose access tokens would outlive their signing key — an AccessTokenLifetime beyond KeyManagementOptions.KeyRetirementAge, the rotation interval plus the retention duration (signing keys).

Clients registered with AddInMemoryClients, AddClientStore<T> or the Dapper configuration store are read through ValidatingClientStore<T>, and a cached client has been through it before it is held. A store registered as IClientStore by hand is not, as in Duende.

A rule of your own

Derive from the default and override the check the rule belongs to — each is a protected virtual step:

/// <summary>The default checks, and one of this deployment's own: every client names the team that owns it.</summary>
public sealed class OwnedClientConfigurationValidator(IOptions<IdentityServerOptions> options)
    : DefaultClientConfigurationValidator(options)
{
    /// <inheritdoc />
    protected override Task ValidatePropertiesAsync(ClientConfigurationValidationContext context)
    {
        if (!context.Client.Properties.TryGetValue("owner", out var owner) || string.IsNullOrWhiteSpace(owner))
        {
            context.SetError("Every client names its owning team in Properties[\"owner\"].");
        }

        return Task.CompletedTask;
    }
}
// Every client is checked by these rules where it is read; one that breaks a rule reads as no client at all.
identityServer.AddClientConfigurationValidator<OwnedClientConfigurationValidator>();

To turn the checks off, register Duende's NopClientConfigurationValidator the same way.

SAML service providers

SAML service providers are checked the same way since 0.71.0; the check is a seam, ISamlServiceProviderConfigurationValidator, and a refusal raises InvalidSamlServiceProviderConfigurationEvent. A blank entity id — whitespace too, since 0.117.0 — is refused. What a service provider registration must be is on the SAML identity provider page.