Overview
AdminUI registers compliance rule-set providers for OAuth 2.1 (draft) and FAPI 2.0. Applications can register additional rule sets, implement a fully custom provider, or customize the OAuth 2.1 and FAPI 2.0 providers.
All providers implement IComplianceRuleSetProvider<DuendeClient>. A provider supplies rule-set metadata, startup validation, and results for a client. ComplianceRuleSet contains the metadata (RuleSetId, Version, DisplayName, Summary, optional SpecificationLink, and LastUpdatedUtc); rules and evaluation logic live in the provider.
Register a Rule Set
Register the provider on the same IServiceCollection used for AddAdminUI():
using IdentityExpress.Manager.BusinessLogic.Compliance.Extensions;
using IdentityExpress.Manager.BusinessLogic.Entities.Services;
using IdentityExpress.Manager.UI.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
services.AddAdminUI();
services.AddComplianceRuleSet<MyOrgPolicyRuleSetProvider>();
AddAdminUI() registers its built-in providers. AddComplianceRuleSet<TProvider>() adds your provider to the registry; it does not replace or merge with an existing rule set. Register custom providers during service configuration, before the compliance registry is first resolved. Providers are singletons, so constructor dependencies must be safe to use from a singleton.
Registered rule sets are available to selection, evaluation, the documentation API, and startup validation. Adding one does not change the default selection (oauth-2.1@1.0); select the custom set through AdminUI or the relevant server settings.
Define a Custom Rule Set
For a conventional set of synchronous predicates, derive from DefaultComplianceRuleSetProvider<TClient>. It evaluates the rules you provide and validates that each rule has a non-empty, unique RuleId within the set during startup.
using System;
using System.Collections.Generic;
using System.Linq;
using IdentityExpress.Manager.BusinessLogic.Compliance.Models;
using IdentityExpress.Manager.BusinessLogic.Compliance.RuleSets;
using IdentityExpress.Manager.BusinessLogic.Constants;
using IdentityExpress.Manager.BusinessLogic.Entities.Services;
using Microsoft.Extensions.Logging;
public sealed class MyOrgPolicyRuleSetProvider(
ILogger<DefaultComplianceRuleSetProvider<DuendeClient>> logger)
: DefaultComplianceRuleSetProvider<DuendeClient>(logger)
{
protected override string RuleSetIdValue => "my-org-policy";
protected override string VersionValue => "1.0";
protected override string DisplayNameValue => "My Organisation Policy";
protected override string SummaryValue => "Internal security baseline for all clients.";
protected override DateTime LastUpdatedUtcValue =>
new(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
protected override IReadOnlyList<IdentityServerComplianceRule> Rules { get; } =
[
new()
{
RuleId = "require-pkce-for-public-authorization-code-clients",
ResultLevel = ComplianceResultLevel.NonCompliant,
Reason = "PKCE is mandatory for public authorization-code clients.",
IsApplicable = context =>
ClientTypeGrouping.OidcPublic.Intersects(context.ClientTypeGrouping) &&
context.Client.AllowedGrantTypes?.Any(grantType =>
string.Equals(
grantType,
GrantType.AuthorizationCode,
StringComparison.OrdinalIgnoreCase)) == true,
MeetsRequirement = context => context.Client.RequirePkce
}
];
}
Each ComplianceRule<TClient> has a unique RuleId, a ResultLevel, a Reason, and a required MeetsRequirement predicate. LearnMoreUrl is optional. IsApplicable is optional and defaults to true; use it to restrict a rule to particular client types or flows. Both predicates receive ComplianceRuleEvaluationContext<TClient>, which contains the rule, the client-type grouping, and the strongly typed client (DuendeClient).
For fully custom evaluation or startup checks, implement IComplianceRuleSetProvider<DuendeClient> directly. Its contract is:
ComplianceRuleSet GetRuleSet();
IReadOnlyList<string> StartupRuleValidation();
List<ComplianceRuleResult> EvaluateRules(ComplianceClientSnapshot<DuendeClient> clientSnapshot);
This gives the provider complete control over rule metadata, validation, and results. DefaultComplianceRuleSetProvider<TClient> is the simpler option when the rule set can be represented as a list of ComplianceRule<TClient> predicates.
Customize a Built-in Rule Set
IdentityServerOAuth21RuleSetProvider and IdentityServerFapi20RuleSetProvider derive from DefaultComplianceRuleSetProvider<DuendeClient> and expose their lists through the protected Rules property. Subclass either provider and override Rules to keep its built-in rules and add your own, or provide a replacement list:
using System.Collections.Generic;
using IdentityExpress.Manager.BusinessLogic.Compliance.Models;
using IdentityExpress.Manager.BusinessLogic.Compliance.RuleSets;
using IdentityExpress.Manager.BusinessLogic.Entities.Services;
using IdentityExpress.Manager.BusinessLogic.IdentityServer.Compliance.RuleSets;
using Microsoft.Extensions.Logging;
public sealed class MyOAuth21RuleSetProvider(
ILogger<DefaultComplianceRuleSetProvider<DuendeClient>> logger)
: IdentityServerOAuth21RuleSetProvider(logger)
{
private static readonly IdentityServerComplianceRule OrganizationRule = new()
{
RuleId = "require-par-for-confidential-clients",
ResultLevel = ComplianceResultLevel.NonCompliant,
Reason = "PAR is mandatory for confidential clients under organisation policy.",
IsApplicable = context =>
ClientTypeGrouping.OidcConfidential.Intersects(context.ClientTypeGrouping),
MeetsRequirement = context => context.Client.RequirePushedAuthorization
};
protected override IReadOnlyList<IdentityServerComplianceRule> Rules =>
[
.. base.Rules,
OrganizationRule
];
}
To use the customized provider with AddAdminUI(), remove the built-in provider descriptor and register your subclass before the compliance registry is first resolved. AddComplianceRuleSet is additive, so leaving both providers registered under the same (RuleSetId, Version) makes the registry throw when it is resolved:
using System.Linq;
using IdentityExpress.Manager.BusinessLogic.Compliance.Extensions;
using IdentityExpress.Manager.BusinessLogic.Compliance.Services;
using IdentityExpress.Manager.BusinessLogic.Entities.Services;
using IdentityExpress.Manager.BusinessLogic.IdentityServer.Compliance.RuleSets;
using IdentityExpress.Manager.UI.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
services.AddAdminUI();
var builtInProvider = services.Single(descriptor =>
descriptor.ServiceType == typeof(IComplianceRuleSetProvider<DuendeClient>) &&
descriptor.ImplementationType == typeof(IdentityServerOAuth21RuleSetProvider));
services.Remove(builtInProvider);
services.AddComplianceRuleSet<DuendeClient, MyOAuth21RuleSetProvider>();
For FAPI 2.0, derive from and remove IdentityServerFapi20RuleSetProvider instead. Keep the built-in provider's rule-set ID and version when the customized set should remain the same selectable set; OAuth 2.1 uses oauth-2.1@1.0, which is also the built-in default.
Rule Set and Rule Requirements
- Every
(RuleSetId, Version)pair must be unique. Registering two providers with the same pair causes the registry to throwInvalidOperationExceptionwhen it is first resolved; registration order does not make one provider win. - Supply non-empty
RuleSetId,Version,DisplayName, andSummaryvalues. - Rule IDs must be non-empty and unique within a provider.
DefaultComplianceRuleSetProvider<TClient>checks this during startup validation. - An empty result list contributes no findings. Select the built-in
Noneset when the intent is to disable compliance validation. - Predicates run synchronously during validation. Keep them fast and side-effect free. Exceptions from
MeetsRequirementare logged and produce aConfigurationErrorresult for that rule.
Result Levels
| Level | Effect |
|---|---|
Compliant |
The rule's MeetsRequirement predicate returned true. |
NonCompliant |
A blocking finding when the requirement is not met. |
Recommendation |
An advisory finding when the requirement is not met. |
NotApplicable |
The rule's IsApplicable predicate returned false. |
ConfigurationError |
The rule could not be evaluated, for example because MeetsRequirement threw. |