Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 11 additions & 31 deletions IdentityServer/v8/UserManagement/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Duende User Management Sample

This sample demonstrates a complete IdentityServer v8 implementation using **Duende User Management** — a user store and authentication platform that ships as a NuGet package and replaces ASP.NET Identity for IdentityServer scenarios.
This sample demonstrates a complete IdentityServer v8 implementation using **Duende User Management** to store users and authenticate them with te below authentication methods.

## What This Sample Shows

Expand All @@ -16,11 +16,11 @@ This sample demonstrates a complete IdentityServer v8 implementation using **Due

### User Profile Management

User Management uses a **schema-driven attribute model** rather than a fixed user table. Attributes like `email`, `name`, `website`, and custom ones like `location` are defined as `AttributeDefinition` entries. Profiles are collections of `AttributeValue` instances tied to a `UserSubjectId`.
User Management uses a schema-driven attribute model rather than a fixed user table. Attributes like `email`, `name`, `website`, and custom ones like `location` are defined as `AttributeDefinition` entries. Profiles are collections of `AttributeValue` instances tied to a `UserSubjectId`.

### Migration from ASP.NET Identity

The Admin → Import page demonstrates bulk-importing users from an existing ASP.NET Identity SQLite database, including:
The IdentityServer `/Admin/Import` page demonstrates bulk-importing users from an existing ASP.NET Identity SQLite database, including:

- Password hash compatibility (imports hashes as-is using a custom `IPasswordHashAlgorithm`)
- Claims-to-attributes mapping (e.g., `given_name` + `family_name` → `name`)
Expand Down Expand Up @@ -49,11 +49,12 @@ This launches:

| Service | URL |
|---------|-----|
| IdentityServer | `https://localhost:5001` |
| Client App | (assigned by Aspire) |
| Mailpit UI | `http://localhost:8025` |
| Mailpit SMTP | `localhost:1025` |
| Aspire Dashboard | `https://localhost:15027` |
| IdentityServer | `https://identityserver.dev.localhost:5001;https://localhost:5001` |
| Client App | `https://client.dev.localhost:5002` |
| ASP.NET Identity Source | `https://aspnet-identity-source.dev.localhost:5003` |
| Aspire Dashboard | `https://aspire.dev.localhost:17300` |
| Mailpit UI | `http://mailpit-aspire.localhost:8025` |
| Mailpit SMTP | `tcp://localhost:1025` |

### Test Credentials

Expand All @@ -68,36 +69,15 @@ In Development mode, a test credentials card appears on the login page:

Passkeys require matching domain/origin configuration. The sample is configured for:

- **Server domain**: `localhost`
- **Allowed origin**: `https://localhost:5001`
- **Server domain**: `identityserver.dev.localhost`
- **Allowed origin**: `https://identityserver.dev.localhost:5001`

If you change the hosting URL, update these values in `Program.cs`.

### Google External Login

Google authentication is configured but hidden when credentials are not present. To enable it, add your Google OAuth client ID and secret to the app configuration.

## Project Structure

```
UserManagement/
├── UserManagementSample/ # IdentityServer application
│ ├── Program.cs # Service registration and configuration
│ ├── SeedData.cs # Creates test users on startup
│ ├── SecondFactorStateCookie.cs # Encrypted cookie for 2FA interim state
│ ├── OtpCookie.cs # Encrypted cookie for OTP flow state
│ ├── Pages/
│ │ ├── Account/ # Login, OTP, password, passkey, external
│ │ ├── Manage/ # Profile, 2FA setup, passkey management
│ │ └── Admin/ # User search, details, import
│ ├── Import/ # ASP.NET Identity migration logic
│ └── Services/ # SecondFactorResolver for passkey 2FA
├── UserManagementSample.AppHost/ # Aspire orchestrator (Mailpit + services)
├── UserManagementSample.Client/ # OIDC client app (Authorization Code + PKCE)
├── UserManagementSample.AspNetIdentitySource/ # Legacy identity DB for import demo
└── UserManagementSample.ServiceDefaults/ # Shared Aspire configuration
```

## Key APIs Demonstrated

### Configuration (Program.cs)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
// Copyright (c) Duende Software. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

var builder = DistributedApplication.CreateBuilder(args);

var mailpit = builder.AddContainer("mailpit", "axllent/mailpit")
.WithContainerName($"user-management-sample-mailpit")
.WithHttpEndpoint(port: 8025, targetPort: 8025, name: "ui", isProxied: false)
.WithEndpoint(port: 1025, targetPort: 1025, name: "smtp", isProxied: false);
var smtpEndpoint = mailpit.GetEndpoint("smtp");

var identityServer = builder.AddProject<Projects.UserManagementSample>("identityserver")
.WithEnvironment("ConnectionStrings__mailpit", mailpit.GetEndpoint("smtp"))
.WaitFor(mailpit);
var identityServer = builder.AddProject<Projects.UserManagementSample>("identity-server")
.WaitFor(mailpit)
.WithSmtp(smtpEndpoint);

builder.AddProject<Projects.UserManagementSample_Client>("client")
.WithReference(identityServer);

builder.AddProject<Projects.UserManagementSample_AspNetIdentitySource>("aspnetidentitysource");

builder.AddProject<Projects.UserManagementSample>("sample");
builder.AddProject<Projects.UserManagementSample_GettingStarted>("gettingstarted");
builder.AddProject<Projects.UserManagementSample_AspNetIdentitySource>("aspnet-identity-source");

builder.Build().Run();

internal static class AspireExtensions
{
/// <summary>
/// Injects SMTP configuration (Smtp:Host, Smtp:Port, Smtp:EnableSsl) from a mailpit endpoint.
/// </summary>
internal static IResourceBuilder<T> WithSmtp<T>(this IResourceBuilder<T> project, EndpointReference smtpEndpoint)
where T : IResourceWithEnvironment =>
project
.WithEnvironment("Smtp__Host", smtpEndpoint.Property(EndpointProperty.Host))
.WithEnvironment("Smtp__Port", smtpEndpoint.Property(EndpointProperty.Port))
.WithEnvironment("Smtp__FromEmail", "no-reply@localhost")
.WithEnvironment("Smtp__FromName", "UserManagement Sample")
.WithEnvironment("Smtp__EnableSsl", "false");
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17300;http://localhost:15300",
"applicationUrl": "https://aspire.dev.localhost:17300",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21300",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<PackageReference Include="Aspire.Hosting.AppHost" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UserManagementSample.GettingStarted\UserManagementSample.GettingStarted.csproj" />
<ProjectReference Include="..\UserManagementSample\UserManagementSample.csproj" />
<ProjectReference Include="..\UserManagementSample.Client\UserManagementSample.Client.csproj" />
<ProjectReference Include="..\UserManagementSample.AspNetIdentitySource\UserManagementSample.AspNetIdentitySource.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"UserManagementSample.AspNetIdentitySource": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://aspnet-identity-source.dev.localhost:5003",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:61692;http://localhost:61693"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"UserManagementSample.Client": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://client.dev.localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5002"
}
}
}
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading