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