Installation with Aspire¶
During application development, IdentityServerNET can be started as a container via the .NET Aspire AppHost.
Install the NuGet package in your AppHost project:
dotnet add package IdentityServerNET.Aspire.Hosting
Quick start¶
var builder = DistributedApplication.CreateBuilder(args);
var webApp = builder.AddProject<Projects.ClientWeb>("clientweb");
var webApi = builder.AddProject<Projects.ClientApi>("clientapi");
var identityServer = builder.AddIdentityServerNET("is-net-dev")
.WithMailPit() // start a local mail catcher
// .WithMailDev() // alternative mail catcher
// .WithBindMountPersistance() // persist data between restarts
.WithConfiguration(config => config
.WithApplicationTitle("My Dev Identity Server")
.RememberLoginDefaultValue(true)
.DenyForgotPasswordChallange()
.DenyManageAccount()
)
.WithMigrations(m => m
.AddAdminPassword("admin")
.AddIdentityResources(["openid", "profile", "role"])
.AddApiResource("my-api", ["read", "write"], apiSecret: "apisecret")
.AddUserRoles(["editor", "viewer"])
.WithUser("dev@example.com", "dev", ["editor"])
.AddWebApplicationClient("my-webapp", "secret", webApp,
scopes: ["openid", "profile", "role"])
.AddApiClient("my-api-client", "secret",
scopes: ["my-api", "my-api.read", "my-api.write"])
)
.WithExternalProviders(ext => ext
.AddMicrosoftIdentityWeb(
builder.Configuration.GetSection("IdentityServer:External:MicrosoftIdentityWeb"))
)
.Build();
webApi.AddReference(identityServer, "Authorization:Authority")
.WaitFor(identityServer);
webApp.AddReference(identityServer, "OpenIdConnectAuthentication:Authority")
.WaitFor(identityServer);
builder.Build().Run();
AddIdentityServerNET(containerName) starts a container from the
gstalt/identityserver-net-dev image (https://hub.docker.com/r/gstalt/identityserver-net-dev).
This image is built with a self-signed development certificate so that HTTPS works
out of the box — a requirement for many OAuth/OIDC flows.
Note
Because the certificate is self-signed, browsers will show a security warning. This is expected for development use — you can safely proceed past the warning.
Container options¶
WithMailPit()¶
Starts an axllent/mailpit mail catcher and automatically wires its SMTP address into IdentityServerNET. Use it to inspect outgoing emails (registration confirmation, password reset, etc.) in a browser UI.
builder.AddIdentityServerNET("is-net")
.WithMailPit() // optional: smtpPort to pin the host port
.Build();
WithMailDev()¶
Alternative mail catcher using maildev/maildev.
Same behaviour as WithMailPit(). Choose whichever you prefer.
WithBindMountPersistance(path?)¶
Mounts a host directory into the container so data (users, clients, signing keys, …) survives
container restarts. Without a parameter, data is written to
%LOCALAPPDATA%/identityserver-net-aspire.
WithVolumePersistance(volumeName?)¶
Same as above but uses a named Docker volume instead of a host directory.
Note
Volume permissions can cause problems with some container runtimes.
Prefer WithBindMountPersistance for local development.
Build()¶
Returns the underlying IResourceBuilder<IdentityServerNetResource>, giving access
to all standard Aspire resource methods (WaitFor, WithEnvironment, etc.).
WithConfiguration()¶
Fine-tune the IdentityServerNET configuration by chaining the available methods inside
the WithConfiguration callback.
Application¶
Method |
Description |
|---|---|
|
Title shown in the navbar and on the login page. |
|
Public base URL (e.g. |
Login¶
Method |
Description |
|---|---|
|
Disables username/password login (useful when only external providers are active). |
|
Hides the Forgot password link. |
|
Removes the Remember me checkbox. |
|
Pre-checks or unchecks Remember me by default. |
Passkey (WebAuthn / FIDO2)¶
Method |
Description |
|---|---|
|
Enables passwordless first-factor sign-in via passkey. |
|
Enables passkey as a second factor after username/password. |
|
WebAuthn Relying Party ID — must match the effective domain (e.g. |
|
Human-readable name shown in the authenticator during registration. |
.WithConfiguration(config => config
.AllowPasskeyPasswordless()
.WithPasskeyServerDomain("example.com")
.WithPasskeyRelyingPartyName("My App")
)
Account¶
Method |
Description |
|---|---|
|
Disables self-registration. |
|
Hides the account management pages. |
Admin UI¶
Method |
Description |
|---|---|
|
Hides the Users section in the admin area. |
|
Hides the Roles section. |
|
Hides the API/Identity Resources section. |
|
Hides the Clients section. |
|
Hides the Signing Credentials section. |
|
Prevents admins from creating new signing certificates. |
|
Shows the Data Transfer (import/export) section. |
WithMigrations()¶
Migrations seed data into IdentityServerNET on first startup. They only create objects that do not yet exist — existing entries are never overwritten.
Admin password¶
.WithMigrations(m => m
.AddAdminPassword("admin")
)
Identity resources¶
.WithMigrations(m => m
.AddIdentityResources(["openid", "profile", "email", "role"])
// or one by one:
.WithIdentityResource("openid")
)
API resources¶
Scopes are created as {name}.{scope} sub-scopes. The top-level scope {name}
is always added automatically.
.WithMigrations(m => m
.AddApiResource("my-api", ["read", "write"])
// With an API secret for token introspection:
.AddApiResource("my-secured-api", ["query"], apiSecret: "apisecret")
)
If apiSecret is set, the resource can be used for token introspection.
The secret is stored as a SHA-256 hash. When configuring introspection clients,
use the API resource credentials — not the OAuth client credentials.
Roles¶
.WithMigrations(m => m
.AddUserRoles(["admin", "editor", "viewer"])
// or:
.WithUserRole("custom-role")
)
Users¶
.WithMigrations(m => m
.WithUser("alice@example.com", "password", ["editor", "viewer"])
)
Clients¶
There are three ways to register clients:
1. Convenience methods (recommended)
.WithMigrations(m => m
// Authorization Code + PKCE — URL derived from an Aspire resource:
.AddWebApplicationClient("my-app", "secret", webApp,
scopes: ["openid", "profile", "role"])
// Authorization Code + PKCE — URL as a string:
.AddWebApplicationClient("partner-app", "secret", "https://partner.example.com",
scopes: ["openid", "profile"],
additionalGrantTypes: ["urn:ietf:params:oauth:grant-type:device_code"])
// Machine-to-machine (Client Credentials):
.AddApiClient("backend-service", "secret",
scopes: ["my-api", "my-api.read"])
// JavaScript / SPA (no client secret, PKCE only):
.AddJavaScriptClient("my-spa", "https://spa.example.com",
scopes: ["openid", "profile"])
)
2. Generic ``AddClient()``
.WithMigrations(m => m
.AddClient(ClientType.WebApplication,
clientId: "my-app",
clientSecret: "secret",
resource: webApp, // or: clientUrl: "https://..."
scopes: ["openid", "profile"],
additionalRedirectUris: ["signin-callback"],
additionalGrantTypes: ["urn:ietf:params:oauth:grant-type:device_code"])
)
ClientType values
Value |
Description |
|---|---|
|
Authorization Code + PKCE. Requires a |
|
Client Credentials (machine-to-machine). No redirect URI needed. |
|
Authorization Code + PKCE without a client secret (SPA). |
|
Blank client — configure everything manually via the admin UI. |
Additional client parameters
Parameter |
Description |
|---|---|
|
Extra redirect URIs relative to |
|
Grant types beyond the template default
(e.g. |
WithExternalProviders()¶
Registers external identity providers.
AddMicrosoftIdentityWeb(section)¶
Reads configuration from an IConfigurationSection:
"IdentityServer": {
"External": {
"MicrosoftIdentityWeb": {
"Name": "Microsoft Identity",
"Domain": "mydomain.onmicrosoft.com",
"TenantId": "...",
"ClientId": "...",
"ClientSecret": "..."
}
}
}
.WithExternalProviders(ext => ext
.AddMicrosoftIdentityWeb(
builder.Configuration.GetSection(
"IdentityServer:External:MicrosoftIdentityWeb"))
)
References¶
Link IdentityServerNET to another Aspire resource so its URL is injected as a configuration value:
webApi.AddReference(identityServer, "Authorization:Authority")
.WaitFor(identityServer);
webApp.AddReference(identityServer, "OpenIdConnectAuthentication:Authority")
.WaitFor(identityServer);
configName is the configuration key in the target project where the Aspire URL
of IdentityServerNET will be written (using __ as the separator).
Dev seeding directly in the project (without a container)¶
When IdentityServerNET is started as an ASP.NET Core project via Aspire
(builder.AddProject<Projects.IdentityServer>(...)) rather than as a Docker
container, dev seeding can be configured in appsettings.Development.json
inside the IdentityServer project.
DevMigrationService reads the IdentityServer:Migrations section at startup
in Development mode and automatically creates clients, resources, roles, and users —
only when an in-memory or fresh database is used.
{
"IdentityServer": {
"Migrations": {
"AdminPassword": "admin",
"IdentityResources": [
{ "Name": "openid" },
{ "Name": "profile" },
{ "Name": "role" }
],
"ApiResources": [
{
"Name": "my-api",
"ApiSecret": "apisecret",
"Scopes": [
{ "Name": "read" },
{ "Name": "write" }
]
}
],
"Roles": [
{ "Name": "editor" },
{ "Name": "viewer" }
],
"Users": [
{
"Name": "dev@example.com",
"Password": "dev",
"Roles": [ "editor" ]
}
],
"Clients": [
{
"ClientType": "WebApplication",
"ClientId": "my-webapp",
"ClientSecret": "secret",
"ClientUrl": "https://localhost:44360",
"AdditionalRedirectUris": [ "ManualCode/Callback" ],
"AdditionalGrantTypes": [ "urn:ietf:params:oauth:grant-type:device_code" ],
"Scopes": [ "openid", "profile", "role", "offline_access" ]
},
{
"ClientType": "ApiClient",
"ClientId": "my-api-client",
"ClientSecret": "secret",
"Scopes": [ "my-api", "my-api.read", "my-api.write" ]
}
]
}
}
}
Note
Migrations (with a capital M) is the correct key name as of the current version.
This section is only applied in Development mode — production databases are never
modified by this configuration.