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:
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.
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:
// 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.
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.
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> |
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> |
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.
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.
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";