Advanced Configuration & Extensibility

Overview

IdentitySuite is designed to work out of the box with sensible defaults, but every layer of the stack can be replaced or extended when your application needs it. Customization is entirely opt-in: you only configure what you need and everything else keeps its default behaviour. Starting from version 2.1.0, extensibility covers the entire database model — entities, DbContexts, stores and managers. In earlier versions only the OpenIddict server endpoints could be replaced.

All extensibility points are exposed through a single entry method. The overload that accepts an Action<IdentitySuiteOptions> delegate is the only place you need to touch:

copy

builder.AddIdentitySuiteServices(options =>
{
    // Identity layer — entities, DbContext, stores, managers
    options.Database.Identity.UserType    = typeof(MyCustomUser);
    options.Database.Identity.DbContextType = typeof(MyIdentityDbContext);

    // OpenIddict layer — database context
    options.Database.OpenIddict.DbContextType = typeof(MyOpenIddictDbContext);

    // OpenIddict layer — replace built-in endpoint handlers
    options.OpenIddictOptions.ServerEndpointOptions.TokenEndpoint = MyEndpoints.Token;
}, logger);
            

Important — why extend rather than replace

IdentitySuite's base classes (IdentitySuiteUser<TKey>, IdentitySuiteRole<TKey>, entity join types, DbContext base classes) carry properties and conventions that the built-in UI depends on to function correctly — things like timestamp tracking, personal data protection, schema layout and navigation properties used by the administration pages. Always inherit from these base classes rather than reimplementing them from scratch to keep the UI fully operational.

The same principle applies to the OpenIddict endpoint delegates. Each delegate receives a set of injected IdentitySuite services (such as ISessionClientDataService) that handle the persistence of client session data across the multi-step OpenID Connect exchange. The demo repository documents exactly which services are available and how to use them inside custom endpoint implementations.

The sections below describe each group of options in detail.

1. Identity

All Identity-related options live under options.Database.Identity and are represented by the IdentityDbOptions class.

1.1 Primary Key Type

By default all Identity entities use Guid as their primary key. You can switch to any type that implements IEquatable<T> — common alternatives are int or string. The chosen type must be consistent across all entity and DbContext type parameters.

copy
options.Database.Identity.KeyType = typeof(int);

1.2 Entities

Every entity used by ASP.NET Core Identity can be replaced with a custom type. The table below lists each option, its default value and the base class your custom type must inherit from.

Option Default type Required base / constraint
UserType DefaultIdentitySuiteUser IdentitySuiteUser<TKey>
RoleType DefaultIdentitySuiteRole IdentitySuiteRole<TKey>
UserClaimType DefaultIdentitySuiteUserClaim IdentitySuiteUserClaim<TKey>
UserRoleType DefaultIdentitySuiteUserRole IdentitySuiteUserRole<TKey>
UserLoginType DefaultIdentitySuiteUserLogin IdentitySuiteUserLogin<TKey>
RoleClaimType DefaultIdentitySuiteRoleClaim IdentitySuiteRoleClaim<TKey>
UserTokenType DefaultIdentitySuiteUserToken IdentitySuiteUserToken<TKey>
UserPasskeyType IdentityUserPasskey<Guid> See warning below

UserPasskeyType — inheritance not supported

Unlike every other entity, UserPasskeyType cannot be extended through inheritance. This is a limitation of EF Core's .ToJson() mapping used internally by the passkey entity. Use IdentityUserPasskey<TKey> directly, or model additional passkey data in a separate table linked by a foreign key (composition).

Example — extending the user entity:

copy

// 1. Define your custom entity
public class MyUser : IdentitySuiteUser<Guid>
{
    public string? Department { get; set; }
    public string? AvatarUrl  { get; set; }
}

// 2. Register it
options.Database.Identity.UserType = typeof(MyUser);
                

1.3 Identity DbContext

When you introduce custom entities you must also provide a matching DbContext. Inherit from IdentityUserDbContext<...>, which already wires up all EF Core relationships, schema conventions and timestamp tracking. Pass all nine type parameters in the same order as the options above.

copy

public class MyIdentityDbContext(DbContextOptions<MyIdentityDbContext> options)
    : IdentityUserDbContext<
    MyUser,                          // UserType
    DefaultIdentitySuiteRole,        // RoleType
    Guid,                            // KeyType
    DefaultIdentitySuiteUserClaim,   // UserClaimType
    DefaultIdentitySuiteUserRole,    // UserRoleType
    DefaultIdentitySuiteUserLogin,   // UserLoginType
    DefaultIdentitySuiteRoleClaim,   // RoleClaimType
    DefaultIdentitySuiteUserToken,   // UserTokenType
    IdentityUserPasskey<Guid>>(options) // UserPasskeyType
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder); // always call base first
        // add your own fluent configurations here
    }
}

// Register context and entity types together
options.Database.Identity.DbContextType = typeof(MyIdentityDbContext);
options.Database.Identity.UserType      = typeof(MyUser);
                

Migrations assembly

If your custom DbContext lives in a different assembly from the EF Core provider package, set options.Database.Identity.MigrationsAssembly to the assembly name where your migrations will be generated.

copy
options.Database.Identity.MigrationsAssembly = "MyApp.Migrations";

1.4 Custom Stores

If you need full control over how users and roles are persisted — for example to add caching, auditing, or to target a non-EF storage backend — you can supply your own store implementations. Setting either property to a non-null type is enough; the framework will use it automatically.

