Portal Community

Registration Methods

// Program.cs — register custom IAM extensions
builder.Services
    .AddPassportAuthentication(opts => { /* ... */ })

    // Custom permission resolvers — evaluated in registration order
    .AddCustomPermissionResolver<ContractorRestrictionResolver>()
    .AddCustomPermissionResolver<BusinessHoursResolver>()
    .AddCustomPermissionResolver<ClearanceLevelResolver>()

    // Custom role providers — all run, results merged
    .AddCustomRoleProvider<ProjectRoleProvider>()
    .AddCustomRoleProvider<OnCallRoleProvider>();

Extension Methods Signature

// AddCustomPermissionResolver — adds a resolver to the pipeline
public static IServiceCollection AddCustomPermissionResolver<TResolver>(
    this IServiceCollection services,
    ServiceLifetime lifetime = ServiceLifetime.Scoped)
    where TResolver : class, ICustomPermissionResolver;

// AddCustomRoleProvider — adds a role provider
public static IServiceCollection AddCustomRoleProvider<TProvider>(
    this IServiceCollection services,
    ServiceLifetime lifetime = ServiceLifetime.Scoped)
    where TProvider : class, ICustomRoleProvider;

Recommended Lifetimes

ScenarioLifetimeReason
Resolver with HttpContext dependencyScopedHttpContext is scoped to the request
Resolver with IMemoryCache onlySingletonIMemoryCache is singleton; resolver is stateless
Resolver with database accessScopedDbContext is scoped; opens a new connection per request
Resolver calling external HTTP APIScoped or Singleton with IHttpClientFactoryHttpClient should use IHttpClientFactory pattern

Conditional Registration

// Only register custom IAM in production or when a feature flag is set
if (builder.Environment.IsProduction())
{
    builder.Services
        .AddCustomPermissionResolver<ContractorRestrictionResolver>()
        .AddCustomRoleProvider<ProjectRoleProvider>();
}

// Feature flag controlled registration
if (builder.Configuration.GetValue<bool>("Features:AdvancedIAM"))
{
    builder.Services.AddCustomPermissionResolver<ClearanceLevelResolver>();
}

Complete Program.cs Example

var builder = WebApplication.CreateBuilder(args);

// Configure services
builder.Services
    .AddHttpContextAccessor()
    .AddMemoryCache()

    // Passport authentication
    .AddPassportAuthentication(opts =>
    {
        opts.Authority   = builder.Configuration["Passport:Authority"];
        opts.ClientId    = builder.Configuration["Passport:ClientId"];
        opts.ClientSecret= builder.Configuration["Passport:ClientSecret"];
    })
    .AddPassportClient()

    // Custom IAM pipeline
    .AddCustomPermissionResolver<ContractorRestrictionResolver>()
    .AddCustomPermissionResolver<BusinessHoursResolver>()
    .AddCustomRoleProvider<ProjectRoleProvider>()

    // Dependencies used by custom resolvers
    .AddScoped<IUserProfileService, UserProfileService>()
    .AddScoped<IProjectRepository, ProjectRepository>()

    // Authorization
    .AddAuthorization(opts =>
    {
        opts.AddPolicy("Finance", p => p.RequireClaim("roles", "finance-manager", "admin"));
        opts.AddPolicy("Admin",   p => p.RequireClaim("roles", "admin"));
    });

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Order Matters for Resolvers

The first resolver to return Allow or Deny wins — subsequent resolvers are not called. Register the most specific or restrictive resolver first. For example, register ContractorRestrictionResolver before DepartmentResolver — if the user is a contractor, deny immediately without needing the department check.