From 4f34478071b9e9dea9b2019b133ffa2c16452266 Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Tue, 29 Sep 2026 16:21:22 -0400 Subject: [PATCH 1/3] Added Readme files for /IdentityServer/v8 --- BFF/v4/Angular/README.md | 10 ++ IdentityServer/v7/AspNetIdentity/README.md | 9 ++ IdentityServer/v8/AspNetIdentity/README.md | 76 +++++++++++++ .../v8/AspNetIdentityPasskeys/README.md | 87 +++++++++++++++ IdentityServer/v8/DPoP/README.md | 85 +++++++++++++++ .../v8/Diagnostics/Aspire/README.md | 86 +++++++++++++++ IdentityServer/v8/Diagnostics/Otel/README.md | 74 +++++++++++++ .../v8/Quickstarts/7_Blazor/README.md | 88 +++++++++++++++ .../v8/Quickstarts/JavaScriptClient/README.md | 85 +++++++++++++++ .../DynamicProviders/README.md | 85 +++++++++++++++ .../UserInteraction/ProfileService/README.md | 85 +++++++++++++++ .../v8/UserInteraction/SpaLoginUi/README.md | 77 ++++++++++++++ .../v8/UserInteraction/StepUp/README.md | 83 +++++++++++++++ .../WindowsAuthentication/README.md | 85 +++++++++++++++ .../WsFederationDynamicProviders/README.md | 100 ++++++++++++++++++ .../UserManagement/GettingStarted/README.md | 91 ++++++++++++++++ samples.slnx | 20 +++- 17 files changed, 1225 insertions(+), 1 deletion(-) create mode 100644 BFF/v4/Angular/README.md create mode 100644 IdentityServer/v7/AspNetIdentity/README.md create mode 100644 IdentityServer/v8/AspNetIdentity/README.md create mode 100644 IdentityServer/v8/AspNetIdentityPasskeys/README.md create mode 100644 IdentityServer/v8/DPoP/README.md create mode 100644 IdentityServer/v8/Diagnostics/Aspire/README.md create mode 100644 IdentityServer/v8/Diagnostics/Otel/README.md create mode 100644 IdentityServer/v8/Quickstarts/7_Blazor/README.md create mode 100644 IdentityServer/v8/Quickstarts/JavaScriptClient/README.md create mode 100644 IdentityServer/v8/UserInteraction/DynamicProviders/README.md create mode 100644 IdentityServer/v8/UserInteraction/ProfileService/README.md create mode 100644 IdentityServer/v8/UserInteraction/SpaLoginUi/README.md create mode 100644 IdentityServer/v8/UserInteraction/StepUp/README.md create mode 100644 IdentityServer/v8/UserInteraction/WindowsAuthentication/README.md create mode 100644 IdentityServer/v8/UserInteraction/WsFederationDynamicProviders/README.md create mode 100644 IdentityServer/v8/UserManagement/GettingStarted/README.md diff --git a/BFF/v4/Angular/README.md b/BFF/v4/Angular/README.md new file mode 100644 index 000000000..f5e68d19c --- /dev/null +++ b/BFF/v4/Angular/README.md @@ -0,0 +1,10 @@ +# BFF Angular Sample + +This sample shows an Angular client being hosted by a BFF project. + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the structure of our samples and how to run them. + +## How to Run + +1. Run the `Angular.Api`, and `Angular.Bff` projects +1. Login with username/password `bob` diff --git a/IdentityServer/v7/AspNetIdentity/README.md b/IdentityServer/v7/AspNetIdentity/README.md new file mode 100644 index 000000000..c37f68c58 --- /dev/null +++ b/IdentityServer/v7/AspNetIdentity/README.md @@ -0,0 +1,9 @@ +# Sample APIs + +These APIs are shared between the various samples. + + + +Most samples use the *SimpleApi* which just accepts any token issued by the sample IdentityServer. The *IdentityController* then echoes back the claims to the client. + +The *ResourceBasedApi* is a API that uses the *ApiResource* concept from Duende IdentityServer. It can make use of some advanced features like audience validation and token introspection. \ No newline at end of file diff --git a/IdentityServer/v8/AspNetIdentity/README.md b/IdentityServer/v8/AspNetIdentity/README.md new file mode 100644 index 000000000..a5ff700b3 --- /dev/null +++ b/IdentityServer/v8/AspNetIdentity/README.md @@ -0,0 +1,76 @@ +# ASP.NET Core Identity sample + +This sample shows how to combine Duende IdentityServer with ASP.NET Core Identity, so that the +default ASP.NET Core Identity UI (login, register, manage account) is the login experience that +IdentityServer uses. + +### What the sample demonstrates + +- `AddAspNetIdentity()` to plug ASP.NET Core Identity into IdentityServer, so IdentityServer + issues tokens for ASP.NET Core Identity users +- using the default ASP.NET Core Identity UI for login, registration and account management +- a customized Identity `Logout` page that calls `IIdentityServerInteractionService.GetLogoutContextAsync` + so IdentityServer's logout prompt and sign-out iframe work with the Identity UI +- seeding a local SQLite user store from the command line +- an interactive OIDC client that signs in through the Identity UI and dumps the resulting claims + +## How to Run + +1. Seed the database. In the Aspire dashboard click **Seed Database** on the `identityserver` + resource, or run the seed manually: + + ```bash + dotnet run --project IdentityServerAspNetIdentity -- /seed + ``` + + > **Note:** seeding calls `EnsureDeleted()` before `Migrate()`, so the SQLite database is + > **recreated from scratch every time**. Any registered users are lost. + +1. Run the Aspire project: + + ```bash + dotnet run --project AspNetIdentity.AppHost + ``` + +1. Open a browser tab to the `client` application at `https://localhost:5002`. +1. Click on the **Secure** tab. You will be redirected to the IdentityServer application hosted at + `https://localhost:5001`. +1. Log in with the seeded user (see below). +1. You will be redirected back to the `client` application, which shows the claims from the tokens. + +You can also go straight to `https://localhost:5001` to use the Identity UI directly (login, register, +manage account). + +## Seeded users + +| Username | Password | Notes | +|----------|----------|-------| +| `AliceSmith@email.example` | `Pass123$` | Email is confirmed, no additional claims | +| `BobSmith@email.example` | `Pass123$` | Adds a custom `location` claim | + +## Projects and URLs + +| Project | URL | +|---------|-----| +| `IdentityServerAspNetIdentity` | `https://localhost:5001` | +| `Client` | `https://localhost:5002` | +| `AspNetIdentity.AppHost` (Aspire dashboard) | `https://localhost:17236` | + +## Prerequisites + +- .NET 10 SDK +- A trusted HTTPS development certificate: `dotnet dev-certs https --trust` + +No database server is needed — the sample uses a local SQLite file (`Users.db`). + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `IdentityServerAspNetIdentity/Program.cs` — the `AddAspNetIdentity()` call and the `/seed` switch +- `IdentityServerAspNetIdentity/SeedData.cs` — the test users +- `IdentityServerAspNetIdentity/Areas/Identity/Pages/Account/Logout.cshtml.cs` — the IdentityServer-aware logout page +- `Client/Program.cs` and `Client/Pages/Secure.cshtml` — the OIDC client diff --git a/IdentityServer/v8/AspNetIdentityPasskeys/README.md b/IdentityServer/v8/AspNetIdentityPasskeys/README.md new file mode 100644 index 000000000..3931e2173 --- /dev/null +++ b/IdentityServer/v8/AspNetIdentityPasskeys/README.md @@ -0,0 +1,87 @@ +# ASP.NET Core Identity with passkeys sample + +This sample shows how to register and sign in with **passkeys** (WebAuthn) using ASP.NET Core Identity +together with Duende IdentityServer. It is a full IdentityServer quickstart host (login, consent, device, +CIBA, grants, diagnostics pages) with the ASP.NET Core Identity store added on top. + +### What the sample demonstrates + +- `AddAspNetIdentity()` combined with the standard IdentityServer quickstart configuration +- setting `options.Stores.SchemaVersion = IdentitySchemaVersions.Version3`, which is required to persist passkeys +- `SignInManager.MakePasskeyCreationOptionsAsync` / `MakePasskeyRequestOptionsAsync` to produce WebAuthn options +- `PerformPasskeyAttestationAsync` / `AddOrUpdatePasskeyAsync` to register a new passkey +- `PasskeySignInAsync` for passwordless sign-in +- a `` custom element (Razor tag helper + `wwwroot/js/passkey-submit.js`) that calls + `navigator.credentials.create` / `navigator.credentials.get` and posts the result back +- **conditional mediation** (passkey autofill) on the login page +- a passkey management UI to list, rename and remove passkeys + +## How to Run + +1. Seed the database. In the Aspire dashboard click **Seed Database** on the `identityserver` + resource, or run the seed manually: + + ```bash + dotnet run --project IdentityServerAspNetIdentityPasskeys -- /seed + ``` + + > **Note:** seeding only runs `Migrate()`, so registered passkeys survive a re-seed. + +1. Run the Aspire project: + + ```bash + dotnet run --project AspNetIdentityPasskeys.AppHost + ``` + +1. Open a browser tab to the `client` application at `https://localhost:5002` and click **Secure**, + or navigate directly to `https://localhost:5001`. +1. Log in as `alice` / `Pass123$` (or `bob` / `Pass123$`) using the password form. +1. Go to `https://localhost:5001/Account/Passkeys` and click **Add a new passkey**. Your browser will + prompt you with the system authenticator (Windows Hello, Touch ID, Android screen lock, or a + security key). You are then asked to give the passkey a name. +1. Back on the passkey list you can **Rename** or **Remove** the passkey. +1. Sign out and log back in using **Log in with a passkey** (or just use the username field and let + passkey autofill do the work). + +## Seeded users + +| Username | Email | Password | +|----------|-------|----------| +| `alice` | `AliceSmith@email.example` | `Pass123$` | +| `bob` | `BobSmith@email.example` | `Pass123$` | + +## Projects and URLs + +| Project | URL | +|---------|-----| +| `IdentityServerAspNetIdentityPasskeys` | `https://localhost:5001` | +| `Client` | `https://localhost:5002` | +| `AspNetIdentityPasskeys.AppHost` (Aspire dashboard) | `https://localhost:17237` | + +## Prerequisites and caveats + +- .NET 10 SDK and a trusted HTTPS development certificate (`dotnet dev-certs https --trust`). + Passkeys require a **secure context**, so you must use `https://localhost:5001`. +- Passkeys are bound to the exact origin **`https://localhost:5001`**. This is configured in + `HostingExtensions.cs` via `IdentityPasskeyOptions.ValidateOrigin`; if you change the port, update + that check or passkeys will stop working. +- You need a **browser that supports WebAuthn Level 3 JSON serialization** + (`PublicKeyCredential.parseCreationOptionsFromJSON` / `parseRequestOptionsFromJSON`) — current + Chrome, Edge, Safari 17.4+ or Firefox 119+. Older browsers show "Some passkey features are missing." +- You need a machine with an **enrolled platform authenticator** (Windows Hello, Touch ID, Android + screen lock) or a FIDO2 security key. A headless VM without one cannot complete the flow. +- The "Sign-in with demo.duendesoftware.com" button requires internet access; local passkey and + password sign-in work offline. + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `IdentityServerAspNetIdentityPasskeys/HostingExtensions.cs` — `AddAspNetIdentity`, `SchemaVersion = Version3`, `ValidateOrigin` +- `IdentityServerAspNetIdentityPasskeys/Passkeys/PasskeyEndpointRouteBuilderExtensions.cs` — the two WebAuthn option endpoints +- `IdentityServerAspNetIdentityPasskeys/Passkeys/PasskeySubmitTagHelper.cs` and `wwwroot/js/passkey-submit.js` — the custom element and WebAuthn calls +- `IdentityServerAspNetIdentityPasskeys/Pages/Account/Passkeys.cshtml.cs` — passkey management +- `IdentityServerAspNetIdentityPasskeys/Pages/Account/Login/Index.cshtml.cs` — password and passkey sign-in diff --git a/IdentityServer/v8/DPoP/README.md b/IdentityServer/v8/DPoP/README.md new file mode 100644 index 000000000..681c0adb8 --- /dev/null +++ b/IdentityServer/v8/DPoP/README.md @@ -0,0 +1,85 @@ +# DPoP (Demonstration of Proof of Possession) sample + +This sample shows how to bind access tokens to a client using **DPoP** ([RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)), +so that a stolen bearer token is useless without the corresponding private key. + +### What the sample demonstrates + +- a client configured with `RequireDPoP = true`, which makes IdentityServer issue a **DPoP-bound access + token**: `typ` is `at+jwt` and the token carries a `cnf.jkt` claim with the client's key thumbprint +- generating a DPoP key on the client and handing it to `Duende.AccessTokenManagement` + (`options.DPoPJsonWebKey`), so a proof is attached automatically to every token request and every API call +- resource server validation with `ConfigureDPoPTokensForScheme`, which checks the proof signature, + the `jkt` to `cnf.jkt` binding, the `htm`/`htu` method and URI, and proof freshness +- `DPoPProofExpirationMode` — this sample uses the default `IssuedAt` mode; the alternative `Nonce` mode + has the API issue a `DPoP-Nonce` that the client must echo, at the cost of an extra round trip +- **replay protection** using a keyed `HybridCache` in front of an `IDistributedCache` +- DPoP for both **interactive** (authorization code) and **machine-to-machine** (client credentials) clients +- an API that echoes back the scheme, the raw DPoP proof, the raw access token and the claims, so the + whole exchange is visible in the browser + +## How to Run + +1. Run the Aspire project: + + ```bash + dotnet run --project DPoP.AppHost + ``` + +1. Open a browser tab to the web client at `https://localhost:5010`. +1. Click on the **Secure** link in the navigation. You will be redirected to IdentityServer at + `https://localhost:5001`. +1. Log in with username `alice` and password `alice` (or `bob` / `bob`) and grant consent. +1. You will be redirected back to the `client` application's `/Secure` page, which lists the user's + claims and the authentication cookie properties. The **Renew Tokens** button forces a fresh + DPoP-bound access token. +1. Click the **Call API** button. The page shows the JSON returned by the API. Look at: + - `"scheme": "DPoP"` — the API selected the DPoP scheme, not `Bearer` + - `proofToken` — a `dpop+jwt` proof signed with `PS256`, containing `jkt`, `htm`, `htu` and `iat` + - `accessToken` — an `at+jwt` whose `cnf.jkt` matches the proof's `jkt` +1. Click **Logout**. The web client revokes the refresh token on sign-out. +1. At the same time, look at the `clientcredentials` console in the Aspire dashboard. That background + service calls the API every 5 seconds with a DPoP-bound client credentials token and logs the same + JSON, demonstrating the machine-to-machine case. + +## Seeded users + +The IdentityServer is the shared `../IdentityServerHost` host, which uses in-memory test users: + +| Username | Password | +|----------|----------| +| `alice` | `alice` | +| `bob` | `bob` | + +The `dpop` client id and secret are `dpop` / `905e4892-7610-44cb-a122-6209b38c882f` if you want to +call the token endpoint by hand. + +## Projects and URLs + +| Project | URL | Role | +|---------|-----|------| +| `IdentityServerHost` (shared, from `../IdentityServerHost`) | `https://localhost:5001` | issues DPoP-bound tokens | +| `Api` | `https://localhost:5005` | validates DPoP proofs | +| `WebClient` | `https://localhost:5010` | interactive client — **open this one** | +| `ClientCredentials` | — | background worker, no URL | +| `DPoP.AppHost` | `https://localhost:17008` | Aspire dashboard | + +## Prerequisites + +- .NET 10 SDK +- A trusted HTTPS development certificate: `dotnet dev-certs https --trust` + +No database, Docker container or external service is required. + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `Api/Program.cs` — `ValidTypes = ["at+jwt"]`, `ConfigureDPoPTokensForScheme`, the proof expiration mode and the replay cache +- `Api/IdentityController.cs` — the response that makes DPoP visible +- `WebClient/Program.cs` — creating the RSA DPoP key and `AddUserAccessTokenHttpClient` +- `ClientCredentials/Program.cs` and `ClientCredentials/DPoPClient.cs` — DPoP for client credentials +- `../IdentityServerHost/src/Clients.cs` — the `dpop` client with `RequireDPoP = true` diff --git a/IdentityServer/v8/Diagnostics/Aspire/README.md b/IdentityServer/v8/Diagnostics/Aspire/README.md new file mode 100644 index 000000000..fe924d80a --- /dev/null +++ b/IdentityServer/v8/Diagnostics/Aspire/README.md @@ -0,0 +1,86 @@ +# Aspire diagnostics sample + +This sample shows how to get **logs, metrics and traces** out of Duende IdentityServer using .NET Aspire +and OpenTelemetry, and how to wire the surrounding services together with Aspire's service discovery, +resilience and health check defaults. + +### What the sample demonstrates + +- `builder.AddServiceDefaults()` in every service, which configures: + - **OpenTelemetry logs** via `builder.Logging.AddOpenTelemetry(...)` + - **OpenTelemetry metrics** from `AddRuntimeInstrumentation()`, `AddBuiltInMeters()` and + `AddMeter("Duende.IdentityServer", "Duende.IdentityServer.Experimental", "IdentityServer")` + - **OpenTelemetry traces** from `AddAspNetCoreInstrumentation()`, `AddHttpClientInstrumentation()`, + `AddGrpcClientInstrumentation()` and `AddSource("Duende.IdentityServer")` + - OTLP exporters, which Aspire enables automatically by setting `OTEL_EXPORTER_OTLP_ENDPOINT` + - health checks at `/health` and `/alive` + - **service discovery** (`AddServiceDiscovery()`) and the **standard resilience handler** for every `HttpClient` +- custom application metrics emitted from the IdentityServer UI pages, using a meter named after this + host: `tokenservice.user_login`, `tokenservice.user_logout`, `tokenservice.consent`, `tokenservice.grants_revoked` +- `Duende.AccessTokenManagement` combined with a deliberately short `AccessTokenLifetime` so that tokens + renew constantly and you can watch a steady stream of token requests in the dashboard +- Aspire service discovery in action: the web frontend resolves the API by its logical name + (`https://apiservice`) rather than a hard-coded port + +## How to Run + +1. Run the Aspire project: + + ```bash + dotnet run --project Aspire.AppHost + ``` + +1. The Aspire dashboard opens automatically at `https://localhost:15264`. +1. On the **Resources** page, click the endpoint link for the `webfrontend` resource to open the app + (or navigate to `https://localhost:5014`). +1. Log in as `alice` / `alice` and approve consent. The home page shows a table of weather data + fetched from the API service. +1. Now explore the dashboard while clicking around the app: + - **Traces** — filter by the `identityserver` service to see spans from the `Duende.IdentityServer` + activity source across the authorize and token endpoints, plus the outgoing calls from the web + frontend to the API service. + - **Metrics** — look for counters under `Duende.IdentityServer.*` and `IdentityServer.tokenservice.*`. + The `user_login` and `consent` counters increment as you sign in and grant consent. + - **Logs** — structured logs from all three services. Because the client access token lifetime is + 70 seconds, you will see a token request roughly every 10 seconds while the session is active. + - **Resources** — each service exposes `/health` and `/alive`. + +## Seeded users + +| Username | Password | +|----------|----------| +| `alice` | `alice` | +| `bob` | `bob` | + +## Projects and URLs + +| Project | URL | Exposed in dashboard | +|---------|-----|----------------------| +| `IdentityServer` | `https://localhost:5001` | yes | +| `Aspire.Web` | `https://localhost:5014` | yes | +| `Aspire.ApiService` | `https://localhost:5325` | no (internal only) | +| `Aspire.AppHost` (Aspire dashboard) | `https://localhost:15264` | — | + +## Prerequisites + +- .NET 10 SDK +- A trusted HTTPS development certificate: `dotnet dev-certs https --trust` + +No database, Docker container or external service is required. + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `Aspire.ServiceDefaults/Extensions.cs` — all the OpenTelemetry, health check, service discovery and resilience wiring +- `Aspire.AppHost/Program.cs` — the service topology +- `IdentityServer/Pages/Telemetry.cs` — the custom `tokenservice.*` counters +- `IdentityServer/HostingExtensions.cs` — the IdentityServer setup and the short token lifetime +- `Aspire.Web/Program.cs` and `Aspire.Web/WeatherApiClient.cs` — service discovery and the user access token handler +- `Aspire.ApiService/Program.cs` — the `weather` scope fallback policy + +For a console-only version of the same idea, with no Aspire and no dashboard, see the +[OpenTelemetry sample](../Otel/README.md). diff --git a/IdentityServer/v8/Diagnostics/Otel/README.md b/IdentityServer/v8/Diagnostics/Otel/README.md new file mode 100644 index 000000000..5315e4780 --- /dev/null +++ b/IdentityServer/v8/Diagnostics/Otel/README.md @@ -0,0 +1,74 @@ +# OpenTelemetry console tracing sample + +This is the minimal version of the [Aspire diagnostics sample](../Aspire/README.md): it shows how to turn on +OpenTelemetry **tracing** inside IdentityServer with no collector, no Aspire and no dashboard. The spans are +printed straight to the console. + +### What the sample demonstrates + +- `builder.Services.AddOpenTelemetry().WithTracing(...)` with a **console exporter**, so spans show up in + the terminal +- subscribing to IdentityServer's **fine-grained tracing sources** via + `IdentityServerConstants.Tracing.Basic`, `.Cache`, `.Services`, `.Stores` and `.Validation` +- `AddHttpClientInstrumentation()`, `AddAspNetCoreInstrumentation()` and `AddSqlClientInstrumentation()` +- setting the OpenTelemetry `resource` service name with `ConfigureResource(r => r.AddService(...))` +- an OTLP exporter you can switch on by uncommenting a single line, if you would rather send spans to a collector +- structured logging through Serilog, including `UseSerilogRequestLogging()` + +## How to Run + +1. Run the project: + + ```bash + dotnet run --project src + ``` + +1. The browser opens at `https://localhost:5001`. **Watch the terminal** — that is where the traces appear. +1. Log in as `alice` / `alice` (or `bob` / `bob`) using the **Login** link. +1. Navigate around the app. Each request produces a Serilog request log line followed by a stream of + OpenTelemetry spans showing the activity, trace and span ids and the originating source + (`Duende.IdentityServer.*`, ASP.NET Core, or `System.Net.Http`). +1. To generate traces on the token endpoint as well, request a token from another terminal: + + ```bash + curl --fail-with-body -X POST "https://localhost:5001/connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials&client_id=m2m.client&client_secret=511536EF-F270-4058-80CA-1C89C192F69A&scope=scope1" + ``` + +1. `https://localhost:5001/diagnostics` shows the claims in the current authentication cookie + (restricted to requests from the local machine). + +## Seeded users + +| Username | Password | +|----------|----------| +| `alice` | `alice` | +| `bob` | `bob` | + +## Projects and URLs + +| Project | URL | +|---------|-----| +| `src` | `https://localhost:5001` | + +There is no AppHost for this sample — it is a single project. + +## Prerequisites + +- .NET 10 SDK +- A trusted HTTPS development certificate: `dotnet dev-certs https --trust` + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `src/HostingExtensions.cs` — the whole OpenTelemetry setup +- `src/Program.cs` — the Serilog bootstrap logger and `UseSerilog` +- `src/Config.cs` — the clients and scopes, including the `m2m.client` used in the `curl` example above + +If you want metrics, logs and a dashboard rather than console traces only, use the +[Aspire sample](../Aspire/README.md) instead. diff --git a/IdentityServer/v8/Quickstarts/7_Blazor/README.md b/IdentityServer/v8/Quickstarts/7_Blazor/README.md new file mode 100644 index 000000000..e1eb98786 --- /dev/null +++ b/IdentityServer/v8/Quickstarts/7_Blazor/README.md @@ -0,0 +1,88 @@ +# Blazor WebAssembly with a BFF sample + +This sample shows how to protect a **Blazor WebAssembly** application using the +[Backend-for-Frontend (BFF) pattern](https://docs.duendesoftware.com/identityserver/bff), so that +tokens never reach the browser. + +### What the sample demonstrates + +- `builder.Services.AddBff()` from `Duende.BFF` to set up the BFF services +- `app.UseBff()` — must be placed between `UseAuthentication()` and `UseAuthorization()` +- `app.MapBffManagementEndpoints()`, which exposes the `/bff/login`, `/bff/logout` and `/bff/user` endpoints + used by the frontend +- `AddOpenIdConnectAccessTokenManagement()` so the BFF caches, refreshes and forwards tokens automatically +- protecting a BFF API endpoint with `.AsBffApiEndpoint()`, which requires a CSRF header from the caller +- `AddBffBlazorClient()` in the Blazor WASM project, providing a typed `BffContext` client +- `AddCascadingAuthenticationState()` so `AuthorizeView` works in Razor components +- a `__Host-` prefixed, `SameSite=Strict` authentication cookie + +> **Note:** this sample does **not** run its own IdentityServer. It is a confidential client of the public +> Duende demo server at `https://demo.duendesoftware.com`, so you need internet access. + +## How to Run + +1. Run the Aspire project: + + ```bash + dotnet run --project AppHost + ``` + + > Only the BFF is started by the AppHost. The Blazor WASM project is served by the BFF itself + > (`MapFallbackToFile("index.html")`), so it is not a separate running resource. + + The Aspire dashboard opens at `https://localhost:17236` and will show a single `bff` resource. + +1. Open a browser tab to `https://localhost:7256`. You are anonymous; the home page shows an empty claim list. +1. Click **bff/login** in the navigation. This redirects to the demo IdentityServer. +1. Log in as `bob` / `bob` (or `alice` / `alice`) and approve the request. +1. You are returned to the app and the home page now shows the claims. Notice that the nav has gained a + **bff/logout** link, taken from the `bff:logout_url` claim that `MapBffManagementEndpoints` emits. +1. Click **Weather**. The page calls `GET /api/data` through the typed BFF client, which attaches the + `X-CSRF: 1` header that `.AsBffApiEndpoint()` requires. The weather rows are returned and rendered. +1. Click **Counter** to see plain client-side Blazor state, with no BFF involved. +1. Click **bff/logout** to clear the cookie and end the session at the demo server. + +## Credentials + +| Username | Password | +|----------|----------| +| `bob` | `bob` | +| `alice` | `alice` | + +These are the users of the public demo IdentityServer at `https://demo.duendesoftware.com`. + +## Projects and URLs + +| Project | URL | +|---------|-----| +| `BFF` (serves both the BFF and the Blazor WASM app) | `https://localhost:7256` | +| `AppHost` (Aspire dashboard) | `https://localhost:17236` | + +## Prerequisites + +- .NET 10 SDK +- A trusted HTTPS development certificate: `dotnet dev-certs https --trust` +- Internet access to `https://demo.duendesoftware.com` + +No database or seeding is required — the demo server holds the configuration. + +> **Note:** `BlazorQuickstart.slnx` contains only the `BFF` and `BlazorWasm` projects, not `AppHost`. +> If you open the solution in an IDE and press run, you will start the BFF without the Aspire dashboard, +> which is fine — just open `https://localhost:7256`. + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `BFF/Program.cs` — the entire BFF setup +- `BFF/weather.json` — the static payload served by the BFF API endpoint +- `BlazorWasm/Program.cs` — `AddBffBlazorClient()` +- `BlazorWasm/Layout/MainLayout.razor` — the `bff/login` and `bff/logout` links +- `BlazorWasm/Pages/Home.razor` — the claims dump +- `BlazorWasm/Pages/Weather.razor` — the call to `GET /api/data` + +For a JavaScript SPA using a BFF instead, see quickstart +[`6_JS_with_backend`](../6_JS_with_backend). diff --git a/IdentityServer/v8/Quickstarts/JavaScriptClient/README.md b/IdentityServer/v8/Quickstarts/JavaScriptClient/README.md new file mode 100644 index 000000000..f9bd1253b --- /dev/null +++ b/IdentityServer/v8/Quickstarts/JavaScriptClient/README.md @@ -0,0 +1,85 @@ +# JavaScript client sample + +This sample shows a **pure JavaScript SPA** talking directly to IdentityServer from the browser, with +tokens held in the browser. It is the standalone version of the older JavaScript quickstart. + +### What the sample demonstrates + +- an OpenID Connect client built with the [`oidc-client`](https://github.com/IdentityModel/oidc-client-js) + library (`Oidc.UserManager`), which is vendored in `wwwroot/lib/oidc-client.js` +- the **authorization code flow with PKCE** via `signinRedirect()` and `signinRedirectCallback()` +- tokens stored in the browser's `sessionStorage` rather than in a server-side session +- a deliberate, hand-rolled `XMLHttpRequest` that attaches `Authorization: Bearer ` to the API + call, so you can see exactly how the raw token is sent +- RP-initiated logout via `signoutRedirect()` + +> **Note:** this is a legacy sample. The project targets `net6.0` and uses the older `Startup` class +> pattern, and it is a *client only* — it contains neither an IdentityServer nor an API. It is not part +> of the repository solution and will need a newer target framework to build on a current SDK. +> +> For a maintained equivalent that runs end to end, use the +> [`6_JS_without_backend`](../6_JS_without_backend) quickstart. If you are looking for the modern +> approach with tokens kept server-side, use [`6_JS_with_backend`](../6_JS_with_backend) instead. + +## How to Run + +Because this project is a client only, you must start the IdentityServer and the API it expects first. +The matching host is the [`6_JS_without_backend`](../6_JS_without_backend) quickstart, which serves: + +| Service | URL | Registered by | +|---------|-----|---------------| +| IdentityServer | `https://localhost:5001` | `6_JS_without_backend` — client `js`, redirect `https://localhost:5003/callback.html` | +| API (`/identity`) | `https://localhost:6001` | `6_JS_without_backend` | +| This SPA | `https://localhost:5003` | this project | + +1. Start the `6_JS_without_backend` quickstart (it has its own Aspire AppHost) so the IdentityServer and + the API are running. +1. Start this project: + + ```bash + dotnet run --project JavaScriptClient.csproj + ``` + +1. The browser opens at `https://localhost:5003/index.html`. The results pane logs **User not logged in**. +1. Click **Login**. You are redirected to `https://localhost:5001/connect/authorize` with + `response_type=code`, a PKCE `code_challenge`, and `redirect_uri=https://localhost:5003/callback.html`. +1. Log in at the IdentityServer as `alice` / `alice` (or `bob` / `bob`) and consent. +1. The IdentityServer redirects to `https://localhost:5003/callback.html?code=...&state=...`, which + calls `signinRedirectCallback()` and returns to `index.html`. The results pane now shows + **User logged in** and the pretty-printed user profile. The access token lives in `sessionStorage`. +1. Click **Call API**. The sample issues a `GET https://localhost:6001/identity` with the bearer token and + shows the JSON identity payload. + - A `401` means the token's audience or scope is wrong. + - A CORS error in the browser dev tools means the API's allowed-origins list is missing + `https://localhost:5003`. +1. Click **Logout**. `signoutRedirect()` goes to the IdentityServer's end-session endpoint, the + `User signed out of IdentityServer` event fires, and you land back on `index.html` logged out. + +## Seeded users + +These are the in-memory test users of the `6_JS_without_backend` IdentityServer: + +| Username | Password | +|----------|----------| +| `alice` | `alice` | +| `bob` | `bob` | + +## Prerequisites + +- .NET SDK able to build a `net6.0` web project +- A running IdentityServer at `https://localhost:5001` with the `js` client registered, and an API at + `https://localhost:6001` — in this repository, the `6_JS_without_backend` quickstart provides both +- Trusted HTTPS development certificates for ports 5001, 6001 and 5003: `dotnet dev-certs https --trust` +- CORS configured on the API for the origin `https://localhost:5003` + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `wwwroot/app.js` — the OIDC configuration and every call +- `wwwroot/index.html` — the login, call API and logout buttons +- `wwwroot/callback.html` — reads `?code` and `?state`, then completes the redirect +- `wwwroot/lib/oidc-client.js` — the vendored `oidc-client` library diff --git a/IdentityServer/v8/UserInteraction/DynamicProviders/README.md b/IdentityServer/v8/UserInteraction/DynamicProviders/README.md new file mode 100644 index 000000000..4bebf2aef --- /dev/null +++ b/IdentityServer/v8/UserInteraction/DynamicProviders/README.md @@ -0,0 +1,85 @@ +# Dynamic identity providers sample + +This sample shows how to add **dynamic external identity providers** to IdentityServer. Instead of being +registered in code, the providers live as rows in the configuration store, so you can add or change an +external IdP without recompiling or redeploying. + +### What the sample demonstrates + +- an OIDC provider stored in the configuration database (`SeedData.cs` creates an `OidcProvider` with + scheme `demoidsrv` pointing at `https://demo.duendesoftware.com`) +- a **static** OIDC scheme registered in code alongside it, so you can compare the two side by side on the + same login page +- `AddConfigurationStoreCache()` with `Caching.IdentityProviderCacheDuration`, which is what makes + dynamic providers (and the rest of the configuration data) cached rather than re-read on every request +- discovering providers on the login page by combining two sources: the static schemes from + `IAuthenticationSchemeProvider.GetAllSchemesAsync()` and the dynamic schemes from + `IIdentityProviderStore.GetAllSchemeNamesAsync()` +- honouring `client.EnableLocalLogin` and `client.IdentityProviderRestrictions` when building the login page +- auto-provisioning external users through the external login callback + +## How to Run + +1. Run the Aspire project: + + ```bash + dotnet run --project IdentityServer.AppHost + ``` + + > The AppHost project is named `IdentityServer.AppHost`, not `DynamicProviders.AppHost`. + +1. In the Aspire dashboard, click **Seed Database** on the `identityserverhost` resource. The clients, + resources and the dynamic provider all live in SQLite, so nothing works until you do this. You can + also run it manually with `dotnet run --project IdentityServerHost -- /seed`. +1. Open a browser tab to the client at `https://localhost:44300`. It redirects to the login page at + `https://localhost:5001/account/login`. +1. The login page shows a **Local Account** card and an **External Account** card with two buttons: + - **Sign-in with demo.duendesoftware.com** — the statically registered `oidc` scheme + - **IdentityServer (dynamic)** — the `demoidsrv` provider loaded from the configuration store +1. Log in locally as `alice` / `alice` to see the local flow. +1. Click either external button to be redirected to `https://demo.duendesoftware.com`, log in as + `alice` / `alice`, and you will be returned to the client with the external user auto-provisioned. +1. Visit `https://localhost:5001/diagnostics` to see the session claims, and `https://localhost:5001/grants` + to see the grants that have been stored. + +## Seeded users + +Local users (in-memory test users): + +| Username | Password | +|----------|----------| +| `alice` | `alice` | +| `bob` | `bob` | + +The same credentials also work on the external `https://demo.duendesoftware.com` provider. + +## Projects and URLs + +| Project | URL | +|---------|-----| +| `IdentityServerHost` | `https://localhost:5001` | +| `Client` | `https://localhost:44300` — **open this one** | +| `IdentityServer.AppHost` (Aspire dashboard) | `https://localhost:17197` | + +## Prerequisites + +- .NET 10 SDK +- A trusted HTTPS development certificate: `dotnet dev-certs https --trust` +- Internet access to `https://demo.duendesoftware.com` for the external providers. Local login works + without it. + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `IdentityServerHost/Pages/Account/Login/Index.cshtml.cs` — merging static and dynamic schemes +- `IdentityServerHost/SeedData.cs` — the `OidcProvider` row that creates the dynamic provider +- `IdentityServerHost/Program.cs` — `AddConfigurationStoreCache()` and the identity provider cache duration +- `IdentityServerHost/Pages/ExternalLogin/Challenge.cshtml.cs` and `Callback.cshtml.cs` — the external flow +- `IdentityServer.AppHost/AppHost.cs` — the **Seed Database** command + +For a WS-Federation version of dynamic providers, see +[`WsFederationDynamicProviders`](../WsFederationDynamicProviders/README.md). diff --git a/IdentityServer/v8/UserInteraction/ProfileService/README.md b/IdentityServer/v8/UserInteraction/ProfileService/README.md new file mode 100644 index 000000000..2ccc42f94 --- /dev/null +++ b/IdentityServer/v8/UserInteraction/ProfileService/README.md @@ -0,0 +1,85 @@ +# Profile service sample + +This sample shows how to control the **claims that end up in your tokens** by implementing a custom +`IProfileService`. + +### What the sample demonstrates + +- registering a custom profile service with `AddProfileService()` +- `GetProfileDataAsync` — the hook that decides what is emitted into the id_token and access token +- `IsActiveAsync` — the hook that decides whether a session is still valid +- adding claims from the **session cookie** (`context.Subject.Claims`) and from a **user store** + (`_users.FindBySubjectId(...)`) +- the `context.Caller` property, which tells you whether IdentityServer is building the token for the + client (`ClaimsProviderAccessToken`) or for the userinfo endpoint (`ClaimsProviderUserInfo`) +- the `context.Application.Identifier` property, which lets you emit different claims per client + +`CustomProfileService.cs` is heavily commented and shows four separate techniques: + +1. emit only the claims the client actually asked for, either from the session or from the user store +2. always emit a `picture` claim, regardless of the requested claim types +3. emit a `tenant` claim when the calling client is `client1` +4. emit a `foo` claim when the caller is `ClaimsProviderAccessToken` + +## How to Run + +1. Run the Aspire project: + + ```bash + dotnet run --project ProfileService.AppHost + ``` + +1. In the Aspire dashboard, click the endpoint link for the `client` resource to open + `https://localhost:44300`, or navigate there directly. +1. You will be redirected to the login page at `https://localhost:5001/account/login`. Log in as + `alice` / `alice`. +1. You are returned to the client's `Home/Secure` page, which lists the user claims and the tokens from + the authentication cookie. +1. Click **Call API**. The client calls the API at `https://localhost:5002/identity` and displays the JSON. + Notice the `foo: bar` claim — that is technique 4, and it appears in the **access token** but not in + the id_token. +1. Click **Refresh token** to perform a refresh token grant and re-issue the session. +1. You can also inspect what was issued directly at `https://localhost:5001/diagnostics` (the access token + is shown there for a locally signed request). + +## Seeded users + +| Username | Password | +|----------|----------| +| `alice` | `alice` | +| `bob` | `bob` | + +## Projects and URLs + +| Project | URL | +|---------|-----| +| `IdentityServerHost` | `https://localhost:5001` | +| `Client` | `https://localhost:44300` — **open this one** | +| `SimpleApi` (shared, from `../../Apis/SimpleApi`) | `https://localhost:5002` | +| `ProfileService.AppHost` (Aspire dashboard) | `https://localhost:17047` | + +## Prerequisites + +- .NET 10 SDK +- A trusted HTTPS development certificate: `dotnet dev-certs https --trust` + +No database seeding is needed — all configuration is in memory. + +> **Note on the `tenant` claim:** technique 3 only fires when `context.Application.Identifier == "client1"`, +> but the only client registered in this sample is `interactive.mvc.sample`. To see the `tenant` claim you +> need to rename that client to `client1` or add a client with that id. + +> **Note on the Google button:** the login page offers "Sign-in with Google", but the baked-in client id +> has no secret, so the external flow will not complete. Local login is the path to use. + +## More Information + +Please take a look [here](https://docs.duendesoftware.com/identityserver/samples) to learn about the +structure of our samples and how to run them. + +The interesting parts of this sample are: + +- `IdentityServerHost/CustomProfileService.cs` — the entire sample +- `IdentityServerHost/Program.cs` — the `AddProfileService()` registration +- `IdentityServerHost/Clients.cs` — the `interactive.mvc.sample` client +- `Client/Controllers/HomeController.cs` — the **Call API** and **Refresh token** actions diff --git a/IdentityServer/v8/UserInteraction/SpaLoginUi/README.md b/IdentityServer/v8/UserInteraction/SpaLoginUi/README.md new file mode 100644 index 000000000..39d680cd4 --- /dev/null +++ b/IdentityServer/v8/UserInteraction/SpaLoginUi/README.md @@ -0,0 +1,77 @@ +# SPA login UI sample + +This sample shows how to replace IdentityServer's default Razor Pages user interaction pages with your +**own** pages — here, hand-written static HTML plus a small JSON API. The result is a login experience that +looks and behaves like a single-page application. + +### What the sample demonstrates + +- pointing IdentityServer's interaction URLs at static files instead of Razor Pages: + `LoginUrl = "/login.html"`, `ConsentUrl = "/consent.html"`, `LogoutUrl = "/logout.html"`, + `ErrorUrl = "/error.html"` +- a controller that implements each interaction with `IIdentityServerInteractionService`: + - `GetAuthorizationContextAsync` — who is asking for what, used to render the login and consent pages + - `GrantConsentAsync` / `DenyAuthorizationAsync` — recording the user's consent decision + - `GetErrorContextAsync` — rendering the error page + - `GetLogoutContextAsync` — including the sign-out iframe URL for front-channel logout +- signing the user in and out with `HttpContext.SignInAsync` / `SignOutAsync` against a `TestUserStore` +- serving the static pages with `app.UseDefaultFiles()` and `app.UseStaticFiles()` +- a `RequireConsent = true` client, so the consent page is always exercised + +## How to Run + +1. Run the Aspire project: + + ```bash + dotnet run --project SpaLoginUi.AppHost + ``` + +1. In the Aspire dashboard, click the endpoint link for the `client` resource to open + `https://localhost:44300`, or navigate there directly. +1. You are redirected to `https://localhost:5001/login.html?returnUrl=...` — a plain static HTML page, + not a Razor page. +1. Log in as `alice` / `alice` and click **Login**. The page posts JSON to `/spa/login` and then + navigates to the returned `validReturnUrl`. +1. You are then shown `https://localhost:5001/consent.html?returnUrl=...`. The page calls + `GET /spa/context` and renders the requesting client and the requested scopes. Optionally tick + **Remember My Decision** and click **Yes, Allow**. Clicking **No, Do Not Allow** instead takes you back + to the client with an access denied error. +1. You land back on the client application showing your claims. +1. Click **Logout** in the client navigation. You are redirected to + `https://localhost:5001/logout.html?logoutId=...`. If the client requests a logout prompt you will be + asked to confirm; otherwise the page loads the sign-out iframe in a hidden `