Customizing Models

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.

Model-related 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);
}, 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.

Looking to replace an OpenIddict endpoint handler instead of a model? See the Customizing Endpoints page.

Identity

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

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);

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);
                

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";

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);
                

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);
                

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);
                

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";