Option Required interface
UserStoreType IUserStore<TUser>
RoleStoreType IRoleStore<TRole>
copy

options.Database.Identity.UserStoreType = typeof(MyUserStore);
options.Database.Identity.RoleStoreType = typeof(MyRoleStore);
                

1.5 Custom Managers

You can replace any of the three built-in ASP.NET Core Identity managers. Your custom type must inherit from the corresponding base class. Again, a non-null value is sufficient to activate the override.

Option Required base class
UserManagerType UserManager<TUser>
SignInManagerType SignInManager<TUser>
RoleManagerType RoleManager<TRole>
copy

public class MyUserManager(
    IUserStore<MyUser> store,
    IOptions<IdentityOptions> optionsAccessor,
    IPasswordHasher<MyUser> passwordHasher,
    IEnumerable<IUserValidator<MyUser>> userValidators,
    IEnumerable<IPasswordValidator<MyUser>> passwordValidators,
    ILookupNormalizer keyNormalizer,
    IdentityErrorDescriber errors,
    IServiceProvider services,
    ILogger<UserManager<MyUser>> logger)
    : UserManager<MyUser>(store, optionsAccessor, passwordHasher,
    userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
    // override what you need
}

options.Database.Identity.UserManagerType = typeof(MyUserManager);
                

1.6 Custom Password Hasher

To plug in a different hashing algorithm — Argon2, bcrypt, a legacy migration adapter, etc. — implement IPasswordHasher<TUser> and register it. The type must be compatible with the configured UserType.

copy

public class MyPasswordHasher : IPasswordHasher<MyUser>
{
    public string HashPassword(MyUser user, string password)         { /* … */ }
    public PasswordVerificationResult VerifyHashedPassword(
    MyUser user, string hashedPassword, string providedPassword) { /* … */ }
}

options.Database.Identity.PasswordHasherType = typeof(MyPasswordHasher);
                

2. OpenIddict — Database

OpenIddict uses a separate DbContext from the Identity layer. Its options are available at options.Database.OpenIddict and are represented by the OpenIddictDbOptions class.

Option Default Description
DbContextType DefaultIdentityServerDbContext DbContext used to persist OpenIddict applications, authorizations, scopes and tokens
KeyType Guid Primary key type for all OpenIddict entities. Must implement IEquatable<T>
MigrationsAssembly null (provider default) Override the assembly where EF Core migrations are located

To provide a custom context, inherit from IdentityServerDbContext<TKey>, which configures all four OpenIddict entity tables under the IdentityServer schema by default. Override OnModelCreating to change the schema, table names or any other mapping to suit your needs.

copy

public class MyOpenIddictDbContext(DbContextOptions<MyOpenIddictDbContext> options)
    : IdentityServerDbContext<Guid>(options)
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder); // always call base first
        // additional configuration here
    }
}

options.Database.OpenIddict.DbContextType      = typeof(MyOpenIddictDbContext);
options.Database.OpenIddict.MigrationsAssembly = "MyApp.Migrations";
            

3. OpenIddict — Server Endpoints

Every OpenID Connect / OAuth 2.0 endpoint exposed by IdentitySuite has a built-in default implementation. You can replace any of them individually by assigning a delegate to the corresponding property on options.OpenIddictOptions.ServerEndpointOptions.

The selection logic is straightforward: if the property is null the built-in handler runs; if it is set, your delegate is called instead and receives all the same services the default implementation would.

Property Route Description
AuthorizeEndpoint GET/POST /Connect/Authorize Validates the authorization request, checks the user session and decides whether to issue tokens or redirect to the login/consent page
ConsentEndpoint POST /Connect/Consent Processes the user's consent decision (accept or deny) and creates the resulting authorization
TokenEndpoint POST /Connect/Token Exchanges credentials, authorization codes or refresh tokens for access/identity tokens; the place to customize claims issued in tokens
UserInfoEndpoint GET/POST /Connect/UserInfo Returns claims about the authenticated user; override to add or filter claims returned to clients
LogoutGetEndpoint GET /Connect/Logout Initiates the end-session flow; stores client session data and redirects to the logout page
LogoutPostEndpoint POST /Connect/Logout Signs out the user and completes the OpenIddict end-session response
VerifyGetEndpoint GET /Connect/Verify Entry point for the Device Authorization flow — validates the user code and redirects to the verification page
VerifyPostEndpoint POST /Connect/Verify Processes the user's accept/deny action during device verification and issues the resulting token

The most common customization is the token endpoint, where you may want to enrich the claims added to the access token:

copy

options.OpenIddictOptions.ServerEndpointOptions.TokenEndpoint = MyEndpoints.Token;

// in MyEndpoints.cs
public static class MyEndpoints
{
    public static async Task<IResult> Token(
        HttpContext httpContext,
        IIdentitySuiteUserService userManager,
        IIdentitySuiteSignInService signInManager,
        IOpenIddictScopeManager scopeManager,
        IOpenIddictApplicationManager applicationManager,
        ILogger logger)
    {
        // your custom token issuance logic
    }
}
            

Complete Endpoint Customization Example

The IdentitySuite demo repository contains a fully working example that replaces all eight built-in endpoint handlers with custom implementations, demonstrating patterns for claims enrichment, consent logic and device verification.

View Demo Project on GitHub