Resource Validator
The IResourceValidator interface validates whether the scopes and resources requested in an authorization or token request are valid and permitted for the requesting client. The default implementation checks against your configured ApiScope, ApiResource, and IdentityResource definitions. Custom implementations enable dynamic validation based on external systems, tenant context, or runtime business rules.
When to Use
Section titled “When to Use”Use a custom resource validator when:
- Resource/scope availability varies by tenant in a multi-tenant system
- Scope authorization depends on external services or databases
- You need to enforce business rules beyond static client configuration
- Scopes are created dynamically and not stored in IdentityServer configuration
Interface
Section titled “Interface”Duende.IdentityServer.Validation.IResourceValidator
Section titled “Duende.IdentityServer.Validation.IResourceValidator”/// <summary>/// Validates requested resources (scopes and resource indicators)/// </summary>public interface IResourceValidator{ /// <summary> /// Validates the requested resources for the client. /// </summary> /// <param name="request">The resource validation request.</param> /// <param name="ct">The cancellation token.</param> Task<ResourceValidationResult> ValidateRequestedResourcesAsync( ResourceValidationRequest request, CancellationToken ct);}ValidateRequestedResourcesAsync
Section titled “ValidateRequestedResourcesAsync”Validates the requested scopes against the client’s permissions and your resource configuration. Returns a ResourceValidationResult containing:
Succeeded-trueif scopes are valid and no invalid scopes/indicatorsResources- The validatedResourcesobject with:IdentityResources- Matched identity resources (e.g.,openid,profile)ApiResources- Matched API resourcesApiScopes- Matched API scopesOfflineAccess- Whetheroffline_accesswas requested
ParsedScopes- TheParsedScopeValueobjects from the scope parserRawScopeValues- The original scope stringsInvalidScopes- Scopes that failed validationInvalidResourceIndicators- Resource indicators that failed validation
ResourceValidationRequest
Section titled “ResourceValidationRequest”The request object provides:
| Member | Description |
|---|---|
Client | The requesting client |
Scopes | The raw scope values (IEnumerable<string>) |
ResourceIndicators | Resource indicators from the request (RFC 8707) |
Default Behavior
Section titled “Default Behavior”The DefaultResourceValidator:
- Looks up each parsed scope in configured
IdentityResources,ApiScopes, and checks foroffline_access - Verifies the client is allowed to request each scope via
Client.AllowedScopes - Resolves
ApiResourcesassociated with the requestedApiScopes - Returns invalid scopes that don’t match configuration or aren’t allowed for the client
Common Scenarios
Section titled “Common Scenarios”Multi-Tenant Resource Validation
Section titled “Multi-Tenant Resource Validation”Validate scopes against tenant-specific configuration:
public class TenantResourceValidator : IResourceValidator{ private readonly IResourceValidator _inner; private readonly ITenantService _tenantService;
public TenantResourceValidator( DefaultResourceValidator inner, ITenantService tenantService) { _inner = inner; _tenantService = tenantService; }
public async Task<ResourceValidationResult> ValidateRequestedResourcesAsync( ResourceValidationRequest request, CancellationToken ct) { // First, run default validation var result = await _inner.ValidateRequestedResourcesAsync(request, ct);
// Then apply tenant-specific restrictions var tenantId = GetTenantFromClient(request.Client); var tenantScopes = await _tenantService.GetAllowedScopesAsync(tenantId, ct);
// Filter to only tenant-allowed scopes var disallowed = result.Resources.ApiScopes .Where(s => !tenantScopes.Contains(s.Name)) .Select(s => s.Name) .ToList();
foreach (var scope in disallowed) { result.InvalidScopes.Add(scope); result.Resources.ApiScopes.Remove( result.Resources.ApiScopes.First(s => s.Name == scope)); }
return result; }}Dynamic Scope Authorization
Section titled “Dynamic Scope Authorization”Validate parameterized scopes against external authorization:
public class DynamicResourceValidator : IResourceValidator{ private readonly DefaultResourceValidator _inner; private readonly IAuthorizationService _authz;
public DynamicResourceValidator( DefaultResourceValidator inner, IAuthorizationService authz) { _inner = inner; _authz = authz; }
public async Task<ResourceValidationResult> ValidateRequestedResourcesAsync( ResourceValidationRequest request, CancellationToken ct) { var result = await _inner.ValidateRequestedResourcesAsync(request, ct);
// Validate parameterized scopes against external authorization foreach (var scope in result.ParsedScopes.Where(s => s.ParsedParameter != null)) { var allowed = await _authz.IsClientAuthorizedForResourceAsync( request.Client.ClientId, scope.ParsedName, scope.ParsedParameter, ct);
if (!allowed) { result.InvalidScopes.Add(scope.RawValue); } }
return result; }}Transaction-Scoped Validation
Section titled “Transaction-Scoped Validation”Validate that transaction IDs exist and are accessible:
public class TransactionResourceValidator : IResourceValidator{ private readonly DefaultResourceValidator _inner; private readonly ITransactionRepository _transactions;
public TransactionResourceValidator( DefaultResourceValidator inner, ITransactionRepository transactions) { _inner = inner; _transactions = transactions; }
public async Task<ResourceValidationResult> ValidateRequestedResourcesAsync( ResourceValidationRequest request, CancellationToken ct) { var result = await _inner.ValidateRequestedResourcesAsync(request, ct);
// Validate transaction scope parameters var txScopes = result.ParsedScopes .Where(s => s.ParsedName == "transaction" && s.ParsedParameter != null);
foreach (var scope in txScopes) { var exists = await _transactions.ExistsAsync(scope.ParsedParameter, ct); if (!exists) { result.InvalidScopes.Add(scope.RawValue); } }
return result; }}Registration
Section titled “Registration”Register your custom resource validator in ConfigureServices:
builder.Services.AddIdentityServer() .AddResourceValidator<TenantResourceValidator>();When decorating the default validator, register it explicitly:
builder.Services.AddTransient<DefaultResourceValidator>();builder.Services.AddIdentityServer() .AddResourceValidator<TenantResourceValidator>();