This is the abridged developer documentation for Duende Software Docs ----- # Duende Software
Docs > Get started building your .NET applications with IdentityServer, User Management, Backend-for-Frontend (BFF) and our open-source tools. Install templates Install the [Duende templates](/identityserver/overview/packaging/#templates) to get started quickly. Terminal ```bash dotnet new install Duende.Templates dotnet new duende-is -o DuendeIdentityServer dotnet run --project DuendeIdentityServer ``` Demo Server Experience IdentityServer in action with our live demo server. Test OAuth 2.0 and OpenID Connect flows, explore client configurations, and see how security tokens work in practice. [Try it out](https://demo.duendesoftware.com) IdentityServer The most flexible and standards-compliant OpenID Connect and OAuth 2.0 framework for ASP.NET Core. [Learn more](/identityserver/) User Management Native user storage, passwordless-first authentication, and full lifecycle management (profiles, roles, groups, multi-tenancy). [Learn more](/identityserver/usermanagement/) Backend-for-Frontend (BFF) Securing SPAs and Blazor WASM Applications once and for all, without storing tokens in the browser. [Learn more](/bff/) Access Token Management OSS .NET identity library for access token management. [Learn more](/accesstokenmanagement/) Identity Model OSS .NET identity library for OAuth 2.0 related protocol operations. [Learn more](/identitymodel/) OIDC Client OSS .NET identity libraries for OpenID Connect related protocol operations. [Learn more](/identitymodel-oidcclient/) Introspection for ASP.NET Core OSS ASP.NET Core authentication handler for OAuth 2.0 token introspection. [Learn more](/introspection/) *** Subscribe To Our Newsletter! Stay ahead in the world of identity and access management! * Latest security best practices * Product updates and releases * Technical tips and implementation guides * Industry news and trends Join our community of developers and security professionals building secure applications. ----- # 404 Not Found > Page not found. Check the URL, try using the search bar, or visit the Duende Developer Community. ----- # Access Token Management > The Duende.AccessTokenManagement library provides automatic access token management features for .NET applications The `Duende.AccessTokenManagement` library provides automatic access token management features for .NET worker and ASP.NET Core web applications: * Automatic acquisition and lifetime management of client credentials based access tokens for machine-to-machine communication (using the `Duende.AccessTokenManagement` package) * Automatic access token lifetime management using a refresh token for API calls on behalf of the currently logged-in user (using the `Duende.AccessTokenManagement.OpenIdConnect` package) * Revocation of access tokens ## Machine-To-Machine Token Management [Section titled “Machine-To-Machine Token Management”](#machine-to-machine-token-management) To get started, install the NuGet Package: ```bash dotnet add package Duende.AccessTokenManagement ``` See [Service Workers and Background Tasks](/accesstokenmanagement/workers/) for more information on how to get started. [GitHub Repository ](https://github.com/DuendeSoftware/foss/tree/main/access-token-management)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.AccessTokenManagement/)View the package on NuGet.org. ## User Token Management [Section titled “User Token Management”](#user-token-management) To get started, install the NuGet Package: ```bash dotnet add package Duende.AccessTokenManagement.OpenIdConnect ``` See [Web Applications](/accesstokenmanagement/web-apps/) for more information on how to get started. [GitHub Repository ](https://github.com/DuendeSoftware/foss/tree/main/access-token-management)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.AccessTokenManagement.OpenIdConnect/)View the package on NuGet.org. ## License And Feedback [Section titled “License And Feedback”](#license-and-feedback) **Duende.AccessTokenManagement** is released as open source under the Apache 2.0 license. Bug reports, feature requests and contributions are welcome via the [Duende community discussions](https://duende.link/community). ----- # Client Assertions > Learn how to use client assertions instead of shared secrets for token client authentication in Duende.AccessTokenManagement. If your token client is using a client assertion instead of a shared secret, you can provide the assertion in two ways: * Use the request parameter mechanism to pass a client assertion to the management * Implement the `IClientAssertionService` interface to centralize client assertion creation Here’s a sample client assertion service using the Microsoft JWT library: * V4 ClientAssertionService.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; public class ClientAssertionService(IOptionsSnapshot options) : IClientAssertionService { public Task GetClientAssertionAsync( ClientCredentialsClientName? clientName = null, TokenRequestParameters? parameters = null) { if (clientName == "invoice") { var options1 = options.Get(clientName); var descriptor = new SecurityTokenDescriptor { Issuer = options1.ClientId!.ToString(), // Set the audience to the url of identity server. Do not use the tokenurl to build the autority. Audience = "https://--url-to-authority-here--", Expires = DateTime.UtcNow.AddMinutes(1), SigningCredentials = GetSigningCredential(), Claims = new Dictionary { { JwtClaimTypes.JwtId, Guid.NewGuid().ToString() }, { JwtClaimTypes.Subject, options.ClientId.ToString()! }, { JwtClaimTypes.IssuedAt, DateTimeOffset.UtcNow.ToUnixTimeSeconds() } }, AdditionalHeaderClaims = new Dictionary { { JwtClaimTypes.TokenType, "client-authentication+jwt" } } }; var handler = new JsonWebTokenHandler(); var jwt = handler.CreateToken(descriptor); return Task.FromResult(new ClientAssertion { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = jwt }); } return Task.FromResult(null); } private SigningCredentials GetSigningCredential() { throw new NotImplementedException(); } } ``` * V3 ClientAssertionService.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; public class ClientAssertionService(IOptionsSnapshot options) : IClientAssertionService { public Task GetClientAssertionAsync( string? clientName = null, TokenRequestParameters? parameters = null) { if (clientName == "invoice") { var options1 = options.Get(clientName); var descriptor = new SecurityTokenDescriptor { Issuer = options1.ClientId, // Set the audience to the url of identity server. Do not use the tokenurl to build the autority. Audience = "https://--url-to-authority-here--", Expires = DateTime.UtcNow.AddMinutes(1), SigningCredentials = GetSigningCredential(), Claims = new Dictionary { { JwtClaimTypes.JwtId, Guid.NewGuid().ToString() }, { JwtClaimTypes.Subject, options1.ClientId! }, { JwtClaimTypes.IssuedAt, DateTime.UtcNow.ToEpochTime() } }, AdditionalHeaderClaims = new Dictionary { { JwtClaimTypes.TokenType, "client-authentication+jwt" } } }; var handler = new JsonWebTokenHandler(); var jwt = handler.CreateToken(descriptor); return Task.FromResult(new ClientAssertion { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = jwt }); } return Task.FromResult(null); } private SigningCredentials GetSigningCredential() { throw new NotImplementedException(); } } ``` For a complete working example, see the [WebClientAssertions sample](https://github.com/DuendeSoftware/foss/tree/main/access-token-management/samples/WebClientAssertions). ----- # Customizing Client Credentials Token Management > Learn how to customize client credentials token management including client options, backchannel communication, and token caching configurations. The most common way to use the access token management for [machine-to-machine communication](/accesstokenmanagement/workers/). However, you may want to customize certain aspects of it. ## Client Options [Section titled “Client Options”](#client-options) You can add token client definitions to your host while configuring the DotNet service provider, e.g.: * V4 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("invoices", client => { client.TokenEndpoint = new Uri("https://sts.company.com/connect/token"); client.ClientId = ClientId.Parse("4a632e2e-0466-4e5a-a094-0455c6105f57"); client.ClientSecret = ClientSecret.Parse("e8ae294a-d5f3-4907-88fa-c83b3546b70c"); client.ClientCredentialStyle = ClientCredentialStyle.AuthorizationHeader; client.Scope = Scope.Parse("list"); client.Resource = Resource.Parse("urn:invoices"); }); ``` * V3 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("invoices", client => { client.TokenEndpoint = "https://sts.company.com/connect/token"; client.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57"; client.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c"; client.ClientCredentialStyle = ClientCredentialStyle.AuthorizationHeader; client.Scope = "list"; client.Resource = "urn:invoices"; }); ``` You can set the following options: * `TokenEndpoint` - URL of the OAuth token endpoint where this token client requests tokens from * `ClientId` - client ID * `ClientSecret` - client secret (if a shared secret is used) * `ClientCredentialStyle` - Specifies how the client ID / secret is sent to the token endpoint. Options are using the authorization header, or POST body values (defaults to header) * `Scope` - the requested scope of access (if any) * `Resource` - the resource indicator (if any) Internally the standard .NET options system is used to register the configuration. This means you can also register clients like this: * V4 Program.cs ```csharp services.Configure("invoices", client => { client.TokenEndpoint = new Uri("https://sts.company.com/connect/token"); client.ClientId = ClientId.Parse("4a632e2e-0466-4e5a-a094-0455c6105f57"); client.ClientSecret = ClientSecret.Parse("e8ae294a-d5f3-4907-88fa-c83b3546b70c"); client.Scope = Scope.Parse("list"); client.Resource = Resource.Parse("urn:invoices"); }); ``` * V3 Program.cs ```csharp services.Configure("invoices", client => { client.TokenEndpoint = "https://sts.company.com/connect/token"; client.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57"; client.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c"; client.Scope = "list"; client.Resource = "urn:invoices"; }); ``` Or use the `IConfigureNamedOptions` if you need access to the ASP.NET Core service provider during registration, e.g.: * V4 ClientCredentialsClientConfigureOptions.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; public class ClientCredentialsClientConfigureOptions(DiscoveryCache cache) : IConfigureNamedOptions { public void Configure(string? name, ClientCredentialsClient options) { if (name == "invoices") { var disco = cache.GetAsync().GetAwaiter().GetResult(); options.TokenEndpoint = new Uri(disco.TokenEndpoint); options.ClientId = ClientId.Parse("4a632e2e-0466-4e5a-a094-0455c6105f57"); options.ClientSecret = ClientSecret.Parse("e8ae294a-d5f3-4907-88fa-c83b3546b70c"); options.Scope = Scope.Parse("list"); options.Resource = Resource.Parse("urn:invoices"); } } public void Configure(ClientCredentialsClient options) { // implement default configure Configure("", options); } } ``` * V3 ClientCredentialsClientConfigureOptions.cs ```csharp using Duende.AccessTokenManagement; using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; public class ClientCredentialsClientConfigureOptions(DiscoveryCache cache) : IConfigureNamedOptions { public void Configure(string? name, ClientCredentialsClient options) { if (name == "invoices") { var disco = cache.GetAsync().GetAwaiter().GetResult(); options.TokenEndpoint = disco.TokenEndpoint; options.ClientId = "4a632e2e-0466-4e5a-a094-0455c6105f57"; options.ClientSecret = "e8ae294a-d5f3-4907-88fa-c83b3546b70c"; options.Scope = "list"; options.Resource = "urn:invoices"; } } public void Configure(ClientCredentialsClient options) { // implement default configure Configure("", options); } } ``` You will also need to register the config options, for example: Program.cs ```csharp services.AddClientCredentialsTokenManagement(); services.AddSingleton(new DiscoveryCache("https://sts.company.com")); services.AddSingleton, ClientCredentialsClientConfigureOptions>(); ``` ## Backchannel Communication [Section titled “Backchannel Communication”](#backchannel-communication) By default, all backchannel communication will be done using a named client from the HTTP client factory. The name is `Duende.AccessTokenManagement.BackChannelHttpClient` which is also a constant called `ClientCredentialsTokenManagementDefaults.BackChannelHttpClientName`. You can register your own HTTP client with the factory using the above name and thus provide your own custom HTTP client. The client registration object has two additional properties to customize the HTTP client: * `HttpClientName` - if set, this HTTP client name from the factory will be used instead of the default one * `HttpClient` - allows setting an instance of `HttpClient` to use. Will take precedence over a client name ## Token caching [Section titled “Token caching”](#token-caching) * V4 In V4, access tokens are cached using [`HybridCache`](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/overview?view=aspnetcore-9.0#hybridcache). ### Using remote caches [Section titled “Using remote caches”](#using-remote-caches) Hybrid cache is a 2 tier cache, with in-memory and remote capabilities. Hybrid cache automatically picks up any IDistributedCache implementation as it’s remote cache. See [Distributed Caching in Asp.Net](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-9.0) on more information on topic. ### Injecting a custom cache [Section titled “Injecting a custom cache”](#injecting-a-custom-cache) By default, we use the default HybridCache implementation. You may want to inject a custom hybrid cache implementation, such as [FusionCache](https://github.com/ZiggyCreatures/FusionCache). You can do this either for the entire system: Program.cs ```csharp services.AddSingletonHybridCache>(new MyCustomCacheImplementation()); ``` Or only for `Duende.AccessTokenManagement`, by using Service Keys: Program.cs ```csharp services.AddSingletonHybridCache>(ServiceProviderKeys.ClientCredentialsTokenCache, new MyCustomCacheImplementation()); ``` ### Customizing cache keys [Section titled “Customizing cache keys”](#customizing-cache-keys) By default, cache keys are built up as follows: `{options.CacheKeyPrefix}::{client_name}::hashed({scope})::hashed({resource})` * `options.CacheKeyPrefix` can be configured using the `ClientCredentialsTokenManagementOptions` * `client_name` is the name of the client * `scope` is the scope parameter (if any) that’s used to request the access token. * `resource` is the resource parameter (if any) that’s used to request the access token. The values of both the `scope` and `resource` hashed (MD5) to ensure that the cache key length is not unbounded. You can implement your own cache key generator by implementing a custom `IClientCredentialsCacheKeyGenerator` and registering this to your service container. This is needed if you’re adding custom parameters to your `TokenRequestParameters` ### Encrypting cache entries [Section titled “Encrypting cache entries”](#encrypting-cache-entries) You may want to share a remote cache with other parts of the application or even with other applications. In that case, it may be wise to encrypt the access tokens in the remote cache. You can achieve this with a custom serializer. Program.cs ```csharp // Explicitly register a serializer for the client credentials tokens with the hybrid cache services.AddHybridCache() .AddSerializer(); // This example uses data protection api. You'll want to configure this to suit your needs services.AddDataProtection(); /// /// Example on how to implement a serializer that encrypts data using ASP.NET Core Data Protection. /// public class EncryptedHybridCacheSerializer : IHybridCacheSerializer { private readonly IDataProtector _protector; public EncryptedHybridCacheSerializer(IDataProtectionProvider provider) { _protector = provider.CreateProtector("ClientCredentialsToken"); } public ClientCredentialsToken Deserialize(ReadOnlySequence source) { // Convert the sequence to a byte array var buffer = source.ToArray(); // Unprotect (decrypt) the data var unprotected = _protector.Unprotect(buffer); // Deserialize the JSON payload return JsonSerializer.Deserialize(unprotected)!; } public void Serialize(ClientCredentialsToken value, IBufferWriter target) { // Serialize the value to JSON var json = JsonSerializer.SerializeToUtf8Bytes(value); // Protect (encrypt) the data var protectedBytes = _protector.Protect(json); // Write to the buffer target.Write(protectedBytes); } } ``` * V3 By default, tokens will be cached using the `IDistributedCache` abstraction in ASP.NET Core. You can either use the in-memory cache version, or a real distributed cache like Redis. For development purposes, you can use the `MemoryDistributedCache`: Program.cs ```csharp services.AddDistributedMemoryCache(); ``` Note that `MemoryDistributedCache` will be cleared whenever the process is restarted. It won’t be shared between multiple instances of your application in a load-balanced environment. As a result, a new token will have to be obtained when you restart your application, and each instance will obtain a different token. For production deployments, we recommend using a [distributed cache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed#establish-distributed-caching-services). The built-in cache in `Duende.AccessTokenManagment` uses two settings from the options, which apply with any `IDistributedCache`: Program.cs ```csharp services.AddClientCredentialsTokenManagement(options => { options.CacheLifetimeBuffer = 60; options.CacheKeyPrefix = "Duende.AccessTokenManagement.Cache::"; }); ``` `CacheLifetimeBuffer` is a value in seconds that will be subtracted from the token lifetime, e.g. if a token is valid for one hour, it will be cached for 59 minutes only. The cache key prefix is used to construct the unique key for the cache item based on client name, requested scopes and resource. Finally, you can also replace the caching implementation altogether by registering your own `IClientCredentialsTokenCache`. ----- # Demonstrating Proof-of-Possession (DPoP) > Demonstrating Proof-of-Possession is a security mechanism that binds access tokens to specific cryptographic keys to prevent token theft and misuse. [DPoP](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop) specifies how to bind an asymmetric key stored within a JSON Web Key (JWK) to an access token. This will make the access token bound to the key such that if the access token were to leak, it cannot be used without also having access to the private key of the corresponding JWK. The Duende.AccessTokenManagement library supports DPoP. ## DPoP Key [Section titled “DPoP Key”](#dpop-key) The main piece that your hosting application needs to concern itself with is how to get (and manage) the DPoP key. This key (and signing algorithm) will be either an “RS”, “PS”, or “ES” style key, and needs to be in the form of a JSON Web Key (or JWK). Consult the specification for more details. The creation and management of this DPoP key is up to the policy of the client. For example is can be dynamically created when the client starts up, and can be periodically rotated. The main constraint is that it must be stored for as long as the client uses any access tokens (and possibly refresh tokens) that they are bound to, which this library will manage for you. Creating a JWK in .NET is simple: Program.cs ```csharp using System.Security.Cryptography; using System.Text.Json; using Microsoft.IdentityModel.Tokens; var rsaKey = new RsaSecurityKey(RSA.Create(2048)); var jwkKey = JsonWebKeyConverter.ConvertFromSecurityKey(rsaKey); jwkKey.Alg = "PS256"; var jwk = JsonSerializer.Serialize(jwkKey); Console.WriteLine(jwk); ``` ## Key Configuration [Section titled “Key Configuration”](#key-configuration) Once you have a JWK you wish to use, then it must be configured or made available to this library. That can be done in one of two ways: * Configure the key at startup by setting the `DPoPJsonWebKey` property on either the `ClientCredentialsTokenManagementOptions` or `UserTokenManagementOptions` (depending on which of the two styles you are using from this library). * Implement the `IDPoPKeyStore` interface to produce the key at runtime. Here’s a sample configuring the key in an application using `AddOpenIdConnectAccessTokenManagement` in the startup code: Program.cs ```csharp services.AddOpenIdConnectAccessTokenManagement(options => { options.DPoPJsonWebKey = jwk; }); ``` Similarly, for an application using `AddClientCredentialsTokenManagement`, it would look like this: Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("client_name", options => { options.DPoPJsonWebKey = jwk; }); ``` ## Proof Tokens At The Token Server’s Token Endpoint [Section titled “Proof Tokens At The Token Server’s Token Endpoint”](#proof-tokens-at-the-token-servers-token-endpoint) Once the key has been configured for the client, then the library will use it to produce a DPoP proof token when calling the token server (including token renewals if relevant). There is nothing explicit needed on behalf of the developer using this library. ### `dpop_jkt` At The Token Server’s Authorize Endpoint [Section titled “dpop\_jkt At The Token Server’s Authorize Endpoint”](#dpop_jkt-at-the-token-servers-authorize-endpoint) When using DPoP and `AddOpenIdConnectAccessTokenManagement`, this library will also automatically include the `dpop_jkt` parameter to the authorize endpoint. ## Proof Tokens At The API [Section titled “Proof Tokens At The API”](#proof-tokens-at-the-api) Once the library has gotten a DPoP bound access token for the client, then if your application is using any of the `HttpClient` client factory helpers (e.g. `AddClientCredentialsHttpClient` or `AddUserAccessTokenHttpClient`) then those outbound HTTP requests will automatically include a DPoP proof token for the associated DPoP access token. ## Considerations [Section titled “Considerations”](#considerations) A point to keep in mind when using DPoP and `AddOpenIdConnectAccessTokenManagement` is that the DPoP proof key is created per user session. This proof key must be store somewhere, and the `AuthenticationProperties` used by both the OIDC and cookie handlers is what is used to store this key. This implies that the OIDC `state` parameter will increase in size, as well the resultant cookie that represents the user’s session. The storage for each of these can be customized with the properties on the options `StateDataFormat` and `SessionStore` respectively. ----- # Extensibility > Learn how to extend and customize Duende.AccessTokenManagement, including custom token retrieval. There are several extension points where you can customize the behavior of Duende.AccessTokenManagement. The extension model is designed to favor composition over inheritance, making it easier to customize and extend while maintaining the library’s core functionality. ## Token Retrieval [Section titled “Token Retrieval”](#token-retrieval) Token retrieval can be customized by implementing the `AccessTokenRequestHandler.ITokenRetriever` interface. This interface defines a single method, `GetTokenAsync`, which is called by the `AccessTokenRequestHandler` to retrieve an access token. A common scenario for this would be if you wanted to implement a different token retrieval flow, that’s currently not implemented, such as [Impersonation or Delegation grants (RFC 8693)](https://datatracker.ietf.org/doc/html/rfc8693). Implementing this particular flow is outside the scope of this document. The following snippet demonstrates how to implement fictive scenario where a custom token retriever dynamically determines which credential flow to use. CustomTokenRetriever.cs ```csharp public class CustomTokenRetriever( UserTokenRequestParameters parameters, IClientCredentialsTokenManager clientCredentialsTokenManager, IUserTokenManager userTokenManagement, IUserAccessor userAccessor, ClientCredentialsClientName clientName) : AccessTokenRequestHandler.ITokenRetriever { public async Task> GetTokenAsync( HttpRequestMessage request, CancellationToken ct) { // You'll have to make a decision on what token parameters to use, // and you can override the default parameters. var param = parameters with { Scope = Scope.Parse("some scope"), ForceTokenRenewal = request.GetForceRenewal() // for retry policies. }; AccessTokenRequestHandler.IToken token; // Get the type from current context. // Using a random number as an example here. int tokenType = new Random().Next(1, 2); if (tokenType == 1) { var getTokenResult = await clientCredentialsTokenManager .GetAccessTokenAsync(clientName, param, ct); if (!getTokenResult.Succeeded) { return getTokenResult.FailedResult; } token = getTokenResult.Token; } else { var user = await userAccessor.GetCurrentUserAsync(ct); var getTokenResult = await userTokenManagement .GetAccessTokenAsync(user, param, ct); if (!getTokenResult.Succeeded) { return getTokenResult.FailedResult; } token = getTokenResult.Token; } return TokenResult.Success(token); } } ``` A custom token handler can be linked to your `HttpClient` by creating an `AccessTokenRequestHandler` and adding it to the request pipeline: Program.cs ```csharp services.AddHttpClient() .AddDefaultAccessTokenResiliency() .AddHttpMessageHandler(provider => { var yourCustomTokenRetriever = new CustomTokenRetriever(...); var logger = provider.GetRequiredService>(); var dPoPProofService = provider.GetRequiredService(); var dPoPNonceStore = provider.GetRequiredService(); return new AccessTokenRequestHandler( tokenRetriever: yourCustomTokenRetriever, dPoPNonceStore: dPoPNonceStore, dPoPProofService: dPoPProofService, logger: logger); }); ``` ## Token Request Customization [Section titled “Token Request Customization”](#token-request-customization) Token request parameters can be customized by implementing the `ITokenRequestCustomizer` interface. This interface allows you to dynamically modify token request parameters based on the incoming HTTP request context, making it ideal for multi-tenant applications where token parameters need to vary per tenant. The customizer is invoked before token retrieval and works with both user and client credentials flows. Unlike implementing a custom token retriever, which replaces the entire token acquisition logic, the customizer focuses on modifying parameters such as scopes, resources or other parts of the `TokenRequestParameters`. ### Multi-Tenant Scenario [Section titled “Multi-Tenant Scenario”](#multi-tenant-scenario) In multi-tenant applications, different tenants often require different parameters. For example, each tenant might have: * A unique API resource or audience identifier * Tenant-specific scopes The `ITokenRequestCustomizer` provides a clean way to handle these variations without needing separate `HttpClient` configurations for each tenant. The following example demonstrates a multi-tenant scenario where the customizer extracts the tenant identifier from the HTTP request and applies tenant-specific token parameters: MultiTenantTokenRequestCustomizer.cs ```csharp public class MultiTenantTokenRequestCustomizer( ITenantResolver tenantResolver, ITenantConfigurationStore tenantConfigStore) : ITokenRequestCustomizer { public async Task Customize( HttpRequestMessage httpRequest, TokenRequestParameters baseParameters, CancellationToken cancellationToken) { // Extract tenant identifier from the request // This could come from a header, subdomain, or route parameter var tenantId = await tenantResolver.GetTenantIdAsync(httpRequest, cancellationToken); // Get tenant-specific configuration var tenantConfig = await tenantConfigStore.GetConfigurationAsync(tenantId, cancellationToken); // Customize parameters with tenant-specific values return baseParameters with { Resource = Resource.Parse(tenantConfig.ApiResource), Scope = Scope.Parse(tenantConfig.RequiredScopes), // Add any additional customizations }; } } ``` An instance of the `ITokenRequestCustomizer` implementation can be registered as part of the call to the `Add*Handler` methods: Program.cs ```csharp var customizer = new MultiTenantTokenRequestCustomizer(tenantResolver, tenantConfigStore); // Client Credentials Token Handler services.AddHttpClient("client-credentials-token-http-client") .AddClientCredentialsTokenHandler(customizer, ClientCredentialsClientName.Parse("pure-client-credentials")); // User Access Token Handler services.AddHttpClient("user-access-token-http-client") .AddUserAccessTokenHandler(customizer); // Client Access Token Handler services.AddHttpClient("client-access-token-http-client") .AddClientAccessTokenHandler(customizer); ``` If you require access to services from the service provider, you can use the `Add*Handler` method overloads that accept a factory delegate: Program.cs ```csharp builder.Services.AddScoped(); // Client Credentials Token Handler services.AddHttpClient("client-credentials-token-http-client") .AddClientCredentialsTokenHandler( serviceProvider => serviceProvider.GetRequiredService(), ClientCredentialsClientName.Parse("pure-client-credentials")); // User Access Token Handler services.AddHttpClient("user-access-token-http-client") .AddUserAccessTokenHandler( serviceProvider => serviceProvider.GetRequiredService()); // Client Access Token Handler services.AddHttpClient("client-access-token-http-client") .AddClientAccessTokenHandler( serviceProvider => serviceProvider.GetRequiredService()); ``` ### Additional Use Cases [Section titled “Additional Use Cases”](#additional-use-cases) Beyond multi-tenancy, `ITokenRequestCustomizer` can be used for: * Dynamically setting scopes based on the target API endpoint * Adding audience or resource parameters based on request headers or route data * Implementing per-request token parameter logic without changing the core retrieval flow ----- # Logging > Documentation for logging configuration and usage in Duende Access Token Management, including log levels and Serilog setup Duende Access Token Management uses the standard logging facilities provided by ASP.NET Core. You generally do not need to perform any extra configuration, as it will use the logging provider you have already configured for your application. For general information on how to configure logging, setting up Serilog, and understanding log levels in Duende products, see our [Logging Fundamentals](/general/logging/) guide. The Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) has a good introduction and description of the built-in logging providers. ## Log Levels [Section titled “Log Levels”](#log-levels) You can control the log output for Duende Access Token Management specifically by configuring the `Duende.AccessTokenManagement` namespace in your logging configuration. For example, to enable debug logging for Access Token Management while keeping other logs at a higher level, you can modify your `appsettings.json`: appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Duende.AccessTokenManagement": "Debug" } } } ``` ----- # Customizing User Token Management > Learn how to customize user token management options, per-request parameters, and token storage mechanisms in ASP.NET Core applications. The most common way to use [access token management is for interactive web applications](/accesstokenmanagement/web-apps/) - however, you may want to customize certain aspects of it. Here’s what you can do. ## General Options [Section titled “General Options”](#general-options) You can pass in some global options when registering token management in the ASP.NET Core service provider. * `ChallengeScheme` - by default the OIDC configuration is inferred from the default challenge scheme. This is recommended approach. If for some reason your OIDC handler is not the default challenge scheme, you can set the scheme name on the options * `UseChallengeSchemeScopedTokens` - the general assumption is that you only have one OIDC handler configured. If that is not the case, token management needs to maintain multiple sets of token artefacts simultaneously. You can opt in to that feature using this setting. * `ClientCredentialsScope` - when requesting client credentials tokens from the OIDC provider, the scope parameter will not be set since its value cannot be inferred from the OIDC configuration. With this setting you can set the value of the scope parameter. * `ClientCredentialsResource` - same as previous, but for the resource parameter * `ClientCredentialStyle` - specifies how client credentials are transmitted to the OIDC provider Program.cs ```csharp builder.Services.AddOpenIdConnectAccessTokenManagement(options => { options.ChallengeScheme = "schemeName"; options.UseChallengeSchemeScopedTokens = false; options.ClientCredentialsScope = "api1 api2"; options.ClientCredentialsResource = "urn:resource"; options.ClientCredentialStyle = ClientCredentialStyle.PostBody; }); ``` ## Per Request Parameters [Section titled “Per Request Parameters”](#per-request-parameters) You can also modify token management parameters on a per-request basis. The `UserTokenRequestParameters` class can be used for that: * `SignInScheme` - allows specifying a sign-in scheme. This is used by the default token store * `ChallengeScheme` - allows specifying a challenge scheme. This is used to infer token service configuration * `ForceRenewal` - forces token retrieval even if a cached token would be available * `Scope` - overrides the globally configured scope parameter * `Resource` - override the globally configured resource parameter * `Assertion` - allows setting a client assertion for the request The request parameters can be passed via the manual API: ```csharp var token = await _tokenManagementService .GetAccessTokenAsync(User, new UserAccessTokenRequestParameters { // ... }); ``` …the extension methods ```csharp var token = await HttpContext.GetUserAccessTokenAsync( new UserTokenRequestParameters { // ... }); ``` …or the HTTP client factory Program.cs ```csharp // registers HTTP client that uses the managed user access token builder.Services.AddUserAccessTokenHttpClient("invoices", parameters: new UserTokenRequestParameters { // ... }, configureClient: client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }); // registers a typed HTTP client with token management support builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }) .AddUserAccessTokenHandler(new UserTokenRequestParameters { // ... }); ``` ## Token Storage [Section titled “Token Storage”](#token-storage) By default, the user’s access and refresh token will be store in the ASP.NET Core authentication session (implemented by the cookie handler). You can modify this in two ways * the cookie handler itself has an extensible storage mechanism via the `TicketStore` mechanism * replace the store altogether by providing an `IUserTokenStore` implementation and registering it in the service provider at application startup ----- # Blazor Server Access Token Management > Learn how to manage access tokens in Blazor Server applications and handle token storage and HTTP client usage with Duende.AccessTokenManagement. Blazor Server applications have the same token management requirements as a regular ASP.NET Core web application. Because Blazor Server streams content to the application over a websocket, there often is no HTTP request or response to interact with during the execution of a Blazor Server application. You therefore cannot use `HttpContext` in a Blazor Server application as you would in a traditional ASP.NET Core web application. This means: * you cannot use `HttpContext` extension methods * you cannot use the ASP.NET authentication session to store tokens * the normal mechanism used to automatically attach tokens to Http Clients making API calls won’t work Fortunately, `Duende.AccessTokenManagement` provides a straightforward solution to these problems. Also see the [*BlazorServer* sample](https://github.com/DuendeSoftware/foss/tree/main/access-token-management/samples/BlazorServer) for source code of a full example. ## Token Storage [Section titled “Token Storage”](#token-storage) Since the tokens cannot be managed in the authentication session, you need to store them somewhere else. The options include an in-memory data structure, a distributed cache like redis, or a database. Duende.AccessTokenManagement describes this store for tokens with the *IUserTokenStore* interface. In non-blazor scenarios, the default implementation that stores the tokens in the session is used. In your Blazor server application, you’ll need to decide where you want to store the tokens and implement the store interface. The store interface is straightforward. `StoreTokenAsync` adds a token to the store for a particular user, `GetTokenAsync` retrieves the user’s token, and *ClearTokenAsync* clears the tokens stored for a particular user. A sample implementation that stores the tokens in memory can be found in the *ServerSideTokenStore* in the [*BlazorServer* sample](https://github.com/DuendeSoftware/foss/tree/main/access-token-management/samples/BlazorServer). Register your token store in the ASP.NET Core service provider and tell Duende.AccessTokenManagement to integrate with Blazor by calling `AddBlazorServerAccessTokenManagement`: Program.cs ```csharp builder.Services.AddOpenIdConnectAccessTokenManagement() .AddBlazorServerAccessTokenManagement(); ``` Once you’ve registered your token store, you need to use it. You initialize the token store with the `TokenValidated` event in the OpenID Connect handler: OidcEvents.cs ```csharp public class OidcEvents : OpenIdConnectEvents { private readonly IUserTokenStore _store; public OidcEvents(IUserTokenStore store) { _store = store; } public override async Task TokenValidated(TokenValidatedContext context) { var exp = DateTimeOffset.UtcNow.AddSeconds(double.Parse(context.TokenEndpointResponse!.ExpiresIn)); await _store.StoreTokenAsync(context.Principal!, new UserToken { AccessToken = AccessToken.Parse(context.TokenEndpointResponse.AccessToken), AccessTokenType = AccessTokenType.Parse(context.TokenEndpointResponse.TokenType), RefreshToken = RefreshToken.Parse(context.TokenEndpointResponse.RefreshToken), Scope = Scope.Parse(context.TokenEndpointResponse.Scope), IdentityToken = IdentityToken.Parse(context.TokenEndpointResponse.IdToken), Expiration = exp, }); await base.TokenValidated(context); } } ``` Once registered and initialized, `Duende.AccessTokenManagement` will keep the store up to date automatically as tokens are refreshed. ## Retrieving And Using Tokens [Section titled “Retrieving And Using Tokens”](#retrieving-and-using-tokens) If you’ve registered your token store with `AddBlazorServerAccessTokenManagement`, Duende.AccessTokenManagement will register the services necessary to attach tokens to outgoing HTTP requests automatically, using the same API as a non-blazor application. You inject an HTTP client factory and resolve named HTTP clients where ever you need to make HTTP requests, and you register the HTTP client’s that use access tokens in the ASP.NET Core service provider with our extension method: Program.cs ```csharp builder.Services.AddUserAccessTokenHttpClient("demoApiClient", configureClient: client => { client.BaseAddress = new Uri("https://demo.duendesoftware.com/api/"); }); ``` ----- # Duende AccessTokenManagement v3.x to v4.0 > Guide for upgrading Duende.AccessTokenManagement from version 3.x to version 4.0, including migration steps for custom implementations and breaking changes. ## Changes [Section titled “Changes”](#changes) ### Moving Towards HybridCache Implementation And Away from Distributed Cache [Section titled “Moving Towards HybridCache Implementation And Away from Distributed Cache”](#moving-towards-hybridcache-implementation-and-away-from-distributed-cache) Microsoft has recently released [HybridCache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid). While this is only released as a .NET 9 assembly, these assemblies work fine in .NET 8. So, while we still support .NET 8 with ATM 4.0, we are moving towards using HybridCache. HybridCache brings significant improvements for us. Because of the two-layered cache, we’ve found it significantly improves performance.\ If you currently use a distributed cache, this should still work seamlessly. If you wish to encrypt access tokens, you can do so by implementing a custom serializer. Documentation on this will follow later. We have added support for using a custom HybridCache instance via keyed services. ### Complete Internal Refactoring [Section titled “Complete Internal Refactoring”](#complete-internal-refactoring) The library has undergone extensive internal changes—so much so that it can be considered a new implementation under the same conceptual umbrella. Despite this, the public API surface remains mostly compatible with earlier versions. * New extensibility model (see below). * All async methods now support cancellation tokens. * Renaming of certain classes and interfaces (see below). * Implementation logic is now internal. #### Reduced Public API Surface [Section titled “Reduced Public API Surface”](#reduced-public-api-surface) All internal implementation details are now marked as internal, reducing accidental coupling and clarifying the intended extension points. In V3, all classes were public and most public methods were marked as virtual. This meant you could override any class by inheriting from it and overriding a single method. While this was very convenient for our consumers, it made it challenging to introduce changes to the library without making breaking changes. We still want to ensure our users’ extensibility needs are met, but via more controlled mechanisms. If you find that you have an extensibility need not covered by the new model, please raise a discussion [in our discussion board](https://duende.link/community). If this is a scenario we want to support, we’ll do our best to accommodate it. ### Explicit Extension Model [Section titled “Explicit Extension Model”](#explicit-extension-model) Instead of relying on implicit behaviors or inheritance, V4 introduces clearly defined extension points, making it easier to customize behavior without relying on internal details. ### Composition Over Inheritance [Section titled “Composition Over Inheritance”](#composition-over-inheritance) The `AccessTokenHandler` has been restructured to use composition rather than inheritance, simplifying the customization of token handling and increasing testability. As part of these changes, the retry logic that was part of the `AccessTokenHandler`’s functionality in V3 has moved to a resiliency policy. If you’re not using `AddClientCredentialsHttpClient` to configure an HTTP client, and you depend on the retry policy to be there, you should use `AddDefaultAccessTokenResiliency` to add our implementation or provide your own retry policy. See [Service Workers](/accesstokenmanagement/workers/) for more details. If you wish to implement a custom access token handling process, for example to implement token exchange, you can now [implement your own `AccessTokenRequestHandler.ITokenRetriever`](/accesstokenmanagement/advanced/extensibility/#token-retrieval). ### Strongly Typed Configuration [Section titled “Strongly Typed Configuration”](#strongly-typed-configuration) Configuration is now represented by strongly typed objects, improving validation, discoverability, and IDE support. This means that where before you could assign strings to the configuration system, you’ll now have to explicitly parse the string values. For example: ```csharp var scheme = Scheme.Parse("oidc"); ``` ### Renamed classes [Section titled “Renamed classes”](#renamed-classes) Several classes have been renamed, either to clarify their usage or to drop the `service` suffix, which only adds noise: * `AccessTokenHandler` is now `AccessTokenRequestHandler` * `IClientCredentialsTokenManagementService` is now `IClientCredentialsTokenManager` * `IClientCredentialsTokenEndpointService` is now `IClientCredentialsTokenEndpoint` * `IUserTokenManagementService` is now `IUserTokenManager` * `ITokenRequestSynchronization` is now `IUserTokenRequestConcurrencyControl` * `IUserTokenEndpointService` is now `IOpenIdConnectUserTokenEndpoint` ----- # Web Applications > Learn how to manage access tokens in web applications, including setup, configuration, and usage with HTTP clients. The `Duende.AccessTokenManagement.OpenIdConnect` library automates all the tasks around access token lifetime management for user-centric web applications. While many of the details can be customized, by default the following is assumed: * ASP.NET Core web application * cookie authentication handler for session management * OpenID Connect authentication handler for authentication and access token requests against an OpenID Connect compliant token service * the token service returns a refresh token Using this library, you can either request `user access tokens` or `client credentials tokens`. User access tokens typically contain information about the currently logged in user, such as the `sub` claim. They are used to access services under the credentials of the currently logged in user. `Client credentials tokens` do not contain information about the currently logged in user and are typically used to do machine-to-machine calls. To get started, you’ll need to add `Duende.AccessTokenManagement.OpenIdConnect` to your solution. Then, there are two fundamental ways to interact with token management: 1. **Automatic** recommended: You request a http client from the IHTTPClientFactory. This http client automatically requests, optionally renews and attaches the access tokens on each request. 2. **Manually** advanced: You request an access token, which you can then use to (for example) authenticate with services. You are responsible for attaching the access token to requests. Let’s look at these steps in more detail. ## Adding AccessTokenManagement To Your Project [Section titled “Adding AccessTokenManagement To Your Project”](#adding-accesstokenmanagement-to-your-project) To use this library, start by adding the library to your .NET projects. ```bash dotnet add package Duende.AccessTokenManagement.OpenIdConnect ``` By default, the token management library will use the ASP.NET Core default authentication scheme for token storage. This is typically the cookie handler and its authentication session. It also used the default challenge scheme for deriving token client configuration for refreshing tokens or requesting client credential tokens (typically the OpenID Connect handler pointing to your trusted authority). Program.cs ```csharp // setting up default schemes and handlers builder.Services.AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("cookie", options => { options.Cookie.Name = "web"; // automatically revoke refresh token at signout time options.Events.OnSigningOut = async e => { await e.HttpContext.RevokeRefreshTokenAsync(); }; }) .AddOpenIdConnect("oidc", options => { options.Authority = "https://sts.company.com"; options.ClientId = "webapp"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.Scope.Clear(); // OIDC related scopes options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); // API scopes options.Scope.Add("invoice"); options.Scope.Add("customer"); // requests a refresh token options.Scope.Add("offline_access"); options.GetClaimsFromUserInfoEndpoint = true; options.MapInboundClaims = false; // important! this store the access and refresh token in the authentication session // this is needed to the standard token store to manage the artefacts options.SaveTokens = true; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" }; }); // adds services for token management builder.Services.AddOpenIdConnectAccessTokenManagement(); ``` ## Automatic Via HTTP Client Factory [Section titled “Automatic Via HTTP Client Factory”](#automatic-via-http-client-factory) Similar to the worker service support, you can register HTTP clients that automatically send the access token of the current user when making API calls. The message handler associated with those HTTP clients will try to make sure, the access token is always valid and not expired. Program.cs ```csharp // registers HTTP client that uses the managed user access token builder.Services.AddUserAccessTokenHttpClient("invoices", configureClient: client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }); ``` This could be also a typed client: Program.cs ```csharp // registers a typed HTTP client with token management support builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.company.com/invoices/"); }) .AddUserAccessTokenHandler(); ``` Of course, the ASP.NET Core web application host could also do machine to machine API calls that are independent of a user. In this case all the token client configuration can be inferred from the OpenID Connect handler configuration. The following registers an HTTP client that uses a client credentials token for outgoing calls: ```csharp // registers HTTP client that uses the managed client access token builder.Services.AddClientAccessTokenHttpClient("masterdata.client", configureClient: client => { client.BaseAddress = new Uri("https://api.company.com/masterdata/"); }); ``` As a typed client: Program.cs ```csharp builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.company.com/masterdata/"); }) .AddClientAccessTokenHandler(); ``` Last but not least, if you registered clients with the factory, you can use them. They will try to make sure that a current access token is always sent along. If that is not possible, ultimately a `401` HTTP status code will be returned to the calling code. ```csharp public async Task CallApi() { var client = _httpClientFactory.CreateClient("invoices"); var response = await client.GetAsync("list"); // rest omitted } ``` …or for a typed client: ```csharp public async Task CallApi([FromServices] InvoiceClient client) { var response = await client.GetList(); // rest omitted } ``` ### gRPC Support [Section titled “gRPC Support”](#grpc-support) If you are using gRPC, you can also use the `AddClientAccessTokenHandler` and `AddUserAccessTokenHandler` methods when registering typed gRPC clients: Program.cs ```csharp builder.Services.AddGrpcClient(o => { o.Address = new Uri("https://localhost:5001"); }) .AddUserAccessTokenHandler(); // or .AddClientAccessTokenHandler(); when using client credentials ``` ## Manually Request Access Tokens [Section titled “Manually Request Access Tokens”](#manually-request-access-tokens) If you want to use access tokens differently or have more advanced needs which the automatic option doesn’t cover, then you can also manually request user access tokens. * V4 You can get the current user access token manually by writing code against the `IUserTokenManager`. Program.cs ```csharp public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; private readonly IUserTokenManager _userTokenManager; public HomeController(IHttpClientFactory httpClientFactory, IUserTokenManager userTokenManager) { _httpClientFactory = httpClientFactory; _userTokenManager = userTokenManager; } public async Task CallApi(CancellationToken ct) { var token = await _userTokenManager.GetAccessTokenAsync(User, ct: ct); var client = _httpClientFactory.CreateClient(); client.SetBearerToken(token.Value); var response = await client.GetAsync("https://api.company.com/invoices", ct); // rest omitted } } ``` * V3 You can get the current user access token manually by writing code against the `IUserTokenManagementService`. Program.cs ```csharp public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; private readonly IUserTokenManagementService _tokenManagementService; public HomeController(IHttpClientFactory httpClientFactory, IUserTokenManagementService tokenManagementService) { _httpClientFactory = httpClientFactory; _tokenManagementService = tokenManagementService; } public async Task CallApi() { var token = await _tokenManagementService.GetAccessTokenAsync(User); var client = _httpClientFactory.CreateClient(); client.SetBearerToken(token.Value); var response = await client.GetAsync("https://api.company.com/invoices"); // rest omitted } } ``` ### HTTP Context Extension Methods [Section titled “HTTP Context Extension Methods”](#http-context-extension-methods) Alternatively, you can also manually request access tokens via these extension methods on the `HttpContext`: * `GetUserAccessTokenAsync` - returns an access token representing the user. If the current access token is expired, it will be refreshed. * `GetClientAccessTokenAsync` - returns an access token representing the client. If the current access token is expired, a new one will be requested * `RevokeRefreshTokenAsync` - revokes the refresh token ```csharp public async Task CallApi() { var token = await HttpContext.GetUserAccessTokenAsync(); var client = _httpClientFactory.CreateClient(); client.SetBearerToken(token.Value); var response = await client.GetAsync("https://api.company.com/invoices"); // rest omitted } ``` ----- # Service Workers and Background Tasks > Learn how to manage OAuth access tokens in worker applications and background tasks using Duende.AccessTokenManagement. A common scenario in worker applications or background tasks (or really any daemon-style applications) is to call APIs using an OAuth token obtained via the client credentials flow. The access tokens need to be requested and [cached](/accesstokenmanagement/advanced/client-credentials/#token-caching) (either locally or shared between multiple instances) and made available to the code calling the APIs. In case of expiration (or other token invalidation reasons), a new access token needs to be requested. The actual business code should not need to be aware of this. For more information, see the [advanced topic on client credentials](/accesstokenmanagement/advanced/client-credentials/). To get started, you will need to add the `Duende.AccessTokenManagement` package to your solution. Next, there are two fundamental ways to interact with token management: 1. **Automatic** recommended: You request an `HttpClient` from the `IHttpClientFactory`. This HTTP client automatically requests, optionally renews and attaches the access tokens on each request. 2. **Manually** advanced: You request an access token, which you can then use to (for example) authenticate with services. You are responsible for attaching the access token to requests. Let’s cover these steps in more detail. ## Adding Duende.AccessTokenManagement [Section titled “Adding Duende.AccessTokenManagement”](#adding-duendeaccesstokenmanagement) Start by adding a reference to the `Duende.AccessTokenManagement` NuGet package to your application. ```bash dotnet add package Duende.AccessTokenManagement ``` You can add the necessary services to the ASP.NET Core service provider by calling `AddClientCredentialsTokenManagement()`. After that you can add one or more named client definitions by calling `AddClient`. * V4 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient(ClientCredentialsClientName.Parse("catalog.client"), client => { client.TokenEndpoint = new Uri("https://demo.duendesoftware.com/connect/token"); client.ClientId = ClientId.Parse("6f59b670-990f-4ef7-856f-0dd584ed1fac"); client.ClientSecret = ClientSecret.Parse("d0c17c6a-ba47-4654-a874-f6d576cdf799"); client.Scope = Scope.Parse("catalog inventory"); }) .AddClient(ClientCredentialsClientName.Parse("invoice.client"), client => { client.TokenEndpoint = new Uri("https://demo.duendesoftware.com/connect/token"); client.ClientId = ClientId.Parse("ff8ac57f-5ade-47f1-b8cd-4c2424672351"); client.ClientSecret = ClientSecret.Parse("4dbbf8ec-d62a-4639-b0db-aa5357a0cf46"); client.Scope = Scope.Parse("invoice customers"); }); ``` * V3 Program.cs ```csharp services.AddClientCredentialsTokenManagement() .AddClient("catalog.client", client => { client.TokenEndpoint = "https://demo.duendesoftware.com/connect/token"; client.ClientId = "6f59b670-990f-4ef7-856f-0dd584ed1fac"; client.ClientSecret = "d0c17c6a-ba47-4654-a874-f6d576cdf799"; client.Scope = "catalog inventory"; }) .AddClient("invoice.client", client => { client.TokenEndpoint = "https://demo.duendesoftware.com/connect/token"; client.ClientId = "ff8ac57f-5ade-47f1-b8cd-4c2424672351"; client.ClientSecret = "4dbbf8ec-d62a-4639-b0db-aa5357a0cf46"; client.Scope = "invoice customers"; }); // in v3, you explicitly need to add a distributed cache implementation, such as in-memory services.AddDistributedMemoryCache(); ``` ## Automatic Token Management Using HTTP Factory [Section titled “Automatic Token Management Using HTTP Factory”](#automatic-token-management-using-http-factory) You can register HTTP clients with the factory that will automatically use the above client definitions to request and use access tokens. The following code registers an `HttpClient` called `invoices` which automatically uses the `invoice.client` definition: * V4 Program.cs ```csharp services.AddClientCredentialsHttpClient("invoices", ClientCredentialsClientName.Parse("invoice.client"), client => { client.BaseAddress = new Uri("https://apis.company.com/invoice/"); }); ``` You can also set up a typed HTTP client to use a token client definition, e.g.: Program.cs ```csharp services.AddHttpClient(client => { client.BaseAddress = new Uri("https://apis.company.com/catalog/"); }) .AddClientCredentialsTokenHandler(ClientCredentialsClientName.Parse("catalog.client")); ``` * V3 Program.cs ```csharp services.AddClientCredentialsHttpClient("invoices", "invoice.client", client => { client.BaseAddress = new Uri("https://apis.company.com/invoice/"); }); ``` You can also set up a typed HTTP client to use a token client definition, e.g.: Program.cs ```csharp services.AddHttpClient(client => { client.BaseAddress = new Uri("https://apis.company.com/catalog/"); }) .AddClientCredentialsTokenHandler("catalog.client"); ``` Once you have set up HTTP clients in the HTTP factory, no token-related code is needed at all, e.g.: WorkerHttpClient.cs ```csharp public class WorkerHttpClient : BackgroundService { private readonly ILogger _logger; private readonly IHttpClientFactory _clientFactory; public WorkerHttpClient(ILogger logger, IHttpClientFactory factory) { _logger = logger; _clientFactory = factory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var client = _clientFactory.CreateClient("invoices"); var response = await client.GetAsync("test", stoppingToken); // rest omitted } } } ``` ## Manually Request Access Tokens [Section titled “Manually Request Access Tokens”](#manually-request-access-tokens) If you want to use access tokens in a different way or have more advanced needs which the automatic option doesn’t cover, then you can also manually request access tokens. * V4 You can retrieve the current access token for a given token client via `IClientCredentialsTokenManager.GetAccessTokenAsync`. WorkerManual.cs ```csharp public class WorkerManual( IHttpClientFactory factory, IClientCredentialsTokenManager tokenManagementService ) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var client = factory.CreateClient(); client.BaseAddress = new Uri("https://apis.company.com/catalog/"); // get access token for client and set on HttpClient var token = await tokenManagementService.GetAccessTokenAsync( ClientCredentialsClientName.Parse("catalog.client"), ct: stoppingToken) .GetToken(); client.SetBearerToken(token.AccessToken.ToString()); var response = await client.GetAsync("list", stoppingToken); // rest omitted } } } ``` The result of the GetAccessTokenAsync method is a `TokenResult`. You can interrogate this to see if the result was successful by checking the `Succeeded` property or by calling `WasSuccessful()`. Alternatively, as you see in the example, you can call the `.GetToken()` which will throw if the token couldn’t be retrieved. You can customize some of the per-request parameters by passing in an instance of `TokenRequestParameters`. This allows forcing a fresh token request (even if a cached token would exist) and also allows setting a per-request scope, resource and client assertion. * V3 You can retrieve the current access token for a given token client via `IClientCredentialsTokenManagementService.GetAccessTokenAsync`. WorkerManual.cs ```csharp public class WorkerManual : BackgroundService { private readonly IHttpClientFactory _clientFactory; private readonly IClientCredentialsTokenManagementService _tokenManagementService; public WorkerManual(IHttpClientFactory factory, IClientCredentialsTokenManagementService tokenManagementService) { _clientFactory = factory; _tokenManagementService = tokenManagementService; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var client = _clientFactory.CreateClient(); client.BaseAddress = new Uri("https://apis.company.com/catalog/"); // get access token for client and set on HttpClient var token = await _tokenManagementService.GetAccessTokenAsync("catalog.client"); client.SetBearerToken(token.Value); var response = await client.GetAsync("list", stoppingToken); // rest omitted } } } ``` You can customize some of the per-request parameters by passing in an instance of `ClientCredentialsTokenRequestParameters`. This allows forcing a fresh token request (even if a cached token would exist) and also allows setting a per-request scope, resource and client assertion. ----- # Backend For Frontend (BFF) Security Framework > A comprehensive security framework for securing browser-based frontends with ASP.NET Core backends The Duende.BFF (Backend For Frontend) security framework packages the necessary components to secure browser-based frontends (e.g., SPAs or Blazor applications) with ASP.NET Core backends. Duende.BFF is a library for building services that comply with the BFF pattern and solve security and identity problems in browser-based applications such as SPAs and Blazor-based applications. It is used to create a backend host that is paired with a frontend application. This backend is called the Backend For Frontend (BFF) host, and is responsible for all the OAuth and OIDC protocol interactions. It completely implements the latest recommendations from the IETF regarding security for browser-based applications. It offers the following functionality: * Protection from Token Extraction attacks * Built-in CSRF Attack protection * Server Side OAuth 2.0 Support * Multi-frontend support (Introduced in V4) * User Management APIs * Back-channel logout * Securing access to both local and external APIs by serving as a reverse proxy. * Server side Session State Management * Blazor Authentication State Management * Open Telemetry support (Introduced in V4) Duende.BFF is free for development, testing and personal projects, but production use requires a license. Special offers may apply. The source code for the BFF framework can be found on GitHub. Builds are distributed through NuGet. Also check out the samples. [GitHub Repository ](https://github.com/DuendeSoftware/products/tree/main/bff)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.BFF)View the package on NuGet.org. ## Getting Started [Section titled “Getting Started”](#getting-started) If you’re upgrading from a previous version, please check our [upgrade guides](/bff/upgrading). If you’re starting a new BFF project, consider the following startup guides: * [Single frontend BFF](/bff/getting-started/single-frontend/) * [Multi-frontend BFF](/bff/getting-started/multi-frontend/) * [Blazor](/bff/getting-started/blazor/) ## Do I Need BFF? [Section titled “Do I Need BFF?”](#do-i-need-bff) If you’re building a browser-based application (SPA or Blazor WASM) that needs to call authenticated APIs, BFF is the recommended security architecture. This section helps you decide. ### BFF Pattern vs. Token-in-Browser [Section titled “BFF Pattern vs. Token-in-Browser”](#bff-pattern-vs-token-in-browser) | Concern | BFF Pattern | Token-in-Browser | | ----------------------------- | ------------------------------------------------- | ---------------------------------------------------------------- | | **Token storage** | Server-side only — browser never sees tokens | Tokens in `localStorage` or `sessionStorage` | | **XSS token theft** | Not possible — no tokens in browser memory | High risk — any injected script can steal tokens | | **Third-party cookie issues** | Not affected — uses first-party session cookies | Silent renewal via `prompt=none` breaks in Safari/Firefox/Chrome | | **CSRF exposure** | Mitigated with `X-CSRF` header + SameSite cookies | Not applicable (tokens sent as `Authorization` header) | | **Session revocation** | Server can forcibly end sessions | No server-side control; wait for token expiry | | **Complexity** | Slightly more infrastructure (BFF host required) | Simpler initial setup, but security is harder to get right | | **IETF recommendation** | ✅ Recommended (OAuth 2.0 for Browser-Based Apps) | ❌ Implicit grant deprecated; token-in-browser discouraged | Security implications of NOT using BFF Storing access tokens in the browser (e.g., `localStorage`, `sessionStorage`, or JavaScript memory) exposes them to theft via XSS attacks and supply-chain attacks through compromised npm packages. Once stolen, an attacker can use the token independently of the user’s browser — there is no way to detect or stop this. Additionally, OIDC silent login (`prompt=none`) relies on third-party cookies, which are blocked by Safari, Firefox, and increasingly Chrome. This means session management in token-in-browser apps will silently break for a growing proportion of users. See [Threats Against Browser-based Applications](#threats-against-browser-based-applications) below for the full threat model. ## Background [Section titled “Background”](#background) Single-Page Applications (SPAs) are increasingly common, offering rich functionality within the browser. Front-end development has rapidly evolved with new frameworks and changing browser security requirements. Consequently, best practices for securing these applications have also shifted dramatically. While implementing OAuth logic directly in the browser was once considered acceptable, this is no longer recommended. Storing any authentication state in the browser (such as access tokens) has proven to be inherently risky (see Threats against browser based applications). Because of this, the IETF is currently recommending delegating all authentication logic to a server-based host via a Backend-For-Frontend pattern as the preferred approach to securing modern web applications. ### The Backend For Frontend Pattern [Section titled “The Backend For Frontend Pattern”](#the-backend-for-frontend-pattern) The BFF pattern (Backend-For-Frontend) pattern states that every browser based application should also have a server side application that handles all authentication requirements, including performing authentication flows and securing access to APIs. The server will now expose http endpoints that the browser can use to login, logout or interrogate the active session. With this, the browser based application can trigger an authentication flow by redirecting to a URL, such as /bff/login. Once the authentication process is completed, the server places a secure authentication cookie in the browser. This cookie is then used to authenticate all subsequent requests, until the user is logged out again. The BFF should expose all APIs that the front-end wants to access securely. So it can either host APIs locally, or act as a reverse proxy towards external APIs. With this approach, the browser based application will not have direct access to the access token. So if the browser based application is compromised, for example with XSS attacks, there is no risk of the attacker stealing the access tokens. As the name of this pattern already implies, the BFF backend is the (only) Backend for the Frontend. They should be considered part of the same application. It should only expose the APIs that the front-end needs to function. ### 3rd party cookies [Section titled “3rd party cookies”](#3rd-party-cookies) In recent years, several browsers (notably Safari and Firefox) have started to block 3rd party cookies. Chrome is planning to do the same in the future. While this is done for valid privacy reasons, it also limits some of the functionality a browser based application can provide. A couple of particularly notable OIDC flows that don’t work for SPAs when third party cookies are blocked are OIDC Session Management and OIDC Silent Login via the prompt=none parameter. ### CSRF protection [Section titled “CSRF protection”](#csrf-protection) There is one thing to keep an eye out for with this pattern, and that’s Cross Site Request Forgery (CSRF). The browser automatically sends the authentication cookie for safe-listed cross-origin requests, which exposes the application to CORS Attacks. Fortunately, this threat can easily be mitigated by a BFF solution by requiring a custom header to be passed along. See more on CORS protection. ### The BFF Framework in an application architecture [Section titled “The BFF Framework in an application architecture”](#the-bff-framework-in-an-application-architecture) The following diagram illustrates how the Duende BFF Security Framework fits into a typical application architecture. ``` flowchart TD subgraph Browser SPA["Browser-Based Application"] CookieJar["🍪 Cookie Jar"] end subgraph BFF["BFF Host"] AuthEndpoints["Authentication
Endpoints"] SessionMgmt["Session
Management"] CookieAuth["Cookie Authorization"] CSRF["CSRF Protection"] Proxy["Proxy to
External APIs"] LocalAPIs["Local APIs"] SessionStore[("Server-Side
Session Storage")] end IdP["Identity Provider"] ExternalAPIs["External APIs"] SPA -->|"login / logout"| AuthEndpoints AuthEndpoints -->|"Set-Cookie"| CookieJar CookieJar -->|"Auth cookie"| CookieAuth AuthEndpoints <-->|"redirect"| IdP AuthEndpoints --> SessionMgmt SessionMgmt --> SessionStore CookieAuth --> CSRF CSRF --> Proxy CSRF --> LocalAPIs Proxy -->|"Bearer token"| ExternalAPIs SessionMgmt -->|"Acquire tokens"| IdP ExternalAPIs -->|"Validate tokens"| IdP ``` The browser based application runs inside the browser’s secure sandbox. It can be built using any type of front-end technology, such as via Vanilla-JS, React, Vue, WebComponents, Blazor, etc. When the user wants to log in, the app can redirect the browser to the authentication endpoints. This will trigger an OpenID Connect authentication flow, at the end of which, it will place an authentication cookie in the browser. This cookie has to be an HTTP Only Same Site and Secure cookie. This makes sure that the browser application cannot get the contents of the cookie, which makes stealing the session much more difficult. The browser will now automatically add the authentication cookie to all calls to the BFF, so all calls to the APIs are secured. This means that embedded (local) APIs are already automatically secured. The app cannot access external Api’s directly, because the authentication cookie won’t be sent to 3rd party applications. To overcome this, the BFF can proxy requests through the BFF host, while exchanging the authentication cookie for a bearer token that’s issued from the identity provider. This can be configured to include or exclude the user’s credentials. As mentioned earlier, the BFF needs protection against CSRF attacks, because of the nature of using authentication cookies. While .net has various built-in methods for protecting against CSRF attacks, they often require a bit of work to implement. The easiest way to protect (just as effective as the .Net provided security mechanisms) is just to require the use of a custom header. The BFF Security framework by default requires the app to add a custom header called x-csrf=1 to the application. Just the fact that this header must be present is enough to protect the BFF from CSRF attacks. ### Logical and Physical Sessions [Section titled “Logical and Physical Sessions”](#logical-and-physical-sessions) When implemented correctly, a user will think of their time interacting with a solution as *“one session”* also known as the **“logical session”**. The user should not be concerned with the steps developers take to provide a seamless experience. Users want to use the app, get their tasks completed, and log out happy. ``` sequenceDiagram actor Alice box logical session participant App end Alice->>App: /login App->>Alice: /account ``` So while the user will only see (and care about) a single session, it’s entirely possible that there will be multiple physical sessions active. For most distributed applications, including those implemented with BFF, **sessions are managed independently by each component of an application architecture.** This means that there are **N+1** physical sessions possible, where **N** is the number of sessions for each service in your solution, and the **+1** being the session managed on the BFF host. Since we are focusing on ASP.NET Core, those sessions typically are stored using the Cookie Authentication handler features of .NET. ``` sequenceDiagram actor Alice box App session participant App end box Service 1 session participant Service 1 end box Service N... session participant Service N... end Alice->>App: /login App->>Alice: /account App->>Service 1: request App->>Service N...: N... request ``` The separation allows each service to manage its session to its specific needs. While it can depend on your requirements, we find most developers want to coordinate the physical session lifetimes, creating a more predictable logical session. If that is your case, we recommend you first start by turning each physical session into a more powerful [server-side session](/bff/fundamentals/session/server-side-sessions/). Server-side sessions are instances that are persisted to data storage and allow for visibility into currently active sessions and better management techniques. Let’s take a look at the advantages of server-side sessions. Server-side sessions at each component allows for: * Receiving back channel logout notifications * Forcibly end a user’s session of that node * Store and view information about a session lifetime * Coordinate sessions across an application’s components * Different claims data Server-side sessions at IdentityServer allow for more powerful features: * Receive back channel logout notifications from upstream identity providers in a federation * Forcibly end a user’s session at IdentityServer * Global inactivity timeout across SSO apps and session coordination * Coordinate sessions to registered clients Keep in mind the distinctions between logical and physical sessions, and you will better understand the interplay between elements in your solution. ### Threats Against Browser-based Applications [Section titled “Threats Against Browser-based Applications”](#threats-against-browser-based-applications) Let’s look at some of the common ways browser-based apps are typically attacked and what their consequences would be. #### Token theft [Section titled “Token theft”](#token-theft) Often, malicious actors are trying to steal access tokens. In this paragraph, we’ll look into several techniques how this is often done and what the consequences are. But it’s important to note that all these techniques rely on the browser-based application having access to the access token. Therefore, these attacks can be prevented by implementing the BFF pattern. ##### Script injection attacks [Section titled “Script injection attacks”](#script-injection-attacks) The most common way malicious actors steal access tokens is by injecting malicious JavaScript code into the browser. This can happen in many different ways. Script injection attacks or supply chain attacks (via compromised NPM packages or cloud-hosted scripts) are just some examples. Since the malicious code runs in the same security sandbox as the application’s code, it has exactly the same privileges as the application code. This means there is no way to securely store and handle access tokens in the browser. There have been attempts to place the code that accesses and uses web tokens in more highly isolated storage areas, such as Web Workers, but these attempts have also been proven to be vulnerable to token exfiltration attacks, so they are not suitable as an alternative. If the browser-based application has access to your access token, so can malicious actors. ##### Other ways of compromising browser security [Section titled “Other ways of compromising browser security”](#other-ways-of-compromising-browser-security) Injecting code is not the only way that browser security can be broken. Sometimes the browser sandbox itself is under attack. Browsers attempt to provide a secure environment in which web pages and their scripts can safely be loaded and executed in isolation. On many occasions, this browser sandbox has been breached by exploits. A recent example is the POC from Google on Browser-Based Spectre Attacks. By bypassing the security sandbox, the attackers are able to read the memory from your application and steal the access tokens. The best way to protect yourself from this is not having any access tokens stored in the application’s memory at all by following the BFF pattern. ##### Consequences of token theft [Section titled “Consequences of token theft”](#consequences-of-token-theft) Once an attacker is able to inject malicious code, there are a number of things the attacker can do. At a minimum, the attacker can take over the current user’s session and in the background perform malicious actions under the credentials of the user. This would only be possible as long as the user has the application open, which limits how long the attacker can misuse the session. It’s worse if the attacker is able to extract the authentication token. The attacker can now access the application directly from his own computer, as long as the access token is valid. For this reason, it’s recommended to keep access token lifetimes short. If the attacker is also able to acquire the refresh token or worse, is able to request new tokens, then the attacker can use the credentials indefinitely. ##### Attacks at OAuth Implicit Grant [Section titled “Attacks at OAuth Implicit Grant”](#attacks-at-oauth-implicit-grant) Sometimes there are vulnerabilities discovered even in the protocols that are underlying most of the web’s security. As a result, these protocols are constantly evolving and updated to reflect the latest knowledge and known vulnerabilities. One example of this is OAuth Implicit grant. This was once a recommended pattern and many applications have implemented this since. However, in recent years it’s become clear that this protocol is no longer deemed secure and in the words of the IETF: > Browser-based clients MUST use the Authorization Code grant type and MUST NOT use the Implicit grant type to obtain access tokens #### CSRF Attacks [Section titled “CSRF Attacks”](#csrf-attacks) Cookie-based authentication (when using Secure and HTTP Only cookies) effectively prevents browser-based token stealing attacks. But this approach is vulnerable to a different type of attack, namely CSRF attacks. This is similar but different from CORS attacks which lies in the definition of what the browser considers a Site vs an Origin and what kind of request a browser considers ‘safe’ for Cross Origin requests. ##### Origins and Sites [Section titled “Origins and Sites”](#origins-and-sites) To a browser, a [site](https://developer.mozilla.org/en-US/docs/Glossary/Site) is defined as TLD (top-level domain - 1). So, a single segment under a top-level domain, such as example in `example.co.uk`, where `co.uk` is the top-level domain. Any subdomain under that (so `site1.example.co.uk` and `www.example.co.uk`) are considered to be from the same site. Contrast this to an origin, which is the scheme + hostname + port. In the previous example, the origins would be `https://example.co.uk` and `https://www.example.co.uk`. The site is the same, but the origin is different. Browsers have built-in control when cookies should be sent. For example, by setting [SameSite=strict](https://owasp.org/www-community/SameSite), the browser will only send along cookies if you are navigating within the same **site** (not origins). Browsers also have built-in **Cross Origin** protection. Most requests that go across different origins (not sites) will by default be subjected to CORS protection. This means that the server needs to say if the requests are safe to use cross-origin. The exclusion to this are requests that the browser considers safe. The following diagram (created based on this article [Wikipedia](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)) shows this quite clearly: ``` flowchart TD; A[JavaScript makes a cross-domain XHR call] --> B{Is it a GET or HEAD?}; subgraph cors-safe B -->|Yes| X[Make actual XHR]; B -->|No| C{Is it a POST?}; C -->|Yes| E{Is the content-type standard?}; C -->|No| D[Make OPTIONS call to server with all custom details]; E -->|No| D; E -->|Yes| F{Are there custom HTTP headers?}; F -->|No| X; F -->|Yes| D; end subgraph cors-verify D --> G{Did server respond with appropriate Access-Control-* headers?}; G -->|No| H[ERROR]; end G -->|Yes| X; style cors-safe fill:#d9ead3,stroke:#6aa84f; style cors-verify fill:#f4cccc,stroke:#cc0000; ``` So some requests, like regular GET or POSTs with a standard content type are NOT subject to CORS validation, but others (IE: deletes or requests with a custom HTTP header) are. ##### CSRF Attack inner workings [Section titled “CSRF Attack inner workings”](#csrf-attack-inner-workings) CSRF attacks exploit the fact that browsers automatically send authentication cookies with requests to the same [site](https://developer.mozilla.org/en-US/docs/Glossary/Site). Should an attacker trick a user that’s logged in to an application into visiting a malicious website, that browser can make malicious requests to the application under the credentials of the user. Same Site cookies already drastically reduce the attack surface because they ensure the browser only sends the cookies when the user is on the same site. So a user logged in to an application at app.company.com will not be vulnerable when visiting malicious-site.com. However, the application can still be at risk. Should other applications running under different subdomains of the same site be compromised, then you are still vulnerable to CSRF attacks. Luring a user to a compromised site under a subdomain will bypass this Same Site protection and leave the application still vulnerable to CSRF attacks. Unfortunately, compromised applications running under different subdomains is a common attack vector, not to be underestimated. ##### Protection against CSRF Attacks [Section titled “Protection against CSRF Attacks”](#protection-against-csrf-attacks) Many frameworks, including [dotnet](https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-9.0), have built-in protection against CSRF attacks. These mitigations require you to make certain changes to your application, such as embedding specific form fields in your application which needs to be re-submitted or reading a specific cookie value. While these protections are effective, there is a simpler and more straight forward solution to preventing any CSRF attack. The trick is to require a custom header on the APIs that you wish to protect. It doesn’t matter what that custom header is or what the value is, for example, some-header=1. The browser-based application now MUST send this header along with every request. However, if a page on the malicious subdomain wants to call this API, it also has to add this custom header. This custom header now triggers a CORS Preflight check. This pre-flight check will fail because it detects that the request is cross-origin. Now the API developer has to develop a CORS policy that will protect against CORS attacks. So, effective CSRF attack protection relies on these pillars: 1. Using **Same-Site=strict** Cookies 2. Requiring a specific header to be sent on every API request (IE: x-csrf=1) 3. having a cors policy that restricts the cookies only to a list of white-listed **origins**. ##### Session Hijacking [Section titled “Session Hijacking”](#session-hijacking) In session hijacking, a malicious actor somehow gets access to the user’s session cookie and is then able to exploit it by effectively cloning the session. Before HTTPS was widespread, session hijacking was a common occurrence, especially when using public Wi-Fi networks. However, since SSL connections are pretty much widespread, this has become more difficult. Not impossible, because there have been cases where trusted certificate authorities have been compromised. Even if SSL is not compromised, there are other ways for malicious actors to hijack the session. For example, if the user’s computer is compromised then browser security can still be bypassed. There have also been occurrences of session hijacking where (malicious) helpdesk employees asked for ‘har’ files (which are effectively complete request traces, including the authentication cookies), which were then used to hijack sessions. Right now, it’s very difficult to completely protect against this type of attack. However, there are interesting new standards being discussed, such as Device Bound Session Credentials. This standard aims to make sure that a session is cryptographically bound to a single device. Even if stolen, it can’t be used by a different device. ## See Also [Section titled “See Also”](#see-also) * [Duende IdentityServer](/identityserver/) — The authorization server BFF authenticates against for OpenID Connect flows * [Access Token Management](/accesstokenmanagement/) — Token lifecycle library used by BFF for automatic token refresh and caching ----- # Architecture > Overview of BFF host architecture, including authentication, session management, and integration with ASP.NET Core components A BFF host is an ASP.NET Core application that acts as a security proxy between the browser and your backend APIs. Understanding the key architectural decisions up front will save you significant rework later. ## How the BFF Fits Into Your System [Section titled “How the BFF Fits Into Your System”](#how-the-bff-fits-into-your-system) The following diagram shows how the BFF protects browser-based applications: ``` flowchart TD subgraph Browser SPA["Browser-Based Application"] CookieJar["🍪 Cookie Jar"] end subgraph BFF["BFF Host"] AuthEndpoints["Authentication
Endpoints"] SessionMgmt["Session
Management"] CookieAuth["Cookie Authorization"] CSRF["CSRF Protection"] Proxy["Proxy to
External APIs"] LocalAPIs["Local APIs"] SessionStore[("Server-Side
Session Storage")] end IdP["Identity Provider"] ExternalAPIs["External APIs"] SPA -->|"login / logout"| AuthEndpoints AuthEndpoints -->|"Set-Cookie"| CookieJar CookieJar -->|"Auth cookie"| CookieAuth AuthEndpoints <-->|"redirect"| IdP AuthEndpoints --> SessionMgmt SessionMgmt --> SessionStore CookieAuth --> CSRF CSRF --> Proxy CSRF --> LocalAPIs Proxy -->|"Bearer token"| ExternalAPIs SessionMgmt -->|"Acquire tokens"| IdP ExternalAPIs -->|"Validate tokens"| IdP ``` The BFF sits between the browser and everything else. The browser only ever holds a **session cookie** — it never sees tokens. The BFF exchanges that cookie for bearer tokens when forwarding requests to downstream APIs. ## Architectural Decisions [Section titled “Architectural Decisions”](#architectural-decisions) ### Decision 1: Where Does Your UI Live? [Section titled “Decision 1: Where Does Your UI Live?”](#decision-1-where-does-your-ui-live) The simplest setup hosts both the UI assets and the BFF from the **same origin**. This makes cookies same-site, eliminates CORS, and avoids [third-party cookie blocking](/bff/architecture/third-party-cookies/). You can also run the frontend on a **separate origin** (e.g. a Vite dev server, a CDN) and point it at the BFF via CORS. This is more complex but enables independent deployment. [UI Hosting ](/bff/architecture/ui-hosting/)Full comparison of same-origin vs. separate-origin hosting ### Decision 2: Cookie-Only vs. Server-Side Sessions [Section titled “Decision 2: Cookie-Only vs. Server-Side Sessions”](#decision-2-cookie-only-vs-server-side-sessions) By default, the BFF stores the entire session in the cookie. This is simple and stateless but has limits: cookie size, no server-side revocation. With **server-side sessions**, the cookie holds only a session ID. The server stores the session state (typically in a database or distributed cache). This enables: * Forced logout across all sessions * Back-channel logout from the identity provider * Querying active sessions [Server-Side Sessions ](/bff/fundamentals/session/server-side-sessions/)Configure server-side session storage for scalability and revocation ### Decision 3: How Do You Expose APIs? [Section titled “Decision 3: How Do You Expose APIs?”](#decision-3-how-do-you-expose-apis) | API Pattern | When to Use | | ----------------------- | ------------------------------------------------------------------------------------------------ | | **Local API** | Business logic hosted inside the BFF process itself. Lowest latency, no token forwarding needed. | | **Remote API (direct)** | External microservice. BFF forwards the request with a bearer token attached. | | **Remote API (YARP)** | External microservice with complex routing rules. BFF uses YARP as the reverse proxy. | [API Types ](/bff/fundamentals/apis/)Decision flowchart for choosing the right API pattern ### Decision 4: Single Frontend vs. Multi-Frontend [Section titled “Decision 4: Single Frontend vs. Multi-Frontend”](#decision-4-single-frontend-vs-multi-frontend) Each BFF instance is tied to **one** browser-based application and **one** OIDC client registration. If you have multiple frontends (e.g. a customer portal and an admin app), run separate BFF instances with separate client IDs. They can share infrastructure (same process, different routes) but should not share session state or token storage. [Common Configurations ](/bff/fundamentals/options/#common-configurations)Multi-frontend configuration example ### Decision 5: Blazor or JavaScript? [Section titled “Decision 5: Blazor or JavaScript?”](#decision-5-blazor-or-javascript) Both are supported, but have different integration patterns: * **JavaScript SPAs** interact with the BFF via `/bff/user`, `/bff/login`, `/bff/logout`, and API endpoints * **Blazor** uses built-in `AuthenticationStateProvider` integration and can call APIs server-side (no token forwarding from browser) [Blazor Fundamentals ](/bff/fundamentals/blazor/)Blazor-specific guidance for rendering modes, data access, and auth state ## Trust Boundaries [Section titled “Trust Boundaries”](#trust-boundaries) ``` flowchart TD subgraph Browser["Browser (untrusted)"] B1["Holds session cookie only
(HttpOnly, Secure, SameSite)"] B2["Never sees access or refresh tokens"] end subgraph BFF["BFF Host (trusted server)"] BFF1["Validates session cookie on every request"] BFF2["Manages access/refresh tokens in server memory or DB"] BFF3["Enforces anti-forgery (X-CSRF header) on API routes"] end subgraph IdP["Identity Provider
(e.g. IdentityServer)"] end subgraph APIs["Downstream APIs
(microservices, external)"] end Browser -->|"HTTPS + Cookie"| BFF BFF -->|"OIDC/OAuth (HTTPS)"| IdP BFF -->|"Bearer token (HTTPS)"| APIs ``` The critical security property: **tokens never cross the trust boundary into the browser**. All token operations happen server-to-server. ## Internals [Section titled “Internals”](#internals) Duende.BFF is built on top of: | Component | Role | Details | | ---------------------------- | ------------------------------------------------------ | ---------------------------------------------------- | | ASP.NET OIDC handler | Protocol processing (auth code + PKCE, token exchange) | Standard ASP.NET middleware | | ASP.NET Cookie handler | Session management and cookie issuance | Extended by BFF for server-side sessions | | Duende.AccessTokenManagement | Token storage, refresh, revocation | [Docs](/accesstokenmanagement/) | | YARP | Reverse proxy for remote APIs | [BFF YARP integration](/bff/fundamentals/apis/yarp/) | ## See Also [Section titled “See Also”](#see-also) [IdentityServer Client Configuration ](/identityserver/fundamentals/clients/)Register your BFF as a confidential OIDC client [Third-Party Cookies ](/bff/architecture/third-party-cookies/)How browser cookie restrictions affect BFF architecture [UI Hosting ](/bff/architecture/ui-hosting/)Options for hosting the frontend alongside the BFF [Middleware Pipeline ](/bff/fundamentals/middleware-pipeline/)Canonical middleware order reference ----- # Multi-frontend support > Overview on what BFF multi-frontend support is, how it works and why you would use it. BFF V4.0 introduces the capability to support multiple BFF Frontends in a single host. This helps to simplify your application landscape by consolidating multiple physical BFF Hosts into a single deployable unit. A single BFF setup consists of: 1. A browser based application, typically built using technology like React, Angular or VueJS. This is typically deployed to a Content Delivery Network (CDN). 2. A BFF host, that will take care of the OpenID Connect login flows. 3. An API surface, exposed and protected by the BFF. With the BFF Multi-frontend support, you can logically host multiple of these BFF Setups in a single host. The concept of a single frontend (with OpenID Connect configuration, an API surface and a browser based app) is now codified inside the BFF. By using a flexible frontend selection mechanism (using Hosts or Paths to distinguish), it’s possible to create very flexible setups. The BFF dynamically configures the aspnet core authentication pipeline according to recommended practices. For example, when doing Host based routing, it will configure the cookies using the most secure settings and with the prefix [`__Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie). Frontends can be added or removed dynamically from the system, without having to restart the system. You can do this via configuration (for example by modifying a configuration file) or programmatically. ## A Typical Example [Section titled “A Typical Example”](#a-typical-example) Consider an enterprise that hosts multiple browser based applications. Each of these applications is developed by a separate team and as such, has its own deployment schedule. There are some internal-facing applications that are exclusively used by internal employees. These internal employees are all present in Microsoft Entra ID, so these internal-facing applications should directly authenticate against Microsoft Entra ID. These applications also use several internal APIs, that due to the sensitivity, should not be accessible by external users. However, they also use some of the more common APIs. These apps are only accessible via an internal DNS name, such as `https://app1.internal.example.com`. There are also several public facing applications, that are used directly by customers. These users should be able to log in using their own identity, via providers like Google, Twitter, or others. This authentication process is handled by Duende IdentityServer. There is constant development ongoing on these applications and it’s not uncommon for new applications to be introduced. There should be single sign-on across all these public facing applications. They are all available on the same domain name, but use path based routing to distinguish themselves, such as `https://app.example.com/app1` There is also a partner portal. This partner portal can only be accessed by employees of the partners. Each partner should be able to bring their own identity provider. This is implemented using the [Dynamic Providers](/identityserver/ui/login/dynamicproviders/) feature of Duende IdentityServer. This setup, with multiple frontends, each having different authentication requirements and different API surfaces, is now supported by the BFF. Each frontend can either rely on the global configuration or override (parts of) this configuration, such as the identity provider or the Client ID and Client Secret to use. It’s also possible to dynamically add or remove frontends, without restarting the BFF host. ## Internals [Section titled “Internals”](#internals) BFF V4 still allows you to manually configure the ASP.NET Core authentication options, by calling `.AddAuthentication().AddOpenIdConnect().AddCookies()`. However, if you wish to use the multi-frontend features, then this setup needs to become dynamic. To achieve this, the BFF automatically configures the ASP.NET Core pipeline: ``` --- title: BFF Middleware Pipeline --- flowchart TD A["FrontendSelectionMiddleware"] --> B["PathMappingMiddleware"] B --> C["OpenIdCallbackMiddleware"] C --> D["Your ASP.NET Core Pipeline"]:::app D --> E["MapRemoteRoutesMiddleware"] E --> F["ProxyIndexMiddleware"] ``` 1. `FrontendSelectionMiddleware` - This middleware performs the frontend selection by seeing which frontend’s selection criteria best matches the incoming request route. It’s possible to mix both path based routing and host based routing, so the most specific will be selected. 2. `PathMappingMiddleware` - If you use path mapping, in the selected frontend, then it will automatically map the frontend’s path so none of the subsequent middlewares know (or need to care) about this fact. 3. `OpenIdCallbackMiddleware` - To dynamically perform the OpenID Connect authentication without explicitly adding each frontend as a scheme, we inject a middleware that will handle the OpenID Connect callbacks. This only kicks in for dynamic frontends. 4. Your own applications logic is executed in this part of the pipeline. For example, calling `.UseAuthentication(), .UseRequestLogging()`, etc. After your application’s logic is executed, there are two middlewares registered as fallback routes: 5. `MapRemoteRoutesMiddleware` - This will handle any configured remote routes. Note, it will not handle plain YARP calls, only routes that are specifically added to a frontend. 6. `ProxyIndexMiddleware` - If configured, this proxies the `index.html` to start the browser based app. If you don’t want this automatic mapping of BFF middleware, you can turn it off using `BffOptions.AutomaticallyRegisterBffMiddleware`. When doing so, you’ll need to manually register and add the middlewares: ```csharp var app = builder.Build(); app.UseBffPreProcessing(); // TODO: your custom middleware goes here app.UseRouting(); app.UseBff(); app.UseBffPostProcessing(); app.Run(); ``` ## Authentication Architecture [Section titled “Authentication Architecture”](#authentication-architecture) When you use multiple frontends, you can’t rely on [manual authentication configuration](/bff/fundamentals/session/handlers/#manually-configuring-authentication). This is because each frontend requires its own scheme, and potentially its own OpenID Connect and Cookie configuration. The BFF registers a dynamic authentication scheme, which automatically configures the OpenID Connect and Cookie Scheme’s on behalf of the frontends. It does this using a custom `AuthenticationSchemeProvider` called `BffAuthenticationSchemeProvider` to return appropriate authentication schemes for each frontend. The BFF will register two schemes: * `duende-bff-oidc` * `duende-bff-cookie` Then, if there are no default authentication schemes registered, it will register ‘duende\_bff\_cookie’ schemes as the `AuthenticationOptions.DefaultScheme`, and ‘duende\_bff\_oidc’ as the `AuthenticationOptions.DefaultAuthenticateScheme` and `AuthenticationOptions.DefaultSignOutScheme`. This will ensure that calls to `Authenticate()` or `Signout()` will use the appropriate schemes. If you’re using multiple frontends, then the BFF will create dynamic schemes with the following signature: `duende_bff_oidc_[frontendname]` and `duende_bff_cookie_[frontendname]`. This ensures that every frontend can use its own OpenID Connect and Cookie settings. ----- # Third Party Cookies > Learn about the impact of third-party cookie blocking on OIDC flows and how the BFF pattern addresses these challenges If the BFF and OpenID Connect Provider (OP) are hosted on different [sites](https://developer.mozilla.org/en-US/docs/Glossary/Site), then some browsers will block cookies from being sent during navigation between those sites. Almost all browsers have the option of blocking third party cookies. Safari and Firefox are the most widely used browsers that do so by default, while Chrome is planning to do so in the future. This change is being made to protect user privacy, but it also impacts OIDC flows traditionally used by SPAs. A couple of particularly notable OIDC flows that don’t work for SPAs when third party cookies are blocked are [OIDC Session Management](https://openid.net/specs/openid-connect-session-1_0.html) and [OIDC Silent Login via the prompt=none parameter](https://openid.net/specs/openid-connect-core-1_0.html#authrequest). ## Session Management [Section titled “Session Management”](#session-management) OIDC Session Management allows a client SPA to monitor the session at the OP by reading a cookie from the OP in a hidden iframe. If third party cookie blocking prevents the iframe from seeing that cookie, the SPA will not be able to monitor the session. The BFF solves this problem using [OIDC back-channel logout](/bff/fundamentals/session/management/back-channel-logout/). The BFF is able to operate server side, and is therefore able to have a back channel to the OP. When the session ends at the OP, it can send a back-channel message to the BFF, ending the session at the BFF. ## Silent Login [Section titled “Silent Login”](#silent-login) OIDC Silent Login allows a client application to start its session without needing any user interaction if the OP has an ongoing session. The main benefit is that a SPA can load in the browser and then start a session without navigating away from the SPA for an OIDC flow, preventing the need to reload the SPA. Similarly to OIDC Session Management, OIDC Silent Login relies on a hidden iframe, though in this case, the hidden iframe makes requests to the OP, passing the *prompt=none* parameter to indicate that user interaction isn’t sensible. If that request includes the OP’s session cookie, the OP can respond successfully and the application can obtain tokens. But if the request does not include a session - either because no session has been started or because the cookie has been blocked - then the silent login will fail, and the user will have to be redirected to the OP for an interactive login. ### BFF With A Federation Gateway [Section titled “BFF With A Federation Gateway”](#bff-with-a-federation-gateway) The BFF supports silent login from the SPA with the /bff/silent-login [endpoint](/bff/fundamentals/session/management/silent-login/). This endpoint is intended to be invoked in an iframe and issues a challenge to login non-interactively with *prompt=none*. Just as in a traditional SPA, this technique will be disrupted by third party cookie blocking when the BFF and OP are third parties. If you need silent login with a third party OP, we recommend that you use the [Federation Gateway](/identityserver/ui/federation/) pattern. In the federation gateway pattern, one identity provider (the gateway) federates with other remote identity providers. Because the client applications only interact with the gateway, the implementation details of the remote identity providers are abstracted. In this case, we shield the client application from the fact that the remote identity provider is a third party by hosting the gateway as a first party to the client. This makes the client application’s requests for silent login always first party. ### Alternatives [Section titled “Alternatives”](#alternatives) Alternatively, you can accomplish a similar goal (logging in without needing to initially load the SPA, only to redirect away from it) by detecting that the user is not authenticated in the BFF and issuing a challenge before the site is ever loaded. This approach is not typically our first recommendation, because it makes allowing anonymous access to parts of the UI difficult and because it requires *samesite=lax* cookies (see below). ----- # UI Hosting > A guide exploring different UI hosting strategies and their benefits when using Backend For Frontend (BFF) systems When building modern web applications, selecting the right hosting strategy for your UI assets is crucial for optimizing performance, simplifying deployment, and ensuring seamless integration with Backend For Frontend (BFF) systems. This guide explores various hosting approaches and their benefits. ## Hosting Options for the UI [Section titled “Hosting Options for the UI”](#hosting-options-for-the-ui) There are several options for hosting the UI assets when using a BFF. * Host the assets within the BFF host using the static file middleware * Host the UI and BFF separately on subdomains of the same site and use CORS to allow cross-origin requests * Serves the index page of the UI from the BFF host, and all other assets are loaded from another domain, such as a CDN ### Serving SPA assets from BFF host [Section titled “Serving SPA assets from BFF host”](#serving-spa-assets-from-bff-host) Hosting the UI together with the BFF is the simplest choice, as requests from the front end to the backend will automatically include the authentication cookie and not require CORS headers. This makes the BFF and the front-end application a single deployable unit. Below shows a graphical overview of what that would look like: ``` flowchart LR subgraph Browser["Browser: https://application.url"] app["app"] end subgraph BFF["BFF Application"] endpoints["BFF endpoints
local / remote API endpoints"] static["Static files middleware"] end subgraph FS["Local Filesystem"] index["index.html"] scripts["script_assets.js"] images["images"] end app -->|"cookie"| endpoints app --> static static --> FS ``` If you create a BFF host using our templates, the UI will be hosted in this way: Terminal ```bash dotnet new duende-bff-remoteapi # or dotnet new duende-bff-localapi ``` Many frontend applications require a build process, which complicates the use of the static file middleware at development time. Visual Studio includes SPA templates that start up a SPA and proxy requests to it during development. Samples of Duende.BFF that take this approach using [React](/bff/samples#reactjs-frontend) and [Angular](/bff/samples#angular-frontend) are available. Microsoft’s templates are easy-to-use at dev time from Visual Studio. They allow you to run the solution, and the template proxies requests to the front end for you. At deploy time, that proxy is removed and the static assets of the site are served by the static file middleware. ### Host The UI Separately [Section titled “Host The UI Separately”](#host-the-ui-separately) You may want to host the UI outside the BFF. At development time, UI developers might prefer to run the frontend outside of Visual Studio (e.g., using the node cli). You might also want to have separate deployments of the frontend and the BFF, and you might want your static UI assets hosted on a CDN. Below is a schematic overview of what that would look like: ``` flowchart LR subgraph Browser["Browser: https://application.url"] app["app"] end subgraph BFF["BFF Application (https://bff.url)"] endpoints["BFF endpoints
local / remote API endpoints"] end subgraph CDN["CDN"] index["index.html"] scripts["script_assets.js"] images["images"] end app -->|"cookie + CORS"| endpoints app -->|"load assets"| CDN ``` The browser accesses the application via the BFF. The BFF proxies the calls to index.html to the CDN. The browser can then download all static assets from the CDN, but then use the BFF (and it’s API’s and user management API’s) secured by the authentication cookie as normal. Effectively, this turns your front-end and BFF Host into two separately deployable units. You’ll need to ensure that the two components are hosted on subdomains of the same domain so that [third party cookie blocking](/bff/architecture/third-party-cookies/) doesn’t prevent the frontend from including cookies in its requests to the BFF host. In order for this architecture to work, the following things are needed: * To make sure that client side routing works, there should be a catch-all route configured that proxies calls to the index.html. Once the index.html is served, the front-end will take over the application specific routing. * The API’s hosted by the BFF and the applications API’s should be excluded from this catch-all routing. However, they should not be visited by the browser directly. * The CDN needs to be configured to allow CORS requests from the application’s origin. * In order to include the auth cookie in those requests, the frontend code will have to [declare that it should send credentials](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included) using the *credentials: “include”* option. A sample of this approach is [available](/bff/samples#separate-host-for-ui). ### Serve The Index Page From The BFF Host [Section titled “Serve The Index Page From The BFF Host”](#serve-the-index-page-from-the-bff-host) Lastly, you could serve the index page of the SPA from the BFF, but have all the other static assets hosted on another host (presumably a CDN). This technique makes the UI and BFF have exactly the same origin, so the authentication cookie will be sent from the frontend to the BFF automatically, and third party cookie blocking and the SameSite cookie attribute won’t present any problems. The following diagram shows how that would work: ``` flowchart LR subgraph Browser["Browser: https://application.url"] app["app"] end subgraph BFF["BFF Application"] endpoints["BFF endpoints
local / remote API endpoints"] proxy["proxy"] end subgraph CDN["CDN (https://the.cdn)"] index["index.html"] scripts["script_assets.js"] images["images"] end app -->|"cookie"| endpoints app -->|"initial request"| proxy proxy -->|"proxy index.html"| CDN app -->|"load assets"| CDN ``` Setting this up for local development takes a bit of effort, however. As you make changes to the frontend, the UI’s build process might generate a change to the index page. If it does, you’ll need to arrange for the index page being served by the BFF host to reflect that change. Additionally, the front end will need to be configurable so that it is able to load its assets from other hosts. The mechanism for doing so will vary depending on the technology used to build the frontend. For instance, Angular includes a number of [deployment options](https://angular.io/guide/deployment) that allow you to control where it expects to find assets. The added complexity of this technique is justified when there is a requirement to host the front end on a different site (typically a CDN) from the BFF. ----- # Diagnostics > Overview of Duende Backend for Frontend (BFF) diagnostic capabilities including logging and OpenTelemetry integration to assist with monitoring and troubleshooting ## Logging [Section titled “Logging”](#logging) Duende Backend for Frontend (BFF) offers several diagnostics possibilities. It uses the standard logging facilities provided by ASP.NET Core, so you don’t need to do any extra configuration to benefit from rich logging functionality, including support for multiple logging providers. See the Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) for a good introduction on logging. BFF follows the standard logging levels defined by the .NET logging framework, and uses the Microsoft guidelines for when certain log levels are used. For general information on how to configure logging in Duende products, see our [Logging Fundamentals](/general/logging/) guide. ### Configuration [Section titled “Configuration”](#configuration) Logs are typically written under the `Duende.Bff` category, with more concrete categories for specific components. To get detailed logs from the BFF middleware with the `Microsoft.Extensions.Logging` framework, you can configure your `appsettings.json` to enable `Debug` level logs for the `Duende.Bff` namespace: appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Duende.Bff": "Debug" } } } ``` ## OpenTelemetry v4.0 [Section titled “OpenTelemetry ”v4.0](#opentelemetry) OpenTelemetry provides a single standard for collecting and exporting telemetry data, such as metrics, logs, and traces. To start emitting OpenTelemetry data in Duende Backend for Frontend (BFF), you need to: * add the OpenTelemetry libraries to your BFF host and client applications * start collecting traces and metrics from the various BFF sources (and other sources such as ASP.NET Core, the `HttpClient`, etc.) The following configuration adds the OpenTelemetry configuration to your service setup, and exports data to an [OTLP exporter](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-with-otel): Program.cs ```csharp var openTelemetry = builder.Services.AddOpenTelemetry(); openTelemetry.ConfigureResource(r => r .AddService(builder.Environment.ApplicationName)); openTelemetry.WithMetrics(metrics => { metrics.AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation() .AddMeter(BffMetrics.MeterName); }); openTelemetry.WithTracing(tracing => { tracing.AddSource(builder.Environment.ApplicationName) .AddAspNetCoreInstrumentation() // Uncomment the following line to enable gRPC instrumentation // (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) //.AddGrpcClientInstrumentation() .AddHttpClientInstrumentation(); }); openTelemetry.UseOtlpExporter(); ``` ## Metrics [Section titled “Metrics”](#metrics) OpenTelemetry metrics are run-time measurements typically used to show graphs on a dashboard, to inspect overall application health, or to set up monitoring rules. The BFF host emits metrics collected through the `Duende.Bff` meter (meter name: `BffMetrics.MeterName`). Add it to your OpenTelemetry configuration with `.AddMeter(BffMetrics.MeterName)`. ### Session Metrics [Section titled “Session Metrics”](#session-metrics) | Metric Name | Type | Description | | ----------------- | ------- | --------------------------------------------------------------- | | `session.started` | Counter | Number of new sessions started (user logins) | | `session.ended` | Counter | Number of sessions ended (logouts, expiry, back-channel logout) | These counters can be used to track login/logout rates and detect unusual session activity (e.g., a spike in `session.ended` could indicate a back-channel logout sweep). ### Example: Prometheus Query [Section titled “Example: Prometheus Query”](#example-prometheus-query) If you are exporting metrics to Prometheus, the following PromQL queries can be useful: ```promql # Login rate over 5 minutes rate(session_started_total[5m]) # Logout rate over 5 minutes rate(session_ended_total[5m]) # Ratio of logouts to logins (high ratio may indicate session problems) rate(session_ended_total[5m]) / rate(session_started_total[5m]) ``` ## Distributed Tracing [Section titled “Distributed Tracing”](#distributed-tracing) BFF participates in distributed tracing via ASP.NET Core’s standard `ActivitySource` integration. When you configure `AddAspNetCoreInstrumentation()` and `AddHttpClientInstrumentation()` in your OpenTelemetry setup, the following BFF operations will appear as spans in your traces: * Incoming requests to BFF management endpoints (`/bff/login`, `/bff/logout`, `/bff/user`, etc.) * Outgoing HTTP requests made by the BFF when proxying to remote APIs * Token refresh calls to the identity provider (via `Duende.AccessTokenManagement`) ### Complete OpenTelemetry Setup [Section titled “Complete OpenTelemetry Setup”](#complete-opentelemetry-setup) Program.cs ```csharp var openTelemetry = builder.Services.AddOpenTelemetry(); openTelemetry.ConfigureResource(r => r .AddService(builder.Environment.ApplicationName)); openTelemetry.WithMetrics(metrics => { metrics .AddAspNetCoreInstrumentation() // HTTP request metrics .AddHttpClientInstrumentation() // Outgoing HTTP call metrics .AddRuntimeInstrumentation() // .NET runtime metrics (GC, threadpool, etc.) .AddMeter(BffMetrics.MeterName); // BFF-specific session metrics }); openTelemetry.WithTracing(tracing => { tracing .AddSource(builder.Environment.ApplicationName) .AddAspNetCoreInstrumentation() // Trace incoming requests .AddHttpClientInstrumentation(); // Trace outgoing HTTP calls (token refresh, proxied API calls) }); // Export to an OTLP-compatible backend (Jaeger, Zipkin, Grafana Tempo, etc.) openTelemetry.UseOtlpExporter(); ``` ### Multi-Frontend Tracing [Section titled “Multi-Frontend Tracing”](#multi-frontend-tracing) When using multiple frontends, log messages and traces include a `frontend` scope property identifying which frontend the activity belongs to. This allows you to filter traces by frontend in your observability backend. ## Diagnostics Endpoint [Section titled “Diagnostics Endpoint”](#diagnostics-endpoint) The BFF also includes a `/bff/diagnostics` endpoint for development-time troubleshooting. It returns the current user and client access tokens. See [Diagnostics Endpoint](/bff/fundamentals/session/management/diagnostics/) for details. ## See Also [Section titled “See Also”](#see-also) [Logging Fundamentals ](/general/logging/)General Duende logging configuration [OpenTelemetry .NET Documentation ](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-with-otel)Setting up OTLP export [Troubleshooting ](/bff/troubleshooting/)Common BFF issues and their solutions ----- # BFF Extensibility > Overview of all extensibility points in Duende.BFF for customizing session management, management endpoints, HTTP forwarding, and token storage. Duende.BFF is designed to be extended at multiple layers. Most production applications will use the defaults, but each area has well-defined extension points for when you need to go beyond the defaults. ## Extensibility Points [Section titled “Extensibility Points”](#extensibility-points) | Area | What You Can Customize | Detail Page | | ------------------------ | ----------------------------------------------------------------------------------- | ---------------------------------------------------- | | **Management Endpoints** | Login, logout, user info, back-channel logout, diagnostics, silent login processing | [Management Endpoints](#management-endpoints) | | **Session Store** | Where server-side session data is persisted (custom database, cache, etc.) | [Session Management](/bff/extensibility/sessions/) | | **HTTP Forwarder** | Custom HTTP clients, request/response transformations for proxied calls | [HTTP Forwarder](/bff/extensibility/http-forwarder/) | | **Token Management** | Token storage backend, per-route token retrieval (delegation, impersonation) | [Token Management](/bff/extensibility/tokens/) | ## Management Endpoints [Section titled “Management Endpoints”](#management-endpoints) Each BFF management endpoint has a corresponding interface that you can implement to customize its behavior. In v4, the pattern is to map a custom route at the same path and call the default endpoint implementation, allowing you to add logic before and after default processing. | Endpoint | Default Path | Interface (v4) | Interface (v3) | Detail | | ------------------- | ------------------- | ---------------------------- | --------------------------- | --------------------------------------------------------------------------------------- | | Login | `/bff/login` | `ILoginEndpoint` | `ILoginService` | [Login Extensibility](/bff/extensibility/management/login/) | | Logout | `/bff/logout` | `ILogoutEndpoint` | `ILogoutService` | [Logout Extensibility](/bff/extensibility/management/logout/) | | User | `/bff/user` | `IUserEndpoint` | `IUserService` | [User Extensibility](/bff/extensibility/management/user/) | | Silent Login | `/bff/silent-login` | `ISilentLoginEndpoint` | `ISilentLoginService` | [Silent Login Extensibility](/bff/extensibility/management/silent-login/) | | Back-Channel Logout | `/bff/backchannel` | `IBackchannelLogoutEndpoint` | `IBackchannelLogoutService` | [Back-Channel Logout Extensibility](/bff/extensibility/management/back-channel-logout/) | | Diagnostics | `/bff/diagnostics` | `IDiagnosticsEndpoint` | `IDiagnosticsService` | [Diagnostics Extensibility](/bff/extensibility/management/diagnostics/) | ### General Pattern (v4) [Section titled “General Pattern (v4)”](#general-pattern-v4) All management endpoint customizations in v4 follow the same pattern: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.LoginPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before the default processing var endpoint = context.RequestServices.GetRequiredService(); await endpoint.ProcessRequestAsync(context, ct); // Custom logic after the default processing }); ``` ## Session Store [Section titled “Session Store”](#session-store) By default, BFF uses either an in-memory store or Entity Framework Core for server-side sessions. To use a different storage backend (Redis, custom database, etc.), implement `IUserSessionStore`: ```csharp builder.Services.AddBff() .AddServerSideSessions(); ``` See [Session Management Extensibility](/bff/extensibility/sessions/) for the full interface and implementation guidance. ## HTTP Forwarder [Section titled “HTTP Forwarder”](#http-forwarder) When using `MapRemoteBffApiEndpoint`, BFF uses a default HTTP client and a default set of request/response transformations. You can customize: * **The HTTP client** — implement `IForwarderHttpClientFactory` to use a proxy, custom certificates, etc. * **Request/response transformations** — add custom headers, modify paths, or replace the default transformer entirely. See [HTTP Forwarder Extensibility](/bff/extensibility/http-forwarder/) for details. ## Token Management [Section titled “Token Management”](#token-management) BFF’s token management (powered by `Duende.AccessTokenManagement`) can be extended in two ways: * **Custom token store** — implement `IUserTokenStore` to store tokens outside of the session cookie or server-side session. * **Per-route token retrieval** — implement `IAccessTokenRetriever` for scenarios like token exchange or impersonation, where different API routes need different tokens. ```csharp app.MapRemoteBffApiEndpoint("/api/delegated", new Uri("https://api.example.com")) .WithAccessToken(RequiredTokenType.User) .WithAccessTokenRetriever(); ``` See [Token Management Extensibility](/bff/extensibility/tokens/) for details. ## See Also [Section titled “See Also”](#see-also) [Configuration Options ](/bff/fundamentals/options/)Settings that control BFF behavior without custom code [Troubleshooting ](/bff/troubleshooting/)Common issues and their solutions ----- # HTTP Forwarder > Learn how to customize the HTTP forwarding behavior in BFF by providing custom HTTP clients and request/response transformations You can customize the HTTP forwarder behavior in two ways * provide a customized HTTP client for outgoing calls * provide custom request/response transformation ## Custom HTTP Clients [Section titled “Custom HTTP Clients”](#custom-http-clients) By default, Duende.BFF will create and cache an HTTP client per configured route or local path. This invoker is set up like this: ```csharp var client = new HttpMessageInvoker(new SocketsHttpHandler { UseProxy = false, AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false }); ``` If you want to customize the HTTP client you can implement the `IForwarderHttpClientFactory` interface (from YARP’s `Yarp.ReverseProxy.Forwarder` namespace), e.g.: ```csharp public class MyInvokerFactory : IForwarderHttpClientFactory { public HttpMessageInvoker CreateClient(ForwarderHttpClientContext context) { return new HttpMessageInvoker(new SocketsHttpHandler { // this API needs a proxy UseProxy = true, Proxy = new WebProxy("https://myproxy"), AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseCookies = false }); } } ``` …and override our registration: ```csharp services.AddSingleton(); ``` ## Custom Transformations When Using Direct Forwarding [Section titled “Custom Transformations When Using Direct Forwarding”](#custom-transformations-when-using-direct-forwarding) The method `MapRemoteBffApiEndpoint` uses default transformations that: * removes the cookie header from the forwarded request * removes local path from the forwarded request * adds the access token to the original request If you wish to change or extend this behavior, you can do this for a single mapped endpoint or for all mapped API endpoints. ### Changing The Transformer For A Single Mapped Endpoint [Section titled “Changing The Transformer For A Single Mapped Endpoint”](#changing-the-transformer-for-a-single-mapped-endpoint) This code block shows an example of how you can extend the default transformers with an additional custom transform. * Duende BFF v4 ```csharp app.MapRemoteBffApiEndpoint("/local", new Uri("https://target/"), context => { // If you want to extend the existing behavior, then you must call the default builder: DefaultBffYarpTransformerBuilders.DirectProxyWithAccessToken("/local", context); // You can also add custom transformers, such as this one that adds an additional header context.AddRequestHeader("custom", "with value"); }); ``` The default transform builder performs these transforms: ```csharp context.AddRequestHeaderRemove("Cookie"); context.AddPathRemovePrefix(pathMatch); context.AddBffAccessToken(pathMatch); ``` * Duende BFF v3 ```csharp app.MapRemoteBffApiEndpoint("/local", "https://target/", context => { // If you want to extend the existing behavior, then you must call the default builder: DefaultBffYarpTransformerBuilders.DirectProxyWithAccessToken("/local", context); // You can also add custom transformers, such as this one that adds an additional header context.AddRequestHeader("custom", "with value"); }); ``` The default transform builder performs these transforms: ```csharp context.AddRequestHeaderRemove("Cookie"); context.AddPathRemovePrefix(localPath); context.AddBffAccessToken(localPath); ``` For more information, also see the [YARP documentation on transforms](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/transforms?view=aspnetcore-9.0) ### Changing The Default Transformer [Section titled “Changing The Default Transformer”](#changing-the-default-transformer) You can change the default transformer builder delegate by registering one in the services collection: * Duende BFF v4 ```csharp BffYarpTransformBuilder builder = (pathMatch, context) => { // If you want to extend the existing behavior, then you must call the default builder: DefaultBffYarpTransformerBuilders.DirectProxyWithAccessToken(pathMatch, context); // You can also add custom transformers, such as this one that adds an additional header context.AddResponseHeader("added-by-custom-default-transform", "some-value"); }; services.AddSingleton(builder); ``` * Duende BFF v3 ```csharp BffYarpTransformBuilder builder = (localPath, context) => { // If you want to extend the existing behavior, then you must call the default builder: DefaultBffYarpTransformerBuilders.DirectProxyWithAccessToken(localPath, context); // You can also add custom transformers, such as this one that adds an additional header context.AddResponseHeader("added-by-custom-default-transform", "some-value"); }; services.AddSingleton(builder); ``` ## Changing The Forwarder Request Configuration [Section titled “Changing The Forwarder Request Configuration”](#changing-the-forwarder-request-configuration) You can also modify the forwarder request configuration, either globally or per mapped path. This can be useful if you want to tweak things like activity timeouts. ```csharp // Register a forwarder config globally: services.AddSingleton(new ForwarderRequestConfig() { ActivityTimeout = TimeSpan.FromMilliseconds(100) }); // Or modify one on a per mapped route basis: app.MapRemoteBffApiEndpoint("/local", new Uri("https://target/"), requestConfig: new ForwarderRequestConfig() { ActivityTimeout = TimeSpan.FromMilliseconds(100) }); ``` ----- # BFF Management Endpoints Extensibility The behavior of each [management endpoint](/bff/fundamentals/session/management) is defined in a service. When you add Duende.BFF to the service container, a default implementation for every management endpoint gets registered. You can add your own implementation by overriding the default after calling `AddBff()`. * V4 The following endpoints are registered in the service container: ```csharp // management endpoints builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); ``` The management endpoint services all inherit from the `IBffEndpoint`, which provides a general-purpose mechanism to add custom logic to the endpoints. IBffEndpoint.cs ```csharp public interface IBffEndpoint { Task ProcessRequestAsync(HttpContext context, CancellationToken ct = default); } ``` You can customize the behavior of the endpoints by implementing the appropriate interface. The [default implementations](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal) can serve as a starting point for your own implementation. If you want to extend the default behavior of a management endpoint, you can add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.LoginPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 ```csharp // management endpoints builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); ``` The management endpoint services all inherit from the `IBffEndpointService`, which provides a general-purpose mechanism to add custom logic to the endpoints. IBffEndpointService.cs ```csharp public interface IBffEndpointService { Task ProcessRequestAsync(HttpContext context); } ``` You can customize the behavior of the endpoints either by implementing the appropriate interface or by extending the default implementation of that interface. In many cases, extending the default implementation is preferred, as this allows you to keep most of the default behavior by calling the base `ProcessRequestAsync` from your derived class. Several of the default endpoint service implementations also define virtual methods that can be overridden to customize their behavior with more granularity. ----- # BFF Back-Channel Logout Endpoint Extensibility The back-channel logout endpoint has several extensibility points organized into two interfaces. The `IBackchannelLogoutEndpoint` is the top-level abstraction that processes requests to the endpoint. This service can be used to add custom request processing logic or to change how it validates incoming requests. When the back-channel logout endpoint receives a valid request, it revokes sessions using the `ISessionRevocationService`. Caution In BFF V3, the `IBackchannelLogoutEndpoint` interface is called `IBackchannelLogoutService` instead. ## Request Processing [Section titled “Request Processing”](#request-processing) * V4 You can customize the behavior of the back-channel logout endpoint by implementing the `ProcessRequestAsync` method of the `IBackchannelLogoutEndpoint` interface. The [default implementation](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal/DefaultBackchannelLogoutEndpoint.cs) can serve as a starting point for your own implementation. If you want to extend the default behavior of the back-channel logout endpoint, you can instead add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.BackChannelLogoutPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 `ProcessRequestAsync` is the top-level function called in the endpoint service `DefaultBackchannelLogoutService`, and can be used to add arbitrary logic to the endpoint. For example, you could take whatever actions you need before normal processing of the request like this: ```csharp public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here return base.ProcessRequestAsync(context); } ``` ## Session Revocation [Section titled “Session Revocation”](#session-revocation) The back-channel logout service will call the registered session revocation service to revoke the user session when it receives a valid logout token. To customize the revocation process, implement the `ISessionRevocationService`. ----- # BFF Diagnostics Endpoint Extensibility The BFF diagnostics endpoint can be customized by implementing the `IDiagnosticsEndpoint`. Caution In BFF V3, the `IDiagnosticsEndpoint` interface is called `IDiagnosticsService` instead. ## Request Processing [Section titled “Request Processing”](#request-processing) * V4 You can customize the behavior of the diagnostics endpoint by implementing the `ProcessRequestAsync` method of the `IDiagnosticsEndpoint` interface. The [default implementation](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal/DefaultDiagnosticsEndpoint.cs) can serve as a starting point for your own implementation. If you want to extend the default behavior of the diagnostics endpoint, you can instead add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.DiagnosticsPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 `ProcessRequestAsync` is the top-level function called in the endpoint service `DefaultDiagnosticsService`, and can be used to add arbitrary logic to the endpoint. For example, you could take whatever actions you need before normal processing of the request like this: ```csharp public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here return base.ProcessRequestAsync(context); } ``` ----- # BFF Login Endpoint Extensibility The BFF login endpoint has extensibility points in two interfaces. The `ILoginEndpoint` is the top-level abstraction that processes requests to the endpoint. This service can be used to add custom request processing logic. The `IReturnUrlValidator` ensures that the `returnUrl` parameter passed to the login endpoint is safe to use. Caution In BFF V3, the `ILoginEndpoint` interface is called `ILoginService` instead. ## Request Processing [Section titled “Request Processing”](#request-processing) * V4 You can customize the behavior of the login endpoint by implementing the `ProcessRequestAsync` method of the `ILoginEndpoint` interface. The [default implementation](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal/DefaultLoginEndpoint.cs) can serve as a starting point for your own implementation. If you want to extend the default behavior of the login endpoint, you can instead add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.LoginPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 `ProcessRequestAsync` is the top-level function called in the endpoint service `DefaultLoginService`, and can be used to add arbitrary logic to the endpoint. For example, you could take whatever actions you need before normal processing of the request like this: ```csharp public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here return base.ProcessRequestAsync(context); } ``` ## Return URL Validation [Section titled “Return URL Validation”](#return-url-validation) To prevent open redirector attacks, the `returnUrl` parameter to the login endpoint must be validated. You can customize this validation by implementing the `IReturnUrlValidator` interface. The default implementation enforces that return URLs are local. ----- # BFF Logout Endpoint Extensibility The BFF logout endpoint has extensibility points in two interfaces. The `ILogoutEndpoint` is the top-level abstraction that processes requests to the endpoint. This service can be used to add custom request processing logic. The `IReturnUrlValidator` ensures that the `returnUrl` parameter passed to the logout endpoint is safe to use. Caution In BFF V3, the `ILogoutEndpoint` interface is called `ILogoutService` instead. ## Request Processing [Section titled “Request Processing”](#request-processing) * V4 You can customize the behavior of the logout endpoint by implementing the `ProcessRequestAsync` method of the `ILogoutEndpoint` interface. The [default implementation](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal/DefaultLogoutEndpoint.cs) can serve as a starting point for your own implementation. If you want to extend the default behavior of the logout endpoint, you can instead add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.LogoutPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 `ProcessRequestAsync` is the top-level function called in the endpoint service `DefaultLogoutService`, and can be used to add arbitrary logic to the endpoint. For example, you could take whatever actions you need before normal processing of the request like this: ```csharp public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here return base.ProcessRequestAsync(context); } ``` ## Return URL Validation [Section titled “Return URL Validation”](#return-url-validation) To prevent open redirector attacks, the `returnUrl` parameter to the logout endpoint must be validated. You can customize this validation by implementing the `IReturnUrlValidator` interface. The default implementation enforces that return URLs are local. ----- # BFF Silent Login Endpoint Extensibility The BFF silent login endpoint can be customized by implementing the `ISilentLoginEndpoint`. Caution In BFF V3, the `ISilentLoginEndpoint` interface is called `ISilentLoginService` instead. Danger The silent login endpoint has been marked as obsolete in BFF V4 and will be removed in a future version. To handle silent login in the future, pass the `prompt=none` parameter on to the login endpoint instead. ## Request Processing [Section titled “Request Processing”](#request-processing) * V4 You can customize the behavior of the silent login endpoint by implementing the `ProcessRequestAsync` method of the `ISilentLoginEndpoint` interface. The [default implementation](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal/DefaultSilentLoginEndpoint.cs) can serve as a starting point for your own implementation. If you want to extend the default behavior of the silent login endpoint, you can instead add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.SilentLoginPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 `ProcessRequestAsync` is the top-level function called in the endpoint service `DefaultSilentLoginService`, and can be used to add arbitrary logic to the endpoint. For example, you could take whatever actions you need before normal processing of the request like this: ```csharp public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here return base.ProcessRequestAsync(context); } ``` ----- # BFF Silent Login Callback Extensibility The BFF silent login callback endpoint can be customized by implementing the `ISilentLoginCallbackEndpoint`. Caution In BFF V3, the `ISilentLoginCallbackEndpoint` interface is called `ISilentLoginCallbackService` instead. ## Request Processing [Section titled “Request Processing”](#request-processing) * V4 You can customize the behavior of the silent login callback endpoint by implementing the `ProcessRequestAsync` method of the `ISilentLoginCallbackEndpoint` interface. The [default implementation](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal/DefaultSilentLoginCallbackEndpoint.cs) can serve as a starting point for your own implementation. If you want to extend the default behavior of the silent login callback endpoint, you can instead add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.SilentLoginCallbackPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 `ProcessRequestAsync` is the top-level function called in the endpoint service `DefaultSilentLoginCallbackService`, and can be used to add arbitrary logic to the endpoint. For example, you could take whatever actions you need before normal processing of the request like this: ```csharp public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here return base.ProcessRequestAsync(context); } ``` ----- # BFF User Endpoint Extensibility The BFF user endpoint can be customized by implementing the `IUserEndpoint`. Caution In BFF V3, the `IUserEndpoint` interface is called `IUserService` instead. ## Request Processing [Section titled “Request Processing”](#request-processing) * V4 You can customize the behavior of the user endpoint by implementing the `ProcessRequestAsync` method of the `IUserEndpoint` interface. The [default implementation](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.x/bff/src/Bff/Endpoints/Internal/DefaultUserEndpoint.cs) can serve as a starting point for your own implementation. If you want to extend the default behavior of the user endpoint, you can instead add a custom endpoint and call the original endpoint implementation: Program.cs ```csharp var bffOptions = app.Services.GetRequiredService>().Value; app.MapGet(bffOptions.UserPath, async (HttpContext context, CancellationToken ct) => { // Custom logic before calling the original endpoint implementation var endpointProcessor = context.RequestServices.GetRequiredService(); await endpointProcessor.ProcessRequestAsync(context, ct); // Custom logic after calling the original endpoint implementation }); ``` * V3 `ProcessRequestAsync` is the top-level function called in the endpoint service `DefaultUserService`, and can be used to add arbitrary logic to the endpoint. For example, you could take whatever actions you need before normal processing of the request like this: ```csharp public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here return base.ProcessRequestAsync(context); } ``` ### Enriching User Claims [Section titled “Enriching User Claims”](#enriching-user-claims) There are several ways how you can enrich the claims for a specific user, depending on where the required data comes from. #### Claims Transformations [Section titled “Claims Transformations”](#claims-transformations) To enrich claims for a user, you can implement a custom `IClaimsTransformation`. Claims transformation executes as part of the authentication process. ```csharp services.AddScoped(); public class CustomClaimsTransformer : IClaimsTransformation { public Task TransformAsync(ClaimsPrincipal principal) { var identity = (ClaimsIdentity)principal.Identity; if (!identity.HasClaim(c => c.Type == "custom_claim")) { identity.AddClaim(new Claim("custom_claim", "your_value")); } return Task.FromResult(principal); } } ``` See the [Claims Transformation](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims?view=aspnetcore-9.0) topic in the ASP.NET Core documentation for more information. #### User Endpoint Claims Enricher v4.0 [Section titled “User Endpoint Claims Enricher ”v4.0](#user-endpoint-claims-enricher) User claims can be enriched by implementing the `IUserEndpointClaimsEnricher` interface. This interface is specific to the user endpoint and runs after authentication. Because this runs within the user endpoint request, you can access the current HTTP context to retrieve the user’s access token. We recommend using the [`GetUserAccessTokenAsync`](/accesstokenmanagement/web-apps/#http-context-extension-methods) extension method from `Duende.AccessTokenManagement.OpenIdConnect`, as it will automatically handle refreshing the token if it has expired. Program.cs ```csharp builder.Services.AddTransient(); ``` CustomUserEndpointClaimsEnricher.cs ```csharp using Duende.Bff; using Duende.Bff.Endpoints; using Duende.AccessTokenManagement.OpenIdConnect; using Microsoft.AspNetCore.Authentication; public class CustomUserEndpointClaimsEnricher : IUserEndpointClaimsEnricher { private readonly IHttpContextAccessor _httpContextAccessor; public CustomUserEndpointClaimsEnricher(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public async Task> EnrichClaimsAsync( AuthenticateResult authenticateResult, IReadOnlyList claims, CancellationToken ct = default) { var newClaims = claims.ToList(); // Get the access token using the extension method // This will automatically handle token refreshing if needed var token = await _httpContextAccessor.HttpContext.GetUserAccessTokenAsync(cancellationToken: ct); if (!string.IsNullOrEmpty(token.AccessToken)) { // Call external API using the access token // ... } // Add custom claims newClaims.Add(new ClaimRecord("custom_data", "some value")); return newClaims; } } ``` ----- # Session Management > Configure and implement custom server-side session storage and lifecycle management through IUserSessionStore interface Server-side sessions enable secure and efficient storage of session data, allowing flexibility through custom implementations of the `IUserSessionStore` interface. This ensures adaptability to various storage solutions tailored to your application’s needs. ## User Session Store [Section titled “User Session Store”](#user-session-store) If using the server-side sessions feature, you need a store for the session data. An Entity Framework Core based implementation of this store is provided. If you wish to use some other type of store, can implement the `IUserSessionStore` interface: * Duende BFF v4 ```csharp /// /// User session store /// public interface IUserSessionStore { /// /// Retrieves a user session /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task GetUserSessionAsync(UserSessionKey key, CancellationToken ct = default); /// /// Creates a user session /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task CreateUserSessionAsync(UserSession session, CancellationToken ct = default); /// /// Updates a user session /// /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task UpdateUserSessionAsync(UserSessionKey key, UserSessionUpdate session, CancellationToken ct = default); /// /// Deletes a user session /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task DeleteUserSessionAsync(UserSessionKey key, CancellationToken ct = default); /// /// Queries user sessions based on the filter. /// /// The partition key to use /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task> GetUserSessionsAsync(PartitionKey partitionKey, UserSessionsFilter filter, CancellationToken ct = default); /// /// Deletes user sessions based on the filter. /// /// The partition key /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task DeleteUserSessionsAsync(PartitionKey partitionKey, UserSessionsFilter filter, CancellationToken ct = default); } ``` Do not store `UserSession` directly Your `IUserSessionStore` implementation is expected to implement custom code to roundtrip the data from the user session to the underlying storage mechanism. You should not rely on existing serializers, such as `System.Text.Json` or `Newtonsoft.Json`, to serialize the `UserSession` object. * Duende BFF v3 ```csharp /// /// User session store /// public interface IUserSessionStore { /// /// Retrieves a user session /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task GetUserSessionAsync(string key, CancellationToken cancellationToken = default); /// /// Creates a user session /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task CreateUserSessionAsync(UserSession session, CancellationToken cancellationToken = default); /// /// Updates a user session /// /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task UpdateUserSessionAsync(string key, UserSessionUpdate session, CancellationToken cancellationToken = default); /// /// Deletes a user session /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task DeleteUserSessionAsync(string key, CancellationToken cancellationToken = default); /// /// Queries user sessions based on the filter. /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task> GetUserSessionsAsync(UserSessionsFilter filter, CancellationToken cancellationToken = default); /// /// Deletes user sessions based on the filter. /// /// /// A token that can be used to request cancellation of the asynchronous operation. /// Task DeleteUserSessionsAsync(UserSessionsFilter filter, CancellationToken cancellationToken = default); } ``` Once you have an implementation, you can register it when you enable server-side sessions: Program.cs ```csharp builder.Services.AddBff() .AddServerSideSessions(); ``` ## User Session Store Cleanup [Section titled “User Session Store Cleanup”](#user-session-store-cleanup) The `IUserSessionStoreCleanup` interface is used to model cleaning up expired sessions. ```csharp /// /// User session store cleanup /// public interface IUserSessionStoreCleanup { /// /// Deletes expired sessions /// Task DeleteExpiredSessionsAsync(CancellationToken cancellationToken = default); } ``` ----- # Token Management > Learn how to customize token storage and management in the BFF framework, including HTTP client configuration and per-route token retrieval The token management library does essentially two things: * stores access and refresh tokens in the current session * refreshes access tokens automatically at the token service when needed Both aspects can be customized. ### Token service communication [Section titled “Token service communication”](#token-service-communication) The token management library uses a named HTTP client from the HTTP client factory for all token service communication. You can provide a customized HTTP client yourself using the well-known name after calling `AddBff`: ```csharp builder.Services.AddHttpClient( ClientCredentialsTokenManagementDefaults.BackChannelHttpClientName, configureClient => { // ... }); ``` ### Custom Token Storage [Section titled “Custom Token Storage”](#custom-token-storage) We recommend that you use the default storage mechanism, as this will automatically be compatible with the Duende.BFF server-side sessions. If you do not use server-side sessions, then the access and refresh token will be stored in the protected session cookie. If you want to change this, you can take over token storage completely. This would involve two steps * turn off the `SaveTokens` flag on the OpenID Connect handler and handle the relevant events manually to store the tokens in your custom store * implement and register the `Duende.AccessTokenManagement.IUserTokenStore` interface The interface is responsible to storing, retrieving and clearing tokens for the automatic token management: ```csharp public interface IUserTokenStore { /// /// Stores tokens /// /// User the tokens belong to /// /// Extra optional parameters /// Task StoreTokenAsync( ClaimsPrincipal user, UserToken token, UserTokenRequestParameters? parameters = null, CancellationToken token = default); /// /// Retrieves tokens from store /// /// User the tokens belong to /// Extra optional parameters /// access and refresh token and access token expiration Task GetTokenAsync( ClaimsPrincipal user, UserTokenRequestParameters? parameters = null, CancellationToken token = default); /// /// Clears the stored tokens for a given user /// /// User the tokens belong to /// Extra optional parameters /// Task ClearTokenAsync( ClaimsPrincipal user, UserTokenRequestParameters? parameters = null, CancellationToken token = default); } ``` ### Per-route Customized Token Retrieval [Section titled “Per-route Customized Token Retrieval”](#per-route-customized-token-retrieval) The token store defines how tokens are retrieved globally. However, you can add custom logic that changes the way that access tokens are retrieved on a per-route basis. For example, you might need to exchange a token to perform delegation or impersonation for some API calls, depending on the remote API. The interface that describes this extension point is the `IAccessTokenRetriever`. ```csharp /// /// Retrieves access tokens /// public interface IAccessTokenRetriever { /// /// Asynchronously gets the access token. /// /// Context used to retrieve the token. /// A task that contains the access token result, which is an /// object model that can represent various types of tokens (bearer, dpop), /// the absence of an optional token, or an error. Task GetAccessTokenAsync(AccessTokenRetrievalContext context, CancellationToken ct = default); } ``` You can implement this interface yourself or extend the `DefaultAccessTokenRetriever`. The `AccessTokenResult` class represents the result of this operation. It is an abstract class with concrete implementations that represent successfully retrieving a bearer token (`BearerTokenResult`), successfully retrieving a DPoP token (`DPoPTokenResult`), failing to find an optional token (`NoAccessTokenResult`), which is not an error, and failure to retrieve a token (`AccessTokenRetrievalError`). Your implementation of GetAccessTokenAsync should return one of those types. Implementations of the `IAccessTokenRetriever` can be added to endpoints when they are mapped using the `WithAccessTokenRetriever` extension method: ```csharp app.MapRemoteBffApiEndpoint( "/api/impersonation", new Uri("https://api.example.com/endpoint/requiring/impersonation") ).WithAccessToken(RequiredTokenType.User) .WithAccessTokenRetriever(); ``` The `GetAccessTokenAsync` method will be invoked on every call to APIs that use the access token retriever. If retrieving the token is an expensive operation, you may need to cache it. It is up to your retriever code to perform caching. ----- # Securing and Accessing API Endpoints > Learn about the different types of APIs in a BFF architecture and how to secure and access them properly A frontend application using the BFF pattern can call two types of APIs: embedded (local) APIs, and proxied remote APIs. ## Choosing an API Approach [Section titled “Choosing an API Approach”](#choosing-an-api-approach) ``` flowchart TD Q1{"Is the API only used
by this frontend?"} Q2{"Do you need load balancing,
service discovery, or
complex routing/transforms?"} Local["✅ Embedded (Local) API
Host the API inside the BFF itself"] Remote["✅ Remote API — Direct Forwarding
MapRemoteBffApiEndpoint()"] Yarp["✅ YARP Integration
Full YARP configuration with BFF extensions"] Q1 -->|Yes| Local Q1 -->|No| Q2 Q2 -->|Yes| Yarp Q2 -->|No| Remote ``` Use the table below for additional guidance on token requirements: | Scenario | Recommended approach | | ----------------------------------------------------------- | -------------------------------------------------- | | API is only used by this frontend | [Embedded (Local) API](local/) | | API is shared by multiple clients or deployed separately | [Remote API — Direct Forwarding](remote/) | | Complex routing, load balancing, or transforms are needed | [YARP](yarp/) | | API requires the logged-in user’s token | Remote or YARP with `RequiredTokenType.User` | | API uses machine-to-machine (client credentials) auth | Remote or YARP with `RequiredTokenType.Client` | | API is publicly accessible (no auth required) | Remote with `RequiredTokenType.None` | | API should use user token if logged in, anonymous otherwise | Remote or YARP with `RequiredTokenType.UserOrNone` | ## Embedded (Local) APIs [Section titled “Embedded (Local) APIs”](#embedded-local-apis) These APIs are embedded inside the BFF and typically exist to support the BFF’s frontend; they are not shared with other frontends or services. See [Embedded APIs](local/) for more information. ## Proxying Remote APIs [Section titled “Proxying Remote APIs”](#proxying-remote-apis) These APIs are deployed on a different host than the BFF, which allows them to be shared between multiple frontends or (more generally speaking) multiple clients. These APIs can only be called via the BFF host acting as a proxy. You can use [Direct Forwarding](remote/) for most scenarios. If you have more complex requirements, you can also directly interact with [YARP](yarp/). ## See Also [Section titled “See Also”](#see-also) * [Token Management](/bff/fundamentals/tokens/) — How BFF attaches access tokens to outgoing API calls * [Access Token Management](/accesstokenmanagement/) — The underlying token lifecycle library * [IdentityServer API Resources](/identityserver/fundamentals/resources/api-resources/) — Configuring scopes for your APIs ----- # Embedded (Local) APIs > Documentation about Embedded (Local) APIs in BFF, including self-contained APIs and those using managed access tokens, along with securing endpoints and configuration details. An *Embedded API* (or local API) is an API located within the BFF host. Embedded APIs are implemented with the familiar ASP.NET abstractions of API controllers or Minimal API endpoints. There are two styles of Embedded APIs: * Self-contained Embedded APIs * Embedded APIs that Make Requests using Managed Access Tokens #### Self-Contained Embedded APIs [Section titled “Self-Contained Embedded APIs”](#self-contained-embedded-apis) These APIs reside within the BFF and don’t make HTTP requests to other APIs. They access data controlled by the BFF itself, which can simplify the architecture of the system by reducing the number of APIs that must be deployed and managed. They are suitable for scenarios where the BFF is the sole consumer of the data. If you require data accessibility from other applications or services, this approach is probably not suitable. #### Embedded APIs That Make Requests Using Managed Access Tokens [Section titled “Embedded APIs That Make Requests Using Managed Access Tokens”](#embedded-apis-that-make-requests-using-managed-access-tokens) Alternatively, you can make the data available as a service and make HTTP requests to that service from your BFF’s Embedded endpoints. The benefits of this style of Embedded Endpoint include: * Your frontend’s network access can be simplified into an aggregated call for the specific data that it needs, which reduces the amount of data that must be sent to the client. * Your BFF endpoint can expose a subset of your remote APIs so that they are called in a more controlled manner than if the BFF proxied all requests to the endpoint. * Your BFF endpoint can include business logic to call the appropriate endpoints, which simplifies your front end code. Your Embedded endpoints can leverage services like the HTTP client factory and Duende.BFF [token management](/bff/fundamentals/tokens/) to make the outgoing calls. The following is a simplified example showing how Embedded endpoints can get managed access tokens and use them to make requests to remote APIs. Program.cs ```csharp app.MapGet("/myApi", async (IHttpClientFactory httpClientFactory, HttpContext context) => { var id = context.Request.Query["id"]; // create HTTP client var client = httpClientFactory.CreateClient(); // get current user access token and set it on HttpClient var token = await context.GetUserAccessTokenAsync(); client.SetBearerToken(token); // call remote API var response = await client.GetAsync($"https://remoteServer/remoteApi?id={id}"); // maybe process response and return to frontend return Results.Text(await response.Content.ReadAsStringAsync()); }); ``` The example above is simplified to demonstrate the way that you might obtain a token. Embedded endpoints will typically enforce constraints on the way the API is called, aggregate multiple calls, or perform other business logic. Embedded endpoints that merely forward requests from the frontend to the remote API may not be needed at all. Instead, you could proxy the requests through the BFF using either the [simple http forwarder](/bff/fundamentals/apis/remote/) or [YARP](/bff/fundamentals/apis/yarp/). ## Securing Embedded API Endpoints [Section titled “Securing Embedded API Endpoints”](#securing-embedded-api-endpoints) Regardless of the style of data access used by an Embedded API, it must be protected against threats such as [CSRF (Cross-Site Request Forgery)](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) attacks. To defend against such attacks and ensure that only the frontend can access these endpoints, we recommend implementing two layers of protection. #### SameSite Cookies [Section titled “SameSite Cookies”](#samesite-cookies) [The SameSite cookie attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value) is a feature of modern browsers that restricts cookies so that they are only sent to pages originating from the [site](https://developer.mozilla.org/en-US/docs/Glossary/Site) where the cookie was originally issued. This is a good first layer of defense, but makes the assumption that you can trust all subdomains of your site. All subdomains within a registrable domain are considered the same site for purposes of SameSite cookies. Thus, if another application hosted on a subdomain within your site is infected with malware, it can make CSRF attacks against your application. #### Anti-forgery Header [Section titled “Anti-forgery Header”](#anti-forgery-header) We recommend requiring an additional custom header on API endpoints, for example: ```plaintext GET /endpoint x-csrf: 1 ``` The value of the header is not important, but its presence, combined with the cookie requirement, triggers a CORS preflight request for cross-origin calls. This effectively isolates the caller to the same origin as the backend, providing a robust security guarantee. Additionally, API endpoints should handle scenarios where the session has expired or authorization fails without triggering an authentication redirect to the upstream identity provider. Instead, they should return Ajax-friendly status codes. ## Setup [Section titled “Setup”](#setup) ### Adding Anti-forgery Protection [Section titled “Adding Anti-forgery Protection”](#adding-anti-forgery-protection) Duende.BFF can automate the pre-processing step of requiring the custom anti-forgery header. To do so, first add the BFF middleware to the pipeline, and then decorate your endpoints to indicate that they should receive BFF pre-processing. 1. **Add Middleware to the pipeline** Add the BFF middleware to the pipeline by calling `UseBff`. Note that the middleware must be placed before the authorization middleware, but after routing. Program.cs ```csharp app.UseAuthentication(); app.UseRouting(); app.UseBff(); app.UseAuthorization(); // map endpoints ``` 2. **Decorate Endpoints** Endpoints that require the pre- and post-processing described above must be decorated with a call to `AsBffApiEndpoint()`. For Minimal API endpoints, you can apply BFF pre- and post-processing when they are mapped. ```csharp app.MapPost("/foo", context => { // ... }) .RequireAuthorization() // no anonymous access .AsBffApiEndpoint(); // BFF pre/post processing ``` For MVC controllers, you can similarly apply BFF pre- and post-processing to controller actions when they are mapped. ```csharp app.MapControllers() .RequireAuthorization() // no anonymous access .AsBffApiEndpoint(); // BFF pre/post processing ``` Alternatively, you can apply the `[BffApi]` attribute directly to the controller or action. ```csharp [Route("myApi")] [BffApi] public class MyApiController : ControllerBase { // ... } ``` ### Disabling Anti-forgery Protection [Section titled “Disabling Anti-forgery Protection”](#disabling-anti-forgery-protection) Disabling anti-forgery protection is possible but not recommended. Antiforgery protection defends against CSRF attacks, so opting out may cause security vulnerabilities. However, if you are defending against CSRF attacks with some other mechanism, you can opt out of Duende.BFF’s CSRF protection. Depending on the version of Duende.BFF, use one of the following approaches. For *version 1.x*, set the `requireAntiForgeryCheck` parameter to `false` when adding the endpoint. For example: Program.cs ```csharp // MVC controllers app.MapControllers() .RequireAuthorization() // WARNING: Disabling antiforgery protection may make // your APIs vulnerable to CSRF attacks .AsBffApiEndpoint(requireAntiforgeryCheck: false); // simple endpoint app.MapPost("/foo", context => { // ... }) .RequireAuthorization() // WARNING: Disabling antiforgery protection may make // your APIs vulnerable to CSRF attacks .AsBffApiEndpoint(requireAntiforgeryCheck: false); ``` On MVC controllers and actions you can set the `RequireAntiForgeryCheck` as a flag in the `BffApiAttribute`, like this: ```csharp [Route("sample")] // WARNING: Disabling antiforgery protection may make // your APIs vulnerable to CSRF attacks [BffApi(requireAntiForgeryCheck: false)] public class SampleApiController : ControllerBase { /* ... */ } ``` In *version 2.x and 3.x*, use the `SkipAntiforgery` fluent API when adding the endpoint. For example: Program.cs ```csharp // MVC controllers app.MapControllers() .RequireAuthorization() .AsBffApiEndpoint() // WARNING: Disabling antiforgery protection may make // your APIs vulnerable to CSRF attacks .SkipAntiforgery(); // simple endpoint app.MapPost("/foo", context => { /* ... */ }) .RequireAuthorization() .AsBffApiEndpoint() // WARNING: Disabling antiforgery protection may make // your APIs vulnerable to CSRF attacks .SkipAntiforgery(); ``` MVC controllers and actions can use the `BffApiSkipAntiforgeryAttribute` (which is independent of the `BffApiAttribute`), like this: ```csharp [Route("sample")] // WARNING: Disabling antiforgery protection may make // your APIs vulnerable to CSRF attacks [BffApiSkipAntiforgeryAttribute] public class SampleApiController : ControllerBase { /* ... */ } ``` ### Skipping Response Handling [Section titled “Skipping Response Handling”](#skipping-response-handling) By default, when BFF pre/post-processing is enabled on an endpoint (via `.AsBffApiEndpoint()`), the BFF framework intercepts authentication challenge and forbid responses. Instead of returning a 302 redirect to the identity provider (which is not useful for API calls), it converts them to API-friendly status codes: * **Challenge** (unauthenticated) → returns **401** (instead of 302 redirect) * **Forbid** (unauthorized) → returns **403** (instead of 302 redirect) If you want to opt out of this behavior and let the default authentication response handling occur (e.g., if your endpoint needs to trigger a redirect), you can use the `SkipResponseHandling()` extension method: ```csharp app.MapGet("/my-endpoint", context => { /* ... */ }) .AsBffApiEndpoint() .SkipResponseHandling(); ``` For MVC controllers, you can use the `[BffApiSkipResponseHandling]` attribute: ```csharp [Route("my-endpoint")] [BffApi] [BffApiSkipResponseHandling] public class MyController : ControllerBase { /* ... */ } ``` ----- # Proxying Remote APIs > Learn how to configure and secure remote API access through BFF using HTTP forwarding and token management. A *Remote API* is an API that is deployed separately from the BFF host. Remote APIs use access tokens to authenticate and authorize requests, but the frontend does not possess an access token to make requests to remote APIs directly. Instead, all access to remote APIs is proxied through the BFF, which authenticates the frontend using its authentication cookie, gets the appropriate access token, and forwards the request to the Remote API with the token attached. There are two different ways to set up Remote API proxying in Duende.BFF. This page describes the built-in simple HTTP forwarder. Alternatively, you can integrate Duende.BFF with Microsoft’s [YARP](/bff/fundamentals/apis/yarp/) reverse proxy, which allows for more complex reverse proxy features provided by YARP combined with the security and identity features of Duende.BFF. ## Direct HTTP Forwarding [Section titled “Direct HTTP Forwarding”](#direct-http-forwarding) Duende.BFF’s direct HTTP forwarder maps routes in the BFF to a remote API surface. It uses [Microsoft YARP](https://github.com/microsoft/reverse-proxy) internally, but is much simpler to configure than YARP. The intent is to provide a developer-centric and simplified way to proxy requests from the BFF to remote APIs when more complex reverse proxy features are not needed. These routes receive automatic anti-forgery protection and integrate with automatic token management. To enable this feature, add a reference to the [`Duende.BFF.Yarp` NuGet package](https://www.nuget.org/packages/Duende.BFF.Yarp), add the remote APIs service to the service provider, and then add the remote endpoint mappings. #### Add Remote API Service to Service Provider [Section titled “Add Remote API Service to Service Provider”](#add-remote-api-service-to-service-provider) To use the HTTP forwarder, register it in the service provider: Program.cs ```csharp builder.Services.AddBff() .AddRemoteApis(); ``` #### Map Remote APIs [Section titled “Map Remote APIs”](#map-remote-apis) Use the `MapRemoteBffApiEndpoint` extension method to describe how to map requests coming into the BFF to remote APIs. `MapRemoteBffApiEndpoint` takes two parameters: the base path of requests that will be mapped externally, and the address to the external API where the requests will be mapped. The `MapRemoteBffApiEndpoint` extension method maps a path and all sub-paths below it. The intent is to allow easy mapping of groups of URLs. For example, you can set up mappings for the `/users`, `/users/{userId}`, `/users/{userId}/books`, and `/users/{userId}/books/{bookId}` endpoints without having to explicitly include all of them: * V4 Program.cs ```csharp app.MapRemoteBffApiEndpoint("/api/users", new Uri("https://remoteHost/users")) .WithAccessToken(RequiredTokenType.User); ``` * V3 Program.cs ```csharp app.MapRemoteBffApiEndpoint("/api/users", new Uri("https://remoteHost/users")) .WithAccessToken(TokenType.User); ``` The `WithAccessToken` method can be added to [specify token requirements](#access-token-requirements) for the remote API. The BFF will automatically forward the correct access token to the remote API, which will be scoped to the client application, the user, or either. ## Securing Remote APIs [Section titled “Securing Remote APIs”](#securing-remote-apis) Remote APIs typically require access control and must be protected against threats such as [CSRF (Cross-Site Request Forgery)](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) attacks. To provide access control, you can specify authorization policies on the mapped routes and configure them with access token requirements. To defend against CSRF attacks, you should use SameSite cookies to authenticate calls from the frontend to the BFF. As an additional layer of defense, APIs mapped with `MapRemoteBffApiEndpoint` are automatically protected with an anti-forgery header. #### SameSite cookies [Section titled “SameSite cookies”](#samesite-cookies) [The SameSite cookie attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value) is a feature of modern browsers that restricts cookies so that they are only sent to pages originating from the [site](https://developer.mozilla.org/en-US/docs/Glossary/Site) where the cookie was originally issued. This prevents CSRF attacks, because cross site requests will no longer implicitly include the user’s credentials. This is a good first layer of defense but makes the assumption that you can trust all subdomains of your site. All subdomains within a registrable domain are considered the same site for purposes of SameSite cookies. Thus, if another application hosted on a subdomain within your site is infected with malware, it can make CSRF attacks against your application. #### Anti-forgery header [Section titled “Anti-forgery header”](#anti-forgery-header) Remote APIs mapped in the BFF always require an additional custom header on API endpoints. For example: ```text GET /endpoint x-csrf: 1 ``` The value of the header is not important, but its presence, combined with the cookie requirement, triggers a CORS preflight request for cross-origin calls. This effectively isolates the caller to the same origin as the backend, providing a robust security guarantee. #### Require authorization [Section titled “Require authorization”](#require-authorization) The `MapRemoteBffApiEndpoint` method returns the appropriate type to integrate with the ASP.NET Core authorization system. You can attach authorization policies to remote endpoints using the `WithAccessToken` extension method, just as you would for a standard ASP.NET core endpoint created with `MapGet`. The authorization middleware will then enforce that policy before forwarding requests on that route to the remote endpoint. #### Access token requirements [Section titled “Access token requirements”](#access-token-requirements) Remote APIs sometimes allow anonymous access but usually require an access token, and the type of access token (user or client) will vary as well. You can specify access token requirements via the `WithAccessToken` extension method. Its `RequiredTokenType` parameter has five options: * **`None`** No token is required. * **`User`** A valid user access token is required and will be forwarded to the remote API. A user access token is an access token obtained during an OIDC flow (or subsequent refresh), and is associated with a particular user. User tokens are obtained when the user initially logs in, and will be automatically refreshed using a refresh token when they expire. * **`Client`** A valid client access token is required and will be forwarded to the remote API. A client access token is an access token obtained through the client credentials flow, and is associated with the client application, not any particular user. Client tokens can be obtained even if the user is not logged in. * **`UserOrClient`** Either a valid user access token or a valid client access token (as fallback) is required and will be forwarded to the remote API. * **`UserOrNone`** A valid user access token will be forwarded to the remote API when logged in. No access token will be sent when not logged in, and no OIDC flow is challenged to get an access token. ----- # YARP extensions > Integration of Duende.BFF with Microsoft's YARP reverse proxy, including token management and anti-forgery protection features. Duende.BFF integrates with Microsoft’s full-featured reverse proxy [YARP](https://microsoft.github.io/reverse-proxy/). YARP includes many advanced features such as load balancing, service discovery, and session affinity. It also has its own extensibility mechanism. Duende.BFF includes YARP extensions for token management and anti-forgery protection so that you can combine the security and identity features of `Duende.BFF` with the flexible reverse proxy features of YARP. ## Adding YARP [Section titled “Adding YARP”](#adding-yarp) To enable Duende.BFF’s YARP integration, add a reference to the *Duende.BFF.Yarp* NuGet package to your project and add YARP and the BFF’s YARP extensions to DI: ```csharp builder.Services.AddBff(); // adds YARP with BFF extensions var yarpBuilder = services.AddReverseProxy() .AddBffExtensions(); ``` ## Configuring YARP [Section titled “Configuring YARP”](#configuring-yarp) YARP is most commonly configured by a config file. The following simple example forwards a local URL to a remote API: ```json { "ReverseProxy": { "Routes": { "todos": { "ClusterId": "cluster1", "Match": { "Path": "/todos/{**catch-all}" } } }, "Clusters": { "cluster1": { "Destinations": { "destination1": { "Address": "https://API.mycompany.com/todos" } } } } } } ``` See the Microsoft [documentation](https://microsoft.github.io/reverse-proxy/articles/config-files.html) for the complete configuration schema. Another option is to configure YARP in code using the in-memory config provider included in the BFF extensions for YARP. The above configuration as code would look like this: ```csharp yarpBuilder.LoadFromMemory( new[] { new RouteConfig() { RouteId = "todos", ClusterId = "cluster1", Match = new() { Path = "/todos/{**catch-all}" } } }, new[] { new ClusterConfig { ClusterId = "cluster1", Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "destination1", new() { Address = "https://API.mycompany.com/todos" } }, } } }); ``` ## Token Management [Section titled “Token Management”](#token-management) Duende.BFF’s YARP extensions provide access token management and attach user or client access tokens automatically to proxied API calls. To enable this, add metadata with the name *Duende.Bff.Yarp.TokenType* to the route or cluster configuration: ```json { "ReverseProxy": { "Routes": { "todos": { "ClusterId": "cluster1", "Match": { "Path": "/todos/{**catch-all}" }, "Metadata": { "Duende.Bff.Yarp.TokenType": "User" } } } } } ``` Similarly to the [simple HTTP forwarder](/bff/fundamentals/apis/remote/#access-token-requirements), the allowed values for the token type are `None`, `User`, `Client`, `UserOrClient`, and `UserOrNone`. Routes that set the `Duende.Bff.Yarp.TokenType` metadata **require** the given type of access token. If it is unavailable (for example, if the `User` token type is specified but the request to the BFF is anonymous), then the proxied request will not be sent, and the BFF will return an HTTP 401: Unauthorized response. If you are using the code config method, call the `WithAccessToken` extension method to achieve the same thing: ```csharp yarpBuilder.LoadFromMemory( new[] { new RouteConfig() { RouteId = "todos", ClusterId = "cluster1", Match = new RouteMatch { Path = "/todos/{**catch-all}" } }.WithAccessToken(RequiredTokenType.User) }, // rest omitted ); ``` Again, the `WithAccessToken` method causes the route to require the given type of access token. If it is unavailable, the proxied request will not be made and the BFF will return an HTTP 401: Unauthorized response. ## Optional User Access Tokens [Section titled “Optional User Access Tokens”](#optional-user-access-tokens) You can attach user access tokens optionally using the `UserOrNone` token type. This causes the user’s access token to be sent with the proxied request when the user is logged in, but makes the request anonymously when the user is not logged in. In configuration, set the `Duende.Bff.Yarp.TokenType` metadata to `UserOrNone`: ```json { "ReverseProxy": { "Routes": { "todos": { "ClusterId": "cluster1", "Match": { "Path": "/todos/{**catch-all}" }, "Metadata": { "Duende.Bff.Yarp.TokenType": "UserOrNone" } } } } } ``` If you are using the code config method, call the `WithAccessToken` extension method with `RequiredTokenType.UserOrNone`: ```csharp yarpBuilder.LoadFromMemory( new[] { new RouteConfig() { RouteId = "todos", ClusterId = "cluster1", Match = new RouteMatch { Path = "/todos/{**catch-all}" } }.WithAccessToken(RequiredTokenType.UserOrNone) }, // rest omitted ); ``` ### Anti-forgery Protection [Section titled “Anti-forgery Protection”](#anti-forgery-protection) Duende.BFF’s YARP extensions can also add anti-forgery protection to proxied API calls. Anti-forgery protection defends against CSRF attacks by requiring a custom header on API endpoints, for example: ```plaintext GET /endpoint x-csrf: 1 ``` The value of the header is not important, but its presence, combined with the cookie requirement, triggers a CORS preflight request for cross-origin calls. This effectively isolates the caller to the same origin as the backend, providing a robust security guarantee. You can add the anti-forgery protection to all YARP routes by calling the `AsBffApiEndpoint` extension method: ```csharp app.MapReverseProxy() .AsBffApiEndpoint(); // or shorter app.MapBffReverseProxy(); ``` If you need more fine-grained control over which routes should enforce the anti-forgery header, you can also annotate the route configuration by adding the `Duende.Bff.Yarp.AntiforgeryCheck` metadata to the route config: ```json { "ReverseProxy": { "Routes": { "todos": { "ClusterId": "cluster1", "Match": { "Path": "/todos/{**catch-all}" }, "Metadata": { "Duende.Bff.Yarp.AntiforgeryCheck": "true" } } } } } ``` This is also possible in code: ```csharp yarpBuilder.LoadFromMemory( new[] { new RouteConfig() { RouteId = "todos", ClusterId = "cluster1", Match = new RouteMatch { Path = "/todos/{**catch-all}" } }.WithAntiforgeryCheck() }, // rest omitted ); ``` To enforce the presence of the anti-forgery headers, you need to add a middleware to the YARP pipeline: Program.cs ```csharp app.MapReverseProxy(proxyApp => { proxyApp.UseAntiforgeryCheck(); }); ``` ----- # BFF Security Framework Blazor Support > Overview of integrating the Duende BFF Security Framework with Blazor applications for secure authentication and authorization. Microsoft’s Blazor framework helps developers build rich, interactive web applications using C# and .NET. While Blazor is well-suited for rich web UIs, it introduces unique challenges around secure authentication and authorization — especially when rendering happens both on the server and in the browser. The Duende BFF Security Framework addresses these challenges by keeping access tokens on the server and providing a unified authentication state across Blazor’s rendering modes. ## Architecture [Section titled “Architecture”](#architecture) A BFF-backed Blazor app has three elements: the **backend** (server-side logic and APIs), the **frontend** (the Blazor application), and the **client** (the browser). The BFF host acts as the combined backend and frontend host: ``` flowchart LR Client[Client / Browser] subgraph BFF Host Backend[Backend APIs] Frontend[Blazor Frontend] end Client <--> Frontend Frontend <--> Backend ``` For a detailed architecture diagram and explanation, see the [Architecture overview](/bff/architecture/). ## Where to Go Next [Section titled “Where to Go Next”](#where-to-go-next) | Topic | Description | | ----------------------------------------------------------------------- | -------------------------------------------------- | | [Rendering Modes & BFF](/bff/fundamentals/blazor/rendering-modes/) | Which Blazor rendering modes work with BFF and why | | [Data Access Patterns](/bff/fundamentals/blazor/data-access/) | How to securely call APIs from Blazor components | | [Getting Started: Blazor](/bff/getting-started/blazor/) | Step-by-step setup guide for a Blazor BFF app | | [Server-Side Sessions](/bff/fundamentals/session/server-side-sessions/) | Persistent session storage for Blazor apps | | [Token Management](/bff/fundamentals/tokens/) | How BFF manages access tokens for Blazor | ## See Also [Section titled “See Also”](#see-also) * [Access Token Management](/accesstokenmanagement/) * [Blazor Server token management](/accesstokenmanagement/blazor-server/) * [IdentityServer Quickstarts](/identityserver/quickstarts/0-overview/) ----- # Blazor Data Access Patterns > How to securely access local and remote APIs from Blazor components using the BFF security framework. Depending on your Blazor rendering mode, you need different strategies for accessing data from components. The BFF security framework provides a consistent model: tokens never leave the server, and browser components access data through BFF-hosted endpoints secured with the authentication cookie. ## Overview [Section titled “Overview”](#overview) ``` flowchart LR WASM["Blazor WASM Component
(browser)"] Server["BFF Host
(server)"] Remote["Remote API
(external)"] DB["Database
(server-side)"] WASM -- "cookie + X-CSRF header" --> Server Server -- "access token" --> Remote Server -- "direct access" --> DB ``` For server-side rendering, components access data directly (database, services). For WASM rendering, components make HTTP calls to BFF-hosted endpoints which handle token attachment. ## Embedded (Local) APIs [Section titled “Embedded (Local) APIs”](#embedded-local-apis) An Embedded API is hosted within the BFF itself. It lives within the server’s security boundary, so no token needs to be passed to the browser. ### Defining the Abstraction [Section titled “Defining the Abstraction”](#defining-the-abstraction) Use an interface to abstract between server and client implementations: Shared/IDataAccessor.cs ```csharp public interface IDataAccessor { Task GetData(); } public record Data(string Value); ``` ### Server Implementation [Section titled “Server Implementation”](#server-implementation) Server/ServerDataAccessor.cs ```csharp internal class ServerDataAccessor : IDataAccessor { public Task GetData() { // Access data directly (database, cache, etc.) return Task.FromResult(new[] { new Data("example") }); } } ``` Register the server implementation and expose it as a BFF endpoint: Server/Program.cs ```csharp builder.Services.AddSingleton(); // ... app.MapGet("/some_data", async (IDataAccessor dataAccessor) => await dataAccessor.GetData()) .RequireAuthorization() .AsBffApiEndpoint(); ``` ### Client (WASM) Implementation [Section titled “Client (WASM) Implementation”](#client-wasm-implementation) On the client, use an `HttpClient` that routes through the BFF host: Client/Program.cs ```csharp builder.Services.AddBffBlazorClient() .AddLocalApiHttpClient(); // Register the concrete implementation with the abstraction builder.Services.AddSingleton(sp => sp.GetRequiredService()); ``` Client/HttpClientDataAccessor.cs ```csharp internal class HttpClientDataAccessor(HttpClient client) : IDataAccessor { public async Task GetData() => await client.GetFromJsonAsync("/some_data") ?? throw new JsonException("Failed to deserialize"); } ``` ## Secured Remote APIs [Section titled “Secured Remote APIs”](#secured-remote-apis) If your BFF needs to proxy requests to a remote API (one that requires a bearer token), configure a remote endpoint on the server and access it from the client via the BFF proxy. ### Server-side Proxy Setup [Section titled “Server-side Proxy Setup”](#server-side-proxy-setup) Server/Program.cs ```csharp app.MapRemoteBffApiEndpoint("/remote-apis/data", new Uri("https://api.example.com/data")) .WithAccessToken(RequiredTokenType.User); ``` Also register an `HttpClient` that attaches the user access token for use in Embedded API endpoints: ```csharp builder.Services.AddUserAccessTokenHttpClient("backend", configureClient: client => client.BaseAddress = new Uri("https://api.example.com/")); ``` ### Client-Side Access [Section titled “Client-Side Access”](#client-side-access) Client/Program.cs ```csharp builder.Services.AddBffBlazorClient(); builder.Services.AddRemoteApiHttpClient("backend"); builder.Services.AddTransient(sp => sp.GetRequiredService().CreateClient("backend")); ``` The diagram below shows the full flow: ``` sequenceDiagram participant WASM as Blazor WASM participant BFF as BFF Host participant API as Remote API WASM->>BFF: GET /remote-apis/data (cookie + X-CSRF) BFF->>BFF: Validate session & get access token BFF->>API: GET /data (Bearer token) API-->>BFF: 200 OK + data BFF-->>WASM: 200 OK + data ``` ## Auto-Rendering Mode [Section titled “Auto-Rendering Mode”](#auto-rendering-mode) In Interactive Auto mode, a component may render on the server first, then transition to WASM. Use the interface-based abstraction pattern from above: inject `IDataAccessor` in your component, and register both `ServerDataAccessor` (for server rendering) and `HttpClientDataAccessor` (for WASM rendering). ```razor @* Component works identically in server and WASM rendering modes *@ @inject IDataAccessor DataAccessor @if (items == null) {

Loading...

} else { @foreach (var item in items) {

@item.Value

} } @code { private Data[]? items; protected override async Task OnInitializedAsync() { items = await DataAccessor.GetData(); } } ``` ## See Also [Section titled “See Also”](#see-also) [Embedded (Local) APIs ](/bff/fundamentals/apis/local/)Full reference for BFF-hosted endpoints [Proxying Remote APIs ](/bff/fundamentals/apis/remote/)Direct forwarding to upstream services [YARP Integration ](/bff/fundamentals/apis/yarp/)Advanced reverse proxy configuration [Rendering Modes & BFF ](/bff/fundamentals/blazor/rendering-modes/)Which Blazor modes need BFF [Getting Started: Blazor ](/bff/getting-started/blazor/)Full setup walkthrough ----- # Blazor Rendering Modes & BFF > Learn which Blazor rendering modes are compatible with the BFF security pattern and why. Blazor supports [several rendering modes](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-9.0#render-modes). The BFF pattern is only applicable to modes where code runs in the browser (client context), because that is where the risk of token exposure exists. ## Rendering Mode Compatibility [Section titled “Rendering Mode Compatibility”](#rendering-mode-compatibility) | Mode | Description | Renders In | Interactive | Use BFF? | | --------------------------- | ------------------------------------------------------------- | ---------------- | ----------- | -------- | | **Static Server** | Static server-side rendering (SSR) | Server | ❌ | ❌ | | **Interactive Server** | Interactive SSR using Blazor Server and WebSockets | Server | ✅ | ❌ | | **Interactive WebAssembly** | Client-side rendering (CSR) using Blazor WASM | Browser | ✅ | ✅ | | **Interactive Auto** | Starts as Interactive Server, switches to WASM after download | Server → Browser | ✅ | ✅ | ## Static Server [Section titled “Static Server”](#static-server) Caution BFF is not necessary for Static Server rendering. Standard ASP.NET Core authentication patterns apply. Static Server renders Blazor components as plain HTML with no client-side interactivity. Because all rendering happens on the server, tokens never reach the browser. Use standard ASP.NET Core cookie authentication instead of BFF. You may still want to use the `AuthenticationStateProvider` for accessing user claims in components. ## Interactive Server [Section titled “Interactive Server”](#interactive-server) Caution BFF is not typically necessary for Interactive Server rendering. All component interactivity is managed server-side via WebSockets (SignalR). In Interactive Server mode, Blazor components run on the server and push UI updates to the browser over a WebSocket connection. Because no application code runs in the browser, tokens remain server-side naturally. You can still use [Session Management](/bff/fundamentals/session/) features of BFF if you want server-side session control, but the BFF security pattern itself is not required. ## Interactive WebAssembly [Section titled “Interactive WebAssembly”](#interactive-webassembly) In Interactive WebAssembly mode, the Blazor runtime and your application code are downloaded to and executed in the browser. This means: * Your components run in the same JavaScript sandbox as the rest of the page * Any access token stored in the WASM memory is potentially accessible to injected scripts * You must never expose access tokens to WASM components The BFF pattern solves this by keeping tokens on the server. WASM components call BFF-hosted API endpoints (using the authentication cookie), and the BFF attaches the access token server-side before forwarding to remote APIs. See the [Getting Started: Blazor guide](/bff/getting-started/blazor/) for setup instructions. ## Interactive Auto [Section titled “Interactive Auto”](#interactive-auto) Interactive Auto combines Interactive Server and Interactive WebAssembly: rendering starts on the server but switches to client-side WASM on subsequent visits after the Blazor bundle is downloaded. Because your application may be running in the browser at any time, you cannot rely on server-side-only token handling. The BFF pattern ensures tokens remain server-side regardless of which rendering mode is active. ## Authentication State [Section titled “Authentication State”](#authentication-state) The `AuthenticationState` contains information about the currently logged-in user, including management claims like the logout URL. Blazor uses `AuthenticationStateProvider` implementations to make authentication state available to components: * **On the server**: The BFF’s `AddServerManagementClaimsTransform` enriches the claims with the logout URL. * **On the client (WASM)**: The `BffClientAuthenticationStateProvider` polls `/bff/user` to keep the client in sync with the server session. This also notifies the frontend if the session is terminated server-side (e.g., back-channel logout). ## Server-Side Token Store [Section titled “Server-Side Token Store”](#server-side-token-store) Blazor Server applications stream content over a WebSocket, so there is often no `HttpContext` available during component execution. This means: * You cannot use `HttpContext` extension methods in Blazor Server components * The normal mechanism to attach tokens to `HttpClient` calls does not work without special setup When you register `AddBlazorServer()`, BFF automatically registers the `ServerSideTokenStore` and Duende.AccessTokenManagement integration so that token management works correctly in Blazor Server. For more details, see [Blazor Server token management](/accesstokenmanagement/blazor-server/). ## See Also [Section titled “See Also”](#see-also) [Data Access Patterns ](/bff/fundamentals/blazor/data-access/)How to call APIs from Blazor components [Getting Started: Blazor ](/bff/getting-started/blazor/)Full walkthrough setup guide [Troubleshooting ](/bff/troubleshooting/)Common Blazor BFF issues ----- # Production Deployment > Guide for deploying Duende BFF to production, covering load balancing, data protection, health checks, cookie domain configuration, and monitoring. This page covers the production-specific concerns you need to address before deploying a BFF host. For middleware pipeline order, see [Middleware Pipeline](/bff/fundamentals/middleware-pipeline/). ## Load Balancing and Sticky Sessions [Section titled “Load Balancing and Sticky Sessions”](#load-balancing-and-sticky-sessions) The BFF uses ASP.NET Core’s Data Protection to encrypt and sign session cookies. In a multi-instance (load-balanced) deployment, **all instances must share the same Data Protection key ring** — otherwise cookies issued by one instance cannot be decrypted by another, causing random logout on failover. ### Shared Key Storage [Section titled “Shared Key Storage”](#shared-key-storage) Configure Data Protection to store keys in a shared location accessible to all instances: ```csharp // Using Azure Blob Storage + Azure Key Vault builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(connectionString, "data-protection", "keys.xml") .ProtectKeysWithAzureKeyVault(keyIdentifier, credential); // Using a network file share or a database builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\dp-keys")) .ProtectKeysWithCertificate(certificate); ``` Do not use in-memory keys in production The default in-memory key ring is regenerated on every restart. Any existing sessions are invalidated when the process restarts or when traffic is routed to a new instance. See also: [Data Protection](/general/data-protection/) ### Server-Side Sessions (Recommended for Multi-Instance) [Section titled “Server-Side Sessions (Recommended for Multi-Instance)”](#server-side-sessions-recommended-for-multi-instance) With cookie-only sessions, every instance must share Data Protection keys. With **server-side sessions**, the cookie only holds an opaque session ID — the session payload is stored in a shared database. This is simpler to operate because: * Key ring only needs to be consistent (not necessarily shared) — the cookie just holds an ID * Sessions can be inspected and revoked server-side * Cookie size is minimized [Server-Side Sessions ](/bff/fundamentals/session/server-side-sessions/)Setup guide for database-backed session storage ### Sticky Sessions [Section titled “Sticky Sessions”](#sticky-sessions) If you cannot use server-side sessions and cannot share a Data Protection key ring, configure your load balancer to route each user consistently to the same instance (“sticky sessions” / session affinity). This is a last resort — prefer shared key storage. ## Health Check Endpoints [Section titled “Health Check Endpoints”](#health-check-endpoints) Expose a health check endpoint so your load balancer and orchestrator (Kubernetes, etc.) can detect unhealthy instances: ```csharp builder.Services.AddHealthChecks(); // In your pipeline (after UseRouting): app.MapHealthChecks("/health"); ``` For a more complete health check that validates downstream dependencies (database, token endpoint reachability): ```csharp builder.Services.AddHealthChecks() .AddDbContextCheck() // EF Core session store .AddUrlGroup(new Uri("https://idp.example.com/.well-known/openid-configuration"), name: "identity-provider"); app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false }); app.MapHealthChecks("/health/ready", new HealthCheckOptions()); ``` * `/health/live` — liveness: is the process running? (no dependency checks) * `/health/ready` — readiness: are all dependencies reachable? (fail this to take the instance out of rotation) ## Cookie Domain Configuration [Section titled “Cookie Domain Configuration”](#cookie-domain-configuration) By default, the BFF session cookie is scoped to the exact host. If you need the cookie to work across subdomains (e.g. `app.example.com` and `api.example.com`): ```csharp builder.Services.AddAuthentication() .AddCookie(options => { options.Cookie.Domain = ".example.com"; // Note leading dot options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); ``` Scope cookies as narrowly as possible Setting a broad cookie domain (`.example.com`) means the cookie is sent to all subdomains, including any you don’t control. Only use this when architecturally required, and ensure all subdomains are trusted. For split-host deployments (frontend on `app.example.com`, BFF on `bff.example.com`), you will need to set the cookie domain AND configure CORS. See [Separate Host for UI](/bff/architecture/ui-hosting/) and the [SplitHosts sample](/bff/samples/). ## Reverse Proxy Configuration [Section titled “Reverse Proxy Configuration”](#reverse-proxy-configuration) The BFF is typically deployed behind a reverse proxy (NGINX, Azure Application Gateway, AWS ALB, etc.). Configure ASP.NET Core to trust forwarded headers: ```csharp builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; // Restrict to your proxy's IP to prevent header spoofing: options.KnownProxies.Add(IPAddress.Parse("10.0.0.100")); }); // Must be the FIRST middleware: app.UseForwardedHeaders(); ``` Without this, `HttpContext.Request.Scheme` will be `http` even when the client uses HTTPS, which causes: * The OIDC redirect URI to be `http://...` (rejected by the identity provider) * The session cookie’s `Secure` flag to have no effect * `HttpContext.Request.Host` to reflect the internal host, breaking the OIDC redirect ## Monitoring and Alerting [Section titled “Monitoring and Alerting”](#monitoring-and-alerting) BFF emits OpenTelemetry metrics and traces. See [Diagnostics](/bff/diagnostics/) for the full list of metric names and activity sources. ### Key Metrics to Alert On [Section titled “Key Metrics to Alert On”](#key-metrics-to-alert-on) | Metric | Alert Condition | Likely Cause | | ----------------------------------------- | ------------------- | ---------------------------------------------------------------------------- | | `session.started` | Sudden drop to 0 | Data Protection key mismatch, pod restart without shared keys | | `session.ended` | Unexpected spike | Back-channel logout sweep, session store purge, Data Protection key rotation | | `session.ended` / `session.started` ratio | Sustained ratio > 1 | Sessions ending faster than starting — investigate IdP or store issues | | HTTP 5xx on `/bff/*` endpoints | Any sustained spike | BFF host error — check logs | ### Recommended Alerts [Section titled “Recommended Alerts”](#recommended-alerts) ```plaintext # Prometheus-style alert examples # Metric names are converted from dot notation to underscores by Prometheus. # No new sessions — potential Data Protection key mismatch alert: BffNoNewSessions expr: rate(session_started_total[5m]) == 0 for: 5m # Abnormal session churn alert: BffSessionChurn expr: rate(session_ended_total[5m]) / rate(session_started_total[5m]) > 2 for: 5m ``` ## See Also [Section titled “See Also”](#see-also) [Middleware Pipeline ](/bff/fundamentals/middleware-pipeline/)Correct middleware ordering for production [Server-Side Sessions ](/bff/fundamentals/session/server-side-sessions/)Recommended for multi-instance deployments [Diagnostics ](/bff/diagnostics/)Metrics and distributed tracing reference [Separate Host for UI ](/bff/architecture/ui-hosting/)Split-host deployment patterns ----- # Middleware Pipeline > The correct ASP.NET Core middleware order for Duende BFF applications, with explanations of what each component does and common misconfiguration pitfalls. Getting the middleware pipeline order right is critical for BFF to function correctly. Placing middleware in the wrong order can silently disable security features with no obvious error message. ## Canonical Pipeline Order [Section titled “Canonical Pipeline Order”](#canonical-pipeline-order) Program.cs ```csharp var app = builder.Build(); // 1. Forwarded headers (if behind a reverse proxy) app.UseForwardedHeaders(); // 2. HTTPS redirection app.UseHttpsRedirection(); // 3. Static files (serve before auth to avoid unnecessary overhead) app.UseStaticFiles(); // 4. Routing — must come before UseBff and UseAuthorization app.UseRouting(); // 5. Authentication — must come before UseBff app.UseAuthentication(); // 6. BFF middleware — must come AFTER UseAuthentication and UseRouting, // but BEFORE UseAuthorization app.UseBff(); // 7. Authorization app.UseAuthorization(); // 8. Map your endpoints app.MapGet("/api/data", () => Results.Ok("hello")) .RequireAuthorization() .AsBffApiEndpoint(); app.Run(); ``` ## Why Order Matters [Section titled “Why Order Matters”](#why-order-matters) Each middleware in the pipeline can only see the work done by the middleware before it. Here’s why each position is required: | Position | Middleware | Why Here | | ---------------------------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------- | | Before `UseBff` | `UseRouting()` | BFF needs the endpoint route resolved to know which endpoints require anti-forgery protection | | Before `UseBff` | `UseAuthentication()` | BFF reads the authenticated user from the `HttpContext`; without this, the user is always null | | After `UseAuthentication`, before `UseAuthorization` | `UseBff()` | BFF anti-forgery checks run here; placing it after `UseAuthorization` silently disables them | | After `UseBff` | `UseAuthorization()` | Authorization decisions depend on BFF’s pre-processing having already run | Silent failure — no error if pipeline is wrong If `UseBff()` is placed **after** `UseAuthorization()`, anti-forgery enforcement is **silently disabled** — no exception is thrown and no log warning is emitted by default. Always verify pipeline order when debugging authentication issues. The `EnforceBffMiddleware` option (enabled by default) adds a check that throws at startup if the BFF management endpoints are called without the BFF middleware being present. However, this does not catch all ordering mistakes. ## BFF v4 — Automatic Middleware Registration [Section titled “BFF v4 — Automatic Middleware Registration”](#bff-v4--automatic-middleware-registration) In BFF v4, when `AutomaticallyRegisterBffMiddleware` is enabled (the default), the middleware components are registered automatically. You still need to call `UseBff()` yourself in the correct position, but the frontend selection, path mapping, OpenID Connect callbacks, and static file proxying middlewares are added automatically. If you need full control over the pipeline, disable automatic registration: ```csharp builder.Services.AddBff(options => { options.AutomaticallyRegisterBffMiddleware = false; }); ``` Then register each component manually: ```csharp // Before Authentication: app.UseForwardedHeaders(); app.UseBffPreProcessing(); // Frontend selection, path mapping, OIDC callbacks app.UseAuthentication(); app.UseRouting(); // The main BFF middleware (anti-forgery): app.UseBff(); app.UseAuthorization(); // After endpoint mapping: app.UseBffPostProcessing(); // Management endpoints, remote API handling, static file proxying ``` ## Blazor Pipeline Order [Section titled “Blazor Pipeline Order”](#blazor-pipeline-order) Blazor applications need a slightly different order to accommodate Blazor’s own middleware: ```csharp app.UseRouting(); app.UseAuthentication(); // BFF must come after UseAuthentication app.UseBff(); app.UseAuthorization(); // Blazor's anti-forgery protection (separate from BFF's anti-forgery) app.UseAntiforgery(); // In v3, also add: // app.MapBffManagementEndpoints(); app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode(); ``` ## Common Mistakes [Section titled “Common Mistakes”](#common-mistakes) | Mistake | Symptom | Fix | | ---------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | | `UseBff()` after `UseAuthorization()` | Anti-forgery silently disabled; `401` on API calls | Move `UseBff()` before `UseAuthorization()` | | Missing `UseAuthentication()` | All users appear anonymous; no redirect to login | Add `app.UseAuthentication()` before `app.UseBff()` | | Missing `UseRouting()` before `UseBff()` | Anti-forgery checks don’t apply correctly to routes | Add `app.UseRouting()` before `app.UseBff()` | | `.AsBffApiEndpoint()` missing | API returns `302` redirect instead of `401` | Add `.AsBffApiEndpoint()` to each API endpoint | ## See Also [Section titled “See Also”](#see-also) [Getting Started: Single Frontend ](/bff/getting-started/single-frontend/)Complete setup example with correct pipeline [Getting Started: Blazor ](/bff/getting-started/blazor/)Blazor-specific pipeline setup [Troubleshooting ](/bff/troubleshooting/)Diagnosing anti-forgery and authentication failures [Configuration Options ](/bff/fundamentals/options/)AutomaticallyRegisterBffMiddleware and related settings ----- # Multi-Frontend > Documentation for multi-frontend support in BFF The Backend For Frontend pattern basically states that there should be a single backend for each frontend. While for some applications / architectures this makes a lot of sense, because there is a 1-to-1 mapping between the API surface and the browser based application, for some other architectures this may not be useful. Especially in micro-service based architectures, where there are many backend APIs and multiple frontends using these APIs, having a dedicated backend service for each frontend introduces quite a lot of operational overhead. To overcome this issue, a single BFF instance can support multiple frontends. Each frontend you configure can: * Define its own OpenID Connect configuration * Define its own Cookie settings * Define its own API surface * Be identified either via path based routing and/or host selection. Adding additional frontends to the BFF has very little impact on the performance on the BFF itself, but keep in mind that the traffic for all the frontends is proxied through the BFF. ## Authentication Configuration [Section titled “Authentication Configuration”](#authentication-configuration) When you use multiple frontends, you can’t rely on [manual authentication configuration](/bff/fundamentals/session/handlers/#manually-configuring-authentication). This is because each frontend requires its own scheme, and potentially its own OpenID Connect and Cookie configuration. Instead, you should rely on [automatic authentication configuration](/bff/fundamentals/session/handlers/#automatic-authentication-configuration). Below is an example on how to configure multiple frontends. ```csharp var bffBuilder = builder.Services .AddBff(); bffBuilder .ConfigureOpenIdConnect(options => { // These are the default values for all frontends. options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }); .AddFrontends( // This frontend will use the default authentication options new BffFrontend(BffFrontendName.Parse("default-frontend")), // This frontend uses most of the same authentication options, new BffFrontend(BffFrontendName.Parse("with-path")) .MapToPath("/with-path") .WithOpenIdConnectOptions(opt => { // but overrides the clientid and client secret. opt.ClientId = "different-client-id"; opt.ClientSecret = "different secret"; }) .WithCookieOptions(opt => { // and overrides the cookie options to use 'lax' cookies. opt.Cookie.SameSite = SameSiteMode.Lax; })); ``` The order in which configuration is applied is 1. programmatic default options (if any) 2. default options from configuration (if any) 3. frontend specific options (if any) Each frontend can have custom OpenID Connect configuration and Cookie Configuration. This can both be configured programmatically as via [Configuration](configuration/). ## Frontend Selection [Section titled “Frontend Selection”](#frontend-selection) Each request to a frontend has to be uniquely defined by either its path, its host or a combination of the two. If you specify neither, then it’s considered the default frontend. Frontends are matched using the following algorithm: 1. **Selection by both host and path:** If there is a frontend that matches both the host AND has the most specific match to a path, it’s selected. 2. **Selection by host only:** Then, if there is a frontend with only hosts configured and it matches the path, it’s selected. 3. **Selection by path only:** Then, if there is a frontend with a matching path specified, it’s selected. 4. **Default frontend:** Then, if there is a default frontend configured, it’s selected. In summary, the most specific match will be selected. ### Implicit Frontend Disabled [Section titled “Implicit Frontend Disabled”](#implicit-frontend-disabled) When you don’t add any frontends, BFF creates an implicit default frontend. This allows BFF to function correctly in single frontend mode. As soon as you add a frontend, this implicit frontend is disabled. If you want to use both explicitly matching frontends (on host headers or paths) and a default (fallback) frontend, you should explicitly add this default frontend. ## Adding A Frontend During Startup [Section titled “Adding A Frontend During Startup”](#adding-a-frontend-during-startup) The simplest way to add frontends is during startup. ```csharp services .AddBff() .AddFrontends(new BffFrontend(BffFrontendName.Parse("frontend1"))); ``` You can call `AddFrontends` with multiple frontends in one go, or call it multiple times. ## Adding / Updating A Frontend Dynamically At Runtime [Section titled “Adding / Updating A Frontend Dynamically At Runtime”](#adding--updating-a-frontend-dynamically-at-runtime) If you want to manipulate the frontends at runtime, you can do so via the `IFrontendCollection` interface. ```csharp var frontends = app.Services.GetRequiredService(); frontends.AddOrUpdate(new BffFrontend(name)); frontends.Remove(name); ``` ## Defining The API Surface [Section titled “Defining The API Surface”](#defining-the-api-surface) A frontend can define its own API surface, by specifying remote APIs. ```csharp var frontend = new BffFrontend(BffFrontendName.Parse("frontend1")) .WithRemoteApis( // map the local path /path to the remote api new RemoteApi("/some_path", new Uri("https://remote-api"))) // You can also configure various options, such as the type of token, // retrieval parameters, etc.. new RemoteApi("/with_options", new Uri("https://remote-api"))) .WithAccessToken(RequiredTokenType.UserOrClient), .WithAccessTokenRetriever(), .WithUserAccessTokenParameters(new BffUserAccessTokenParameters { Resource = Resource.Parse("urn:isolated-api") })); ``` See the topic on [Token Management](/bff/fundamentals/tokens/) for more information about the various token management options. ## Handling SPA Static Assets [Section titled “Handling SPA Static Assets”](#handling-spa-static-assets) BFF can be configured to handle the static file assets that are typically used when developing Single-Page Application (SPA)-based app. ### Proxying Only `index.html` [Section titled “Proxying Only index.html”](#proxying-only-indexhtml) When deploying a multi-frontend BFF, it makes most sense to have the frontend’s configured with an `index.html` file that is retrieved from a Content Delivery Network (CDN). This can be done in various ways. For example, if you use Vite, you can publish static assets with a base URL configured. This will make sure that any static asset, (such as images, scripts, etc) are retrieved directly from the CDN for best performance. ```csharp var frontend = new BffFrontend(BffFrontendName.Parse("frontend1")) .WithCdnIndexHtmlUrl(new Uri("https://my_cdn/some_app/index.html")) ``` When you do this, the BFF automatically wires up a catch-all route that serves the `index.html` for that specific frontend. See [Serve the index page from the BFF host](/bff/architecture/ui-hosting/#serve-the-index-page-from-the-bff-host) for more information. #### Transforming the `index.html` [Section titled “Transforming the index.html”](#transforming-the-indexhtml) If you need to modify the `index.html` before it is served to the client (for example, to inject frontend-specific configuration or environment variables), you can implement the `IIndexHtmlTransformer` interface: ```csharp public class MyIndexHtmlTransformer : IIndexHtmlTransformer { public Task Transform(string indexHtml, BffFrontend frontend, CancellationToken ct = default) { // Inject a frontend-specific config script tag var transformed = indexHtml.Replace( "", $""); return Task.FromResult(transformed); } } ``` Register the transformer in the service collection: ```csharp services.AddSingleton(); ``` The transformer is called after the `index.html` is fetched from the CDN and before it is cached. The cache duration is controlled by the [`IndexHtmlDefaultCacheDuration`](/bff/fundamentals/options/#cdn--static-assets) option. ### Proxying All Static Assets [Section titled “Proxying All Static Assets”](#proxying-all-static-assets) When developing a Single-Page Application (SPA), it’s very common to use a development webserver such as Vite. While Vite can publish static assets with a base URL, this doesn’t work well during development. The best development experience can be achieved by configuring the BFF to proxy all static assets from the development server: ```csharp var frontend = new BffFrontend(BffFrontendName.Parse("frontend1")) .WithProxiedStaticAssets(new Uri("https://localhost:3000")); // https://localhost:3000 would be the URL of your development web server. ``` While this can also be done in production, it will proxy all static assets through BFF. This will increase the bandwidth consumed by the BFF and reduce the overall performance of your application. ### Proxying Assets Based On Environment [Section titled “Proxying Assets Based On Environment”](#proxying-assets-based-on-environment) If you’re using a local development server during development and a CDN in production, you can configure asset proxying as follows: ```csharp // In this example, the environment name from the application builder is used to determine // if we're running in production or not. var runningInProduction = () => builder.Environment.EnvironmentName == Environments.Production; // Then, when configuring the frontend, you can switch when the static assets will be proxied. new BffFrontend(BffFrontendName.Parse("default-frontend")) .WithBffStaticAssets(new Uri("https://localhost:5010/static"), useCdnWhen: runningInProduction); ``` ----- # BFF Multi-Frontend Configuration > Documentation for managing BFF multi-frontend configuration It’s possible to configure frontends for the BFF via `IConfiguration`. This enables dynamic loading / changing of frontends, including their OpenID Connect configuration, BFF Configuration, and Remote APIs. ```csharp var bffConfig = new ConfigurationBuilder() .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "BffConfig.json"), optional: false, reloadOnChange: true) services .AddBff() .LoadConfiguration(bffConfig); ``` The configuration supports dynamic reloading (so any new frontend added / removed is immediately reflected). ### BffConfiguration [Section titled “BffConfiguration”](#bffconfiguration) * `defaultOidcSettings` OIDC settings applied globally to all frontends unless overridden.\ Type: OidcConfiguration object ([see below](#oidcconfiguration-json-properties)). * `defaultCookieSettings` Cookie settings applied globally to all frontends unless overridden.\ Type: CookieConfiguration object ([see below](#cookieconfiguration-json-properties)). * `frontends` Dictionary of frontend configurations.\ Each key is a frontend name, and the value is a BffFrontendConfiguration object ([see below](#bfffrontendconfiguration-json-properties)). *** ### BffFrontendConfiguration JSON Properties [Section titled “BffFrontendConfiguration JSON Properties”](#bfffrontendconfiguration-json-properties) * `cdnIndexHtmlUrl` The `index.html` that should be used for this frontend (usually on a CDN). When using this property, a fallback route will be created that only proxies the `index.html`. Other static assets are supposed to be retrieved directly from the CDN by the browser. Example: `"https://cdn.yourapp.com/some_app/index.html"` * `staticAssetsUrl` The URL where all static assets can be found. This registers a fallback route that will proxy all static assets from this URL. This is usually used during development, when you’re using a development web server such as Vite. Example: `"https://localhost:3000/"` * `matchingPath` The path prefix for requests routed to this frontend.\ Example: `"/from-config"` * `matchingHostHeader` The host to match for this frontend. Example: `"https://localhost:5005"` * `oidc` OIDC settings specific to this frontend.\ Type: OidcConfiguration object ([see below](#oidcconfiguration-json-properties)). * `cookies` Cookie settings specific to this frontend.\ Type: CookieConfiguration object ([see below](#cookieconfiguration-json-properties)). * `remoteApis` Remote APIs for this frontend. Type: RemoteApiConfiguration object. ([see below](#remoteapiconfiguration-json-properties)). ### RemoteApiConfiguration JSON Properties [Section titled “RemoteApiConfiguration JSON Properties”](#remoteapiconfiguration-json-properties) * `pathMatch` String. The local path that will be used to access the remote API.\ Example: `"/api/user-token"` * `targetUri` String. The target URI of the remote API.\ Example: `"https://localhost:5010"` * `requiredTokenType` String. The token requirement for accessing the remote API.\ Possible values: `"None"`, `"User"`, `"Client"`, `"UserOrClient"`, `"UserOrNone"`\ Default: `"User"` * `tokenRetrieverTypeName` String. The type name of the access token retriever to use for this remote API. * `userAccessTokenParameters` Object. Parameters for retrieving a user access token ([see below](#useraccesstokenparameters-json-properties)). * `activityTimeout` String. How long a request is allowed to remain idle between operations before being canceled.\ Use C# `TimeSpan` serialization format, e.g. `"00:01:40"` for 100 seconds. * `allowResponseBuffering` Boolean. Allows write buffering when sending a response back to the client (if supported by the server).\ Note: Enabling this can break server-sent events (SSE) scenarios. *** ### UserAccessTokenParameters JSON Properties [Section titled “UserAccessTokenParameters JSON Properties”](#useraccesstokenparameters-json-properties) * `signInScheme` String. The scheme used for signing in the user (typically the cookie authentication scheme).\ Example: `"Cookies"` * `challengeScheme` String. The authentication scheme to be used for challenges.\ Example: `"OpenIdConnect"` * `forceRenewal` Boolean. Whether to force renewal of the access token. * `resource` String. The resource for which the access token is requested.\ Example: `"https://api.example.com"` ### OidcConfiguration JSON Properties [Section titled “OidcConfiguration JSON Properties”](#oidcconfiguration-json-properties) * `clientId` The client ID of the OpenID Connect client. * `clientSecret` The client secret of the OpenID Connect client. * `callbackPath` The path or URI to which the OpenID Connect client will redirect after authentication. * `authority` The authority URI, typically the issuer or identity provider endpoint. * `responseType` The response type that the OpenID Connect client will request. * `responseMode` The response mode that the OpenID Connect client will use to return the authentication response. * `mapInboundClaims` Boolean. Whether to map inbound claims from the OpenID Connect provider to the user’s claims in the application. * `saveTokens` Boolean. Whether to save the tokens received from the OpenID Connect provider. * `scope` Array of strings. The scopes that the OpenID Connect client will request from the provider. * `getClaimsFromUserInfoEndpoint` Boolean. Whether to retrieve claims from the UserInfo endpoint of the OpenID Connect provider. ### CookieConfiguration JSON Properties [Section titled “CookieConfiguration JSON Properties”](#cookieconfiguration-json-properties) * `httpOnly` Boolean. Indicates whether the cookie is inaccessible by client-side script. Defaults to true. * `sameSite` String. The SameSite attribute of the cookie. Defaults to `"Strict"`.\ Possible values: `"None"`, `"Lax"`, `"Strict"` * `securePolicy` String. The policy used to determine if the cookie is sent only over HTTPS.\ Possible values: `"Always"`, `"None"`, `"SameAsRequest"` * `name` String. The name of the cookie. * `maxAge` String. The max-age for the cookie. Example: “0:01:00 for 1 minute * `path` String. The cookie path. The BFF will configure the default values for this property. Example: `"/"` * `domain` String. The domain to associate the cookie with. The BFF will configure the default values for this property.\ Example: `"example.com"` ### Example [Section titled “Example”](#example) ```json { "defaultOidcSettings": { "clientId": "global-client", "authority": "https://login.example.com" }, "defaultCookieSettings": null, "frontends": { "some_frontend": { "cdnIndexHtmlUrl": "https://localhost:5005/static/index.html", "matchingPath": "/from-config", "oidc": { "clientId": "frontend1-client", "scope": ["openid", "profile", "email"] }, "remoteApis": [ { "pathMatch": "/todos", "targetUri": "https://localhost:5020/todos/", "requiredTokenType": "User" } ] } } } ``` ----- # Configuration Options > Comprehensive guide to configuring Duende BFF framework including general settings, paths, session management, and API options The `Duende.BFF.BffOptions` allows to configure several aspects of the BFF framework. You set the options at startup time: ```csharp builder.Services.AddBff(options => { // configure options here... }) ``` ## Common Configurations [Section titled “Common Configurations”](#common-configurations) The sections below show complete, annotated options blocks for the most common deployment scenarios. The full reference for every option follows after. ### Production Deployment [Section titled “Production Deployment”](#production-deployment) ```csharp builder.Services.AddBff(options => { // Required for production options.LicenseKey = builder.Configuration["Duende:LicenseKey"]; // Revoke refresh tokens on logout (default: true — keep enabled) options.RevokeRefreshTokenOnLogout = true; // Log out all sessions for a user when back-channel logout is received // Set to true if you want global logout across devices options.BackchannelLogoutAllUserSessions = false; // Session cleanup (v4+): call .AddSessionCleanupBackgroundProcess() instead options.SessionCleanupInterval = TimeSpan.FromMinutes(10); }) // Use Entity Framework for production-grade session storage .AddServerSideSessions() .AddEntityFrameworkServerSideSessions(options => { options.UseSqlServer(connectionString); }); ``` ### Development with a Separate Frontend (Split Host) [Section titled “Development with a Separate Frontend (Split Host)”](#development-with-a-separate-frontend-split-host) When your SPA is served by a separate dev server (e.g., Vite on `localhost:3000`) and the BFF is on a different port: ```csharp builder.Services.AddBff(options => { // Allow the separate frontend origin to use silent login options.AllowedSilentLoginReferers = ["https://localhost:3000"]; }) .ConfigureCookies(options => { // Lax is required when the IDP is on a different site than the BFF options.Cookie.SameSite = SameSiteMode.Lax; }); // Allow CORS requests from the dev server builder.Services.AddCors(options => { options.AddPolicy("DevSpa", policy => policy.WithOrigins("https://localhost:3000") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials()); }); ``` ### Multi-Frontend with Per-Frontend OIDC [Section titled “Multi-Frontend with Per-Frontend OIDC”](#multi-frontend-with-per-frontend-oidc) ```csharp builder.Services.AddBff() // Global OIDC defaults (overridden per-frontend where needed) .ConfigureOpenIdConnect(options => { options.Authority = "https://login.example.com"; options.ClientId = "shared-client"; options.ClientSecret = "secret"; options.SaveTokens = true; options.Scope.Add("offline_access"); }) // Register named frontends .AddFrontends( new BffFrontend(BffFrontendName.Parse("main-app")) .WithCdnIndexHtmlUrl(new Uri("https://cdn.example.com/app/index.html")), new BffFrontend(BffFrontendName.Parse("admin-app")) .MapToPath("/admin") .WithOpenIdConnectOptions(opt => { // Admin frontend uses a different client ID opt.ClientId = "admin-client"; opt.ClientSecret = "admin-secret"; }) .WithCdnIndexHtmlUrl(new Uri("https://cdn.example.com/admin/index.html")) ); ``` ## General [Section titled “General”](#general) * **`EnforceBffMiddleware`** Enables checks in the user management endpoints that ensure that the BFF middleware has been added to the pipeline. Since the middleware performs important security checks, this protects from accidental configuration errors. You can disable this check if it interferes with some custom logic you might have. Defaults to true. * **`LicenseKey`** This sets the license key for Duende.BFF. A license key is required for production deployments. See [licensing](/general/licensing/) for details about how to configure the license key. * **`AnonymousSessionResponse`** (added in 2.0) This sets the response status code behavior on the [user endpoint](/bff/fundamentals/session/management/user/) to either return 401 or 200 with a *null* payload when the user is anonymous. * **`DiagnosticsEnvironments`** The ASP.NET environment names that enable the diagnostics endpoint. Defaults to “Development”. * **`BackChannelHttpHandler`** A HTTP message handler that’s used to configure backchannel communication. Typically used during testing. Configuring this will automatically configure the BackChannelHttpHandler property in *OpenIDConnectOptions* and also set it as the primary http message handler for retrieving the index.html. * **`AutomaticallyRegisterBffMiddleware`** (added in 4.0) When using BFF V4 with multiple frontends, several middlewares are automatically added to the pipeline (frontend selection, path mapping, OpenID Connect callbacks, management endpoints, and static file proxying). If you need full control over the middleware pipeline, set this to `false` and register the middleware manually: ```csharp builder.Services.AddBff(options => { options.AutomaticallyRegisterBffMiddleware = false; }); ``` When disabled, you must call `UseBffPreProcessing()` early in the pipeline (before authentication) and `UseBffPostProcessing()` at the end: ```csharp app.UseForwardedHeaders(); app.UseBffPreProcessing(); // Frontend selection, path mapping, OpenID callbacks app.UseAuthentication(); app.UseRouting(); app.UseBff(); // Anti-forgery checks app.UseAuthorization(); // map your endpoints here... app.UseBffPostProcessing(); // Management endpoints, remote API handling, static file proxying ``` You can also use the individual middleware methods for even more granular control: * `UseBffFrontendSelection()` — Selects the current frontend based on host/path matching * `UseBffPathMapping()` — Adjusts `PathBase` and `Path` for the selected frontend * `UseBffOpenIdCallbacks()` — Handles OpenID Connect callback requests * `UseBffAntiForgery()` — Validates anti-forgery headers (same as `UseBff()`) * `UseBffStaticFileProxying()` — Proxies static file requests to CDN or development server * **`StaticAssetsClientName`** If BFF is configured to automatically retrieve the `index.html`, or to proxy the static assets, it needs an HTTP client to do so. With this name, you can automatically configure this HTTP client in the `HttpClientFactory`. * **`AllowedSilentLoginReferers`** For silent login to work, you normally need to have the BFF backend and the frontend on the same origin. If you have a split host scenario, meaning the backend on a different origin (but same site) as the frontend, then you can use the referer header to differentiate which browser window to post the silent login results to. This array must then contain the list of allowed referer header values. ## Paths [Section titled “Paths”](#paths) * **`LoginPath`** Sets the path to the login endpoint. Defaults to */bff/login*. * **`SilentLoginPath`** Sets the path to the silent login endpoint. Defaults to */bff/silent-login*. * **`SilentLoginCallbackPath`** Sets the path to the silent login callback endpoint. Defaults to */bff/silent-login-callback*. * **`LogoutPath`** Sets the path to the logout endpoint. Defaults to */bff/logout*. * **`UserPath`** Sets the path to the user endpoint. Defaults to */bff/user*. * **`BackChannelLogoutPath`** Sets the path to the backchannel logout endpoint. Defaults to */bff/backchannel*. * **`DiagnosticsPath`** Sets the path to the diagnostics endpoint. Defaults to */bff/diagnostics*. ## Session Management [Section titled “Session Management”](#session-management) * **`ManagementBasePath`** Base path for management endpoints. Defaults to */bff*. * **`RequireLogoutSessionId`** Flag that specifies if the `sid` claim needs to be present in the logout request as query string parameter. Used to prevent cross site request forgery. Defaults to `true`. * **`RevokeRefreshTokenOnLogout`** Specifies if the user’s refresh token is automatically revoked at logout time. Defaults to `true`. * **`BackchannelLogoutAllUserSessions`** Specifies if during backchannel logout all matching user sessions are logged out. If `true`, all sessions for the subject will be revoked. If false, just the specific session will be revoked. Defaults to `false`. * **`~~EnableSessionCleanup~~`** (removed in V4) Indicates if expired server side sessions should be cleaned up. This requires an implementation of IUserSessionStoreCleanup to be registered in the ASP.NET Core service provider. Defaults to `false`. In V4, you need to opt into this value by calling `.AddSessionCleanupBackgroundProcess()` * **`SessionCleanupInterval`** Interval at which expired sessions are cleaned up. Defaults to *10 minutes*. ## APIs [Section titled “APIs”](#apis) * **`AntiForgeryHeaderName`** Specifies the name of the header used for anti-forgery header protection. Defaults to `X-CSRF`. * **`AntiForgeryHeaderValue`** Specifies the expected value of Anti-forgery header. Defaults to `1`. * **`DPoPJsonWebKey`** Specifies the Json Web Key to use when creating DPoP proof tokens. Defaults to null, which is appropriate when not using DPoP. * **`RemoveSessionAfterRefreshTokenExpiration`** Flag that specifies if a user session should be removed after an attempt to use a Refresh Token to acquire a new Access Token fails. This behavior is only triggered when proxying requests to remote APIs with TokenType.User or TokenType.UserOrClient. Defaults to True. * **`DisableAntiForgeryCheck`** (added in V4) A delegate that determines if the anti-forgery check should be disabled for a given request. The default is not to disable anti-forgery checks. ## CDN / Static Assets [Section titled “CDN / Static Assets”](#cdn--static-assets) * **`IndexHtmlDefaultCacheDuration`** (added in V4) If you use CDN Index HTML proxying (via `BffFrontend.CdnIndexHtmlUrl`), this controls how long the fetched `index.html` is cached. Defaults to *5 minutes*. ## Diagnostics [Section titled “Diagnostics”](#diagnostics) * **`Diagnostics`** (added in V4) Options that control the way that diagnostic data is logged. This is a nested options object with the following properties: * **`LogFrequency`** — Frequency at which diagnostic summaries are logged. Defaults to *1 hour*. * **`ChunkSize`** — Max size of diagnostic data log message chunks in bytes. Defaults to *8160 bytes* (8 KB minus 32 bytes for log message formatting overhead). ## BFF Blazor Server Options [Section titled “BFF Blazor Server Options”](#bff-blazor-server-options) In the Blazor Server, you configure the `BffBlazorServerOptions` by using the `AddBlazorServer` method. ```csharp builder.Services.AddBlazorServer(opt => { // configure options here.. }) ``` The following options are available: * **`ServerStateProviderPollingInterval`** The delay, in milliseconds, between polling requests by the BffServerAuthenticationStateProvider to the /bff/user endpoint. Defaults to 5000 ms. ## BFF Blazor Client Options [Section titled “BFF Blazor Client Options”](#bff-blazor-client-options) In WASM, you configure the `BffBlazorClientOptions` using the `AddBffBlazorClient` method: ```csharp builder.Services.AddBffBlazorClient(opt => { // configure options here... }) ``` The following options are available: * **`RemoteApiPath`** The base path to use for remote APIs. * **`RemoteApiBaseAddress`** The base address to use for remote APIs. If unset (the default), the blazor hosting environment’s base address is used. * **`StateProviderBaseAddress`** The base address to use for the state provider’s calls to the /bff/user endpoint. If unset (the default), the blazor hosting environment’s base address is used. * **`WebAssemblyStateProviderPollingDelay`** The delay, in milliseconds, before the BffClientAuthenticationStateProvider will start polling the /bff/user endpoint. Defaults to 1000 ms. * **`WebAssemblyStateProviderPollingInterval`** The delay, in milliseconds, between polling requests by the BffClientAuthenticationStateProvider to the /bff/user endpoint. Defaults to 5000 ms. ## Proxy Servers and Load Balancers v4.0 [Section titled “Proxy Servers and Load Balancers ”v4.0](#proxy-servers-and-load-balancers) When your BFF is hosted behind another reverse proxy or load balancer, you’ll want to use `X-Forwarded-*` headers. BFF automatically registers the `ForwardedHeaders` middleware in the pipeline, without any additional configuration. You will need to configure which headers should be considered by the middleware, typically the `X-Forwarded-For` and `X-Forwarded-Proto` headers. Here’s an example of how you can configure this. Program.cs ```csharp builder.Services.Configure(options => { // Consider configuring the 'KnownProxies' and the 'AllowedHosts' to prevent IP spoofing attacks options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); ``` See [proxy servers and load balancers](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-9.0) in the Microsoft documentation for more information. ----- # Authentication & Session Management > Learn how BFF manages authentication sessions — from the initial OIDC login to server-side session storage, token lifecycle, and back-channel logout. Authentication in a BFF application flows through several layers. Understanding how these layers connect helps you configure sessions correctly and debug problems when they arise. ## How Sessions Work [Section titled “How Sessions Work”](#how-sessions-work) ``` sequenceDiagram participant Browser participant BFF participant IdP as Identity Provider Browser->>BFF: GET /bff/login BFF->>IdP: OIDC Authorization Request IdP-->>Browser: Login UI Browser->>IdP: Credentials IdP-->>BFF: Authorization Code BFF->>IdP: Token Request IdP-->>BFF: Access Token + Refresh Token + ID Token BFF->>BFF: Store tokens in session BFF-->>Browser: Set-Cookie (session cookie) Note over Browser,BFF: Session established Browser->>BFF: GET /api/data (with cookie) BFF->>BFF: Validate session BFF->>BFF: Get/refresh access token BFF-->>Browser: API response ``` ### The Session Cookie [Section titled “The Session Cookie”](#the-session-cookie) After a successful login, BFF sets an **HttpOnly, Secure, SameSite** cookie in the browser. This cookie is the browser’s proof of session — it is sent automatically on every subsequent request to the BFF host. The cookie itself is signed and encrypted by ASP.NET Core’s data protection stack. The browser never has access to the access token or refresh token. These are stored server-side. ### Cookie-Based vs. Server-Side Sessions [Section titled “Cookie-Based vs. Server-Side Sessions”](#cookie-based-vs-server-side-sessions) By default, BFF stores session state (including tokens) inside the encrypted cookie. This works but has limitations: | | Cookie-Based (default) | Server-Side Sessions | | --------------------------- | ---------------------------------------------------- | ---------------------------------- | | **Token storage** | Inside the encrypted cookie | Server-side store (DB, memory) | | **Cookie size** | Grows with claims/tokens — can hit browser 4KB limit | Fixed small size (session ID only) | | **Server-initiated logout** | Not possible | ✅ Possible | | **Back-channel logout** | Not supported | ✅ Supported | | **Session visibility** | None | ✅ Query all active sessions | | **Scale-out** | Cookie encryption keys must be shared | Session store must be shared | ### Token Lifecycle [Section titled “Token Lifecycle”](#token-lifecycle) Tokens stored in the session are managed automatically: 1. **Access token** — When an API call is made through the BFF, the access token is retrieved from the session. If it is expired or close to expiring, BFF automatically refreshes it using the refresh token. 2. **Refresh token** — Stored server-side (in the session). Revoked automatically at logout. 3. **ID token** — Used during logout to send a `id_token_hint` to the identity provider. See [Token Management](/bff/fundamentals/tokens/) for how to access tokens programmatically. ## Management Endpoints [Section titled “Management Endpoints”](#management-endpoints) The BFF exposes several HTTP endpoints for managing the user’s session. These endpoints are called by the frontend to trigger authentication flows or query session state. | Endpoint | Default Path | Purpose | | ------------------- | ------------------- | --------------------------------------------- | | Login | `/bff/login` | Start the OIDC authentication flow | | Logout | `/bff/logout` | End the session and sign out | | User | `/bff/user` | Return current user claims and session state | | Silent Login | `/bff/silent-login` | Non-interactive login (deprecated in v4) | | Back-Channel Logout | `/bff/backchannel` | Receive server-to-server logout notifications | | Diagnostics | `/bff/diagnostics` | Show current tokens (development only) | ## In This Section [Section titled “In This Section”](#in-this-section) | Page | Description | | -------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | [Authentication Handlers](/bff/fundamentals/session/handlers/) | OIDC and cookie handler configuration | | [Server-Side Sessions](/bff/fundamentals/session/server-side-sessions/) | Persistent session storage with Entity Framework or custom stores | | [OIDC Prompts](/bff/fundamentals/session/oidc-prompts/) | Controlling interactive vs. silent authentication | | [Login Endpoint](/bff/fundamentals/session/management/login/) | How to trigger login from the frontend | | [Logout Endpoint](/bff/fundamentals/session/management/logout/) | How to trigger logout and CSRF protection | | [User Endpoint](/bff/fundamentals/session/management/user/) | Reading user claims and session state | | [Back-Channel Logout](/bff/fundamentals/session/management/back-channel-logout/) | Server-initiated session termination | | [Silent Login](/bff/fundamentals/session/management/silent-login/) | Non-interactive login (deprecated) | | [Diagnostics](/bff/fundamentals/session/management/diagnostics/) | Development-time token inspection | ## See Also [Section titled “See Also”](#see-also) [IdentityServer Configuration ](/identityserver/configuration/)Configure the identity provider your BFF authenticates against [IdentityServer Clients ](/identityserver/fundamentals/clients/)Register your BFF as a confidential client [IdentityServer Server-Side Sessions ](/identityserver/ui/server-side-sessions/)Coordinate logout across all components [Access Token Management ](/accesstokenmanagement/)How tokens are refreshed when sessions are active [Troubleshooting ](/bff/troubleshooting/)Common session and authentication issues ----- # ASP.NET Core Authentication System > Learn how to configure and use ASP.NET Core authentication handlers for OpenID Connect and cookie-based session management in BFF applications To configure authentication in the BFF, you’ll need to configure both the OpenID Connect login flow and the cookie handlers. ## Automatic Authentication Configuration V4 [Section titled “Automatic Authentication Configuration ”](#automatic-authentication-configuration) In V4, a simplified mechanism for wiring up authentication has been introduced. The main purpose for the BFF is to handle the OpenID Connect login flow and to protect the APIs using Cookies. In V3, you explicitly had to configure the ASP.NET Core authentication system to enable this. In V4, this is now simplified. A call `BffBuilder.ConfigureOpenIdConnect()` will make sure that: 1. The authentication pipeline is configured with the appropriate authentication schemes. 2. The OpenID Connect pipeline is configured with default values. 3. The CookieHandler is configured using recommended practices. This can be tweaked by calling `BffBuilder.ConfigureCookies()` Below is an example on how to configure the BFF’s authentication pipeline. ```csharp services.AddBff() .ConfigureOpenIdConnect(options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }); ``` Each frontend can have custom OpenID Connect configuration and Cookie Configuration. This can both be configured programmatically via [Configuration](/bff/fundamentals/multi-frontend/configuration/). ## Manually Configuring Authentication [Section titled “Manually Configuring Authentication”](#manually-configuring-authentication) You typically use the following two ASP.NET Core authentication handlers to implement remote authentication: * the OpenID Connect authentication handler to interact with the remote OIDC / OAuth token service, e.g. Duende IdentityServer * the cookie handler to do local session management The BFF relies on the configuration of the ASP.NET Core default authentication schemes. Both the OpenID Connect authentication handler and cookie handler need to be configured, with the ASP.NET Core authentication system default schemes specified: * `DefaultScheme` should be the cookie handler, so the BFF can do local session management; * `DefaultChallengeScheme` should be the OpenID Connect handler, so the BFF defaults to remote authentication; * `DefaultSignOutScheme` should be the OpenID Connect handler, so the BFF uses remote sign-out. A minimal configuration looks like this: ```csharp builder.Services.AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("cookie", options => { // ... }) .AddOpenIdConnect("oidc", options => { // ... }); ``` Now let’s look at some more details! ### The OpenID Connect Authentication Handler [Section titled “The OpenID Connect Authentication Handler”](#the-openid-connect-authentication-handler) The OpenID Connect (OIDC) handler connects the application to the authentication / access token system. It can be configured to use any OpenID Connect provider: [Duende IdentityServer](https://duendesoftware.com/products/identityserver/), [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id), [Auth0](https://auth0.com/), [Google Cloud Identity Platform](https://cloud.google.com/identity-platform), [Amazon Cognito](https://aws.amazon.com/cognito/), and more. The exact settings to use depend on the OIDC provider and its configuration settings. We recommend to: * use authorization code flow with PKCE * use a `response_mode` of `query` since this plays nicer with `SameSite` cookies * use a strong client secret. Since the BFF can be a confidential client, it is possible to use strong client authentication like JWT assertions, JAR, or mTLS. Shared secrets work as well. * turn off inbound claims mapping * save the tokens into the authentication session so they can be automatically managed * request a refresh token using the `offline_access` scope ```csharp builder.Services.AddAuthentication().AddOpenIdConnect("oidc", options => { options.Authority = "https://demo.duendesoftware.com"; // confidential client using code flow + PKCE options.ClientId = "spa"; options.ClientSecret = "secret"; options.ResponseType = "code"; // query response type is compatible with strict SameSite mode options.ResponseMode = "query"; // get claims without mappings options.MapInboundClaims = false; options.GetClaimsFromUserInfoEndpoint = true; // save tokens into authentication session // to enable automatic token management options.SaveTokens = true; // request scopes options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("API"); // and refresh token options.Scope.Add("offline_access"); }); ``` The OIDC handler will use the default sign-in handler (the cookie handler) to establish a session after successful validation of the OIDC response. ### The Cookie Handler [Section titled “The Cookie Handler”](#the-cookie-handler) The cookie handler is responsible for establishing the session and manage authentication session related data. Things to consider: * determine the session lifetime and if the session lifetime should be sliding or absolute * it is recommended to use a cookie name [prefix](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.3) if compatible with your application * use the highest available `SameSite` mode that is compatible with your application, e.g. `strict`, but at least `lax` ```csharp builder.Services.AddAuthentication().AddCookie("cookie", options => { // set session lifetime options.ExpireTimeSpan = TimeSpan.FromHours(8); // sliding or absolute options.SlidingExpiration = false; // host prefixed cookie name options.Cookie.Name = "__Host-spa"; // strict SameSite handling options.Cookie.SameSite = SameSiteMode.Strict; }); ``` ### Choosing Between SameSite.Lax and SameSite.Strict [Section titled “Choosing Between SameSite.Lax and SameSite.Strict”](#choosing-between-samesitelax-and-samesitestrict) The [SameSite cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value) is a feature of modern browsers that restricts cookies so that they are only sent to pages originating from the [site](https://developer.mozilla.org/en-US/docs/Glossary/Site) where the cookie was originally issued. This prevents CSRF attacks and helps with improving privacy, because cross-site requests will no longer implicitly include the user’s credentials. If you configure `SameSiteMode.Strict`, this means that if a user originates from an external site and is redirected or linked to the BFF application, then the authentication cookie is not sent automatically. So, the application will consider the user to be not logged in, even though there may be a valid authentication cookie in the cookie jar. If the user refreshes the page, or visits a link on your site that forces a complete page reload, then the authentication cookie will be sent along normally again. This also happens when you have an identity provider that’s hosted on a different site than the BFF, in combination with `SameSiteMode.Strict`. After successful authentication at the IdP, the user will be redirected back to the BFF site. The server will then place an authentication cookie in the browser, but the browser will not automatically include it in subsequent requests until the full page is manually reloaded by the user. This means the user appears to still be logged out, even though the cookie is there. So, if you have an Identity Provider that’s hosted under a different site than your BFF, you may want to configure your cookie policy to be `SameSiteMode.Lax`. ----- # BFF Session Management Endpoints > Overview of Duende.BFF endpoints for session management operations including login, logout, and user information retrieval Duende.BFF adds endpoints for performing typical session-management operations such as triggering login and logout and getting information about the currently logged-on user. These endpoint are meant to be called by the frontend. In addition, Duende.BFF adds an implementation of the OpenID Connect back-channel notification endpoint to overcome the restrictions of third party cookies in front-channel notification in modern browsers. You enable the endpoints by adding the relevant services into the ASP.NET Core service provider: Program.cs ```csharp // Add BFF services to DI - also add server-side session management builder.Services.AddBff(options => { // default value options.ManagementBasePath = "/bff"; }; ``` Starting with BFF v4, the BFF automatically wires up the management endpoints. If you disable this behavior (using `AutomaticallyRegisterBffMiddleware`), this is how you can map the management endpoints: Program.cs ```csharp var app = builder.Build(); // Preprocessing pipeline, which would have been automatically added to start of the request the pipeline. app.UseBffPreProcessing(); // Your logic, such as: app.UseRouting(); app.UseBff(); // post processing pipeline that would have been automatically added to the end of the request pipeline. app.UseBffPostProcessing(); app.Run(); ``` The `UsePreprocessing` method adds all handling for multiple frontend support. Alternatively, you can call these methods direct: ```csharp app.UseBffFrontendSelection(); app.UseBffPathMapping(); app.UseBffOpenIdCallbacks(); ``` `UseBffPostProcessing` adds all BFF management endpoints and handlers for proxying `index.html`. You can also map each endpoint individually by calling the various `MapBffManagementXxxEndpoint` methods, for example `endpoints.MapBffManagementLoginEndpoint()`. The following pages describe the default behavior of the management endpoints. See the [extensibility](/bff/extensibility) section for information about how to customize the behavior of the endpoints. ----- # BFF Back-Channel Logout Endpoint > Documentation for the OpenID Connect Back-Channel Logout endpoint implementation in BFF, enabling server-to-server session termination without browser involvement. The */bff/backchannel* endpoint is an implementation of the [OpenID Connect Back-Channel Logout](https://openid.net/specs/openid-connect-backchannel-1_0.html) specification. The remote identity provider can use this endpoint to end the BFF’s session via a server to server call, without involving the user’s browser. This design avoids problems with 3rd party cookies associated with front-channel logout. ## Typical Usage [Section titled “Typical Usage”](#typical-usage) The back-channel logout endpoint is invoked by the remote identity provider when it determines that sessions should be ended. IdentityServer will send back-channel logout requests if you [configure](/identityserver/reference/v8/models/client/#authentication--session-management) your client’s `BackChannelLogoutUri`. When a session ends at IdentityServer, any client that was participating in that session that has a back-channel logout URI configured will be sent a back-channel logout request. This typically happens when another application signs out. [Expiration](/identityserver/ui/server-side-sessions/session-expiration/) of [IdentityServer server side sessions](/identityserver/ui/server-side-sessions/) can also be configured to send back-channel logout requests, though this is disabled by default. ## Dependencies [Section titled “Dependencies”](#dependencies) The back-channel logout endpoint depends on [server-side sessions in the BFF](/bff/fundamentals/session/server-side-sessions/), which must be enabled to use this endpoint. Note that such server-side sessions are distinct from server-side sessions in IdentityServer. ## Revoke All Sessions [Section titled “Revoke All Sessions”](#revoke-all-sessions) Back-channel logout tokens include a sub (subject ID) and sid (session ID) claim to describe which session should be revoked. By default, the back-channel logout endpoint will only revoke the specific session for the given subject ID and session ID. Alternatively, you can configure the endpoint to revoke every session that belongs to the given subject ID by setting the `BackchannelLogoutAllUserSessions` [option](/bff/fundamentals/options/#session-management) to true. ## Customize This Endpoint [Section titled “Customize This Endpoint”](#customize-this-endpoint) To add custom request processing logic or customize session revocation behavior, see [Back-Channel Logout Endpoint Extensibility](/bff/extensibility/management/back-channel-logout/). ----- # BFF Diagnostics Endpoint > Learn about the BFF diagnostics endpoint that provides access to user and client access tokens for development testing purposes. The `/bff/diagnostics` endpoint returns the current user and client access token for testing purposes. The endpoint tries to retrieve and show current tokens. It may invoke both a refresh token flow for the user access token and a client credential flow for the client access token. To use the diagnostics endpoint, make a `GET` request to `/bff/diagnostics`. Typically, this is done in a browser to diagnose a problem during development. ## Customize This Endpoint [Section titled “Customize This Endpoint”](#customize-this-endpoint) To add custom logic to the diagnostics endpoint, see [Diagnostics Endpoint Extensibility](/bff/extensibility/management/diagnostics/). ----- # BFF Login Endpoint > Learn how to initiate authentication and handle return URLs using the BFF login endpoint in your frontend applications The */bff/login* endpoint begins the authentication process. To use it, typically javascript code will navigate away from the frontend application to the login endpoint: ```js window.location = "/login"; ``` In Blazor, instead use the `NavigationManager` to navigate to the login endpoint: ```csharp Navigation.NavigateTo($"bff/login", forceLoad: true); ``` The login endpoint triggers an authentication challenge using the default challenge scheme, which will typically use the OpenID Connect [handler](/bff/fundamentals/session/handlers/). ## Return Url [Section titled “Return Url”](#return-url) After authentication is complete, the login endpoint will redirect back to your front end application. By default, this redirect goes to the root of the application. You can use a different URL instead by including a local URL as the *returnUrl* query parameter. ```js window.location = "/login?returnUrl=/logged-in"; ``` ## Customize This Endpoint [Section titled “Customize This Endpoint”](#customize-this-endpoint) To add custom logic before or after the login endpoint processes a request, see [Login Endpoint Extensibility](/bff/extensibility/management/login/). ----- # BFF Logout Endpoint > Learn how to use the BFF logout endpoint to sign out users and handle CSRF protection in your application The */bff/logout* endpoint signs out of the appropriate ASP.NET Core [authentication schemes](/bff/fundamentals/session/handlers/) to both delete the BFF’s session cookie and to sign out from the remote identity provider. To use the logout endpoint, typically your javascript code will navigate away from your front end to the logout endpoint, similar to the login endpoint. However, unlike the login endpoint, the logout endpoint requires CSRF protection, otherwise an attacker could destroy sessions by making cross-site GET requests. The session id is used to provide this CSRF protection by requiring it as a query parameter to the logout endpoint (assuming that a session id was included during login). For convenience, the correct logout url is made available as a claim in the */bff/user* endpoint, making typical logout usage look like this: ```js var logoutUrl = userClaims["bff:logout_url"]; // assumes userClaims is the result of a call to /bff/user window.location = logoutUrl; ``` ## Return Url [Section titled “Return Url”](#return-url) After signout is complete, the logout endpoint will redirect back to your front end application. By default, this redirect goes to the root of the application. You can use a different URL instead by including a local URL as the *returnUrl* query parameter. ```js var logoutUrl = userClaims["bff:logout_url"]; window.location = `${logoutUrl}&returnUrl=/logged-out`; ``` ## Revocation Of Refresh Tokens [Section titled “Revocation Of Refresh Tokens”](#revocation-of-refresh-tokens) If the user has a refresh token, the logout endpoint can revoke it. This is enabled by default because revoking refresh tokens that will not be used anymore is generally good practice. Normally any refresh tokens associated with the current session won’t be used after logout, as the session where they are stored is deleted as part of logout. However, you can disable this revocation with the `RevokeRefreshTokenOnLogout` option. ## Customize This Endpoint [Section titled “Customize This Endpoint”](#customize-this-endpoint) To add custom logic before or after the logout endpoint processes a request, see [Logout Endpoint Extensibility](/bff/extensibility/management/logout/). ----- # BFF Silent Login Endpoint > Endpoint for non-interactive authentication using an existing session at the remote identity provider **Added in v1.2.0.** The */bff/silent-login* endpoint triggers authentication similarly to the login endpoint, but in a non-interactive way. The expected usage pattern is that the application code loads in the browser and triggers a request to the *User Endpoint*. If that indicates that there is no BFF session, then the *Silent Login Endpoint* can be requested to attempt to automatically log the user in, using an existing session at the remote identity provider. This non-interactive design relies upon the use of an *iframe* to make the silent login request. The result of the silent login request in the *iframe* will then use *postMessage* to notify the parent window of the outcome. If the result is that a session has been established, then the application logic can either re-trigger a call to the *User Endpoint*, or reload the entire page (depending on the preferred design). If the result is that a session has not been established, then the application redirects to the login endpoint to log the user in interactively. To trigger the silent login, the application code must have an *iframe* and then set its *src* to the silent login endpoint. For example in your HTML: ```html ``` And then in JavaScript: ```javascript document.querySelector('#bff-silent-login').src = '/bff/silent-login'; ``` To receive the result, the application should handle the *message* event in the browser and look for the *data.isLoggedIn* property on the event object: ```javascript window.addEventListener("message", e => { if (e.data && e.data.source === 'bff-silent-login' && e.data.isLoggedIn) { // we now have a user logged in silently, so reload this window window.location.reload(); } }); ``` ## Customize This Endpoint [Section titled “Customize This Endpoint”](#customize-this-endpoint) To add custom logic to the silent login endpoint, see [Silent Login Endpoint Extensibility](/bff/extensibility/management/silent-login/). ----- # BFF User Endpoint > The BFF user endpoint provides information about the currently authenticated user and their session status The */bff/user* endpoint returns data about the currently logged-on user and the session. It is typically invoked at application startup to check if the user has authenticated, and if so, to get profile data about the user. It can also be used to periodically query if the session is still valid. ## Output [Section titled “Output”](#output) If there is no current session, the user endpoint returns a response indicating that the user is anonymous. By default, this is a 401 status code, but this can be [configured](#anonymous-session-response-option). If there is a current session, the user endpoint returns a JSON array containing the claims in the ASP.NET Core authentication session and several BFF specific claims. For example: ```json [ { "type": "sid", "value": "173E788068FFB728806501F4F46C52D6" }, { "type": "sub", "value": "88421113" }, { "type": "idp", "value": "local" }, { "type": "name", "value": "Bob Smith" }, { "type": "bff:logout_url", "value": "/logout?sid=173E788068FFB728806501F4F46C52D6" }, { "type": "bff:session_expires_in", "value": 28799 }, { "type": "bff:session_state", "value": "q-Hl1V9a7FCZE5o-vH9qpmyVKOaeVfMQBUJLrq-lDJU.013E58C33C7409C6011011B8291EF78A" } ] ``` ## User Claims [Section titled “User Claims”](#user-claims) Since the user endpoint returns the claims that are in the ASP.NET Core session, anything that changes the session will be reflected in its output. You can customize the contents of the session via the OpenID Connect handler’s [ClaimAction](https://docs.microsoft.com/en-us/dotnet/API/microsoft.aspnetcore.authentication.claimactioncollectionmapextensions?view=aspnetcore-7.0) infrastructure, or by using [claims transformation](https://docs.microsoft.com/en-us/dotnet/API/microsoft.aspnetcore.authentication.iclaimstransformation?view=aspnetcore-7.0). For example, if you add a [claim](/identityserver/fundamentals/claims/) to the [userinfo endpoint](/identityserver/reference/v8/endpoints/userinfo/) at IdentityServer that you would like to include in the */bff/user* endpoint, you need to add a corresponding ClaimAction in the BFF’s OpenID Connect Handler to include the claim in the BFF’s session. ## Management Claims [Section titled “Management Claims”](#management-claims) In addition to the claims in the ASP.NET Core Session, Duende.BFF adds three additional claims: **bff:session\_expires\_in** This is the number of seconds the current session will be valid for. **bff:session\_state** This is the session state value of the upstream OIDC provider that can be used for the JavaScript *check\_session* mechanism (if provided). **bff:logout\_url** This is the URL to trigger logout. If the upstream provider includes a `sid` claim, the BFF logout endpoint requires this value as a query string parameter for CSRF protection. This behavior can be configured with the `RequireLogoutSessionId` in the [options](/bff/fundamentals/options/). ## Typical Usage [Section titled “Typical Usage”](#typical-usage) To use the endpoint, make an HTTP GET request to it from your frontend javascript code. For example, your application could use the fetch API to make requests to the user endpoint like this: session.js ```js var req = new Request("/bff/user", { headers: new Headers({ "X-CSRF": "1", }), }); var resp = await fetch(req); if (resp.ok) { userClaims = await resp.json(); console.log("user logged in", userClaims); } else if (resp.status === 401) { console.log("user not logged in"); } ``` ## Cross-Site Request Forgery [Section titled “Cross-Site Request Forgery”](#cross-site-request-forgery) To protect against cross-site request forgery, you need to add a [static header](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#use-of-custom-request-headers) to the GET request. The header’s name and required value can be configured in the [options](/bff/fundamentals/options/). ## Anonymous Session Response Option [Section titled “Anonymous Session Response Option”](#anonymous-session-response-option) The `AnonymousSessionResponse` option allows you to change the behavior of the user endpoint to return 200 instead of 401 when the user is anonymous. If `AnonymousSessionResponse` is set to `AnonymousSessionResponse.Response200`, then the endpoint’s response will set its status code to 200 and its payload will contain the literal `null` (the response body will be the characters ‘n’, ‘u’, ‘l’, ‘l’ without quotes). ## Cookie Sliding [Section titled “Cookie Sliding”](#cookie-sliding) If your ASP.NET Core session cookie is configured to use a sliding expiration, you need to be able to query the session state without extending the session’s lifetime; a periodic check for user activity shouldn’t itself count as user activity. To prevent the call to the user endpoint from sliding the cookie, add the *slide=false* parameter to the request. site.js ```js var req = new Request("/bff/user?slide=false", { headers: new Headers({ "X-CSRF": "1", }), }); ``` ## Customize This Endpoint [Section titled “Customize This Endpoint”](#customize-this-endpoint) To add custom logic, enrich user claims, or change the claims returned by this endpoint, see [User Endpoint Extensibility](/bff/extensibility/management/user/). ----- # OpenID Connect Prompts > OpenID Connect prompt support in Duende BFF V4 OpenID Connect supports a `prompt` parameter that can be used to control the user experience as it relates to the current authentication session. Duende BFF v4 supports this parameter by forwarding it to the backing identity provider to allow for more fine-grained control during unique client interactions. This documentation outlines the `prompt` parameter support and what values you might use to achieve different outcomes. ## Prompt parameter options [Section titled “Prompt parameter options”](#prompt-parameter-options) The [OpenID Connect specification](https://openid.net/specs/openid-connect-core-1_0.html) defines an **optional** `prompt` parameter that can be used to control the user experience as it relates to the current authentication session. The following values are supported: | value | description | | ---------------- | ------------------------------------------------------------------------------------------------- | | `none` | Must not display any authentication or consent user interface | | `login` | Should prompt the user to reauthenticate | | `consent` | Should prompt the user for consent | | `select_account` | Should prompt user to choose an account given their are multiple accounts for the current session | These values can be passed to the BFF by adding them to the `prompt` query parameter to the login request URL. For example, the following request would prompt the user to reauthenticate: ```http /bff/login?prompt=login ``` The inclusion of the `prompt` parameter in the login request URL will cause the BFF to forward it to the backing identity provider at which point the identity provider will determine the appropriate user experience based on the value of the `prompt` parameter. For example, if the `prompt` parameter is set to `login`, the identity provider will prompt the user to reauthenticate. ## Scenarios and Situations [Section titled “Scenarios and Situations”](#scenarios-and-situations) The `prompt` parameter can be used in situations where additional security is required, you want to reestablish the account identity, or a high-impact action is about to be taken. For example, the following hypothetical scenarios might require the use of the `prompt` parameter: * Attempting to transfer funds from a bank account to another * A destructive action such as deleting an account * Performing an action that alters a high-value account setting such as an email address ## Silent Login Deprecation (v3 to v4) [Section titled “Silent Login Deprecation (v3 to v4)”](#silent-login-deprecation-v3-to-v4) When migrating from Duende BFF v3 to v4, you may notice deprecation warnings regarding the [silent login](/bff/fundamentals/session/management/silent-login/) feature located at the management endpoint `/silent-login`. To resolve the warning, update the silent login URL in your frontend applications to point to the login endpoint instead, including the `prompt=none` query parameter: ```diff const silentLoginPath = '/bff/silent-login'; const silentLoginPath = '/bff/login?prompt=none'; ``` By default, BFF v4 [automatically registers the management endpoints](/bff/fundamentals/session/management/). In case you opted out of the automatic registration feature, you may still need to explicitly call `app.MapBffManagementSilentLoginEndpoints()` if you are manually mapping the management endpoints. ----- # Server-Side Sessions > Learn how to implement and configure server-side sessions in BFF to manage user session data storage and enable session revocation capabilities By default, ASP.NET Core’s cookie handler will store all user session data in a protected cookie. This works very well unless cookie size or revocation becomes an issue. Duende.BFF includes all the plumbing to store your sessions server-side. The cookie will then only be used to transmit the session ID between the browser and the BFF host. This has the following advantages * the cookie size will be very small and constant - regardless how much data (e.g. token or claims) is stored in the authentication session * the session can be also revoked outside the context of a browser interaction, for example when receiving a back-channel logout notification from the upstream OpenID Connect provider ## Configuring Server-Side Sessions [Section titled “Configuring Server-Side Sessions”](#configuring-server-side-sessions) Server-side sessions can be enabled in the application’s startup: ```csharp builder.Services.AddBff() .AddServerSideSessions(); ``` The default implementation stores the session in-memory. This is useful for testing, but for production you typically want a more robust storage mechanism. We provide an implementation of the session store built with EntityFramework (EF) that can be used with any database with an EF provider (e.g. Microsoft SQL Server). You can also use a custom store. See [extensibility](/bff/extensibility/sessions/#user-session-store) for more information. ## Using Entity Framework for the Server-Side Session Store [Section titled “Using Entity Framework for the Server-Side Session Store”](#using-entity-framework-for-the-server-side-session-store) To use the EF session store, install the `Duende.BFF.EntityFramework` NuGet package: ```bash dotnet add package Duende.BFF.EntityFramework ``` Next, you can register the session store by calling `AddEntityFrameworkServerSideSessions`, like this: ```csharp var cn = _configuration.GetConnectionString("db"); builder.Services.AddBff() .AddEntityFrameworkServerSideSessions(options=> { options.UseSqlServer(cn); }); ``` The method of `AddEntityFrameworkServerSideSessions` registers the `SessionDbContext` along with a `UserSessionStore` as transient dependencies. For developers looking to take advantage of DbContext pooling or have more fine-grained control over their DbContext creation registration and creation process, you can use the `AddEntityFrameworkServerSideSessionsServices` method instead. This method registers all the required services for server-side session except for the `SessionDbContext`, which will now be managed by the DbContext pooling mechanism. ```csharp var cn = _configuration.GetConnectionString("db"); builder.Services.AddDbContextPool(opt => { // configure your db context pool options here options.UseSqlServer(cn); }); builder.Services.AddBff() .AddEntityFrameworkServerSideSessionsServices() ``` Note, you’ll still need to let the server side session store know about the `SessionDbContext` by calling `AddEntityFrameworkServerSideSessions` with the `SessionDbContext` implementation as a generic argument. ### Entity Framework Migrations [Section titled “Entity Framework Migrations”](#entity-framework-migrations) Most data stores that you might use with Entity Framework use a schema to define the structure of their data. `Duende.BFF.EntityFramework` doesn’t make any assumptions about the underlying datastore, how (or indeed even if) it defines its schema, or how schema changes are managed by your organization. For these reasons, Duende does not directly support database creation, schema changes, or data migration by publishing database scripts. You are expected to manage your database in the way your organization sees fit. Using EF migrations is one possible approach to that, which Duende facilitates by publishing entity classes in each version of `Duende.BFF.EntityFramework`. An example project that uses those entities to create migrations is [here](https://github.com/DuendeSoftware/products/tree/main/bff/migrations/UserSessionDb). To quickly create Entity Framework migrations, run the following command in the project directory that has access to Entity Framework Core’s tools: ```bash dotnet ef migrations add UserSessions -o Migrations -c SessionDbContext ``` The project must also reference the `Duende.BFF.EntityFramework` NuGet package and the `Microsoft.EntityFrameworkCore.Design` NuGet package, along with a specific database provider and its corresponding configuration, including the connection string. ## Session Store Cleanup [Section titled “Session Store Cleanup”](#session-store-cleanup) Added in v1.2.0. Abandoned sessions will remain in the store unless something removes the stale entries. * V4 If you wish to have such sessions cleaned up periodically, then you can add the session cleanup host and configure the `SessionCleanupInterval` options: Program.cs ```csharp builder.Services.AddBff(options => { options.SessionCleanupInterval = TimeSpan.FromMinutes(5); }) .AddServerSideSessions(); ``` This requires an implementation of [`IUserSessionStoreCleanup`](/bff/extensibility/sessions#user-session-store-cleanup) in the ASP.NET Core service provider. If using Entity Framework Core, then the `IUserSessionStoreCleanup` implementation is provided for you when you use `AddEntityFrameworkServerSideSessions`. You can then add the `SessionCleanupBackgroundProcess`: Program.cs ```csharp var cn = _configuration.GetConnectionString("db"); builder.Services.AddBff() .AddEntityFrameworkServerSideSessions(options => { options.UseSqlServer(cn); }) .AddSessionCleanupBackgroundProcess(); ``` * V3 If you wish to have such sessions cleaned up periodically, then you can configure the `EnableSessionCleanup` and `SessionCleanupInterval` options: Program.cs ```csharp builder.Services.AddBff(options => { options.EnableSessionCleanup = true; options.SessionCleanupInterval = TimeSpan.FromMinutes(5); }) .AddServerSideSessions(); ``` This requires an implementation of [`IUserSessionStoreCleanup`](/bff/extensibility/sessions#user-session-store-cleanup) in the ASP.NET Core service provider. If using Entity Framework Core, then the `IUserSessionStoreCleanup` implementation is provided for you when you use `AddEntityFrameworkServerSideSessions`. Just enable session cleanup: Program.cs ```csharp var cn = _configuration.GetConnectionString("db"); builder.Services.AddBff(options => { options.EnableSessionCleanup = true; }) .AddEntityFrameworkServerSideSessions(options => { options.UseSqlServer(cn); }); ``` ----- # Token Management > Learn how to manage and utilize access tokens in BFF applications for secure API communication Duende.BFF includes an automatic token management feature. This uses the access and refresh token stored in the authentication session to always provide a current access token for outgoing API calls. For most scenarios, there is no additional configuration necessary. The token management will infer the configuration and token endpoint URL from the metadata of the OpenID Connect provider. The easiest way to retrieve the current access token is to use an extension method on `HttpContext`: ```csharp var token = await HttpContext.GetUserAccessTokenAsync(); ``` You can then use the token to set it on an `HttpClient`instance: ```csharp var client = new HttpClient(); client.SetBearerToken(token); ``` We recommend to use the `HttpClientFactory` to create HTTP clients that are already aware of the token management plumbing. For this you would register a named client in your application startup e.g. like this: Program.cs ```csharp // registers HTTP client that uses the managed user access token builder.Services.AddUserAccessTokenHttpClient("apiClient", configureClient: client => { client.BaseAddress = new Uri("https://remoteServer/"); }); ``` And then retrieve a client instance like this: ```csharp app.MapGet("/myApi", async (IHttpClientFactory httpClientFactory, HttpContext context) => { // create HTTP client with automatic token management var client = httpClientFactory.CreateClient("apiClient"); // call remote API var response = await client.GetAsync("remoteApi"); // rest omitted }); ``` If you prefer to use typed clients, you can do that as well: ```csharp // registers a typed HTTP client with token management support services.AddHttpClient(client => { client.BaseAddress = new Uri("https://remoteServer/"); }).AddUserAccessTokenHandler(); ``` And then use that client, for example like this on an endpoint: ```csharp app.MapGet("/myApi", async (MyTypedClient client) => { var response = await client.GetData(); // rest omitted }); ``` The client will internally always try to use a current and valid access token. If for any reason this is not possible, the 401 status code will be returned to the caller. ### Reuse of Refresh Tokens [Section titled “Reuse of Refresh Tokens”](#reuse-of-refresh-tokens) We recommend that you configure IdentityServer to issue reusable refresh tokens to BFF clients. Because the BFF is a confidential client, it does not need one-time use refresh tokens. Reusable refresh tokens are desirable because they avoid performance and user experience problems associated with one time use tokens. See the discussion on [rotating refresh tokens](/identityserver/tokens/refresh/) and the [OAuth 2.0 Security Best Current Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.2.2) for more details. ### Manually revoking refresh tokens [Section titled “Manually revoking refresh tokens”](#manually-revoking-refresh-tokens) Duende.BFF revokes refresh tokens automatically at logout time. This behavior can be disabled with the *RevokeRefreshTokenOnLogout* option. If you want to manually revoke the current refresh token, you can use the following code: ```csharp await HttpContext.RevokeUserRefreshTokenAsync(); ``` This will invalidate the refresh token at the token service. ## See Also [Section titled “See Also”](#see-also) * [Access Token Management](/accesstokenmanagement/) — The `Duende.AccessTokenManagement` library that powers BFF token refresh * [User Token Management](/accesstokenmanagement/web-apps/) — Detailed user token lifecycle documentation * [Client Credential Tokens](/accesstokenmanagement/workers/) — Machine-to-machine token management * [IdentityServer Refresh Tokens](/identityserver/tokens/refresh/) — Configuring refresh token rotation and reuse * [IdentityServer Client Configuration](/identityserver/configuration/dcr/) — Setting up confidential BFF clients * [Server-Side Sessions](/bff/fundamentals/session/server-side-sessions/) — Where tokens are stored server-side ----- # Getting started > A collection of getting started guides to start with the BFF Currently, the most recent version is v4. If you’re upgrading from a previous version, please check our [upgrade guides](/bff/upgrading). If you’re starting a new BFF project, consider the following startup guides: * [Single frontend BFF](/bff/getting-started/single-frontend/) * [Multi-frontend BFF](/bff/getting-started/multi-frontend/) * [Blazor](/bff/getting-started/blazor/) ## Applying the Duende Backend for Frontend (BFF) Security Framework [Section titled “Applying the Duende Backend for Frontend (BFF) Security Framework”](#applying-the-duende-backend-for-frontend-bff-security-framework) [YouTube video player](https://www.youtube.com/embed/6zMSwlGBmxs) ----- # Blazor Applications > A walkthrough showing how to set up and configure a BFF (Backend For Frontend) application using Blazor This quickstart walks you through how to create a BFF Blazor application. The source code for this quickstart is available on [GitHub](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/BlazorAutoRendering). ## What You’ll Build [Section titled “What You’ll Build”](#what-youll-build) By the end of this guide you will have a Blazor application (Server + WASM) that authenticates users via OpenID Connect, stores session state server-side through the BFF, and calls a weather API using a BFF-managed HTTP client — with no access tokens exposed to the browser. ## Creating the project structure [Section titled “Creating the project structure”](#creating-the-project-structure) 1. **Create a Blazor App** ```shell mkdir BlazorBffApp cd BlazorBffApp dotnet new blazor --interactivity auto --all-interactive ``` This creates a Blazor application with a Server project and a client project. 2. **Configure the BffApp Server Project** To configure the server, the first step is to add the BFF Blazor package. ```shell cd BlazorBffApp dotnet add package Duende.BFF.Blazor ``` Then configure the application to use BFF. Add this to your services: * Duende BFF v4 ```csharp // BFF setup for Blazor builder.Services.AddBff() .ConfigureOpenIdConnect(options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }) .ConfigureCookies(options => { // Because we use an identity server that's configured on a different site // (duendesoftware.com vs localhost), we need to configure the SameSite property to Lax. // Setting it to Strict would cause the authentication cookie not to be sent after logging in. // The user would have to refresh the page to get the cookie. // Recommendation: Set it to 'strict' if your IDP is on the same site as your BFF. options.Cookie.SameSite = SameSiteMode.Lax; }) .AddServerSideSessions() // Add in-memory implementation of server-side sessions .AddBlazorServer(); // Make sure authentication state is available to all components. builder.Services.AddCascadingAuthenticationState(); builder.Services.AddAuthorization(); ``` * Duende BFF v3 ```csharp // BFF setup for Blazor (v3) builder.Services.AddBff() .AddServerSideSessions() // Add in-memory implementation of server-side sessions .AddBlazorServer(); // Configure the authentication builder.Services .AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("cookie", options => { // Configure the cookie with __Host prefix for maximum security options.Cookie.Name = "__Host-blazor"; // Because we use an identity server that's configured on a different site // (duendesoftware.com vs localhost), we need to configure the SameSite property to Lax. // Setting it to Strict would cause the authentication cookie not to be sent after logging in. // The user would have to refresh the page to get the cookie. // Recommendation: Set it to 'strict' if your IDP is on the same site as your BFF. options.Cookie.SameSite = SameSiteMode.Lax; }) .AddOpenIdConnect("oidc", options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }); // Make sure authentication state is available to all components. builder.Services.AddCascadingAuthenticationState(); builder.Services.AddAuthorization(); ``` To configure the web app pipeline, add the following after `builder.Build()`: * Duende BFF v4 ```csharp app.UseRouting(); app.UseAuthentication(); // Add the BFF middleware which performs anti-forgery protection app.UseBff(); app.UseAuthorization(); app.UseAntiforgery(); // In v4, management endpoints (/bff/login, /bff/logout, etc.) are // registered automatically — no call to MapBffManagementEndpoints() needed. ``` * Duende BFF v3 ```csharp app.UseRouting(); app.UseAuthentication(); // Add the BFF middleware which performs anti-forgery protection app.UseBff(); app.UseAuthorization(); app.UseAntiforgery(); // In v3, management endpoints must be registered explicitly app.MapBffManagementEndpoints(); ``` ## Configuring the BffApp.Client project [Section titled “Configuring the BffApp.Client project”](#configuring-the-bffappclient-project) 1. **Configure the Client Project** To add the BFF to the client project, add the following: ```shell cd .. cd BlazorBffApp.Client dotnet add package Duende.BFF.Blazor.Client ``` Then add the following to your `Program.cs`: ```csharp builder.Services .AddBffBlazorClient(); // Provides auth state provider that polls the /bff/user endpoint builder.Services .AddCascadingAuthenticationState(); ``` Your application is ready to use BFF now. ## Configuring your application to use BFF’s features [Section titled “Configuring your application to use BFF’s features”](#configuring-your-application-to-use-bffs-features) Add the following components to your `BlazorBffApp.Client/Components` folder: 1. **LoginDisplay.razor** The following code shows a login / logout button depending on your authentication state. Note: use the logout link from the `LogoutUrl` claim, because it contains both the correct route and the session id. BlazorBffApp.Client/Components/LoginDisplay.razor ```razor @using Duende.Bff.Blazor.Client @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.Extensions.Options @rendermode InteractiveAuto @inject IOptions Options Hello, @context.User.Identity?.Name Log Out Log in Log in @code { string BffLogoutUrl(AuthenticationState context) { var logoutUrl = context.User.FindFirst(Constants.ClaimTypes.LogoutUrl); return $"{Options.Value.StateProviderBaseAddress}{logoutUrl?.Value}"; } } ``` 2. **RedirectToLogin.razor** The following code will redirect users to the identity provider for authentication. Once authentication is complete, users will be redirected back to where they came from. BlazorBffApp.Client/Components/RedirectToLogin.razor ```razor @inject NavigationManager Navigation @rendermode InteractiveAuto @code { protected override void OnInitialized() { var returnUrl = Uri.EscapeDataString("/" + Navigation.ToBaseRelativePath(Navigation.Uri)); Navigation.NavigateTo($"bff/login?returnUrl={returnUrl}", forceLoad: true); } } ``` 3. **Modifications to Routes.razor** Replace the contents of `Routes.razor` so it matches below: BlazorBffApp.Client/Routes.razor ```razor @using Microsoft.AspNetCore.Components.Authorization @using BlazorBffApp.Client.Components @if (context.User.Identity?.IsAuthenticated != true) { } else {

You (@context.User.Identity?.Name) are not authorized to access this resource.

}
``` This ensures that accessing a page that requires authorization automatically redirects the user to the identity provider for authentication. 4. **Modifications to MainLayout.razor** Modify your `MainLayout.razor` to include the `LoginDisplay`: BlazorBffApp.Client/Layout/MainLayout.razor ```razor @inherits LayoutComponentBase @using BlazorBffApp.Client.Components
@Body
An unhandled error has occurred. Reload 🗙
``` Now your application supports logging in and out. ## Exposing APIs [Section titled “Exposing APIs”](#exposing-apis) Now we’re going to expose an embedded API for weather forecasts to Blazor WebAssembly (WASM) and call it via an `HttpClient`. 1. **Configuring the Client app** Add a class called `WeatherHttpClient` to the `BlazorBffApp.Client` project: BlazorBffApp.Client/WeatherHttpClient.cs ```csharp public class WeatherHttpClient(HttpClient client) : IWeatherClient { public async Task GetWeatherForecasts() => await client.GetFromJsonAsync("WeatherForecast") ?? throw new JsonException("Failed to deserialize"); } public class WeatherForecast { public DateOnly Date { get; set; } public int TemperatureC { get; set; } public string? Summary { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } // The IWeatherClient interface abstracts between server and client implementations. public interface IWeatherClient { Task GetWeatherForecasts(); } ``` Then register this in `Program.cs`: BlazorBffApp.Client/Program.cs ```csharp builder.Services .AddBffBlazorClient() // Provides auth state provider that polls the /bff/user endpoint // Register an HTTP client configured to fetch data from the BFF host. .AddLocalApiHttpClient(); // Register the concrete implementation with the abstraction builder.Services.AddSingleton(); ``` 2. **Configuring the server** Add a class called `ServerWeatherClient` to your `BlazorBffApp` server project: BlazorBffApp/ServerWeatherClient.cs ```csharp public class ServerWeatherClient : IWeatherClient { public Task GetWeatherForecasts() { var startDate = DateOnly.FromDateTime(DateTime.Now); string[] summaries = [ "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" ]; return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = startDate.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = summaries[Random.Shared.Next(summaries.Length)] }).ToArray()); } } ``` Then add an endpoint to your HTTP pipeline and register the server implementation: BlazorBffApp/Program.cs ```csharp // Register the server-side implementation builder.Services.AddSingleton(); // ... app.MapGet("/WeatherForecast", (IWeatherClient weatherClient) => weatherClient.GetWeatherForecasts()) .RequireAuthorization() .AsBffApiEndpoint(); ``` 3. **Displaying Weather Information From The API** By default, the Blazor template ships with a weather page. Change the content of `Weather.razor` to this: BlazorBffApp.Client/Pages/Weather.razor ```razor @page "/weather" @using BlazorBffApp.Client.Components @using Microsoft.AspNetCore.Authorization @rendermode InteractiveWebAssembly @attribute [Authorize] Weather ``` Now add a `WeatherComponent.razor`: BlazorBffApp.Client/Components/WeatherComponent.razor ```razor @inject IWeatherClient WeatherClient

Weather

This component demonstrates showing data.

@if (forecasts == null) {

Loading...

} else { @foreach (var forecast in forecasts) { }
Date Temp. (C) Temp. (F) Summary
@forecast.Date.ToShortDateString() @forecast.TemperatureC @forecast.TemperatureF @forecast.Summary
} @code { private WeatherForecast[]? forecasts; protected override async Task OnInitializedAsync() { forecasts = await WeatherClient.GetWeatherForecasts(); } } ``` Token availability in Blazor components Access tokens are managed server-side by the BFF host and are never available in Blazor WASM components directly. Always use `AddLocalApiHttpClient()` to create HTTP clients that route through the BFF host — never try to retrieve or pass tokens to client-side components. See the [Troubleshooting guide](/bff/troubleshooting/) if tokens appear unavailable. ## See Also [Section titled “See Also”](#see-also) * [Single Frontend Getting Started](/bff/getting-started/single-frontend/) — Simpler setup for a single SPA * [Blazor Fundamentals](/bff/fundamentals/blazor/) — Rendering modes, data access patterns, and auth state * [Local APIs](/bff/fundamentals/apis/local/) — Embedding API endpoints in the BFF host * [Token Management](/bff/fundamentals/tokens/) — How BFF handles access token refresh automatically * [Server-Side Sessions](/bff/fundamentals/session/server-side-sessions/) — Persisting sessions with Entity Framework * [Access Token Management](/accesstokenmanagement/) — The underlying token lifecycle library * [Troubleshooting](/bff/troubleshooting/) — Common Blazor BFF issues and fixes ----- # Getting Started - Multiple Frontends > A guide on how to create a BFF application with multiple frontends. Duende.BFF (Backend for Frontend) supports multiple frontends in a single BFF host. This is useful for scenarios where you want to serve several SPAs or frontend apps from the same backend, each with their own authentication and API proxying configuration. ## What You’ll Build [Section titled “What You’ll Build”](#what-youll-build) By the end of this guide you will have a single BFF host that serves multiple frontend applications, each with independently configurable OpenID Connect settings and remote API proxying. ## Setting Up A BFF Project For Multiple Frontends [Section titled “Setting Up A BFF Project For Multiple Frontends”](#setting-up-a-bff-project-for-multiple-frontends) 1. **Create A New ASP.NET Core Project** Terminal ```bash dotnet new web -n MyMultiBffApp cd MyMultiBffApp ``` 2. **Add The Duende.BFF NuGet Package** Terminal ```bash dotnet add package Duende.BFF ``` 3. **OpenID Connect Configuration** Configure OpenID Connect authentication for your BFF host. This is similar to the single frontend setup, but applies to all frontends unless overridden per frontend. Program.cs ```csharp builder.Services.AddBff() .ConfigureOpenIdConnect(options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); // Add this scope if you want to receive refresh tokens options.Scope.Add("offline_access"); }) .ConfigureCookies(options => { // Because we use an identity server that's configured on a different site // (duendesoftware.com vs localhost), we need to configure the SameSite property to Lax. // Setting it to Strict would cause the authentication cookie not to be sent after logging in. // The user would have to refresh the page to get the cookie. // Recommendation: Set it to 'strict' if your IDP is on the same site as your BFF. options.Cookie.SameSite = SameSiteMode.Lax; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseRouting(); // adds antiforgery protection for local APIs app.UseBff(); // adds authorization for local and remote API endpoints app.UseAuthorization(); app.Run(); ``` 4. **Configure BFF In `Program.cs`** * Static Register multiple frontends directly in code using `AddFrontends`: Program.cs ```csharp builder.Services.AddBff() .AddFrontends( new BffFrontend(BffFrontendName.Parse("default-frontend")) .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")), new BffFrontend(BffFrontendName.Parse("admin-frontend")) .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/admin/index.html")) ); // ...existing code for authentication, authorization, etc. ``` * From Config You can also load frontend configuration from an `IConfiguration` source, such as a JSON file: Example `bffconfig.json`: ```json { "defaultOidcSettings": null, "defaultCookieSettings": null, "frontends": { "from_config": { "cdnIndexHtmlUrl": "https://localhost:5005/static/index.html", "matchingPath": "/from-config", "oidc": { "clientId": "bff.multi-frontend.config" }, "remoteApis": [ { "pathMatch": "/api/client-token", "targetUri": "https://localhost:5010", "requiredTokenType": "Client" } ] } } } ``` Load and use the configuration in `Program.cs`: Program.cs ```csharp var bffConfig = new ConfigurationBuilder() .AddJsonFile("bffconfig.json") .Build(); builder.Services.AddBff() .LoadConfiguration(bffConfig); // ...existing code for authentication, authorization, etc. ``` 5. **Remote API Proxying** You can configure remote API proxying in two ways: * **Single YARP proxy for all frontends:** You can set up a single YARP proxy for all frontends, as shown in the [Single Frontend Guide](/bff/getting-started/single-frontend/). * **Direct proxying per frontend:** You can configure remote APIs for each frontend individually: Program.cs ```csharp builder.Services.AddBff() .AddFrontends( new BffFrontend(BffFrontendName.Parse("default-frontend")) .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")) .WithRemoteApis( new RemoteApi("/api/user-token", new Uri("https://localhost:5010")) ) ); ``` This allows each frontend to have its own set of proxied remote APIs. 6. **Server Side Sessions** Server side session configuration is the same as in the single frontend scenario. See the [Single Frontend Guide](/bff/getting-started/single-frontend/) for details. ## See Also [Section titled “See Also”](#see-also) * [Single Frontend Getting Started](/bff/getting-started/single-frontend/) — Simpler BFF setup for one frontend * [Multi-Frontend Fundamentals](/bff/fundamentals/multi-frontend/) — Deep-dive into multi-frontend configuration * [Remote APIs](/bff/fundamentals/apis/remote/) — Proxying calls to upstream services * [YARP Integration](/bff/fundamentals/apis/yarp/) — Advanced proxy configuration * [Server-Side Sessions](/bff/fundamentals/session/server-side-sessions/) — Persisting sessions in production * [Access Token Management](/accesstokenmanagement/) — Token lifecycle managed by BFF ----- # Getting Started - Single Frontend > A guide on how to create a BFF application with a single frontend. Duende.BFF (Backend for Frontend) is a library that helps you build secure, modern web applications by acting as a security gateway between your frontend and backend APIs. This guide will walk you through setting up a simple BFF application with a single frontend. ## What You’ll Build [Section titled “What You’ll Build”](#what-youll-build) By the end of this guide you will have an ASP.NET Core host that: * Authenticates users via OpenID Connect and stores the session server-side * Exposes secure local API endpoints with CSRF protection * Optionally proxies remote API calls with automatic token attachment ## Setting Up A BFF project [Section titled “Setting Up A BFF project”](#setting-up-a-bff-project) 1. **Create A New ASP.NET Core Project** Create a new ASP.NET Core Web Application: ```sh dotnet new web -n MyBffApp cd MyBffApp ``` 2. **Add The Duende.BFF NuGet Package** Install the Duende.BFF package: ```sh dotnet add package Duende.BFF ``` 3. **Configure BFF In `Program.cs`** Add the following to your `Program.cs`: * Duende BFF v4 ```csharp builder.Services.AddBff() .ConfigureOpenIdConnect(options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); // Add this scope if you want to receive refresh tokens options.Scope.Add("offline_access"); }) .ConfigureCookies(options => { // Because we use an identity server that's configured on a different site // (duendesoftware.com vs localhost), we need to configure the SameSite property to Lax. // Setting it to Strict would cause the authentication cookie not to be sent after logging in. // The user would have to refresh the page to get the cookie. // Recommendation: Set it to 'strict' if your IDP is on the same site as your BFF. options.Cookie.SameSite = SameSiteMode.Lax; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseRouting(); // adds antiforgery protection for local APIs app.UseBff(); // adds authorization for local and remote API endpoints app.UseAuthorization(); app.Run(); ``` * Duende BFF v3 ```csharp builder.Services.AddBff(); // Configure the authentication builder.Services .AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("cookie", options => { // Configure the cookie with __Host prefix for maximum security options.Cookie.Name = "__Host-blazor"; // Because we use an identity server that's configured on a different site // (duendesoftware.com vs localhost), we need to configure the SameSite property to Lax. // Setting it to Strict would cause the authentication cookie not to be sent after logging in. // The user would have to refresh the page to get the cookie. // Recommendation: Set it to 'strict' if your IDP is on the same site as your BFF. options.Cookie.SameSite = SameSiteMode.Lax; }) .AddOpenIdConnect("oidc", options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); // Add this scope if you want to receive refresh tokens options.Scope.Add("offline_access"); }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseRouting(); // adds antiforgery protection for local APIs app.UseBff(); // adds authorization for local and remote API endpoints app.UseAuthorization(); // login, logout, user, backchannel logout... app.MapBffManagementEndpoints(); app.Run(); ``` Make sure to replace the Authority, ClientID and ClientSecret with values from your identity provider. Also consider if the scopes are correct. 4. **Adding Local APIs** If your browser-based application uses local APIs, you can add those directly to your BFF app. The BFF supports both controllers and minimal APIs to create local API endpoints. It’s important to mark up the APIs with .AsBffApiEndpoint(), because this adds CSRF protection. * Minimal Apis Program.cs ```csharp // Adds authorization for local and remote API endpoints app.UseAuthorization(); // Place your custom routes after the 'UseAuthorization()' app.MapGet("/hello-world", () => "hello-world") .AsBffApiEndpoint(); // Adds CSRF protection to the controller endpoints ``` * Controllers Program.cs ```csharp builder.Services.AddControllers(); // ... app.UseAuthorization(); // When mapping the api controllers, place this after // UseAuthorization() app.MapControllers() .RequireAuthorization() .AsBffApiEndpoint(); // This statement adds CSRF protection to the controller endpoints ``` LocalApiController.cs ```csharp [Route("hello")] public class LocalApiController : ControllerBase { [Route("world")] [HttpGet] public IActionResult SelfContained() { return Ok("hello world"); } } ``` 5. **Adding Remote APIs** If you also want to call remote api’s from your browser based application, then you should proxy the calls through the BFF. The BFF extends the capabilities of [Yarp](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/getting-started?view=aspnetcore-9.0) in order to achieve this. Terminal ```bash dotnet add package Duende.BFF.Yarp ``` * Direct forwarding Program.cs ```csharp builder.Services.AddBff() .AddRemoteApis(); // Adds the capabilities needed to perform proxying to remote APIs. // ... // Map any call (including child routes) from /api/remote to https://remote-api-address app.MapRemoteBffApiEndpoint("/api/remote", new Uri("https://remote-api-address")) .WithAccessToken(RequiredTokenType.Client); ``` * Yarp Program.cs ```csharp builder.Services.AddBff() .AddRemoteApis() // This adds the capabilities needed to perform proxying to remote api's. .AddYarpConfig(new RouteConfig() // This statement configures yarp. { RouteId = "route_id", ClusterId = "cluster_id", Match = new RouteMatch { Path = $"api/remote/{{**catch-all}}" } }, new ClusterConfig() { ClusterId = "cluster_id", Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "destination_1", new DestinationConfig { Address = "https://remote-api-address" } } } }); // ... app.UseAuthorization(); // Add the Yarp middleware that will proxy the requests. app.MapReverseProxy(proxyApp => { proxyApp.UseAntiforgeryCheck(); }); ``` You can also use an `IConfiguration` instead of programmatically configuring the proxy. 6. **Adding Server-Side Sessions** * In-Memory By default, Duende.BFF uses an in-memory session store. This is suitable for development and testing, but not recommended for production as sessions will be lost when the application restarts. Program.cs ```csharp builder.Services.AddBff() .AddServerSideSessions(); // Uses in-memory session store by default // ...existing code for authentication, authorization, etc. ``` * Entity Framework For production scenarios, you can use Entity Framework to persist sessions in a database. First, add the NuGet package: Terminal ```bash dotnet add package Duende.BFF.EntityFramework ``` Then configure the session store in your `Program.cs`: Program.cs ```csharp builder.Services.AddBff() .AddServerSideSessions() .AddEntityFrameworkServerSideSessions(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); // ...existing code for authentication, authorization, etc. ``` You will also need to run the Entity Framework migrations to create the necessary tables. In-memory sessions are not production-ready The default in-memory session store loses all sessions on application restart and cannot be shared across multiple instances. Always use Entity Framework-backed sessions in production. ## Frontend Integration [Section titled “Frontend Integration”](#frontend-integration) With the BFF host running, your frontend (JavaScript SPA, React, Angular, etc.) needs to call a few BFF endpoints for authentication and to make API calls. Below is a minimal vanilla JavaScript pattern you can adapt. ### Check the current user session [Section titled “Check the current user session”](#check-the-current-user-session) On load, call `/bff/user` to check whether the user is logged in. This endpoint returns the user’s claims as JSON when authenticated, or a `401`/empty response when anonymous. ```javascript // Fetch the current user from the BFF session async function getUser() { const response = await fetch('/bff/user', { headers: { 'X-CSRF': '1' } }); if (response.ok) { return await response.json(); // Array of { type, value } claim objects } return null; // Not authenticated } ``` ### Login and logout links [Section titled “Login and logout links”](#login-and-logout-links) Use plain anchor tags pointing to the BFF management endpoints. Do **not** use `fetch` for these — they must trigger a full browser redirect. ```html Log in Log out ``` ```javascript // Wire up logout link with the session-bound URL from /bff/user const user = await getUser(); if (user) { const logoutUrlClaim = user.find(c => c.type === 'bff:logout_url'); document.getElementById('logout-link').href = logoutUrlClaim?.value ?? '/bff/logout'; } ``` ### Calling BFF API endpoints [Section titled “Calling BFF API endpoints”](#calling-bff-api-endpoints) Every request to a BFF API endpoint **must** include the `X-CSRF: 1` header. Without it, the BFF will reject the request with `401 Unauthorized`. ```javascript // Centralized fetch wrapper — always add the X-CSRF header async function bffFetch(url, options = {}) { const response = await fetch(url, { ...options, headers: { 'X-CSRF': '1', ...options.headers, }, }); // Redirect to login if the session has expired if (response.status === 401) { window.location.href = `/bff/login?returnUrl=${encodeURIComponent(window.location.pathname)}`; return; } return response; } // Example usage const data = await bffFetch('/api/weather'); const json = await data.json(); ``` ### Proactive session polling (optional) [Section titled “Proactive session polling (optional)”](#proactive-session-polling-optional) To detect server-initiated session termination (e.g., back-channel logout), poll `/bff/user` periodically: ```javascript // Poll every 60 seconds; redirect to login if session ends setInterval(async () => { const user = await getUser(); if (!user) { window.location.href = '/bff/login'; } }, 60_000); ``` ## See Also [Section titled “See Also”](#see-also) * [Multiple Frontends](/bff/getting-started/multi-frontend/) — Serve several SPAs from the same BFF host * [Blazor Applications](/bff/getting-started/blazor/) — BFF setup for Blazor Server and WASM * [Local APIs](/bff/fundamentals/apis/local/) — Full reference for embedded API endpoints and CSRF protection * [Remote APIs](/bff/fundamentals/apis/remote/) — Direct forwarding to upstream services * [Token Management](/bff/fundamentals/tokens/) — How BFF handles access token refresh automatically * [Server-Side Sessions](/bff/fundamentals/session/server-side-sessions/) — Persistent session configuration * [Access Token Management](/accesstokenmanagement/) — The underlying token lifecycle library used by BFF ----- # Getting Started - Templates > A guide on how to install the BFF project templates. Project templates for Duende BFF are shipped as part of the Duende .NET project templates. Refer the [templates documentation](/identityserver/overview/packaging/#templates) for more information on how to install the templates. ## Available templates [Section titled “Available templates”](#available-templates) ### BFF Remote API [Section titled “BFF Remote API”](#bff-remote-api) ```shell dotnet new duende-bff-remoteapi ``` Creates a basic JavaScript-based BFF host that configures and invokes a [remote API via the BFF proxy](/bff/fundamentals/apis/remote/). ### BFF Local API [Section titled “BFF Local API”](#bff-local-api) ```shell dotnet new duende-bff-localapi ``` Creates a basic JavaScript-based BFF host that invokes a [local API](/bff/fundamentals/apis/local/) co-hosted with the BFF. ### BFF Blazor [Section titled “BFF Blazor”](#bff-blazor) ```shell dotnet new duende-bff-blazor ``` Creates a Blazor application that [uses the interactive auto render mode](/bff/fundamentals/blazor/), and secures the application across all render modes consistently using Duende.BFF.Blazor. ----- # Backend For Frontend (BFF) Samples > A collection of sample applications demonstrating how to use the BFF security framework with different frontend technologies. This section contains a collection of clients using our BFF security framework. ## JavaScript Frontend [Section titled “JavaScript Frontend”](#javascript-frontend) This sample demonstrates a vanilla JavaScript SPA secured by the BFF. You will learn how to call `/bff/user` to retrieve session claims, wire up login/logout links, and make CSRF-protected API calls using `X-CSRF: 1` — without any JS framework dependencies. [JavaScript Frontend Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/JsBffSample)Vanilla JS SPA with BFF: session claims, login/logout, and CSRF-protected API calls ## ReactJs Frontend [Section titled “ReactJs Frontend”](#reactjs-frontend) This sample shows how to integrate React with the BFF framework. You will learn how to manage login state via `/bff/user`, protect routes based on session claims, and proxy API requests through the BFF with automatic token forwarding. [ReactJS Frontend Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/React)React SPA with BFF: session-driven auth state, protected routes, and token-forwarded API calls ## Angular Frontend [Section titled “Angular Frontend”](#angular-frontend) This sample shows how to integrate Angular with the BFF framework. You will learn how to build an Angular auth service backed by `/bff/user`, add an HTTP interceptor for the CSRF header, and handle 401 redirects gracefully. [Angular Frontend Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/Angular)Angular SPA with BFF: auth service, CSRF interceptor, and 401 redirect handling ## Vue Frontend Community [Section titled “Vue Frontend ”Community](#vue-frontend) This sample (contributed by [@Marco Cabrera](https://github.com/mck231)) shows how to integrate Vue 3 with the BFF framework. You will learn how to expose session state from `/bff/user` in a Vue composable and make authenticated API calls with CSRF protection. [Vue Frontend Sample (Community) ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/Vue)Vue 3 SPA with BFF: session composable, CSRF-protected API calls ## Blazor WASM [Section titled “Blazor WASM”](#blazor-wasm) This sample shows how to use Blazor WebAssembly as the frontend with the BFF host. You will learn how to configure `AuthorizationMessageHandler` to forward tokens from the BFF session and call backend APIs securely from client-side Blazor code. [Blazor WASM Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/BlazorWasm)Blazor WASM with BFF: AuthorizationMessageHandler and secure API calls from the browser ## Blazor Auto Rendering [Section titled “Blazor Auto Rendering”](#blazor-auto-rendering) This sample demonstrates Blazor Auto rendering mode (server-side prerender + WASM hydration) combined with BFF authentication. You will learn how to share auth state across render modes and avoid common pitfalls with interactive components that call protected APIs. [Blazor Auto Rendering Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/BlazorAutoRendering)Blazor Auto mode with BFF: shared auth state across server-side and WASM render modes ## YARP Integration [Section titled “YARP Integration”](#yarp-integration) This sample shows how to use the Duende BFF extensions for [Microsoft YARP](https://microsoft.github.io/reverse-proxy/) to proxy API requests. You will learn how to configure YARP routes with BFF token forwarding, eliminating the need for manual `AddRemoteApis` registration. [YARP Integration Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/JsBffYarpSample)BFF with YARP: token-forwarding reverse proxy routes for remote APIs ## OpenAPI [Section titled “OpenAPI”](#openapi) This sample shows how to expose and consume an OpenAPI (Swagger) spec from a BFF-protected API. You will learn how to configure Swagger UI to authenticate via the BFF session and make test requests without needing a separate bearer token. [OpenAPI Sample ](https://github.com/DuendeSoftware/samples/tree/main/BFF/v4/OpenApi)BFF with OpenAPI: Swagger UI authenticated via BFF session cookies ## Separate Host for UI [Section titled “Separate Host for UI”](#separate-host-for-ui) This sample shows how to run the frontend (e.g. a dev Vite server) on a different origin from the BFF host and use CORS to allow cross-site session and API requests. You will learn how to configure `AllowedOrigins`, CORS policy, and cookie `SameSite` settings for split-host development and production deployments. [Separate Host for UI Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/SplitHosts)Split-host BFF: CORS configuration for frontend and backend on different origins ## Docker Hosting Community [Section titled “Docker Hosting ”Community](#docker-hosting) This sample (contributed by [@Marco Cabrera](https://github.com/mck231)) shows how to run the BFF host and IdentityServer together using Docker Compose. You will learn how to configure networking between containers, set authority URLs, and handle Data Protection key persistence in a containerized environment. [Docker Sample (Community) ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/docker)BFF + IdentityServer in Docker Compose: container networking and Data Protection key persistence ## DPoP [Section titled “DPoP”](#dpop) This sample shows how to configure the BFF for [DPoP (Demonstrating Proof of Possession)](/identityserver/tokens/pop/) so that all tokens are sender-constrained. You will learn how to enable DPoP on both the BFF and the downstream API, preventing token replay attacks even if tokens are intercepted. [DPoP Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/DPoP)BFF with DPoP: sender-constrained tokens to prevent token replay attacks ## Token Exchange using the IAccessTokenRetriever [Section titled “Token Exchange using the IAccessTokenRetriever”](#token-exchange-using-the-iaccesstokenretriever) This sample shows how to implement a custom `IAccessTokenRetriever` that performs RFC 8693 token exchange for impersonation. When logged in as Alice you receive a token scoped to Bob, and vice versa — demonstrating how to swap or enrich tokens before they are forwarded to downstream APIs. [Token Exchange Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v4/TokenExchange)Custom IAccessTokenRetriever with RFC 8693 token exchange for user impersonation ## New User Onboarding with Blazor Auto Rendering Community [Section titled “New User Onboarding with Blazor Auto Rendering ”Community](#new-user-onboarding-with-blazor-auto-rendering) This sample (contributed by [@hugh-maaskant](https://github.com/hugh-maaskant)) shows how to handle a new-user onboarding flow where additional profile data is collected by the application — not the identity provider. You will learn how to intercept post-login redirects, store onboarding data in the application database, and resume the original request after onboarding completes. [New User Onboarding Sample (Community) ](https://github.com/hugh-maaskant/BlazorBffOnboarding)New user onboarding with Blazor Auto: intercept post-login, collect profile data in app DB ## Feedback [Section titled “Feedback”](#feedback) Feel free to [ask the developer community](https://github.com/DuendeSoftware/community/discussions) if you are looking for a particular sample and can’t find it here. [Developer Community Forum ](https://github.com/DuendeSoftware/community/discussions)Join the Duende Developer Community for discussions and feedback ----- # Troubleshooting > Diagnose and fix common problems with Duende BFF: anti-forgery failures, CORS errors, session expiration, YARP misconfigurations, Blazor token issues, and more. This page covers the most common problems encountered when building and operating a Duende BFF application. Each scenario is described in **symptom → cause → solution** format. *** ### Symptom: Anti-Forgery Token Validation Failures — `401 Unauthorized` with missing `X-CSRF` header [Section titled “Symptom: Anti-Forgery Token Validation Failures — 401 Unauthorized with missing X-CSRF header”](#symptom-anti-forgery-token-validation-failures--401-unauthorized-with-missing-x-csrf-header) **Cause:** The BFF enforces the presence of a custom `X-CSRF: 1` header on all API endpoints decorated with `.AsBffApiEndpoint()`. Requests that do not include this header are rejected. **Solution:** Add the `X-CSRF: 1` header to every `fetch()` call targeting a BFF API endpoint. The easiest approach is a centralized wrapper: ```javascript function bffFetch(url, options = {}) { return fetch(url, { ...options, headers: { 'X-CSRF': '1', ...options.headers, }, }); } ``` Also verify that: * `app.UseBff()` appears **after** `app.UseRouting()` and `app.UseAuthentication()`, and **before** `app.UseAuthorization()` in your middleware pipeline. * The endpoint is decorated with `.AsBffApiEndpoint()` (Minimal API) or `[BffApi]` / `.AsBffApiEndpoint()` at mapping time (MVC). See [Middleware Pipeline](/bff/fundamentals/middleware-pipeline/) for the canonical order and a table of common mistakes. Caution If `UseBff()` is placed after `UseAuthorization()`, anti-forgery enforcement is silently disabled with no error. Always verify middleware order. *** ### Symptom: CORS Errors With BFF Endpoints — failed `OPTIONS` preflight on `/bff/user` or API endpoints [Section titled “Symptom: CORS Errors With BFF Endpoints — failed OPTIONS preflight on /bff/user or API endpoints”](#symptom-cors-errors-with-bff-endpoints--failed-options-preflight-on-bffuser-or-api-endpoints) **Cause:** The BFF and the SPA are on different origins. CORS errors here are usually a sign that the BFF and frontend are not being served from the same origin, which defeats part of the BFF pattern’s security model. **Solution:** The BFF is designed to serve the frontend from the same origin. If you must host them on different origins, configure a CORS policy that explicitly allows the SPA origin and allows credentials: ```csharp builder.Services.AddCors(options => { options.AddPolicy("SpaPolicy", policy => { policy.WithOrigins("https://app.example.com") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); // Required for cookie-based auth }); }); // Must come before UseAuthentication and UseBff app.UseCors("SpaPolicy"); ``` *** ### Symptom: Session Expiration Causing Silent Failures — SPA stops receiving data, `401` with no user-facing error [Section titled “Symptom: Session Expiration Causing Silent Failures — SPA stops receiving data, 401 with no user-facing error”](#symptom-session-expiration-causing-silent-failures--spa-stops-receiving-data-401-with-no-user-facing-error) **Cause:** The BFF session (stored in the authentication cookie) has expired. BFF API endpoints return `401` instead of a redirect when the session expires, so the SPA must handle this explicitly. **Solution:** Detect `401` responses in your fetch wrapper and redirect to the BFF login endpoint: ```javascript async function bffFetch(url, options = {}) { const response = await fetch(url, { ...options, headers: { 'X-CSRF': '1', ...options.headers }, }); if (response.status === 401) { window.location.href = `/bff/login?returnUrl=${encodeURIComponent(window.location.pathname)}`; return; } return response; } ``` Also consider: * Polling `/bff/user` periodically to detect session expiry proactively. * Configuring absolute and sliding session lifetimes on the cookie handler to match your requirements. * Using [server-side sessions](/bff/fundamentals/session/server-side-sessions/) to enable server-initiated session termination. *** ### Symptom: YARP Proxy Misconfiguration — proxied requests return `404`, missing token, or bypass anti-forgery [Section titled “Symptom: YARP Proxy Misconfiguration — proxied requests return 404, missing token, or bypass anti-forgery”](#symptom-yarp-proxy-misconfiguration--proxied-requests-return-404-missing-token-or-bypass-anti-forgery) **Cause:** Common YARP configuration mistakes include: * Missing `UseAntiforgeryCheck()` in the `MapReverseProxy` pipeline. * Typos in metadata keys when using `appsettings.json` configuration. * Route patterns that don’t include `{**catch-all}` to capture sub-paths. **Solution:** Ensure `UseAntiforgeryCheck()` is explicitly included: ```csharp app.MapReverseProxy(proxyApp => { proxyApp.UseAntiforgeryCheck(); // Required — not automatic for YARP }); ``` When configuring via `appsettings.json`, metadata keys are case-sensitive: ```json "Metadata": { "Duende.Bff.Yarp.TokenType": "User", "Duende.Bff.Yarp.AntiforgeryCheck": "true" } ``` For route patterns, ensure sub-paths are captured: ```json "Match": { "Path": "/api/{**catch-all}" } ``` Caution A typo in a YARP metadata key fails silently — no token is attached and no anti-forgery check is enforced. Always test proxied routes with an authenticated request and verify the `Authorization` header reaches the upstream service. *** ### Symptom: Blazor WASM — Token Not Available in Components, exception or null when calling `GetUserAccessTokenAsync` [Section titled “Symptom: Blazor WASM — Token Not Available in Components, exception or null when calling GetUserAccessTokenAsync”](#symptom-blazor-wasm--token-not-available-in-components-exception-or-null-when-calling-getuseraccesstokenasync) **Cause:** In Blazor WASM, `HttpContext` is not available. Access tokens are managed server-side by the BFF host and must never be exposed to client-side components. **Solution:** Use `AddLocalApiHttpClient()` to register a typed HTTP client that routes through the BFF host. The BFF host attaches the token server-side before forwarding: ```csharp // Client-side Program.cs builder.Services .AddBffBlazorClient() .AddLocalApiHttpClient(); ``` The `WeatherHttpClient` then calls the BFF host’s local API endpoint (which does have access to `HttpContext` and can call `GetUserAccessTokenAsync()`), rather than calling the remote API directly. Caution Never attempt to retrieve an access token in a Blazor WASM component and pass it to JavaScript or store it in the component state. This defeats the BFF security model. *** ### Symptom: Silent Login Failures — `prompt=none` fails in Safari/Firefox, users unexpectedly logged out [Section titled “Symptom: Silent Login Failures — prompt=none fails in Safari/Firefox, users unexpectedly logged out”](#symptom-silent-login-failures--promptnone-fails-in-safarifirefox-users-unexpectedly-logged-out) **Cause:** Modern browsers block third-party cookies. The `prompt=none` / silent renew flow in traditional SPAs relies on an iframe that sends a cookie to the identity provider — this breaks when third-party cookies are blocked. **Solution:** The BFF pattern is specifically designed to avoid this problem. Token renewal is handled server-side using refresh tokens, which do not rely on third-party cookies. Ensure: 1. `offline_access` scope is requested so a refresh token is issued. 2. `SaveTokens = true` is set on the OIDC handler. 3. The BFF’s `Duende.AccessTokenManagement` integration is active (it is by default). ```csharp options.Scope.Add("offline_access"); // Required for refresh tokens options.SaveTokens = true; // Required to store tokens in the session ``` See [Third-Party Cookies](/bff/architecture/third-party-cookies/) for a deeper discussion of how browser cookie restrictions affect authentication flows. *** ### Symptom: 302 Redirect Instead of 401 on API Endpoints — SPA receives HTML instead of JSON [Section titled “Symptom: 302 Redirect Instead of 401 on API Endpoints — SPA receives HTML instead of JSON”](#symptom-302-redirect-instead-of-401-on-api-endpoints--spa-receives-html-instead-of-json) **Cause:** The API endpoint is not marked as a BFF API endpoint, so ASP.NET Core’s default challenge behavior (302 redirect) applies instead of BFF’s 401 response. **Solution:** Add `.AsBffApiEndpoint()` to the endpoint: ```csharp // Minimal API app.MapGet("/api/data", () => Results.Ok("data")) .RequireAuthorization() .AsBffApiEndpoint(); // Converts 302 challenge to 401 // MVC controllers app.MapControllers() .RequireAuthorization() .AsBffApiEndpoint(); ``` This instructs the BFF middleware to return `401` for unauthenticated requests rather than issuing a redirect challenge. Your SPA can then detect the `401` and navigate to `/bff/login`. *** ### Symptom: Cookie Size Exceeding Browser Limits — users with many roles cannot log in, cookie silently dropped [Section titled “Symptom: Cookie Size Exceeding Browser Limits — users with many roles cannot log in, cookie silently dropped”](#symptom-cookie-size-exceeding-browser-limits--users-with-many-roles-cannot-log-in-cookie-silently-dropped) **Cause:** All claims are stored in the authentication cookie by default. Large numbers of claims (e.g., from many roles or large identity tokens) can cause the cookie to exceed the 4KB browser limit. ASP.NET Core chunks cookies, but excessively large sessions still cause issues. **Solution:** Switch to [server-side sessions](/bff/fundamentals/session/server-side-sessions/). The browser cookie then only holds a session ID (a small opaque value), and all claims are stored in the server-side session store: ```csharp builder.Services.AddBff() .AddEntityFrameworkServerSideSessions(options => { options.UseSqlServer(connectionString); }); ``` Additionally, filter unnecessary claims from the session using an `IClaimsTransformation` or by configuring the OIDC handler to not request unnecessary scopes: ```csharp // Only request claims you actually need options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); // Don't add scopes whose claims you don't use ``` *** ## See Also [Section titled “See Also”](#see-also) * [Getting Started: Single Frontend](/bff/getting-started/single-frontend/) — Correct initial setup * [Getting Started: Blazor](/bff/getting-started/blazor/) — Blazor-specific configuration * [Local APIs](/bff/fundamentals/apis/local/) — CSRF protection for embedded API endpoints * [YARP Integration](/bff/fundamentals/apis/yarp/) — Advanced proxy configuration * [Server-Side Sessions](/bff/fundamentals/session/server-side-sessions/) — Production session persistence * [Token Management](/bff/fundamentals/tokens/) — Access token refresh and revocation * [Third-Party Cookies](/bff/architecture/third-party-cookies/) — Browser cookie restrictions and BFF * [Access Token Management](/accesstokenmanagement/) — The underlying token lifecycle library ----- # Upgrading BFF Security Framework > Guide for upgrading Duende.BFF versions, including NuGet package updates Upgrading to a new Duende.BFF version is done by updating the NuGet package and handling any breaking changes. [GitHub Repository ](https://github.com/DuendeSoftware/products/tree/main/bff)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.BFF)View the package on NuGet.org. ----- # Duende BFF Security Framework v2.x to v3.0 > Guide for upgrading Duende BFF Security Framework from version 2.x to version 3.0, including migration steps for custom implementations and breaking changes. Duende BFF Security Framework v3.0 is a significant release that includes: * .NET 9 support * Blazor support * Several fixes and improvements ## Upgrading [Section titled “Upgrading”](#upgrading) If you rely on the default extension methods for wiring up the BFF, then V3 should be a drop-in replacement. ### Migrating From Custom Implementations Of IHttpMessageInvokerFactory [Section titled “Migrating From Custom Implementations Of IHttpMessageInvokerFactory”](#migrating-from-custom-implementations-of-ihttpmessageinvokerfactory) In Duende.BFF V2, there was an interface called `IHttpMessageInvokerFactory`. This class was responsible for creating and wiring up yarp’s `HttpMessageInvoker`. This interface has been removed in favor YARP’s `IForwarderHttpClientFactory`. One common scenario for creating a custom implementation of this class was for mocking the http client during unit testing. If you wish to inject a http handler for unit testing, you should now inject a custom `IForwarderHttpClientFactory`. For example: ```csharp // A Forwarder factory that forwards the messages to a message handler (which can be easily retrieved from a testhost) public class BackChannelHttpMessageInvokerFactory(HttpMessageHandler backChannel) : IForwarderHttpClientFactory { public HttpMessageInvoker CreateClient(ForwarderHttpClientContext context) => new HttpMessageInvoker(backChannel); } // Wire up the forwarder in your application's test host: services.AddSingleton( new BackChannelHttpMessageInvokerFactory(_apiHost.Server.CreateHandler())); ``` ### Migrating From Custom Implementations Of IHttpTransformerFactory [Section titled “Migrating From Custom Implementations Of IHttpTransformerFactory”](#migrating-from-custom-implementations-of-ihttptransformerfactory) The `IHttpTransformerFactory` was a way to globally configure the YARP tranform pipeline. In V3, the way that the default `endpoints.MapRemoteBffApiEndpoint()` method builds up the YARP transform has been simplified significantly. Most of the logic has been pushed down to the `AccessTokenRequestTransform`. Here are common scenario’s for implementing your own `IHttpTransformerFactory` and how to upgrade: #### Replacing Defaults [Section titled “Replacing Defaults”](#replacing-defaults) If you used a custom implementation of `IHttpTransformerFactory` to change the default behavior of `MapRemoteBffApiEndpoint()`, for example to add additional transforms, then you can now inject a custom delegate into the ASP.NET Core service provider: ```csharp services.AddSingleton(CustomDefaultYarpTransforms); //... // This is an example of how to add a response header to ALL invocations of MapRemoteBffApiEndpoint() private void CustomDefaultBffTransformBuilder(string localpath, TransformBuilderContext context) { context.AddResponseHeader("added-by-custom-default-transform", "some-value"); DefaultBffYarpTransformerBuilders.DirectProxyWithAccessToken(localpath, context); } ``` Another way of doing this is to create a custom extensionmethod `MyCustomMapRemoteBffApiEndpoint()` that wraps the `MapRemoteBffApiEndpoint()` and use that everywhere in your application. This is a great way to add other defaults that should apply to all endpoints, such as requiring a specific type of access token. #### Configuring Transforms For A Single Route [Section titled “Configuring Transforms For A Single Route”](#configuring-transforms-for-a-single-route) Another common usecase for overriding the `IHttpTransformerFactory` was to have a custom transform for a single route, by applying a switch statement and testing for specific routes. Now, there is an overload on the `endpoints.MapRemoteBffApiEndpoint()` that allows you to configure the pipeline directly: ```csharp endpoints.MapRemoteBffApiEndpoint( "/local-path", _apiHost.Url(), context => { // do something custom: IE: copy request headers context.CopyRequestHeaders = true; // wire up the default transformer logic DefaultTransformers.DirectProxyWithAccessToken("/local-path", context); }) // Continue with normal BFF configuration, for example, allowing optional user access tokens .WithOptionalUserAccessToken(); ``` ### Removed method RemoteApiEndpoint.Map(localpath, apiAddress). [Section titled “Removed method RemoteApiEndpoint.Map(localpath, apiAddress).”](#removed-method-remoteapiendpointmaplocalpath-apiaddress) The Map method was no longer needed as most of the logic had been moved to either the `MapRemoteBffApiEndpoint` and the DefaultTransformers. The map method also wasn’t very explicit about what it did and a number of test scenario’s tried to verify if it wasn’t called wrongly. You are now expected to call the method `MapRemoteBffApiEndpoint`. This method now has a nullable parameter that allows you to inject your own transformers. ### AccessTokenRetrievalContext Properties Are Now Typed [Section titled “AccessTokenRetrievalContext Properties Are Now Typed”](#accesstokenretrievalcontext-properties-are-now-typed) The LocalPath and ApiAddress properties are now typed. They used to be strings. If you rely on these, for example for implementing a custom `IAccessTokenRetriever`, then you should adjust their usage accordingly. ```csharp /// /// The locally requested path. /// public required PathString PathMatch { get; set; } /// /// The remote address of the API. /// public required Uri ApiAddress { get; set; } ``` ### AddAddEntityFrameworkServerSideSessionsServices Renamed To AddEntityFrameworkServerSideSessionsServices [Section titled “AddAddEntityFrameworkServerSideSessionsServices Renamed To AddEntityFrameworkServerSideSessionsServices”](#addaddentityframeworkserversidesessionsservices-renamed-to-addentityframeworkserversidesessionsservices) If you used the method `AddAddEntityFrameworkServerSideSessionsServices()` in your code, please replace it with the corrected `AddEntityFrameworkServerSideSessionsServices()`. ### StateProviderPollingDelay and StateProviderPollingInterval Split Into Separate Options For WebAssembly and Server. [Section titled “StateProviderPollingDelay and StateProviderPollingInterval Split Into Separate Options For WebAssembly and Server.”](#stateproviderpollingdelay-and-stateproviderpollinginterval-split-into-separate-options-for-webassembly-and-server) If you used `BffBlazorOptions.StateProviderPollingInterval` or `BffBlazorOptions.StateProviderPollingDelay` to configure different polling settings, you should now consider if this same setting applies to either Server, WASM or both. Set the appropriate properties accordingly. ### Server Side Sessions Database Migrations [Section titled “Server Side Sessions Database Migrations”](#server-side-sessions-database-migrations) No [Entity Framework database migrations](/bff/fundamentals/session/server-side-sessions/#entity-framework-migrations) are required for the server side sessions feature when using the `Duende.BFF.EntityFramework` package. The database structure remains the same: serversidesessions.sql ```sqlite CREATE TABLE "UserSessions" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_UserSessions" PRIMARY KEY AUTOINCREMENT, "ApplicationName" TEXT NULL, "SubjectId" TEXT NOT NULL, "SessionId" TEXT NULL, "Created" TEXT NOT NULL, "Renewed" TEXT NOT NULL, "Expires" TEXT NULL, "Ticket" TEXT NOT NULL, "Key" TEXT NOT NULL ); CREATE UNIQUE INDEX "IX_UserSessions_ApplicationName_Key" ON "UserSessions" ("ApplicationName", "Key"); CREATE UNIQUE INDEX "IX_UserSessions_ApplicationName_SessionId" ON "UserSessions" ("ApplicationName", "SessionId"); CREATE UNIQUE INDEX "IX_UserSessions_ApplicationName_SubjectId_SessionId" ON "UserSessions" ("ApplicationName", "SubjectId", "SessionId"); CREATE INDEX "IX_UserSessions_Expires" ON "UserSessions" ("Expires"); ``` ----- # Duende BFF Security Framework v3.0 to v4.0 > Guide for upgrading Duende BFF Security Framework from version 3.x to version 4.0, including migration steps for custom implementations and breaking changes. ## Migration Checklist [Section titled “Migration Checklist”](#migration-checklist) Use this checklist to track your upgrade. Each item links to the detailed section below. * [ ] Update `Duende.BFF` NuGet package to v4.x * [ ] [Replace `TokenType` enum with `RequiredTokenType`](#remote-apis) — move `using` to `Duende.Bff.AccessTokenManagement` * [ ] [Replace `.RequireAccessToken()` with `.WithAccessToken()`](#remote-apis) on all remote API registrations * [ ] [Replace `.WithOptionalUserAccessToken()` with `.WithAccessToken(RequiredTokenType.UserOrNone)`](#remote-apis) * [ ] [Update YARP token type config](#configuring-token-types-in-yarp) to use `RequiredTokenType` enum values * [ ] [Rename custom service classes](#service-to-endpoint-updates) (`IUserService` → `IUserEndpoint`, etc.) and update to new extensibility pattern * [ ] [Update `IUserSessionStore` implementations](#custom-session-store) — replace `string key` with `UserSessionKey` struct * [ ] [Update `GetUserAccessTokenAsync` namespace](#access-token-retrieval) — use `Duende.AccessTokenManagement.OpenIdConnect` * [ ] [Optionally migrate to new simplified wireup](#simplified-wireup-without-explicit-authentication-setup) (`.ConfigureOpenIdConnect()` + `.ConfigureCookies()`) * [ ] [Run EF Core database migration](#server-side-sessions-database-migrations) if using server-side sessions (`ApplicationName` → `PartitionKey`) * [ ] Verify YARP-based API proxying still works end-to-end Database schema breaking change The `UserSessions.ApplicationName` column is renamed to `PartitionKey`. If multiple BFF v3 apps share the same session database, upgrade all of them simultaneously or provision a new database for the v4 instance. *** Duende BFF Security Framework v4.0 is a significant release that includes: * Multi-frontend support * OpenTelemetry support * Support for login prompts * Several fixes and improvements The extensibility approach has been drastically changed, and many `virtual` methods containing implementation logic are now internal instead. ## Upgrading [Section titled “Upgrading”](#upgrading) This release introduces many breaking changes. This upgrade guide covers cases where a breaking change was introduced. ### Remote APIs [Section titled “Remote APIs”](#remote-apis) The syntax for configuring remote APIs has changed slightly: Program.cs ```diff // Use a client credentials token -app.MapRemoteBffApiEndpoint("/api/client-token", "https://localhost:5010") -.RequireAccessToken(TokenType.Client); +app.MapRemoteBffApiEndpoint("/api/client-token", new Uri("https://localhost:5010")) +.WithAccessToken(RequiredTokenType.Client); // Use the client token only if the user is logged in -app.MapRemoteBffApiEndpoint("/api/optional-user-token", "https://localhost:5010") -.WithOptionalUserAccessToken(); +app.MapRemoteBffApiEndpoint("/api/optional-user-token", new Uri("https://localhost:5010")) +.WithAccessToken(RequiredTokenType.UserOrNone); ``` * The enum `TokenType` has been renamed to `RequiredTokenType`, and moved from the `Duende.Bff` to `Duende.Bff.AccessTokenManagement` namespace. * The methods to configure the token type have all been replaced with a new method `WithAccessToken()` * Requesting an optional access token should no longer be done by calling `WithOptionalUserAccessToken()`. Use `WithAccessToken(RequiredTokenType.UserOrNone)` instead. ### Configuring Token Types In YARP [Section titled “Configuring Token Types In YARP”](#configuring-token-types-in-yarp) The required token type configuration in YARP has also changed slightly. It uses the enum values from `RequiredTokenType`. ### Extending The BFF [Section titled “Extending The BFF”](#extending-the-bff) #### Service To Endpoint Updates [Section titled “Service To Endpoint Updates”](#service-to-endpoint-updates) Service interfaces and their default implementations have been renamed and have changed, resulting in an updated extensibility model: * Generally, the interfaces have been renamed, e.g. from `IUserService` to `IUserEndpoint`. * Default implementation is now internal, but can be used when overriding the endpoint: ```diff -public class MyUserService : DefaultUserService -{ -public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) - { // Custom logic here -return base.ProcessRequestAsync(context); - } -} +var bffOptions = app.Services.GetRequiredService>().Value; +app.MapGet(bffOptions.UserPath, async (HttpContext context, CancellationToken ct) => +{ // ... custom logic before calling the endpoint implementation ... +var endpointProcessor = context.RequestServices.GetRequiredService(); +await endpointProcessor.ProcessRequestAsync(context, ct); // ... custom logic after calling the endpoint implementation ... +}); ``` For more information, see the [endpoints documentation](/bff/extensibility/management/). #### Custom Session Store [Section titled “Custom Session Store”](#custom-session-store) If you have a custom implementation of `IUserSessionStore`, the interface has changed to support multiple frontends. In all methods, the `string key` has been replaced with a strongly typed `UserSessionKey` struct, which contains the `PartitionKey` and `SessionId`: * `PartitionKey` - Corresponds to the frontend name (or `ApplicationName` in V3). * `SessionId` - The user’s session identifier. ```diff public class MySessionStore : IUserSessionStore { -public Task GetUserSessionAsync(string key, CancellationToken cancellationToken) + public Task GetUserSessionAsync(UserSessionKey key, CancellationToken cancellationToken) { // ... } // ... } ``` Also see [related database changes and migrations](#server-side-sessions-database-migrations). #### Access Token Retrieval [Section titled “Access Token Retrieval”](#access-token-retrieval) The `HttpContext.GetUserAccessTokenAsync` extension method has been removed from the `Duende.Bff` namespace. You should now use the extension method from the `Duende.AccessTokenManagement.OpenIdConnect` namespace. ```csharp using Duende.AccessTokenManagement.OpenIdConnect; // ... var token = await HttpContext.GetUserAccessTokenAsync(); ``` #### Simplified Wireup Without Explicit Authentication Setup [Section titled “Simplified Wireup Without Explicit Authentication Setup”](#simplified-wireup-without-explicit-authentication-setup) The V3 style of wireup still works, but BFF V4 comes with a newer style of wireup: ```csharp services.AddBff() .ConfigureOpenIdConnect(options => { options.Authority = "your authority"; options.ClientId = "your client id"; options.ClientSecret = "secret"; // ... other OpenID Connect options. } .ConfigureCookies(options => { // The cookie options are automatically configured with recommended practices. // However, you can change the config here. }); ``` Adding this will automatically configure a Cookie and OpenID Connect flow. #### Adding Multiple Frontends [Section titled “Adding Multiple Frontends”](#adding-multiple-frontends) You can statically add a list of frontends by calling the `AddFrontends` method. ```csharp .AddFrontends( new BffFrontend(BffFrontendName.Parse("default-frontend")) .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")), new BffFrontend(BffFrontendName.Parse("with-path")) .WithOpenIdConnectOptions(opt => { opt.ClientId = "bff.multi-frontend.with-path"; opt.ClientSecret = "secret"; }) .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")) .MapToPath("/with-path"), new BffFrontend(BffFrontendName.Parse("with-domain")) .WithOpenIdConnectOptions(opt => { opt.ClientId = "bff.multi-frontend.with-domain"; opt.ClientSecret = "secret"; }) .WithCdnIndexHtmlUrl(new Uri("https://localhost:5005/static/index.html")) .MapToHost(HostHeaderValue.Parse("https://app1.localhost:5005")) .WithRemoteApis( new RemoteApi("/api/user-token", new Uri("https://localhost:5010")), new RemoteApi("/api/client-token", new Uri("https://localhost:5010")) ) ``` #### Loading Configuration From `IConfiguration` [Section titled “Loading Configuration From IConfiguration”](#loading-configuration-from-iconfiguration) Loading configuration, including OpenID Connect configuration from `IConfiguration` is now supported: ```csharp services.AddBff().LoadConfiguration(bffConfig); ``` This enables you to configure your OpenID Connect options, including secrets, and configure the list of frontends. This also adds a file watcher, to automatically add / remove frontends from the config file. See the type `BffConfiguration` to see what settings can be configured. ## Handling SPA Static Assets [Section titled “Handling SPA Static Assets”](#handling-spa-static-assets) The BFF can be configured to handle the static file assets that are typically used when developing SPA based apps. ### Proxying Only `index.html` [Section titled “Proxying Only index.html”](#proxying-only-indexhtml) When deploying a multi-frontend BFF, it makes most sense to have the frontends configured with an `index.html` file that is retrieved from a Content Delivery Network (CDN). This can be done in various ways. For example, if you use Vite, you can publish static assets with a base URL configured. This will make sure that any static asset, (such as images, scripts, etc.) are retrieved directly from the CDN for best performance. ```csharp var frontend = new BffFrontend(BffFrontendName.Parse("frontend1")) .WithCdnIndexHtml(new Uri("https://my_cdn/some_app/index.html")) ``` The BFF automatically wires up a catch-all route that serves`index.html` for that specific frontend. See [Serve the index page from the BFF host](/bff/architecture/ui-hosting/#serve-the-index-page-from-the-bff-host) for more information. ### Proxying All Static Assets [Section titled “Proxying All Static Assets”](#proxying-all-static-assets) When developing a Single-Page Application (SPA), it’s very common to use a development webserver such as Vite. While Vite can publish static assets with a base URL, this doesn’t work well during development. The best development experience can be achieved by configuring the BFF to proxy all static assets from the development server: ```csharp var frontend = new BffFrontend(BffFrontendName.Parse("frontend1")) .WithProxiedStaticAssets(new Uri("https://localhost:3000")); // https://localhost:3000 would be the URL of your development web server. ``` While this can also be done in production, it will proxy all static assets through the BFF. This will increase the bandwidth consumed by the BFF and reduce the overall performance of your application. ### Proxying Assets Based On Environment [Section titled “Proxying Assets Based On Environment”](#proxying-assets-based-on-environment) If you’re using a local development server during development and a CDN in production, you can configure this as follows: ```csharp // In this example, the environment name from the application builder is used to determine // if we're running in production or not. var runningInProduction = () => builder.Environment.EnvironmentName == Environments.Production; // Then, when configuring the frontend, you can switch when the static assets will be proxied. new BffFrontend(BffFrontendName.Parse("default-frontend")) .WithBffStaticAssets(new Uri("https://localhost:5010/static"), useCdnWhen: runningInProduction); ``` ### Server Side Sessions Database Migrations [Section titled “Server Side Sessions Database Migrations”](#server-side-sessions-database-migrations) When using the server side sessions feature backed by the `Duende.BFF.EntityFramework` package, you will need to script [Entity Framework database migrations](/bff/fundamentals/session/server-side-sessions/#entity-framework-migrations) and apply these changes to your database. ```shell dotnet ef migrations add BFFUserSessionsV4 -o Migrations -c SessionDbContext ``` In the `UserSessions` table, a number of changes were introduced: * The `ApplicationName` column was renamed to `PartitionKey`. This column will contain the BFF frontend name. * Related indexes were updated. serversidesessions.sql ```sqlite ALTER TABLE "UserSessions" RENAME COLUMN "ApplicationName" TO "PartitionKey"; DROP INDEX "IX_UserSessions_ApplicationName_SubjectId_SessionId"; CREATE UNIQUE INDEX "IX_UserSessions_PartitionKey_SubjectId_SessionId" ON "UserSessions" ("PartitionKey", "SubjectId", "SessionId"); DROP INDEX "IX_UserSessions_ApplicationName_SessionId"; CREATE UNIQUE INDEX "IX_UserSessions_PartitionKey_SessionId" ON "UserSessions" ("PartitionKey", "SessionId"); DROP INDEX "IX_UserSessions_ApplicationName_Key"; CREATE UNIQUE INDEX "IX_UserSessions_PartitionKey_Key" ON "UserSessions" ("PartitionKey", "Key"); ``` ----- # AI Agent Skills and MCP Server > Enhance your AI coding assistant with Duende-specific knowledge using Agent Skills for domain expertise and an MCP server for documentation and samples retrieval. When you use AI coding assistants with Duende products, you may find that general-purpose models lack deep expertise on Duende-specific configuration patterns, protocol nuances, and production best practices. Generic responses can miss critical details, like how to configure refresh token rotation, set up a federation gateway, or wire IdentityServer into .NET Aspire. To address this, Duende provides two complementary tools that give your AI coding assistant specialized knowledge: **Duende Agent Skills** and the **Duende Documentation MCP Server**. You can use either or both, depending on your workflow. Agent Skills and the MCP server address different aspects of the same problem and work well together: * **Agent Skills** provide *knowledge*: structured, curated guidance on *what* to look up, *when*, and *how* to apply it. They are static files that run locally in your development environment. When your AI assistant encounters an identity-related task, skills give it the judgment to produce accurate, Duende-specific answers. * **MCP Server** provides *tools*: search, fetch, and sample retrieval against the full Duende documentation, blog, and sample code. It runs as a local server process and gives the AI assistant direct access to the latest published content. Think of skills as the expertise and the MCP server as the reference library. Skills help the AI *know what to do*; the MCP server helps it *look things up*. Together, they give your AI assistant both deep domain knowledge and access to up-to-date authoritative content. ## Which Tool Should You Use? [Section titled “Which Tool Should You Use?”](#which-tool-should-you-use) Choose the approach that fits your workflow: * **Want domain expertise baked into every response?** Install Agent Skills. Your AI assistant will automatically use the relevant skill when it encounters identity-related tasks. * **Want to search and fetch the latest docs and samples?** Register the MCP Server. Your AI assistant gains tools to look up current documentation on demand. * **Want both?** Install both. They are independent and complementary: skills provide structured knowledge while the MCP server provides live content retrieval. [Get Started with Agent Skills ](https://github.com/DuendeSoftware/duende-skills)Installation instructions, skill catalog, and benchmarks [Get Started with the MCP Server ](https://github.com/DuendeSoftware/products/blob/main/docs-mcp/README.md)Setup instructions for VS Code, Rider, and Claude Code ## Agent Skills [Section titled “Agent Skills”](#agent-skills) Duende IdentityServer Agent Skills are a set of `SKILL.md` files following the open [Agent Skills format](https://agentskills.io/). Each skill is a structured knowledge module covering a specific area of identity and access management. ### What They Cover [Section titled “What They Cover”](#what-they-cover) The skills library includes a number of skills and specialized agents across these areas: * **IdentityServer configuration and hosting**: setup, middleware pipeline, clients, resources, scopes, signing credentials, server-side sessions, Dynamic Client Registration (DCR) * **Token management**: token types, refresh token rotation, token exchange, DPoP, mTLS, Pushed Authorization Requests (PAR), FAPI 2.0 compliance * **API protection**: JWT bearer authentication, reference token introspection, scope-based authorization, proof-of-possession * **UI flows**: login, logout, consent, error pages, federation gateways, external providers, Home Realm Discovery * **ASP.NET Core authentication and authorization**: OIDC, JWT Bearer, cookies, policies, claims-based authorization * **Duende BFF**: Backend-for-Frontend security for SPAs, session management, API proxying * **Deployment and operations**: reverse proxy configuration, data protection, health checks, OpenTelemetry, key management, SAML 2.0 * **Testing**: integration testing with `WebApplicationFactory`, mock token issuance, protocol validation * **Specialized agents**: an IdentityServer specialist and an OAuth/OIDC specialist for complex troubleshooting ### Setup [Section titled “Setup”](#setup) Clone the [Duende Agent Skills](https://github.com/DuendeSoftware/duende-skills) repository and copy the skill folders into the skills directory for your AI coding assistant. Each skill is a folder containing a `SKILL.md` file. Copy the individual skill folders into the path your AI assistant expects: | AI Coding Assistant | Skills Path | | :------------------ | :--------------------------- | | GitHub Copilot | `.github/skills/` | | Claude Code | `.claude/skills/` | | OpenCode | `~/.config/opencode/skills/` | | Cursor | `.cursor/skills/` | | Gemini CLI | `.gemini/skills/` | | Codex CLI | `.codex/skills/` | For example, to set up skills for GitHub Copilot: * Windows (PowerShell) PowerShell ```powershell git clone https://github.com/DuendeSoftware/duende-skills.git New-Item -ItemType Directory -Force -Path .github\skills Copy-Item -Recurse duende-skills\skills\* .github\skills\ ``` * macOS / Linux Terminal ```bash git clone https://github.com/DuendeSoftware/duende-skills.git mkdir -p .github/skills cp -r duende-skills/skills/* .github/skills/ ``` Adjust the target path for your AI coding assistant (see the table above). For example, replace `.github/skills/` with `.claude/skills/` for Claude Code, or `~/.config/opencode/skills/` for OpenCode. Once the skill folders are in place, your AI assistant discovers and loads them automatically. No further configuration is needed. When your assistant encounters an identity-related task like configuring token lifetimes or setting up an external provider, it loads the relevant skill without any explicit prompting from you. ### Verify It Works [Section titled “Verify It Works”](#verify-it-works) Ask your AI assistant an identity-specific question, for example: `How do I configure refresh token rotation in IdentityServer?`. If the skills are loaded correctly, the response references Duende-specific configuration and mentions IdentityServer options like `RefreshTokenUsage`. ### Measured Impact [Section titled “Measured Impact”](#measured-impact) Every skill is evaluated using realistic prompts with concrete assertions. In benchmarks, AI responses with skills loaded significantly outperform baseline responses, with the biggest gains in deeply Duende-specific areas like UI flows, API protection, and SAML configuration. See the repository for the latest benchmark results, or run them against your model of choice. [Duende Agent Skills ](https://github.com/DuendeSoftware/duende-skills)Installation instructions, full skill catalog, and benchmark results ## MCP Server [Section titled “MCP Server”](#mcp-server) The Duende Documentation MCP Server implements the open [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) to give AI coding assistants direct access to Duende documentation, blog posts, and sample code. It runs locally and uses SQLite full-text search to index content from multiple sources. ### What It Can Do [Section titled “What It Can Do”](#what-it-can-do) The MCP server provides several tools to your AI assistant: * **Free-text search** across documentation, blog posts, or samples * **Fetch a specific page** from the documentation site * **Get all content for a sample**: retrieve the full code of a Duende sample project * **Get a specific file from a sample**: retrieve individual files from sample code The server indexes content from three sources, keeping its local database up to date with background indexing: * **Documentation**: parsed from the Duende documentation site’s [`llms.txt`](https://docs.duendesoftware.com/llms.txt) * **Blog**: indexed from the RSS feed at [duendesoftware.com/blog](https://duendesoftware.com/blog/) * **Samples**: downloaded from GitHub, including all `.cs`, `.cshtml`, and relevant `.js` files ### Requirements [Section titled “Requirements”](#requirements) * **.NET 10 SDK**: the MCP server is distributed via the `dnx` tool included in the SDK * **Network access**: the server indexes content from remote sources (documentation site, RSS feed, GitHub) * **A compatible AI coding assistant**: any IDE or CLI tool that supports the MCP protocol No Duende license is required to use the MCP server. ### Setup [Section titled “Setup”](#setup-1) To run the Duende Documentation MCP Server, you need the `dnx` tool (included in the .NET 10 SDK) in your system’s `PATH`. The `dnx` tool can download and run applications packaged and distributed through NuGet. Here are some examples of how to register the MCP server in your IDE: * VS Code You can register the MCP server [in your user settings](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server-to-your-user-settings) to make it available in any workspace, or add a `.vscode/mcp.json` file to your workspace: .vscode/mcp.json ```json { "servers": { "duende-mcp": { "type": "stdio", "command": "dnx", "args": [ "Duende.Documentation.Mcp", "--yes", "--", "--database", "/path/to/database.db" ], "env": {} } } } ``` Replace `/path/to/database.db` with the location where the MCP server should store its SQLite index. * JetBrains Rider In Rider settings, navigate to **Tools | AI Assistant | Model Context Protocol (MCP)**. Add a new MCP server, select **As JSON**, and enter: ```json { "mcpServers": { "duende-mcp": { "command": "dnx", "args": [ "Duende.Documentation.Mcp", "--yes", "--", "--database", "/path/to/database.db" ] } } } ``` Replace `/path/to/database.db` with the location where the MCP server should store its SQLite index. * Claude Code Run the following command: PowerShell ```powershell # Windows (PowerShell) claude mcp add --transport stdio duende-mcp ` -- dnx Duende.Documentation.Mcp --yes ` -- --database C:\path\to\database.db ``` Terminal ```bash # macOS / Linux claude mcp add --transport stdio duende-mcp \ -- dnx Duende.Documentation.Mcp --yes \ -- --database /path/to/database.db ``` Replace the database path with the location where the MCP server should store its SQLite index. The MCP server creates its SQLite database at the path you specify in the `--database` parameter. On first run, it indexes documentation, blog posts, and samples in the background. Subsequent starts reuse the existing index and refresh it incrementally. ### Verify It Works [Section titled “Verify It Works”](#verify-it-works-1) Ask your AI assistant a Duende-specific question, for example: `What is automatic key management?`. If the MCP server is working, the response draws on the indexed documentation and references Duende-specific content. Adding `use Duende` to a prompt can help direct the AI assistant to query the MCP server when the topic could match multiple sources. ### Example Prompts [Section titled “Example Prompts”](#example-prompts) Once the MCP server is registered, you can ask your AI assistant questions like: * `What is a client in OpenID Connect?` * `How can I validate a JWT token in ASP.NET Core?` * `What is automatic key management?` * `Can I add passkeys to Razor Pages? Use Duende.` [Duende Documentation MCP Server ](https://github.com/DuendeSoftware/products/blob/main/docs-mcp/README.md)Setup instructions for VS Code, Rider, and Claude Code ## Support and Feedback [Section titled “Support and Feedback”](#support-and-feedback) For questions, feedback, or to report issues with either the Agent Skills or the MCP server, visit the [Duende community](https://github.com/DuendeSoftware/community/discussions). [Duende Community Forum ](https://github.com/DuendeSoftware/community/discussions)Ask questions and discuss with the Duende developer community ## Disclaimer [Section titled “Disclaimer”](#disclaimer) Duende’s AI developer tools (including the Duende Documentation MCP Server and Duende Agent Skills) are designed to provide Large Language Models (LLMs) with verified, structured context from Duende’s documentation and product knowledge. These tools improve the quality and relevance of AI-assisted development with Duende products, including IdentityServer, BFF and our Open Source offerings, but they do not guarantee the correctness, security, or completeness of AI-generated output. All code, configuration, and architectural decisions produced with the assistance of these tools must be reviewed and validated by qualified developers before deployment to any environment. Duende Software is not responsible for AI-generated output that results from the use of these tools. ----- # ASP.NET Core Data Protection > Comprehensive guide covering key aspects of ASP.NET Core Data Protection. Any Duende server-side application, like IdentityServer or BFF, is developed and deployed as an ASP.NET Core application. While there are a lot of decisions to make, this also means that your implementation can be built, deployed, hosted, and managed with the same technology you’re using for any other ASP.NET applications you have. It is important to correctly configure ASP.NET Core Data Protection in your application. ## About ASP.NET Core Data Protection [Section titled “About ASP.NET Core Data Protection”](#about-aspnet-core-data-protection) Duende’s SDKs, like IdentityServer and BFF, make extensive use of ASP.NET’s [data protection](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/) feature. It is crucial that you configure data protection correctly when deploying your application in production. ## Data Protection Keys [Section titled “Data Protection Keys”](#data-protection-keys) In local development, ASP.NET automatically creates data protection keys, but in a deployed environment, you will need to ensure that your data protection keys are stored in a persistent way and shared across all load balanced instances of your implementation. This means you’ll need to choose where to store and how to protect the data protection keys, as appropriate for your environment. Microsoft has [extensive documentation on data protection](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview) describing how to configure storage and protection of data protection keys. A typical implementation should include data protection configuration code, like this: Program.cs ```csharp builder.Services.AddDataProtection() // Choose an extension method for key persistence, such as // PersistKeysToFileSystem, PersistKeysToDbContext, // PersistKeysToAzureBlobStorage, PersistKeysToAWSSystemsManager, or // PersistKeysToStackExchangeRedis .PersistKeysToFoo() // Choose an extension method for key protection, such as // ProtectKeysWithCertificate, ProtectKeysWithAzureKeyVault .ProtectKeysWithBar() // Explicitly set an application name to prevent issues with // key isolation. .SetApplicationName("My.Duende.IdentityServer"); ``` Ensure data protection keys are persisted Always make sure data protection is configured to persist data protection keys to storage, using `.PersistKeysTo...()` for your storage mechanism. If you lose your data protection keys, all data protected with those keys is no longer be readable. Additionally, ensure the storage mechanism itself is durable. For example, if you are using the default file system based key store, make sure that the configured path is not stored on ephemeral storage. If you are using Redis to store data protection keys using `PersistKeysToStackExchangeRedis()`, ensure that your Redis service is configured to persist data to a database backup or append-only file. Otherwise, you will lose all data protection keys when your Redis instance reboots. For a more advanced setup, you can create a [key escrow sink](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/extensibility/key-management?view=aspnetcore-10.0#xmlkeymanager), allowing you to store new data protection keys into a secure storage (e.g., Azure Key Vault) before the new keys are encrypted. This enables you to restore existing data protection keys in case they become corrupted or lost. ## Common Problems [Section titled “Common Problems”](#common-problems) Common data protection problems occur when data is protected with a key that is not available when the data is later read. A common symptom is `CryptographicException`s in the application logs. For example, when IdentityServer’s automatic key management fails to read its signing keys due to a data protection failure, it will log an error message such as `"Error unprotecting key with kid {Signing Key ID}."`, and log the underlying `System.Security.Cryptography.CryptographicException`, with a message like `"The key {Data Protection Key ID} was not found in the key ring."` Failures to read automatic signing keys are often the first place where a data protection problem manifests, but any of many places where ASP.NET uses data protection might also throw `CryptographicException`s. There are several ways that data protection problems can occur: 1. In load balanced environments, every instance of a Duende server-side app needs to be configured to share data protection keys. Without shared data protection keys, each load balanced instance will only be able to read the data that it writes. 2. Data protected data could be generated in a development environment and then accidentally included into the build output. This is most commonly the case for automatically managed signing keys that are stored on disk. If you are using automatic signing key management with the default file system based key store, you should exclude the `~/keys` directory from source control and make sure keys are not included in your builds. Note that if you are using our Entity Framework based implementation of the operational data stores, then the keys will instead be stored in the database. 3. Data protection derives keys isolated per application name from the generated key material. If you don’t specify a name, the content root path of the application will be used. In .NET 6.0, Microsoft introduced a breaking change: they changed how ASP.NET Core sets the content root path, which can cause Data Protection issues. This change was reverted in .NET 7.0, and Microsoft has [documented a workaround in case your application has to restore the correct application name](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview#set-the-application-name-setapplicationname). A better solution is to always specify an explicit application name, but know that changing the application name will cause all existing data protected with the previous application name to become unreadable. 4. When hosting your web application on Microsoft IIS, [special configuration may be required for data protection](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/advanced#data-protection). In most default deployments, IIS falls back to using an ephemeral storage for data protection keys, which means that new keys are generated every time the application pool restarts. We recommend storing data protection keys in a shared location, such as a protected file share or database, and configuring IIS to use that location for data protection. ----- # Glossary > A comprehensive glossary of security and identity management terms, including features and concepts used in Duende IdentityServer The glossary below provides definitions and explanations of commonly used terms and features within the security and identity management domain. Explore each term to gain a deeper understanding of its functionality and relevance. ## Client [Section titled “Client”](#client) A client is a piece of software that requests tokens from your IdentityServer - either for authenticating a user ( requesting an identity token) or for accessing a resource (requesting an access token). A client must be first registered with your IdentityServer before it can request tokens and is identified by a unique client ID. There are many different client types, e.g. web applications, native mobile or desktop applications, SPAs, server processes, etc. [Documentation ](/identityserver/fundamentals/clients)Learn more about clients ## Connected Application [Section titled “Connected Application”](#connected-application) A connected application is any application or service registered with your Duende IdentityServer instance that relies on it for identity, access, or federation. Each connected application has a unique registration that defines how it interacts with IdentityServer and what it is allowed to do. Connected applications fall into four categories: 1. Interactive applications use OpenID Connect (OIDC) to authenticate users and obtain tokens. These include web applications, native mobile or desktop applications, and SPAs, each identified by its own [Client ID](/general/glossary/#client). 2. Machine-to-machine clients request access tokens without user interaction, typically using the client credentials grant. Background services, APIs calling other APIs, and MCP clients are common examples. 3. Third-party API consumer that requires a client ID and client secret, typically in a SaaS situation or B2B situation. 4. SAML Service Providers use SAML 2.0 to establish federated trust with IdentityServer acting as the Identity Provider (IdP), enabling single sign-on for applications that rely on SAML-based authentication. ## Core Protocols [Section titled “Core Protocols”](#core-protocols) **License: Lite, Standard, Advanced, Custom** Core protocols cover the OpenID Connect (OIDC) and OAuth 2.0 foundations that most applications need. If you’re building a typical web app, API, or mobile client, core protocols handle authentication, token issuance, and token validation out of the box. Here’s what’s included: | Protocol | What It Does | | :------------------------------------------------- | :--------------------------------------------------------------------- | | OAuth 2.0 Core | Token-based authorization for APIs | | OpenID Connect Core | User authentication on top of OAuth 2.0 | | OIDC Discovery / Authorization Server Metadata | Clients auto-discover your server’s endpoints and capabilities | | Authorization Code Flow | The recommended flow for interactive apps (with PKCE) | | Client Credentials Flow | Machine-to-machine token acquisition | | Token Exchange | Swap one token for another across trust boundaries | | Step-up Authentication | Require stronger authentication for sensitive operations | | JSON Web Tokens (JWT) | Standard token format for access and identity tokens | | JWT Access Token Profile | Structured JWT access tokens per the IETF profile | | JWT Client Authentication | Clients authenticate using signed JWTs instead of shared secrets | | JWT Introspection Response | Token metadata returned as a signed JWT | | Bearer Token Usage | Standard `Authorization: Bearer` header for API calls | | Token Revocation | Invalidate tokens before they expire | | Token Introspection | APIs verify opaque tokens against the server | | RP-Initiated / Front-Channel / Back-Channel Logout | Full suite of logout flows | | PAR (Pushed Authorization Requests) | Clients send auth parameters directly to the server before redirect | | DPoP (Demonstrating of Proof-of-Possession) | Binds tokens to a client’s key pair so stolen tokens can’t be replayed | | Form Post Response Mode | Auth responses delivered via POST instead of the query string | | Multiple Response Types | Support for different OAuth response modes | For the majority of .NET applications, core protocols handle every scenario: users log in, APIs validate tokens, machines talk to machines, and sessions end cleanly. ## Extended Protocols [Section titled “Extended Protocols”](#extended-protocols) **License: Standard, Advanced, Custom** Extended protocols address requirements that go beyond typical web and API scenarios. | Protocol | What It Does | | :------------------------------------------------- | :-------------------------------------------------------------------------------- | | mTLS (Mutual TLS) | Binds tokens to a client certificate for sender-constrained access | | JAR (JWT-Secured Authorization Requests) | Wraps the authorization request in a signed JWT for integrity and confidentiality | | Resource Indicators | Lets a client specify which API it’s targeting when requesting tokens | | CIBA (Client-Initiated Backchannel Authentication) | Authentication triggered by a backend service, not a browser redirect | | Device Authorization Grant | Login flow for devices without a browser (smart TVs, CLI tools, IoT) | | DCR (Dynamic Client Registration) | Clients register themselves programmatically instead of being pre-configured | These protocols solve real problems, but they’re problems that surface in specific business contexts rather than in every project. ## Automatic Key Management [Section titled “Automatic Key Management”](#automatic-key-management) **License: Business (legacy), Enterprise (legacy), Standard (add-on), Advanced, Custom** The automatic key management feature creates and manages key material for signing tokens and follows best practices for handling this key material, including storage and rotation. [More Details ](https://duendesoftware.com/blog/20201028-key-management)Automatic Key Management post [Documentation ](/identityserver/fundamentals/key-management/#automatic-key-management)Learn more about key rotation ## Server-side Session Management [Section titled “Server-side Session Management”](#server-side-session-management) **License: Business (legacy), Enterprise (legacy), Standard, Advanced, Custom** The server-side session management feature extends the ASP.NET Core cookie authentication handler to maintain a user’s authentication session state in a server-side store, rather than putting it all into a self-contained cookie. Using server-side sessions enables more architectural features in your IdentityServer, such as: * query and manage active user sessions (e.g. from an administrative app). * detect session expiration and perform cleanup, both in IdentityServer and in client apps. * centralize and monitor session activity in order to achieve a system-wide inactivity timeout. [More Details ](https://duendesoftware.com/blog/20220406-session-management)Server-side Session Management post [Documentation ](/identityserver/ui/server-side-sessions/)Learn more about Server-side Session Management ## BFF Security Framework [Section titled “BFF Security Framework”](#bff-security-framework) **License: Lite, Standard, Advanced, Custom** The Duende Backend For Frontend (BFF) security framework packages up guidance and the necessary components to secure browser-based frontends (e.g. SPAs or Blazor WASM applications) with ASP.NET Core backends. [More Details ](https://duendesoftware.com/blog/20210326-bff)BFF Security Framework post [Documentation ](/bff/)Learn more about BFF ## Dynamic Client Registration [Section titled “Dynamic Client Registration”](#dynamic-client-registration) **License: Business (legacy), Enterprise (legacy), Standard, Advanced, Custom** Implementation of [RFC 8707](https://tools.ietf.org/html/rfc8707). Provides a standards-based endpoint to register clients and their configuration. [Documentation ](/identityserver/configuration)Learn more about Dynamic Client Registration ## Pushed Authorization Requests [Section titled “Pushed Authorization Requests”](#pushed-authorization-requests) **License: Business (legacy), Lite, Standard, Advanced, Custom** Implementation of [RFC 9126](https://www.rfc-editor.org/rfc/rfc9126.html). Provides a more secure way to start a browser-based token/authentication request. [Documentation ](/identityserver/tokens/par)Learn more about Pushed Authorization Requests ## Dynamic Authentication Providers [Section titled “Dynamic Authentication Providers”](#dynamic-authentication-providers) **License: Enterprise (legacy), Advanced, Custom** The dynamic configuration feature allows dynamic loading of configuration for OpenID Connect providers from a store. This is designed to address the performance concern and allowing changes to the configuration to a running server. [More Details ](https://duendesoftware.com/blog/20210517-dynamic-providers)Dynamic Authentication Providers post [Documentation ](/identityserver/fundamentals/key-management/#automatic-key-management)Learn more about Dynamic Authentication Providers ## Resource Isolation [Section titled “Resource Isolation”](#resource-isolation) **License: Enterprise (legacy), Standard, Advanced, Custom** The resource isolation feature allows a client to request access tokens for an individual resource server. This allows API-specific features such as access token encryption and isolation of APIs that are not in the same trust boundary. [More Details ](https://duendesoftware.com/blog/20260210-implementing-zero-trust-with-resource-isolation)Resource Isolation post [Documentation ](/identityserver/fundamentals/resources/isolation/)Learn more about Resource Isolation ## Client-Initiated Backchannel Authentication (CIBA) [Section titled “Client-Initiated Backchannel Authentication (CIBA)”](#client-initiated-backchannel-authentication-ciba) **License: Enterprise (legacy), Standard, Advanced, Custom** Duende IdentityServer supports the Client-Initiated Backchannel Authentication Flow (also known as CIBA). This allows a user to log in with a higher security device (e.g. their mobile phone) than the device on which they are using an application (e.g. a public kiosk). CIBA is one of the requirements to support the Financal-grade API compliance. [More Details ](https://duendesoftware.com/blog/20220107-ciba)Client-Initiated Backchannel Authentication post [Documentation ](/identityserver/ui/ciba/)Learn more about CIBA ## Proof-of-Possession At The Application Layer / DPoP [Section titled “Proof-of-Possession At The Application Layer / DPoP”](#proof-of-possession-at-the-application-layer--dpop) **License: Enterprise (legacy), Standard, Advanced, Custom** A mechanism for sender-constraining OAuth 2.0 tokens via a proof-of-possession mechanism on the application level. This mechanism allows for the detection of replay attacks with access and refresh tokens. [Documentation ](/identityserver/tokens/pop/)Learn more about Proof-of-Possession ## SAML 2.0 Identity Provider [Section titled “SAML 2.0 Identity Provider”](#saml-20-identity-provider) **License: Enterprise (legacy), Standard (add-on), Advanced, Custom** IdentityServer can act as a SAML 2.0 Identity Provider (IdP), issuing SAML assertions to Service Providers (SPs) that use the SAML 2.0 protocol rather than OAuth 2.0 / OpenID Connect. This is useful for integrating with enterprise SaaS applications (e.g. Salesforce, ServiceNow) or legacy SSO systems that require SAML-based federation. [Documentation ](/identityserver/saml/)Learn more about SAML 2.0 Identity Provider support ## Financial-Grade Security and Conformance (FAPI 2.0) [Section titled “Financial-Grade Security and Conformance (FAPI 2.0)”](#financial-grade-security-and-conformance-fapi-20) **License: Standard (add-on), Advanced (add-on), Custom (add-on)** The [Financial-grade API (FAPI) 2.0 Security Profile](https://openid.net/specs/fapi-security-profile-2_0-final.html) is an API security profile based on OAuth 2.0 designed to protect APIs in high-value scenarios such as e-health and e-government. Duende IdentityServer implements the FAPI 2.0 best current practice features and includes a built-in conformance report that assesses your server and client configuration against OAuth 2.1 and FAPI 2.0 specifications. [Documentation ](/identityserver/tokens/fapi-2-0-specification/)Learn more about FAPI 2.0 compliance [Conformance Report ](/identityserver/diagnostics/conformance-report/)Assess your configuration against FAPI 2.0 ## Single Deployment [Section titled “Single Deployment”](#single-deployment) A single deployment acts as a single OpenID Connect / OAuth authority hosted at a single URL. It can consist of multiple physical or virtual nodes for load-balancing or fail-over purposes. ## Multiple Deployments [Section titled “Multiple Deployments”](#multiple-deployments) Can be either completely independent single deployments, or a single deployment that acts as multiple authorities. ## Multiple Authorities [Section titled “Multiple Authorities”](#multiple-authorities) A single logical deployment that acts as multiple logical token services on multiple URLs or host names (e.g. for branding, isolation or multi-tenancy reasons). ## Redistribution [Section titled “Redistribution”](#redistribution) Redistribution occurs when you bundle Duende IdentityServer as an integrated component of a product or service that you sell, lease, or provide to third parties. Redistribution typically applies to Independent Software Vendors (ISVs) who ship IdentityServer as part of a larger solution that customers host on their own local or cloud infrastructure. Each customer installation of IdentityServer is considered a separate redistribution. ## User Management - Licensed Users [Section titled “User Management - Licensed Users”](#user-management---licensed-users) The total of unique identity records stored in Duende User Management, identified by User Management user ID per billing period. ## FIDO2 [Section titled “FIDO2”](#fido2) FIDO2 is the FIDO Alliance’s umbrella standard for strong, passwordless authentication. It encompasses two specifications: **WebAuthn** (the browser/platform API) and **CTAP** (Client to Authenticator Protocol, which defines how authenticators such as hardware security keys communicate with a platform). FIDO2 credentials are based on public-key cryptography and are phishing-resistant by design. ## MFA (Multi-Factor Authentication) [Section titled “MFA (Multi-Factor Authentication)”](#mfa-multi-factor-authentication) Multi-Factor Authentication requires a user to present two or more independent verification factors before being granted access. Factors are typically categorized as something you *know* (e.g. a password or PIN), something you *have* (e.g. a TOTP authenticator app or hardware key), and something you *are* (e.g. a biometric). Requiring multiple factors significantly reduces the risk of account compromise from stolen credentials. ## OTP (One-Time Password) [Section titled “OTP (One-Time Password)”](#otp-one-time-password) A One-Time Password is a temporary, single-use code delivered out-of-band (typically via email or SMS) that authenticates a user without requiring a traditional password. Because each code is valid for only a short window and cannot be reused, OTPs provide a simple form of passwordless or second-factor authentication. ## Passkey [Section titled “Passkey”](#passkey) A passkey is a FIDO2 credential that replaces a traditional password. It is bound to a specific device (or synced across a user’s devices via a platform credential manager) and is unlocked locally using biometrics or a PIN. The private key never leaves the device, making passkeys resistant to phishing and server-side credential theft. ## Recovery Code [Section titled “Recovery Code”](#recovery-code) A recovery code is a one-time backup code generated at enrollment time and stored securely by the user. It can be used to regain account access when primary authenticators (such as a TOTP app or passkey device) are unavailable. Each code is valid for a single use only. ## Space (User Management) [Section titled “Space (User Management)”](#space-user-management) In Duende User Management, a *space* is the fundamental isolation unit. Each space has its own isolated user store, configuration, and authentication settings. This enables deployments where users, roles, and groups in one space are completely separated from those in another. A typical use case is supporting multiple tenants in a single deployment. ## Subject ID [Section titled “Subject ID”](#subject-id) A Subject ID (`sub`) is a stable, unique identifier assigned to a user within the system. It is analogous to, and typically maps directly to, the `sub` claim in OpenID Connect tokens. The Subject ID remains constant across sessions and should be used as the canonical reference to a user rather than mutable attributes such as username or email address. ## TOTP (Time-Based One-Time Password) [Section titled “TOTP (Time-Based One-Time Password)”](#totp-time-based-one-time-password) A Time-Based One-Time Password is a short-lived numeric code generated by an authenticator app (e.g. Google Authenticator, Microsoft Authenticator) according to [RFC 6238](https://www.rfc-editor.org/rfc/rfc6238). The code is derived from a shared secret and the current time, and is typically valid for 30 seconds. TOTP is widely used as a second factor in MFA flows. ## WebAuthn (Web Authentication) [Section titled “WebAuthn (Web Authentication)”](#webauthn-web-authentication) WebAuthn is the [W3C Web Authentication standard](https://www.w3.org/TR/webauthn/) that defines a browser API for creating and using public-key credentials. It is the web-facing component of FIDO2. Relying parties (web applications) use WebAuthn to register and authenticate users with device-bound or synced credentials (passkeys), hardware security keys, or platform authenticators, all without transmitting a password. ## Standard Developer Support [Section titled “Standard Developer Support”](#standard-developer-support) Online [developer community forum](https://github.com/DuendeSoftware/community/discussions) for Duende Software product issues and bugs. [Duende Developer Community ](https://github.com/DuendeSoftware/community/discussions)Learn more about the Duende Developer Community ## Priority / Premium Developer Support [Section titled “Priority / Premium Developer Support”](#priority--premium-developer-support) **License: Enterprise (legacy), Standard, Advanced, Custom** Helpdesk system with guaranteed response time for Duende Software product issues and bugs. [More Details ](https://duendesoftware.com/license/PrioritySupportLicense.pdf)Download the Priority Support License PDF ----- # Licensing > Details about Duende IdentityServer and BFF licensing requirements, editions, configuration options, and trial mode functionality. Duende products, except for our [open source tools](https://duendesoftware.com/products/opensource), require a license for production use. The [Duende Software website](https://duendesoftware.com/) provides an overview of different products and license editions. Licenses can be configured via a file system, programmatic startup, or external configuration services like Azure Key Vault, with trial mode available for development and testing. ## IdentityServer [Section titled “IdentityServer”](#identityserver) Duende IdentityServer requires a license for production use, with three editions available (Starter, Business, and Enterprise) that offer various features based on organizational needs. A [community edition](https://duendesoftware.com/products/communityedition/) is available as well. ### Editions [Section titled “Editions”](#editions) There are three license editions which include different [features](https://duendesoftware.com/products/features). #### Lite Edition [Section titled “Lite Edition”](#lite-edition) The Lite edition includes the core OIDC and OAuth protocol implementation. This is an economical option that is a good fit for organizations with basic needs. It’s also a great choice if you have an aging [IdentityServer4 implementation that needs to be updated](/identityserver/upgrades/identityserver4-to-duende-identityserver-v8/) and licensed. The Lite edition includes all the features that were part of IdentityServer4, along with support for the latest .NET releases, improved observability through [OpenTelemetry support](/identityserver/diagnostics/otel/), and years of bug fixes and enhancements. #### Standard Edition [Section titled “Standard Edition”](#standard-edition) The Standard edition adds additional features that go beyond the core protocol support included in the Starter edition. This is a popular license because it adds the most commonly needed tools and features outside a basic protocol implementation. Feature highlights include resource isolation, the OpenId Connect CIBA flow support, and server side sessions. #### Advanced Edition [Section titled “Advanced Edition”](#advanced-edition) Finally, the Advanced edition includes everything in the Standard edition and adds support for features that are typically used by enterprises with particularly complex architectures or that handle particularly sensitive data. Highlights include automatic key management, SAML, and priority developer support. This is the best option when you have a specific threat model or architectural need for these features. #### Starter Edition (legacy) [Section titled “Starter Edition (legacy)”](#starter-edition-legacy) The (legacy) Starter edition includes the core OIDC and OAuth protocol implementation. #### Business Edition (legacy) [Section titled “Business Edition (legacy)”](#business-edition-legacy) The (legacy) Business edition adds additional features that go beyond the core protocol support included in the Starter edition. Feature highlights include support for server side sessions and automatic signing key management. #### Enterprise Edition (legacy) [Section titled “Enterprise Edition (legacy)”](#enterprise-edition-legacy) The (legacy) Enterprise edition includes everything in the Business edition and adds resource isolation, the OpenId Connect CIBA flow, and dynamic federation. ### Redistribution [Section titled “Redistribution”](#redistribution) If you want to redistribute Duende IdentityServer to your customers as part of a product, you can use our [redistributable license](https://duendesoftware.com/products/identityserverredist). ### License Validation and Logging [Section titled “License Validation and Logging”](#license-validation-and-logging) All license validation happens at runtime and is self-contained. It does not leave the host, and there are no outbound network calls related to license validation. #### Startup Validation [Section titled “Startup Validation”](#startup-validation) IdentityServer loads and parses the license key at startup. If the key is present but invalid, an error is logged at that point. Beyond that, no further checks happen at startup. IdentityServer does not compare your configuration against the license at startup; that all happens at runtime, when features are actively used. #### Runtime Validation [Section titled “Runtime Validation”](#runtime-validation) IdentityServer never blocks or disables features at runtime based on licensing. A licensing oversight should never cause an outage. The runtime validator only logs; it does not prevent IdentityServer from functioning. The following features are validated at runtime. If you use one of them without the required license entitlement, IdentityServer logs a warning (rate-limited to once every 5 minutes per feature): * [Server Side Sessions](/identityserver/ui/server-side-sessions/) * [Demonstrating Proof-of-Possession (DPoP)](/identityserver/tokens/pop/) * [Resource Isolation](/identityserver/fundamentals/resources/isolation/) * [Client Initiated Backchannel Authentication (CIBA)](/identityserver/ui/ciba/) * [Dynamic Identity Providers](/identityserver/ui/login/dynamicproviders/) * [Automatic Key Management](/identityserver/fundamentals/key-management/) * [Financial-Grade Security and Conformance Report](/identityserver/diagnostics/conformance-report/) * [SAML IdP and SAML Service Provider](/identityserver/saml/) * [User Management](/identityserver/usermanagement/) For quantized limits like client count and issuer count, IdentityServer logs a warning when you exceed your licensed limit but stay within the grace threshold. If you exceed the grace threshold, it logs an error instead. An expired license also results in an error being logged. #### Trial Mode [Section titled “Trial Mode”](#trial-mode) Running IdentityServer without a license is perfectly fine for development, testing, and personal projects. There is no request limit and no automatic shutdown. All features remain available. The only difference you will notice is that IdentityServer logs a warning when you use a licensed feature without a license configured: ```text {FeatureName} is being used but no Duende license is configured. Please start a conversation with us: https://duende.link/l/contact ``` These warnings are rate-limited to once per five minutes per feature, so they won’t flood your logs. You can silence them entirely by configuring a license key, even in non-production environments. #### Redistribution [Section titled “Redistribution”](#redistribution-1) If you want to redistribute Duende IdentityServer to your customers as part of a product, you can use our [redistributable license](https://duendesoftware.com/products/identityserverredist). It can be cumbersome to deploy updated licenses in redistribution scenarios, especially if your deployment cycle does not coincide with the duration of your IdentityServer license. In that situation, update the license key at the next deployment to your redistribution customers. You are always responsible for ensuring your license is renewed. #### Log Severity [Section titled “Log Severity”](#log-severity) The severity of log messages depends on the nature of the message. All messages are rate-limited to once per 5 minutes per feature or SKU. | Type of message | Severity | | ------------------------------------------------- | ------------- | | Feature used, no license configured | Warning | | Feature used, not covered by license | Warning | | Quantized limit exceeded (within grace threshold) | Warning | | Quantized limit exceeded (beyond grace threshold) | Error | | License expired | Error | | License valid | Informational | ## BFF Security Framework [Section titled “BFF Security Framework”](#bff-security-framework) The Duende BFF Security Framework requires a license for production use, with two editions available (Starter and Enterprise) that offer various features based on organizational needs. ### Editions [Section titled “Editions”](#editions-1) BFF is a library designed to enhance the security of browser-based applications by moving authentication flows to the server side. The Duende BFF Security Framework requires a license for production use, and is available in two editions that [include different functionality](https://duendesoftware.com/products/bff) based on organizational needs. ### Redistribution [Section titled “Redistribution”](#redistribution-2) If you want to redistribute Duende BFF to your customers as part of a product, please [reach out to sales](https://duendesoftware.com/contact/sales). ### License Validation and Logging [Section titled “License Validation and Logging”](#license-validation-and-logging-1) The BFF license is validated during runtime. All license validation is self-contained and does not leave the host. There are no outbound network calls related to license validation. #### BFF v3.1+ Runtime Validation [Section titled “BFF v3.1+ Runtime Validation”](#bff-v31-runtime-validation) BFF v3.1 does not technically enforce the presence of a license key. At runtime, if no license is present, an error message will be logged. #### BFF v4 Runtime Validation [Section titled “BFF v4 Runtime Validation”](#bff-v4-runtime-validation) BFF v4 requires a valid license in production environments. When no license is present, the system operates in [trial mode](#bff-trial-mode) with a limitation of maximum of five sessions per host (not technically enforced) with any excess resulting in error logging. Trial mode is also enabled when the license could not be validated, for example when the signature validation fails. When an expired license is used, the system will continue to function with only a warning written to the logs, and not fall back to trial mode. #### BFF Trial Mode [Section titled “BFF Trial Mode”](#bff-trial-mode) Using BFF without a license is considered Trial Mode. When running in Trial Mode, you will see the following error logged on startup: ```text You do not have a valid license key for the Duende software. BFF will run in trial mode. This is allowed for development and testing scenarios. If you are running in production you are required to have a licensed version. Please start a conversation with us: https://duende.link/l/bff/contact ``` In Trial Mode, BFF will be limited to a maximum of five (5) sessions per host. Sessions exceeding the limit will cause the host to log an error for every consecutive authenticated session: ```text BFF is running in trial mode. The maximum number of allowed authenticated sessions (5) has been exceeded. See https://duende.link/l/bff/trial for more information. ``` The trial mode session limit is not distributed or shared across multiple nodes. ## License Key [Section titled “License Key”](#license-key) The license key can be configured in one of three ways: * Via a well-known file on the file system * Via `IConfiguration` (for example, `appsettings.json` or environment variables) * Programmatically in your startup code You can also use other configuration sources such as Azure Key Vault, by using the programmatic approach. We consider the license key to be private to your organization, but not necessarily a secret. If you’re using private source control that is scoped to your organization, storing your license key within it is acceptable. ### File System [Section titled “File System”](#file-system) Duende products like IdentityServer and the BFF Security Framework look for a file named `Duende_License.key` in the [ContentRootPath](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostenvironment.contentrootpath?#microsoft-extensions-hosting-ihostenvironment-contentrootpath) of your application. If present, the content of the file will be used as the license key. By default, `ContentRootPath` is the directory that contains the application’s `.csproj` file during development, and the application’s base directory in published deployments. Place the license key file there: ```text MyIdentityServer/ ├── Duende_License.key ← place license here ├── MyIdentityServer.csproj ├── Program.cs ├── appsettings.json └── ... ``` ### Configuration v8.0 [Section titled “Configuration ”v8.0](#configuration) IdentityServer can read the license key directly from `IConfiguration`, so you do not need to write any startup code. If `LicenseKey` is not set in your `AddIdentityServer` call, IdentityServer checks the following configuration keys in order, using the first non-empty value it finds: 1. `Duende:IdentityServer:LicenseKey` 2. `Duende:LicenseKey` Whitespace is trimmed, and empty or whitespace-only values are ignored. Add the license key to `appsettings.json` using the IdentityServer-specific key: appsettings.json ```json { "Duende": { "IdentityServer": { "LicenseKey": "eyJhbG..." } } } ``` Or use the shorter key: appsettings.json ```json { "Duende": { "LicenseKey": "eyJhbG..." } } ``` Because [`IConfiguration`](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration) supports many providers, you can also supply the key via environment variables (for example, `Duende__IdentityServer__LicenseKey` or `Duende__LicenseKey`), Azure App Configuration, Azure Key Vault, or any other configuration source. ### Startup [Section titled “Startup”](#startup) If you prefer to load the license key programmatically, you can do so in your startup code. This allows you to use the ASP.NET configuration system to load the license key from any [configuration provider](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-7.0#cp), including environment variables, `appsettings.json`, external configuration services such as Azure App Configuration, Azure Key Vault, etc. #### IdentityServer [Section titled “IdentityServer”](#identityserver-1) The `AddIdentityServer` method accepts a lambda expression to configure various options in your IdentityServer, including the `LicenseKey`. Set the value of this property to the content of the license key file. Program.cs ```csharp builder.Services.AddIdentityServer(options => { // the content of the license key file options.LicenseKey = "eyJhbG..."; }); ``` #### BFF Security Framework [Section titled “BFF Security Framework”](#bff-security-framework-1) The `AddBff` method accepts a lambda expression to configure various options in your BFF host, including the `LicenseKey`. Set the value of this property to the content of the license key file. Program.cs ```csharp builder.Services.AddBff(options => { // the content of the license key file options.LicenseKey = "eyJhbG..."; }); ``` ### Azure Key Vault [Section titled “Azure Key Vault”](#azure-key-vault) When deploying your application to Microsoft Azure, you can make use of [Azure Key Vault](https://azure.microsoft.com/products/key-vault/) to load the Duende license key at startup. Similarly to setting the license key programmatically, you can use the `AddIdentityServer` or `AddBff` method, and use the overload that accepts a lambda expression to configure the `LicenseKey` property. Program.cs ```csharp var keyVaultUrl = new Uri("https://.vault.azure.net/"); var secretClient = new Azure.Security.KeyVault.Secrets.SecretClient( keyVaultUrl, new Azure.Identity.DefaultAzureCredential() ); KeyVaultSecret licenseKeySecret = secretClient.GetSecret(""); var licenseKey = licenseKeySecret.Value; // Inject the secret (license key) into the IdentityServer configuration builder.Services.AddIdentityServer(options => { options.LicenseKey = licenseKey; }); ``` If you are using [Azure App Configuration](https://azure.microsoft.com/products/app-configuration/), you can use a similar approach to load the license key into your application host. ----- # Logging Fundamentals > General guidance on configuring logging for Duende Software products using Microsoft.Extensions.Logging and Serilog. All Duende Software products (IdentityServer, Backend for Frontend (BFF), Access Token Management, etc.) use the standard logging facilities provided by ASP.NET Core (`Microsoft.Extensions.Logging`). This means they integrate seamlessly with whatever logging provider you choose for your application. This guide provides general instructions for setting up logging that apply to all our products. ## Log Levels [Section titled “Log Levels”](#log-levels) We adhere to the standard Microsoft guidelines for log levels. Understanding these levels helps you configure the appropriate verbosity for your environment. * **`Trace`** * **Usage:** Extremely detailed information for troubleshooting complex issues. * **Production:** **Do not enable** in production unless specifically instructed for diagnostics. May contain sensitive data (e.g., token hashes, PII). * **`Debug`** * **Usage:** Internal flow details, useful for understanding *why* a decision was made (e.g., policy evaluation, token validation steps). * **Production:** Generally disabled in production, but safe to enable temporarily for deeper investigation. * **`Information`** * **Usage:** High-level events tracking the general flow (e.g., “Request started”, “Token issued”). * **Production:** Often the default level for production. * **`Warning`** * **Usage:** Unexpected events that didn’t stop the application but might require investigation (e.g., “Invalid client configuration detected”). * **`Error`** * **Usage:** Exceptions and errors that cannot be handled gracefully. * **`Critical`** * **Usage:** Failures that require immediate attention (e.g., “Signing key not found”). ## Setup for Microsoft.Extensions.Logging [Section titled “Setup for Microsoft.Extensions.Logging”](#setup-for-microsoftextensionslogging) This is the default logging provider for ASP.NET Core. If you haven’t configured a third-party logger, this is what you are using. You can configure log levels in your `appsettings.json` file. To get detailed logs from Duende products, you often want to set the `Duende` namespace (or specific sub-namespaces) to `Debug`. appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", // Enable Debug logs for all Duende products "Duende": "Debug" } } } ``` ## Setup for Serilog [Section titled “Setup for Serilog”](#setup-for-serilog) [Serilog](https://serilog.net) is a popular structured logging library for .NET. We highly recommend it for its flexibility and rich sink ecosystem (Console, File, Seq, Elasticsearch, etc.). ### 1. Installation [Section titled “1. Installation”](#1-installation) Install the necessary packages: ```bash dotnet add package Serilog.AspNetCore ``` ### 2. Configuration In `Program.cs` [Section titled “2. Configuration In Program.cs”](#2-configuration-in-programcs) Configure Serilog early in your application startup to capture all logs, including startup errors. Program.cs ```csharp using Serilog; var builder = WebApplication.CreateBuilder(args); // Configure Serilog builder.Host.UseSerilog((ctx, lc) => lc .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") .Enrich.FromLogContext() .ReadFrom.Configuration(ctx.Configuration)); var app = builder.Build(); app.UseSerilogRequestLogging(); // Optional: cleaner HTTP request logging // ... rest of your pipeline ``` ### 3. Configuration In `appsettings.json` [Section titled “3. Configuration In appsettings.json”](#3-configuration-in-appsettingsjson) You can then control log levels via `appsettings.json`. This approach allows you to change log levels without recompiling your code. ```json { "Serilog": { "MinimumLevel": { "Default": "Information", "Override": { "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "System": "Warning", // Enable detailed logging for Duende products "Duende": "Debug" } } } } ``` ## Troubleshooting Specific Products and Components [Section titled “Troubleshooting Specific Products and Components”](#troubleshooting-specific-products-and-components) If you are debugging a specific component, you can target its namespace to reduce noise. | Product | Namespace | | --------------------------- | ------------------------------ | | **IdentityServer** | `Duende.IdentityServer` | | **BFF** | `Duende.Bff` | | **User Management** | `Duende.UserManagement` | | **Access Token Management** | `Duende.AccessTokenManagement` | Example `appsettings.json` for debugging only BFF interactions: ```json "Duende.Bff": "Debug", "Duende.IdentityServer": "Information" ``` ----- # Security Best Practices > A comprehensive guide to security practices and procedures used in Duende Software development lifecycle This document describes how the integrity of software produced by Duende Software is maintained during the software development life cycle. ## Data processing [Section titled “Data processing”](#data-processing) Our products are off-the shelf downloadable developer components. They are not managed services or SaaS - nor do we store, have access to, or process any of our customers’ data or their customers’ data. ## Systems Access [Section titled “Systems Access”](#systems-access) * Multiple systems are used in the development life cycle, including GitHub, NuGet, and Microsoft Azure Key Vault. * Multi-factor authentication is required for all services mentioned above. * Only a limited subset of Duende Software employees act as administrators for each system. ## Software Development [Section titled “Software Development”](#software-development) * All code is stored in [GitHub](https://github.com/duendesoftware). * Any code added to a project must be added via pull request. * At least one other staff member must review a pull request before it can be merged to a release branch. * Static code security analysis is performed for every check-in (using GitHub [CodeQL](https://codeql.github.com/)). ## Testing [Section titled “Testing”](#testing) * Automated test suites are run on code in every pull request branch. * Pull requests cannot be merged if the automated test suite fails. ## Deployment [Section titled “Deployment”](#deployment) * Merging a pull request does not immediately release new features to users, this requires an additional release step. * All compiled software packages with associated source are available as GitHub releases. * Compiled software libraries (such as Duende IdentityServer) are published to [NuGet](https://www.nuget.org/). * Packages must be pushed to NuGet by a Duende Software staff member only after additional validation by the staff member. * All NuGet packages are signed with a code signing certificate * The private key (RSA 4096 bits) is stored in Azure Key Vault. * The private key never leaves Key Vault and the signature process is performed by Key Vault. * NuGet will validate the package signature with Duende’s public key to verify they were legitimately built by Duende Software and have not been compromised or tampered with. * NuGet client tooling can be configured to accept signed packages only. * Once on NuGet, the package is available for end users to update their own solutions. * End users still must take explicit action to upgrade after reviewing the package’s release notes. ## Vulnerability Management Process [Section titled “Vulnerability Management Process”](#vulnerability-management-process) * Potential security vulnerabilities can be responsibly disclosed via our [contact form](https://duendesoftware.com/contact/general). * We guarantee to reply within two US business days. * All licenses include a security notification service. * Whenever a medium severity or higher security vulnerability has been confirmed and fixed, customers will get a private update prior to public release. * We will publish an official advisory ## Dependencies [Section titled “Dependencies”](#dependencies) IdentityServer has two dependencies: * [Microsoft .NET](https://dot.net) * [IdentityModel](https://github.com/IdentityModel) * maintained by Duende Software using the same principles as outlined above ## Certification [Section titled “Certification”](#certification) Duende IdentityServer is a [certified](https://openid.net/certification/) implementation of OpenID Connect. ## Package Signing [Section titled “Package Signing”](#package-signing) NuGet packages published by Duende are cryptographically signed to ensure their authenticity and integrity. Our certificate is signed by DigiCert, which is a widely trusted certificate authority and installed by default in most environments. This means that in many circumstances, the NuGet tools can validate our packages’ signatures automatically. However, some environments (notably the dotnet sdk docker image which is sometimes used in build pipelines) do not trust the certificate. In that case, it might be necessary to add the root certificate to NuGet’s code signing certificate bundle. * Packages released after January 1, 2025 (IdentityServer 7.1+): Use DigiCert’s [root certificate](https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA2562021CA1.crt.pem) ( PEM). * Packages released before January 1, 2025: Use Sectigo’s [root certificate](http://crt.sectigo.com/SectigoPublicCodeSigningRootR46.p7c) (P7C). #### Trusting The DigiCert Certificate [Section titled “Trusting The DigiCert Certificate”](#trusting-the-digicert-certificate) Here is an example of how to configure NuGet to trust the DigiCert root CA on the dotnet sdk docker image. This applies for Duende packages released *`after`* January 1, 2025, such as IdentityServer 7.1 and newer versions. Note the dotnet sdk docker image already includes the tools used in this section. If you are using another container image, make sure the following tools are available in the image: `wget`, `openssl`, `cat`, and the .NET SDK. First, get the DigiCert certificate: Terminal ```bash wget https://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA2562021CA1.crt.pem ``` Next, you validate that the thumbprint of the certificate is correct. Bootstrapping trust in a certificate chain can be challenging. Fortunately, most desktop environments already trust this certificate, so you can compare the downloaded certificate’s thumbprint to the thumbprint of the certificate on a machine that already trusts it. You should verify this independently, but for your convenience, the thumbprint is `8F:B2:8D:D3:CF:FA:5D:28:6E:7C:71:8A:A9:07:CB:4F:9B:17:67:C2`. You can check the thumbprint of the downloaded certificate with openssl: Terminal ```bash openssl x509 -in DigiCertTrustedG4CodeSigningRSA4096SHA2562021CA1.crt.pem -fingerprint -sha1 -noout ``` Then append that PEM to the certificate bundle at `/usr/share/dotnet/sdk/9.0.102/trustedroots/codesignctl.pem`: Terminal ```bash cat DigiCertTrustedG4CodeSigningRSA4096SHA2562021CA1.crt.pem >> /usr/share/dotnet/sdk/9.0.102/trustedroots/codesignctl.pem ``` After that, NuGet packages signed by Duende can be successfully verified, even if they are not distributed by NuGet.org: Terminal ```bash dotnet nuget verify Duende.IdentityServer.7.1.x.nupkg ``` #### Trusting The Sectigo Certificate [Section titled “Trusting The Sectigo Certificate”](#trusting-the-sectigo-certificate) Here is an example of how to configure NuGet to trust the Sectigo root CA on the dotnet sdk docker image for Duende packages released *`before`* January 1, 2025 Note the dotnet sdk docker image already includes the tools used in this section. If you are using another container image, make sure the following tools are available in the image: `wget`, `openssl`, `cat`, and the .NET SDK. First, get the Sectigo certificate and convert it to PEM format: Terminal ```bash wget http://crt.sectigo.com/SectigoPublicCodeSigningRootR46.p7c openssl pkcs7 -inform DER -outform PEM -in SectigoPublicCodeSigningRootR46.p7c -print_certs -out sectigo.pem ``` Next, you should validate that the thumbprint of the certificate is correct. Bootstrapping trust in a certificate chain can be challenging. Fortunately, most desktop environments already trust this certificate, so you can compare the downloaded certificate’s thumbprint to the thumbprint of the certificate on a machine that already trusts it. You should verify this independently, but for your convenience, the thumbprint is `CC:BB:F9:E1:48:5A:F6:3C:E4:7A:BF:8E:9E:64:8C:25:04:FC:31:9D`. You can check the thumbprint of the downloaded certificate with openssl: Terminal ```bash openssl x509 -in sectigo.pem -fingerprint -sha1 -noout ``` Then append that PEM to the certificate bundle at `/usr/share/dotnet/sdk/8.0.303/trustedroots/codesignctl.pem`: Terminal ```bash cat sectigo.pem >> /usr/share/dotnet/sdk/8.0.303/trustedroots/codesignctl.pem ``` After that, NuGet packages signed by Duende can be successfully verified, even if they are not distributed by NuGet.org: Terminal ```bash dotnet nuget verify Duende.IdentityServer.7.0.x.nupkg ``` ----- # Support & Issues > Comprehensive guide for accessing source code, reporting issues, and obtaining support for Duende products. This document provides information on accessing Duende’s source code, issue tracking, and community forums for support and discussions. It also outlines support policies, including priority support for enterprise users, and the procedure to report security vulnerabilities. ## Source Code [Section titled “Source Code”](#source-code) You can find all source code for Duende Products and its supporting repos in our [organization](https://github.com/duendesoftware). [Source Code ](https://github.com/duendesoftware)Learn more about Duende's codebase ## Issue Tracker [Section titled “Issue Tracker”](#issue-tracker) Join our [developer community forum](https://github.com/DuendeSoftware/community/discussions) to ask questions and discuss potential bugs. Follow our [product releases](https://github.com/DuendeSoftware/products/releases) and get notified of new releases as soon as they are published. [Release Notes ](https://github.com/DuendeSoftware/products/releases)See what's new in the latest releases ## Support [Section titled “Support”](#support) Duende Software offers three support tiers designed to meet organizations at their stage of growth and operational complexity. Each tier builds on the last, providing a clear path from community-driven help to dedicated, high-touch engagement for mission-critical deployments. | Support Tier Comparison | | | | | | ------------------------------------------- | :--------------------------------------------------: | :-------------: | :-------------: | :--------------------------: | | **Support Level** | **Community** | **Priority** | **Priority** | **Premium** | | **Applicable Licenses** | Community, Lite, Starter (legacy), Business (legacy) | Standard | Advanced | Custom, Enterprise+ (legacy) | | **Support Channel** | Public GitHub forum | Dedicated email | Dedicated email | Dedicated email + TAM | | **Response SLA** | No SLA | 2 business days | 2 business days | 1 business day | | **Escalation Calls** | – | 2 per year | 4 per year | 4 per year | | **Dedicated TAM** | – | – | – | Named TAM | | **Proactive Check-ins** | – | – | – | ✓ | | **Security Notification Service** | – | – | ✓ | ✓ | | **Feature Requests** | Public GitHub forum | Email channel | Email channel | Via TAM | | **Support for Duende open-source packages** | – | – | – | ✓ | ### Community Support [Section titled “Community Support”](#community-support) **License: Free, Community, Lite, Starter (legacy), Business (legacy)** Community Support connects users with a network of fellow developers and Duende team members through our public discussion forums. Whether you’re troubleshooting an integration, exploring a new feature, or looking to share knowledge, the community is an active and knowledgeable resource. To get help, start a discussion [on the developer community forum](https://github.com/DuendeSoftware/community/discussions). | Community Support Overview | | | :------------------------- | :------------------------------------------------------------------------------------------ | | Support Channel | [Public developer community forum](https://github.com/DuendeSoftware/community/discussions) | | SLA | None | [Get support on GitHub from the Duende community ](https://github.com/DuendeSoftware/community/discussions)Duende Developer Community Forum ### Priority Support [Section titled “Priority Support”](#priority-support) **License: Standard, Advanced, Enterprise (legacy)** [Priority Support](https://duendesoftware.com/license/PrioritySupportLicense.pdf) provides direct access to the Duende team through a dedicated email channel, with guaranteed response times tied to your license tier. For complex issues requiring deeper investigation, escalation calls are available – a focused video session where our team works alongside yours to diagnose and resolve product issues. | Priority Support Overview | | | | :------------------------ | :--------------------------- | :---------------------------- | | **License** | Standard | Advanced, Enterprise (legacy) | | **Support Channel** | Dedicated email channel | Dedicated email channel | | **Response SLA** | 2 business day response | 2 business day response | | **Escalation Calls** | Up to 2 escalations per year | Up to 4 escalations per year | **Note:** Escalation calls are development-focused pairing sessions, not ongoing operational support or on-call coverage. Response times are measured against standard US business days. Observed holidays and other events are excluded. [Priority Support PDF ](https://duendesoftware.com/license/PrioritySupportLicense.pdf)Learn more about Priority Support ### Premium Support [Section titled “Premium Support”](#premium-support) **License: Standard, Advanced, Custom, Enterprise (legacy)** Premium Support is designed for organizations with mission-critical or complex identity infrastructure where downtime presents significant business risk. It provides the benefits of Priority Support alongside a dedicated, proactive, and high-touch engagement model. This tier offers enhanced strategic continuity and the quickest guaranteed response times. **Dedicated Technical Account Manager (TAM):** Your TAM serves as a named point of contact who develops deep familiarity with your implementation, architecture, and business needs. Rather than re-explaining your environment with every interaction, your TAM brings continuity and context to every conversation, whether you’re onboarding, upgrading, or troubleshooting. **1 Business Day SLA Response:** Issues are acknowledged within 1 business day. This ensures a guaranteed response window regardless of when a problem surfaces, which is vital for organizations where swift acknowledgement is critical. **Priority Escalation:** Premium customers receive elevated priority for escalation calls, ensuring video pairing sessions are scheduled with urgency rather than standard queue prioritization. **Proactive Engagement:** Your TAM will periodically check in to review upcoming releases, flag breaking changes relevant to your configuration, and ensure you’re positioned to take advantage of new capabilities before they become blockers. | Premium Support Overview | | | :------------------------------------------ | :---------------------------------------------------------- | | **Support Channel** | Dedicated email channel + Named Technical Account Manager | | **SLA** | 1 business day response | | **Escalation Calls** | Up to 4 per year, priority scheduling | | **Account Manager** | Named TAM with deep familiarity of your environment | | **Proactive Engagement** | Periodic check-ins, release reviews, breaking change alerts | | **Support for Duende open-source packages** | Included | ## Supported Versions [Section titled “Supported Versions”](#supported-versions) Duende differentiates between two categories of NuGet packages: **Product packages** and **Component packages**. **Product packages** are the primary, user-facing products: IdentityServer and BFF. The following support rules apply: * **Major versions** are supported until the end of the associated .NET SDK lifecycle. The “associated” .NET version is determined by the target framework(s) supported at the time of release. A package can target multiple .NET versions. For example, if a release ships with support for both .NET 8 and .NET 10, it is associated with both, and support continues until the last associated .NET version reaches end-of-life. * **Minor versions** receive security fixes until the end of the associated .NET SDK lifecycle. * Product packages do **not** follow Semantic Versioning (SemVer); minor releases may contain breaking API changes. Internal namespaces Types in `Internal` namespaces, even when exposed as `public`, are intended for internal use by Duende. No bugfixes, security fixes or product support is provided for these types, and these types may change between versions. **Component packages** are transitive dependencies and supporting libraries, such as User Management, Storage, Jobs, and similar packages. The following support rules apply: * The **current major and minor** versions are supported for both bug fixes and security fixes. * Component packages follow Semantic Versioning (SemVer). The following sections track release and end of support dates for Duende product packages. ### Duende IdentityServer [Section titled “Duende IdentityServer”](#duende-identityserver) ### Duende Backend For Frontend (BFF) [Section titled “Duende Backend For Frontend (BFF)”](#duende-backend-for-frontend-bff) ## Reporting a security vulnerability [Section titled “Reporting a security vulnerability”](#reporting-a-security-vulnerability) [Security issues and bugs should be reported privately here](https://duendesoftware.com/contact/general). You should receive a response within **two business days**. [Report a security vulnerability ](https://duendesoftware.com/contact/general)privately report a security vulnerability ----- # Duende IdentityModel > Duende.IdentityModel for OpenID Connect and OAuth 2.0 related protocol operations, providing object models and utilities for identity-related operations The `Duende.IdentityModel` package is the base library for OpenID Connect and OAuth 2.0 related protocol operations. It provides an object model to interact with the endpoints defined in the various OAuth and OpenId Connect specifications. The types included represent the requests and responses, and constants defined in the specifications, such as standard scope, claim, and parameter names. The library also contains extension methods to invoke requests and other convenience methods for performing common identity related operations. [GitHub Repository ](https://github.com/DuendeSoftware/foss/tree/main/identity-model)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.IdentityModel/)View the package on NuGet.org. ----- # Duende IdentityModel OIDC Client > A certified OpenID Connect relying party library for building native clients with .NET, supporting various UI frameworks and authentication flows The `Duende.IdentityModel.OidcClient` library is a certified OpenID Connect relying party and implements [RFC 8252](https://tools.ietf.org/html/rfc8252/), “OAuth 2.0 for native Applications”. The `Duende.IdentityModel.OidcClient.Extensions` library provides support for [DPoP](https://datatracker.ietf.org/doc/html/rfc9449) extensions to Duende.IdentityModel.OidcClient for sender-constraining tokens. ## Use Cases [Section titled “Use Cases”](#use-cases) OidcClient targets .NET Standard, making it suitable for .NET and .NET Framework. It can be used to build OIDC native clients with a variety of .NET UI tools. * .NET MAUI * WPF with the system browser * WPF with an embedded browser * WinForms with an embedded browser * Cross-platform Console Applications (relies on kestrel for processing the callback) * Windows Console Applications (relies on an HttpListener - a wrapper around the windows HTTP.sys driver) * Windows Console Applications using custom uri schemes ## License and Feedback [Section titled “License and Feedback”](#license-and-feedback) `Duende.IdentityModel.OidcClient` is released as open source under the [Apache 2.0 license](https://github.com/DuendeSoftware/foss/blob/main/LICENSE). Bug reports and contributions are welcome at [the GitHub repository](https://github.com/DuendeSoftware/foss). [GitHub Repository ](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.IdentityModel.OidcClient/)View the package on NuGet.org. ----- # Demonstrating Proof-of-Possession (DPoP) > Learn how to leverage Demonstrating Proof-of-Possession when using OidcClient to build a native OIDC client. [DPoP](https://datatracker.ietf.org/doc/html/rfc9449) specifies how to bind an asymmetric key stored within a JSON Web Key (JWK) to an access token. This will make the access token bound to the key such that if the access token were to leak, it cannot be used without also having access to the private key of the corresponding JWK. The `Duende.IdentityModel.OidcClient.Extensions` library adds supports for DPoP to OidcClient. ## DPoP Key [Section titled “DPoP Key”](#dpop-key) Before we begin, your application needs to have a DPoP key in the form of a JSON Web Key (or JWK). According to the [DPoP specification](https://datatracker.ietf.org/doc/html/rfc9449), this key needs to use an asymmetric algorithm (“RS”, “ES”, or “PS” style). You can create a JWK in .NET using the `Duende.IdentityModel.OidcClient.Extensions` library. The `JsonWebKeys` class has several static methods to help with creating JWKs using various algorithms. Program.cs ```csharp using Duende.IdentityModel.OidcClient.DPoP; // Creates a JWK using the PS256 algorithm: var jwk = JsonWebKeys.CreateRsaJson(); Console.WriteLine(jwk); ``` Caution In a production scenario, you’ll want to store this JWK in a secure location and use ASP.NET’s [data protection](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/) to further protect the JWK. See [our data protection guide](/identityserver/deployment/#aspnet-core-data-protection) for more information. ## Initializing the OIDC client with DPoP support [Section titled “Initializing the OIDC client with DPoP support”](#initializing-the-oidc-client-with-dpop-support) We will need to extend the `OidcClientOptions` before we can use DPoP. After creating the `OidcClientOptions` to connect our client application with the Identity Provider, we retrieve a JWK to use for DPoP, and add that JWK to our `options` by calling the `ConfigureDPoP` extension method: Program.cs ```csharp using Duende.IdentityModel.OidcClient; using Duende.IdentityModel.OidcClient.DPoP; var options = new OidcClientOptions { Authority = "https://demo.duendesoftware.com", ClientId = "native.dpop", Scope = "openid profile email offline_access", // ... }; // creates a new JWK, or returns an existing one var jwk = GetDPoPJwk(); // Enable DPoP options.ConfigureDPoP(jwk); var oidcClient = new OidcClient(options); ``` ## Proof Tokens for the API [Section titled “Proof Tokens for the API”](#proof-tokens-for-the-api) Now that we’ve configured the `OidcClientOptions` with DPoP support and created an `OidcClient` instance, you can use this instance to create an `HttpMessageHandler` which will: * manage access and refresh tokens * add DPoP proof tokens to HTTP requests The `OidcClient` provides `CreateDPoPHandler` as a convenience method to create such a handler, which can be used with the .NET `HttpClient`. Program.cs ```csharp var sessionRefreshToken = "..."; // read from a previous session, if any var handler = oidcClient.CreateDPoPHandler(jwk, sessionRefreshToken); var apiClient = new HttpClient(handler); ``` For a full example, have a look at our [WPF with the system browser](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/Wpf) sample. ----- # OIDC Client Automatic Mode > Learn how to implement automatic OAuth/OIDC authentication by encapsulating browser interactions using OidcClient OpenID Connect (OIDC) is an identity layer on top of the OAuth 2.0 protocol. It allows clients to verify the identity of the end-user based on the authentication performed by an authorization server, as well as obtain basic profile information. An essential part of the OIDC flow is the use of a browser to interact with the end-user and to obtain permissions to access protected resources. In the OidcClient library, you can encapsulate the browser interaction by implementing the [IBrowser](https://github.com/DuendeSoftware/foss/blob/main/identity-model-oidc-client/src/IdentityModel.OidcClient/Browser/IBrowser.cs) interface. Using `IBrowser` helps create a reusable component for all OIDC interaction. ```csharp // Copyright (c) Duende Software. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. namespace Duende.IdentityModel.OidcClient.Browser; /// /// Models a browser /// public interface IBrowser { /// /// Invokes the browser. /// /// The options. /// A token that can be used to cancel the request /// Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default); } ``` The `BrowserResult` represents the result of the browser interaction, including any OIDC payloads that are returned from the authentication server. ```csharp // Copyright (c) Duende Software. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. namespace Duende.IdentityModel.OidcClient.Browser; /// /// The result from a browser login. /// /// public class BrowserResult : Result { /// /// Gets or sets the type of the result. /// /// /// The type of the result. /// public BrowserResultType ResultType { get; set; } /// /// Gets or sets the response. /// /// /// The response. /// public string Response { get; set; } } ``` For a simple example, the following code shows how to use the [SystemBrowser](https://github.com/DuendeSoftware/foss/blob/main/identity-model-oidc-client/clients/ConsoleClientWithBrowser/SystemBrowser.cs) to invoke a browser on the host desktop platform. The `SystemBrowser` is a naive implementation that uses the [System.Diagnostics.Process](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process) class to start the system default browser. ```csharp var options = new OidcClientOptions { Authority = "https://demo.duendesoftware.com", ClientId = "native", RedirectUri = redirectUri, Scope = "openid profile api", Browser = new SystemBrowser() }; var client = new OidcClient(options); ``` Once the `IBrowser` is configured, the `LoginAsync` method can be invoked to start the authentication flow. ```csharp var result = await client.LoginAsync(); ``` Setting the `Browser` property reduces the need to process browser respones and to handle the `BrowserResult` directly. When using this automatic mode, the `LoginAsync` method will return a [`LoginResult`](https://github.com/DuendeSoftware/foss/blob/19370c6d4820a684d41d1d40b8192ee8b873b8f0/identity-model-oidc-client/src/IdentityModel.OidcClient/LoginResult.cs) which will contain a `ClaimsPrincipal` with the user’s claims along with the `IdentityToken` and `AccessToken`. ----- # OIDC Client Logging > Learn how to configure and customize logging in OidcClient using Microsoft.Extensions.Logging.ILogger `OidcClient` logs errors, warnings, and diagnostic information using `Microsoft.Extensions.Logging.ILogger`, the standard .NET logging library. You can use any logging provider to store your logs however you like, by setting the `LoggerFactory` property on `OidcClientOptions`: Program.cs ```csharp using Duende.IdentityModel; using Duende.IdentityModel.OidcClient; var builder = Host.CreateApplicationBuilder(args); builder.Services.AddSingleton(svc => { var loggerFactory = svc.GetRequiredService(); var options = new OidcClientOptions { Authority = "https://demo.duendesoftware.com", ClientId = "interactive.public", Scope = "openid profile email offline_access", RedirectUri = "app://localhost/", PostLogoutRedirectUri = "app://localhost/", LoggerFactory = loggerFactory }; return new OidcClient(options); }); var app = builder.Build(); var client = app.Services.GetService(); ``` Using this approach, you can use other logging frameworks, like [Serilog](https://github.com/serilog/serilog-extensions-hosting) for example. For general information on how to configure logging in .NET applications, see our [Logging Fundamentals](/general/logging/) guide. ## Log Levels [Section titled “Log Levels”](#log-levels) The `OidcClient` logs at the following levels: * `Trace` * `Debug` * `Information` * `Error` You can set the log level in your `appsettings.json` by modifying the following snippet. appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Duende.IdentityModel.OidcClient": "Error" } } } ``` ----- # OIDC Client Manual Mode > Guide for implementing manual mode in OidcClient to handle browser interactions and token processing OpenID Connect is a protocol that allows you to authenticate users using a browser and involves browser-based interactions. When using this library you can choose between two modes: [automatic](/identitymodel-oidcclient/automatic/) and manual. We recommend using automatic mode when possible, but sometimes you need to use manual mode when you want to handle browser interactions yourself. With manual mode, `OidcClient` is still useful, as it helps with creating the necessary start URL and state parameters needed to complete an OIDC flow. You’ll need to handle all browser interactions yourself with custom code. This is beneficial for scenarios where you want to customize the browser experience or when you want to integrate with other platform-specific browser libraries. ```csharp var options = new OidcClientOptions { Authority = "https://demo.duendesoftware.com", ClientId = "native", RedirectUri = redirectUri, Scope = "openid profile api" }; var client = new OidcClient(options); // generate start URL, state, nonce, code challenge var state = await client.PrepareLoginAsync(); ``` When the browser work is done, `OidcClient` can take over to process the response, get the access/refresh tokens, contact userinfo endpoint etc.: ```csharp var result = await client.ProcessResponseAsync(data, state); ``` When using this manual mode, and processing the response, the `ProcessResponseAsync` method will return a [`LoginResult`](https://github.com/DuendeSoftware/foss/blob/19370c6d4820a684d41d1d40b8192ee8b873b8f0/identity-model-oidc-client/src/IdentityModel.OidcClient/LoginResult.cs) which will contain a `ClaimsPrincipal` with the user’s claims along with the `IdentityToken` and `AccessToken`. ----- # Duende IdentityModel OIDC Client Samples > A collection of sample applications demonstrating how to use IdentityModel.OidcClient with various platforms and UI frameworks. Samples of IdentityModel.OidcClient are available [on GitHub](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples). Our samples show how to use an OidcClient with a variety of platforms and UI tools, including: * [.NET MAUI](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/Maui) * [WPF with the system browser](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/Wpf) * [WPF with an embedded browser](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/WpfWebView2) * [WinForms with an embedded browser](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/WinFormsWebView2) * [Cross Platform Console Applications](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/NetCoreConsoleClient) (relies on kestrel for processing the callback) * [Windows Console Applications](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/HttpSysConsoleClient) (relies on an HttpListener - a wrapper around the windows HTTP.sys driver) * [Windows Console Applications using custom uri schemes](https://github.com/DuendeSoftware/foss/tree/main/identity-model-oidc-client/samples/WindowsConsoleSystemBrowser) All samples use a [demo instance of Duende IdentityServer](https://demo.duendesoftware.com) as their OIDC Provider. You can see its [source code on GitHub](https://github.com/DuendeSoftware/demo.duendesoftware.com). You can log in with *alice/alice* or *bob/bob* ## Additional samples [Section titled “Additional samples”](#additional-samples) * [Unity3D](https://github.com/peterhorsley/Unity3D.Authentication.Example) ## No Longer Maintained [Section titled “No Longer Maintained”](#no-longer-maintained) These samples are no longer maintained because their underlying technology is no longer supported. * [UWP](https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/tree/archived/uwp/Uwp) * [Xamarin](https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/tree/archived/xamarin/XamarinAndroidClient) * [Xamarin Forms](https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/tree/archived/xamarin/XamarinForms) * [Xamarin iOS - AuthenticationServices](https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/tree/archived/xamarin/iOS_AuthenticationServices) * [Xamarin iOS - SafariServices](https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/tree/archived/xamarin/iOS_SafariServices) ----- # Client Assertions > How to use client assertions (private_key_jwt / client_secret_jwt) for client authentication in protocol requests. Client assertions are an alternative to client secrets for authenticating confidential clients at token endpoints. Instead of sending a shared secret, the client creates a signed JWT (or SAML assertion) and includes it in the request. This is defined in [RFC 7523 — JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication](https://datatracker.ietf.org/doc/html/rfc7523) and is commonly known as the `private_key_jwt` or `client_secret_jwt` authentication methods defined in [OpenID Connect Core §9](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication). All protocol request types that derive from `ProtocolRequest` expose two properties for setting client assertions: `ClientAssertion` and `ClientAssertionFactory`. ## ClientAssertion [Section titled “ClientAssertion”](#clientassertion) The `ClientAssertion` property lets you attach a pre-built assertion to any protocol request. Set its `Type` and `Value` and they will be included as the `client_assertion_type` and `client_assertion` parameters: ```csharp var response = await client.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientAssertion = { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = mySignedJwt }, ClientCredentialStyle = ClientCredentialStyle.PostBody }); ``` ## ClientAssertionFactory [Section titled “ClientAssertionFactory”](#clientassertionfactory) *Added in `Duende.IdentityModel` 7.2.0* The `ClientAssertionFactory` property accepts a `Func>` — a factory function that creates a **fresh** `ClientAssertion` on demand. This was introduced to support scenarios where a protocol request may need to be **retried**, and each attempt requires a new assertion with unique `jti` and `iat` claims. The primary motivating scenario is **DPoP** (Demonstrating Proof of Possession). When a DPoP token request receives a `use_dpop_nonce` error, the HTTP handler retries the request with an updated DPoP proof. If the client assertion were static, the server could reject the retry because it has already seen that assertion’s `jti`. The factory solves this by generating a new assertion for each attempt. ```csharp var response = await client.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientAssertionFactory = () => Task.FromResult(new ClientAssertion { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = CreateSignedJwt() // generates a fresh JWT each time }), ClientCredentialStyle = ClientCredentialStyle.PostBody }); ``` When `ClientAssertionFactory` is set, the factory is stored on the `HttpRequestMessage.Options` so that DPoP retry handlers (and other delegating handlers in the pipeline) can invoke it to obtain a new assertion on each attempt. ### Usage with Duende.IdentityModel.OidcClient [Section titled “Usage with Duende.IdentityModel.OidcClient”](#usage-with-duendeidentitymodeloidcclient) Both the `ClientAssertion` and `ClientAssertionFactory` properties exist on `ProtocolRequest` to support [`Duende.IdentityModel.OidcClient`](/identitymodel-oidcclient/). The OidcClient library builds on IdentityModel’s protocol requests internally, and when configured with client assertion-based authentication, it sets these properties on the underlying requests it creates. When `ClientAssertionFactory` is set, it is used during both: * **Pushed Authorization Requests (PAR)** — the factory is invoked to produce a fresh assertion for the PAR endpoint request. * **Token requests** — the factory is invoked again to produce a fresh assertion for the token endpoint request. This ensures each request carries its own unique assertion, which is essential when the authorization server enforces `jti` uniqueness across requests. ----- # Device Authorization Endpoint > Documentation for OAuth 2.0 device flow authorization endpoint using HttpClient extension methods The client library for the [OAuth 2.0 device flow](https://tools.ietf.org/html/rfc7662) device authorization is provided as an extension method for `HttpClient`. The following code sends a device authorization request: ```csharp var client = new HttpClient(); var response = await client.RequestDeviceAuthorizationAsync(new DeviceAuthorizationRequest { Address = "https://demo.duendesoftware.com/connect/device_authorize", ClientId = "device" }); ``` The response is of type `DeviceAuthorizationResponse` and has properties for the standard response parameters. You also have access to the raw response and to a parsed JSON document (via the `Raw` and `Json` properties). Before using the response, you should always check the `IsError` property to make sure the request was successful: ```csharp if (response.IsError) throw new Exception(response.Error); var userCode = response.UserCode; var deviceCode = response.DeviceCode; var verificationUrl = response.VerificationUri; var verificationUrlComplete = response.VerificationUriComplete; ``` ----- # Discovery Endpoint > Documentation for using the OpenID Connect discovery endpoint client library, including configuration, validation, and caching features The client library for the [OpenID Connect discovery endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html) is provided as an extension method for `HttpClient`. The `GetDiscoveryDocumentAsync` method returns a `DiscoveryDocumentResponse` object that has both strong and weak typed accessors for the various elements of the discovery document. You should always check the `IsError` and `Error` properties before accessing the contents of the document: ```csharp var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://demo.duendesoftware.com"); if (disco.IsError) throw new Exception(disco.Error); ``` [Standard elements](#discoverydocumentresponse-properties-reference) can be accessed by using properties: ```csharp var tokenEndpoint = disco.TokenEndpoint; var keys = disco.KeySet.Keys; ``` Custom elements (or elements not covered by the standard properties) can be accessed like this: ```csharp // returns string or null var stringValue = disco.TryGetString("some_string_element"); // return a nullable boolean var boolValue = disco.TryGetBoolean("some_boolean_element"); // return array (maybe empty) var arrayValue = disco.TryGetStringArray("some_array_element"); // returns JToken var rawJson = disco.TryGetValue("some_element"); ``` ### Discovery Policy [Section titled “Discovery Policy”](#discovery-policy) By default, the discovery response is validated before it is returned to the client, validation includes: * enforce that HTTPS is used (except for localhost addresses) * enforce that the issuer matches the authority * enforce that the protocol endpoints are on the same DNS name as the `authority` * enforce the existence of a keyset Policy violation errors will set the `ErrorType` property on the `DiscoveryDocumentResponse` to `PolicyViolation`. All the standard validation rules can be modified using the `DiscoveryPolicy` class, e.g. disabling the issuer name check: ```csharp var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest { Address = "https://demo.duendesoftware.com", Policy = { ValidateIssuerName = false } }); ``` #### Cross-Host Endpoints [Section titled “Cross-Host Endpoints”](#cross-host-endpoints) When the URIs in the discovery document are on a different base address than the issuer URI (for example, a [Dynamic Client Registration endpoint](/identityserver/configuration/dcr/#adding-the-registration-endpoint-to-the-discovery-document) hosted on a separate service), the discovery policy will reject those endpoints by default with: ```text Endpoint is on a different host than authority ``` You can resolve this by adding the additional host to `AdditionalEndpointBaseAddresses` (recommended), or by setting `ValidateEndpoints = false` to disable endpoint validation entirely. The same applies to any component that has its own `DiscoveryPolicy`, such as `OAuth2IntrospectionOptions.DiscoveryPolicy`. Each instance needs to be configured independently. ```csharp // Using AdditionalEndpointBaseAddresses (recommended) var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest { Address = "https://authority.example.com", Policy = { AdditionalEndpointBaseAddresses = [ "https://config-api.example.com" ] } }); // Or when using DiscoveryCache var cache = new DiscoveryCache( "https://authority.example.com", () => factory.CreateClient(), new DiscoveryPolicy { AdditionalEndpointBaseAddresses = [ "https://config-api.example.com" ] }); ``` You can also customize validation strategy based on the authority with your own implementation of `IAuthorityValidationStrategy`. By default, comparison uses ordinal string comparison. To switch to `Uri` comparison: ```csharp var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest { Address = "https://demo.duendesoftware.com", Policy = { AuthorityValidationStrategy = new AuthorityUrlValidationStrategy() } }); ``` ### Caching The Discovery Document [Section titled “Caching The Discovery Document”](#caching-the-discovery-document) You should periodically update your local copy of the discovery document, to be able to react to configuration changes on the server. This is especially important for playing nice with automatic key rotation. The `DiscoveryCache` class can help you with that. The following code will set up the cache, retrieve the document the first time it is needed, and then cache it for 24 hours: ```csharp var cache = new DiscoveryCache("https://demo.duendesoftware.com"); ``` You can then access the document like this: ```csharp var disco = await cache.GetAsync(); if (disco.IsError) throw new Exception(disco.Error); ``` You can specify the cache duration using the `CacheDuration` property and also specify a custom discovery policy by passing in a `DiscoveryPolicy` to the constructor. ### Caching And HttpClient Instances [Section titled “Caching And HttpClient Instances”](#caching-and-httpclient-instances) By default, the discovery cache will create a new instance of `HttpClient` every time it needs to access the discovery endpoint. You can modify this behavior in two ways, either by passing in a pre-created instance into the constructor, or by providing a function that will return an `HttpClient` when needed. The following code will set up the discovery cache in the ASP.NET Core service provider and will use the `HttpClientFactory` to create clients: ```csharp services.AddSingleton(r => { var factory = r.GetRequiredService(); return new DiscoveryCache(Constants.Authority, () => factory.CreateClient()); }); ``` ### DiscoveryDocumentResponse Properties Reference [Section titled “DiscoveryDocumentResponse Properties Reference”](#discoverydocumentresponse-properties-reference) The following table lists the standard properties on the `DiscoveryDocumentResponse` class: | Property | Description | | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Policy | Gets or sets the discovery policy used to configure how the discovery document is processed | | KeySet | Gets or sets the JSON Web Key Set (JWKS) associated with the discovery document | | MtlsEndpointAliases | Gets the mutual TLS (mTLS) endpoint aliases | | Issuer | Gets the issuer identifier for the authorization server | | AuthorizeEndpoint | Gets the authorization endpoint URL | | TokenEndpoint | Gets token endpoint URL | | UserInfoEndpoint | Gets user info endpoint URL | | IntrospectionEndpoint | Gets the introspection endpoint URL | | RevocationEndpoint | Gets the revocation endpoint URL | | DeviceAuthorizationEndpoint | Gets the device authorization endpoint URL | | BackchannelAuthenticationEndpoint | Gets the backchannel authentication endpoint URL | | JwksUri | Gets the URI of the JSON Web Key Set (JWKS) | | EndSessionEndpoint | Gets the end session endpoint URL | | CheckSessionIframe | Gets the check session iframe URL | | RegistrationEndpoint | Gets the dynamic client registration (DCR) endpoint URL | | PushedAuthorizationRequestEndpoint | Gets the pushed authorization request (PAR) endpoint URL | | FrontChannelLogoutSupported | Gets a flag indicating whether front-channel logout is supported | | FrontChannelLogoutSessionSupported | Gets a flag indicating whether a session ID (sid) parameter is supported at the front-channel logout endpoint | | GrantTypesSupported | Gets the supported grant types | | CodeChallengeMethodsSupported | Gets the supported code challenge methods | | ScopesSupported | Gets the supported scopes | | SubjectTypesSupported | Gets the supported subject types | | ResponseModesSupported | Gets the supported response modes | | ResponseTypesSupported | Gets the supported response types | | ClaimsSupported | Gets the supported claims | | TokenEndpointAuthenticationMethodsSupported | Gets the authentication methods supported by the token endpoint | | TokenEndpointAuthenticationSigningAlgorithmsSupported | Gets the signing algorithms supported by the token endpoint for client authentication | | BackchannelTokenDeliveryModesSupported | Gets the supported backchannel token delivery modes | | BackchannelUserCodeParameterSupported | Gets a flag indicating whether the backchannel user code parameter is supported | | RequirePushedAuthorizationRequests | Gets a flag indicating whether the use of pushed authorization requests (PAR) is required | | IntrospectionSigningAlgorithmsSupported | Gets the signing algorithms supported for introspection responses | | IntrospectionEncryptionAlgorithmsSupported | Gets the encryption “alg” values supported for encrypted JWT introspection responses | | IntrospectionEncryptionEncValuesSupported | Gets the encryption “enc” values supported for encrypted JWT introspection responses | | Scopes | The list of scopes associated to the token or an empty array if no `scope` claim is present | | ClientId | The client identifier for the OAuth 2.0 client that requested the token or `null` if the `client_id` claim is missing | | UserName | The human-readable identifier for the resource owner who authorized the token or `null` if the `username` claim is missing | | TokenType | The type of the token as defined in section 5.1 of OAuth 2.0 (RFC6749) or `null` if the `token_type` claim is missing | | Expiration | The expiration time of the token or `null` if the `exp` claim is missing | | IssuedAt | The issuance time of the token or `null` if the `iat` claim is missing | | NotBefore | The validity start time of the token or `null` if the `nbf` claim is missing | | Subject | The subject of the token or `null` if the `sub` claim is missing | | Audiences | The service-specific list of string identifiers representing the intended audience for the token or an empty array if no `aud` claim is present | | Issuer | The string representing the issuer of the token or `null` if the `iss` claim is missing | | JwtId | The string identifier for the token or `null` if the `jti` claim is missing | ----- # Dynamic Client Registration > Documentation for OpenID Connect Dynamic Client Registration library extension method for HttpClient that enables client registration and response handling The client library for [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html) is provided as an extension method for [`System.Net.Http.HttpClient`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient). The following code sends a registration request: ```csharp var client = new HttpClient(); var response = await client.RegisterClientAsync(new DynamicClientRegistrationRequest { Address = Endpoint, Document = new DynamicClientRegistrationDocument { RedirectUris = { redirectUri }, ApplicationType = "native" } }); ``` The response is of type `RegistrationResponse` and has properties for the standard response parameters. You also have access to the raw response and to a parsed JSON document (via the `Raw` and `Json` properties). Before using the response, you should always check the `IsError` property to make sure the request was successful: ```csharp if (response.IsError) throw new Exception(response.Error); var clientId = response.ClientId; var secret = response.ClientSecret; ``` ----- # General Usage > Overview of IdentityModel client libraries common design patterns and usage for OpenID Connect and OAuth 2.0 endpoint interactions. IdentityModel contains client libraries for many interactions with endpoints defined in OpenID Connect and OAuth 2.0. All of these libraries have a common design, let’s examine the various layers using the client for the token endpoint. ## Request and response objects [Section titled “Request and response objects”](#request-and-response-objects) All protocol request are modeled as request objects and have a common base class called `ProtocolRequest` which has properties to set the endpoint address, client ID, client secret, client assertion, and the details of how client secrets are transmitted (e.g. authorization header vs POST body). `ProtocolRequest` derives from `HttpRequestMessage` and thus also allows setting custom headers etc. The following code snippet creates a request for a client credentials grant type: ```csharp var request = new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientSecret = "secret" }; ``` While in theory you could now call `Prepare` (which internally sets the headers, body and address) and send the request via a plain `HttpClient`, typically there are more parameters with special semantics and encoding required. That’s why we provide extension methods to do the low level work. Equally, a protocol response has a corresponding `ProtocolResponse` implementation that parses the status codes and response content. The following code snippet would parse the raw HTTP response from a token endpoint and turn it into a `TokenResponse` object: ```csharp var tokenResponse = await ProtocolResponse .FromHttpResponseAsync(httpResponse); ``` Again these steps are automated using the extension methods. So let’s have a look at an example next. ## Extension methods [Section titled “Extension methods”](#extension-methods) For each protocol interaction, an extension method for `HttpMessageInvoker` (that’s the base class of `HttpClient`) exists. The extension methods expect a request object and return a response object. It is your responsibility to set up and manage the lifetime of the `HttpClient`, e.g. manually: ```csharp var client = new HttpClient(); var response = await client.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientSecret = "secret" }); ``` You might want to use other techniques to obtain an `HttpClient`, e.g. via the HTTP client factory: ```csharp var client = HttpClientFactory.CreateClient("my_named_token_client"); var response = await client.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientSecret = "secret" }); ``` All other endpoint client follow the same design. ## Client Credential Style [Section titled “Client Credential Style”](#client-credential-style) Any request type implementing `ProtocolRequest` has the ability to configure the client credential style, which specifies how the client will transmit the client ID and secret. `ClientCredentialStyle` options include `PostBody` and the default value of `AuthorizationHeader`. ```csharp var client = HttpClientFactory.CreateClient("my_named_token_client"); var response = await client.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientSecret = "secret", // set the client credential style ClientCredentialStyle = ClientCredentialStyle.AuthorizationHeader }); ``` For interoperability between OAuth implementations, we allow you to choose either approach, depending on which specification version you are targeting. When using IdentityServer, both header and body approaches are supported and *“it just works”*. [RFC 6749](https://datatracker.ietf.org/doc/rfc6749/), the original OAuth spec, says that support for the basic auth header is mandatory, and that the POST body is optional. OAuth 2.1 reverses this: now the body is mandatory and the header is optional. In the previous OAuth specification version, the header caused bugs and interoperability problems. To follow both RFC 6749 and RFC 2617 (which is where basic auth headers are specified), you have to form url encode the client id and client secret, concatenate them both with a colon in between, and then base64 encode the final value. To try to avoid that complex process, OAuth 2.1 now prefers the POST body mechanism. References: * [RFC 6749](https://datatracker.ietf.org/doc/rfc6749/) section 2.3.1 * [RFC 2617 section 2](https://www.rfc-editor.org/rfc/rfc2617#section-2) * [OAuth 2.1 Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/) Here is a complete list of `ProtocolRequest` implementors that expose the `ClientCredentialStyle` option: * `Duende.IdentityModel.Client.AuthorizationCodeTokenRequest` * `Duende.IdentityModel.Client.BackchannelAuthenticationRequest` * `Duende.IdentityModel.Client.BackchannelAuthenticationTokenRequest` * `Duende.IdentityModel.Client.ClientCredentialsTokenRequest` * `Duende.IdentityModel.Client.DeviceAuthorizationRequest` * `Duende.IdentityModel.Client.DeviceTokenRequest` * `Duende.IdentityModel.Client.DiscoveryDocumentRequest` * `Duende.IdentityModel.Client.DynamicClientRegistrationRequest` * `Duende.IdentityModel.Client.JsonWebKeySetRequest` * `Duende.IdentityModel.Client.PasswordTokenRequest` * `Duende.IdentityModel.Client.PushedAuthorizationRequest` * `Duende.IdentityModel.Client.RefreshTokenRequest` * `Duende.IdentityModel.Client.TokenExchangeTokenRequest` * `Duende.IdentityModel.Client.TokenIntrospectionRequest` * `Duende.IdentityModel.Client.TokenRequest` * `Duende.IdentityModel.Client.TokenRevocationRequest` * `Duende.IdentityModel.Client.UserInfoRequest` ----- # Token Introspection Endpoint > Learn how to use the OAuth 2.0 token introspection endpoint to validate and inspect access tokens using HttpClient extensions. The client library for [OAuth 2.0 token introspection (RFC 7662)](https://tools.ietf.org/html/rfc7662) is provided by the `IntrospectionClient` class, and as an extension method for `HttpClient`. ## Token Introspection Request [Section titled “Token Introspection Request”](#token-introspection-request) The following code sends a reference token to an introspection endpoint: * Using IntrospectionClient ```csharp var clientOptions = new IntrospectionClientOptions { Address = Endpoint, ClientId = "client", ClientSecret = "secret", ResponseFormat = ResponseFormat.Json }; var httpClient = new HttpClient(); var introspectionClient = new IntrospectionClient(httpClient, clientOptions); var introspectionResponse = await introspectionClient.Introspect("token"); ``` * Using HttpClient extension ```csharp var client = new HttpClient(); var introspectionResponse = await client.IntrospectTokenAsync(new TokenIntrospectionRequest { Address = Endpoint, Token = "token", ResponseFormat = ResponseFormat.Json }); ``` ## Token Introspection Response [Section titled “Token Introspection Response”](#token-introspection-response) The response of a token introspection request is an object of type `TokenIntrospectionResponse`. Before using the response, you should always check the `IsError` property to make sure the request was successful: ```csharp if (introspectionResponse.IsError) throw new Exception(introspectionResponse.Error); var isActive = introspectionResponse.IsActive; var claims = introspectionResponse.Claims; ``` The `TokenIntrospectionResponse` class exposes the raw response through its `Raw` property, and to the parsed JSON document through its `Json` property. In addition, it provides access to the following standard response parameters: | Property | Value | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Scopes` | The list of scopes associated to the token or an empty array if no `scope` claim is present. | | `ClientId` | The client identifier for the OAuth 2.0 client that requested the token or `null` if the `client_id` claim is missing. | | `UserName` | The human-readable identifier for the resource owner who authorized the token or `null` if the `username` claim is missing. | | `TokenType` | The type of the token as defined in [section 5.1 of OAuth 2.0 (RFC6749)](https://datatracker.ietf.org/doc/html/rfc6749#section-5.1) or `null` if the `token_type` claim is missing. | | `Expiration` | The expiration time of the token or `null` if the `exp` claim is missing. | | `IssuedAt` | The issuance time of the token or `null` if the `iat` claim is missing. | | `NotBefore` | The validity start time of the token or `null` if the `nbf` claim is missing. | | `Subject` | The subject of the token or `null` if the `sub` claim is missing. | | `Audiences` | The service-specific list of string identifiers representing the intended audience for the token or an empty array if no `aud` claim is present. | | `Issuer` | The string representing the issuer of the token or `null` if the `iss` claim is missing. | | `JwtId` | The string identifier for the token or `null` if the `jti` claim is missing. | ## JWT Response Format v7.1 [Section titled “JWT Response Format ”v7.1](#jwt-response-format) Introspection requests can optionally pass a parameter to indicate that a signed JWT rather than JSON payload is desired. Such a JWT response is most often useful for non-repudiation. For example, an API might rely on the claims from introspection to produce digitally signed documents or issue certificates, with the Authorization Server assuming legal liability for the introspected data. A JWT introspection response can be stored and its signature independently verified as part of an audit. ### Requesting JWT Response Format [Section titled “Requesting JWT Response Format”](#requesting-jwt-response-format) To request the JWT response format, set the `ResponseFormat` option to `ResponseFormat.Jwt`. ```csharp var client = new HttpClient(); var introspectionResponse = await client.IntrospectTokenAsync( new TokenIntrospectionRequest { Address = Endpoint, Token = "token", ResponseFormat = ResponseFormat.Jwt }); ``` ### Validating JWT Signature [Section titled “Validating JWT Signature”](#validating-jwt-signature) By default, when the introspection endpoint returns a JWT, the system performs only a basic format check on the response. Full cryptographic validation of the JWT’s signature and claims is not performed. This approach is generally appropriate because the introspection request is made over a direct back-channel connection from the application to the introspection endpoint. This connection is secured by TLS, which guarantees the authenticity and integrity of the response in transit. The introspected claims can safely be used immediately without an additional cryptographic validation. An extensibility point is available to provide your own implementation of `ITokenIntrospectionJwtResponseValidator`. ITokenIntrospectionJwtResponseValidator.cs ```csharp public interface ITokenIntrospectionJwtResponseValidator { void Validate(string rawJwtResponse); } ``` A custom validator can be applied using the `TokenIntrospectionRequest.JwtResponseValidator` property or using `IntrospectionClientOptions`: ```csharp var client = new HttpClient(); var introspectionResponse = await client.IntrospectTokenAsync( new TokenIntrospectionRequest { Address = Endpoint, Token = "token", ResponseFormat = ResponseFormat.Jwt, JwtResponseValidator = new CustomIntrospectionJwtResponseValidator() }); ``` ----- # Token Revocation Endpoint > Client library implementation for OAuth 2.0 token revocation endpoint using HttpClient extension methods The client library for [OAuth 2.0 token revocation](https://tools.ietf.org/html/rfc7009) is provided as an extension method for `HttpClient`. The following code revokes an access token at a revocation endpoint: ```csharp var client = new HttpClient(); var result = await client.RevokeTokenAsync(new TokenRevocationRequest { Address = "https://demo.duendesoftware.com/connect/revocation", ClientId = "client", ClientSecret = "secret", Token = accessToken }); ``` The response is of type `TokenRevocationResponse` gives you access to the raw response and to a parsed JSON document (via the `Raw` and `Json` properties). Before using the response, you should always check the `IsError` property to make sure the request was successful: ```csharp if (response.IsError) throw new Exception(response.Error); ``` ----- # Token Endpoint > Documentation for the OAuth 2.0 and OpenID Connect token endpoint client library, providing extension methods for HttpClient to handle various token request flows The client library for the token endpoint ([OAuth 2.0](https://tools.ietf.org/html/rfc6749#section-3.2) and [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html#tokenendpoint)) is provided as a set of extension methods for `HttpClient`. This allows creating and managing the lifetime of the `HttpClient` the way you prefer: statically or via a factory like the Microsoft `HttpClientFactory`. ## Requesting a token [Section titled “Requesting a token”](#requesting-a-token) The main extension method is called `RequestTokenAsync`. It has direct support for standard parameters like client ID/secret (or assertion) and grant type, but it also allows setting arbitrary other parameters via a dictionary. All other extensions methods ultimately call this method internally: ```csharp var client = new HttpClient(); var response = await client.RequestTokenAsync(new TokenRequest { Address = "https://demo.duendesoftware.com/connect/token", GrantType = "custom", ClientId = "client", ClientSecret = "secret", Parameters = { { "custom_parameter", "custom value"}, { "scope", "api1" } } }); ``` The response is of type `TokenResponse` and has properties for the standard token response parameters like `access_token`, `expires_in` etc. You also have access to the raw response and to a parsed JSON document (via the `Raw` and `Json` properties). Before using the response, you should always check the `IsError` property to make sure the request was successful: ```csharp if (response.IsError) throw new Exception(response.Error); var token = response.AccessToken; var custom = response.Json.TryGetString("custom_parameter"); ``` ## Requesting a token using the `client_credentials` Grant Type [Section titled “Requesting a token using the client\_credentials Grant Type”](#requesting-a-token-using-the-client_credentials-grant-type) The `RequestClientCredentialsToken` extension method has convenience properties for the `client_credentials` grant type: ```csharp var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientSecret = "secret", Scope = "api1" }); ``` ## Requesting a token using the `password` Grant Type [Section titled “Requesting a token using the password Grant Type”](#requesting-a-token-using-the-password-grant-type) The `RequestPasswordToken` extension method has convenience properties for the `password` grant type: ```csharp var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client", ClientSecret = "secret", Scope = "api1", UserName = "bob", Password = "bob" }); ``` ## Requesting a token using the `authorization_code` Grant Type [Section titled “Requesting a token using the authorization\_code Grant Type”](#requesting-a-token-using-the-authorization_code-grant-type) The `RequestAuthorizationCodeToken` extension method has convenience properties for the `authorization_code` grant type and PKCE: ```csharp var response = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest { Address = IdentityServerPipeline.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Code = code, RedirectUri = "https://app.com/callback", // optional PKCE parameter CodeVerifier = "xyz" }); ``` ## Requesting a token using the `refresh_token` Grant Type [Section titled “Requesting a token using the refresh\_token Grant Type”](#requesting-a-token-using-the-refresh_token-grant-type) The `RequestRefreshToken` extension method has convenience properties for the `refresh_token` grant type: ```csharp var response = await _client.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = TokenEndpoint, ClientId = "client", ClientSecret = "secret", RefreshToken = "xyz" }); ``` ## Requesting a Device Token [Section titled “Requesting a Device Token”](#requesting-a-device-token) The `RequestDeviceToken` extension method has convenience properties for the `urn:ietf:params:oauth:grant-type:device_code` grant type: ```csharp var response = await client.RequestDeviceTokenAsync(new DeviceTokenRequest { Address = disco.TokenEndpoint, ClientId = "device", DeviceCode = authorizeResponse.DeviceCode }); ``` ----- # UserInfo Endpoint The client library for the [OpenID Connect UserInfo](https://openid.net/specs/openid-connect-core-1_0.html#userinfo) endpoint is provided as an extension method for `HttpClient`. The following code sends an access token to the UserInfo endpoint: ```csharp var client = new HttpClient(); var response = await client.GetUserInfoAsync(new UserInfoRequest { Address = disco.UserInfoEndpoint, Token = token }); ``` The response is of type `UserInfoResponse` and has properties for the standard response parameters. You also have access to the raw response and to a parsed JSON document (via the `Raw` and `Json` properties). Before using the response, you should always check the `IsError` property to make sure the request was successful: ```csharp if (response.IsError) throw new Exception(response.Error); var claims = response.Claims; ``` ----- # Base64 URL Encoding > Documentation for Base64 URL encoding and decoding utilities in Duende IdentityModel, used for JWT token serialization JWT serialization involves transforming the three core components of a JWT (Header, Payload, Signature) into a single, compact, URL-safe string. [Base64 URL encoding](https://tools.ietf.org/html/rfc4648#section-5) is used instead of standard Base64 because it doesn’t include characters like `+`, `/`, or `=`, making it safe to use directly in URLs and HTTP headers without requiring further encoding. In newer .NET versions, you can use the `Base64Url` class found in the `System.Buffers.Text` namespace to decode Base64 payloads using the `DecodeFromChars` method: ```csharp using System.Buffers.Text; var jsonString = Base64Url.DecodeFromChars(payload); ``` Encoding can be done using the `EncodeToString` method: ```csharp using System.Buffers.Text; var bytes = Encoding.UTF8.GetBytes("some string"); var encodedString = Base64Url.EncodeToString(bytes); ``` Alternatively, ASP.NET Core has built-in support for Base64 encoding and decoding via [WebEncoders.Base64UrlEncode](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.webencoders.base64urlencode) and [WebEncoders.Base64UrlDecode](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.webencoders.base64urldecode). To use these methods, ensure you have the following package installed: ```bash dotnet add package Microsoft.AspNetCore.WebUtilities ``` Then use the following code: ```csharp using System.Text; using Microsoft.AspNetCore.WebUtilities; var bytes = "hello"u8.ToArray(); var b64url = WebEncoders.Base64UrlEncode(bytes); bytes = WebEncoders.Base64UrlDecode(b64url); var text = Encoding.UTF8.GetString(bytes); Console.WriteLine(text); ``` ----- # Protocol and Claim Type Constants > Explore constant string classes provided by IdentityModel for OAuth 2.0, OpenID Connect protocol values, and JWT claim types When working with OAuth 2.0, OpenID Connect and claims, there are a lot of **✨magic strings** for claim types and protocol values. IdentityModel provides a couple of constant strings classes to help with that. ## OAuth 2.0 And OpenID Connect Protocol Values [Section titled “OAuth 2.0 And OpenID Connect Protocol Values”](#oauth-20-and-openid-connect-protocol-values) The `OidcConstants` class provides a set of constants for OAuth 2.0 and OpenID Connect protocol values. #### AuthorizeRequest [Section titled “AuthorizeRequest”](#authorizerequest) | Name | Value | | ------------------- | ----------------------- | | Scope | `scope` | | ResponseType | `response_type` | | ClientId | `client_id` | | RedirectUri | `redirect_uri` | | State | `state` | | ResponseMode | `response_mode` | | Nonce | `nonce` | | Display | `display` | | Prompt | `prompt` | | MaxAge | `max_age` | | UiLocales | `ui_locales` | | IdTokenHint | `id_token_hint` | | LoginHint | `login_hint` | | AcrValues | `acr_values` | | CodeChallenge | `code_challenge` | | CodeChallengeMethod | `code_challenge_method` | | Request | `request` | | RequestUri | `request_uri` | | Resource | `resource` | | DPoPKeyThumbprint | `dpop_jkt` | ### AuthorizeErrors [Section titled “AuthorizeErrors”](#authorizeerrors) | Name | Value | | ------------------------------- | ----------------------------------- | | InvalidRequest | `invalid_request` | | UnauthorizedClient | `unauthorized_client` | | AccessDenied | `access_denied` | | UnsupportedResponseType | `unsupported_response_type` | | InvalidScope | `invalid_scope` | | ServerError | `server_error` | | TemporarilyUnavailable | `temporarily_unavailable` | | UnmetAuthenticationRequirements | `unmet_authentication_requirements` | | InteractionRequired | `interaction_required` | | LoginRequired | `login_required` | | AccountSelectionRequired | `account_selection_required` | | ConsentRequired | `consent_required` | | InvalidRequestUri | `invalid_request_uri` | | InvalidRequestObject | `invalid_request_object` | | RequestNotSupported | `request_not_supported` | | RequestUriNotSupported | `request_uri_not_supported` | | RegistrationNotSupported | `registration_not_supported` | | InvalidTarget | `invalid_target` | ### AuthorizeResponse [Section titled “AuthorizeResponse”](#authorizeresponse) | Name | Value | | ---------------- | ------------------- | | Scope | `scope` | | Code | `code` | | AccessToken | `access_token` | | ExpiresIn | `expires_in` | | TokenType | `token_type` | | RefreshToken | `refresh_token` | | IdentityToken | `id_token` | | State | `state` | | SessionState | `session_state` | | Issuer | `iss` | | Error | `error` | | ErrorDescription | `error_description` | ### DeviceAuthorizationResponse [Section titled “DeviceAuthorizationResponse”](#deviceauthorizationresponse) | Name | Value | | ----------------------- | --------------------------- | | DeviceCode | `device_code` | | UserCode | `user_code` | | VerificationUri | `verification_uri` | | VerificationUriComplete | `verification_uri_complete` | | ExpiresIn | `expires_in` | | Interval | `interval` | ### EndSessionRequest [Section titled “EndSessionRequest”](#endsessionrequest) | Name | Value | | --------------------- | -------------------------- | | IdTokenHint | `id_token_hint` | | PostLogoutRedirectUri | `post_logout_redirect_uri` | | State | `state` | | Sid | `sid` | | Issuer | `iss` | | UiLocales | `ui_locales` | ### TokenRequest [Section titled “TokenRequest”](#tokenrequest) | Name | Value | | ----------------------- | ----------------------- | | GrantType | `grant_type` | | RedirectUri | `redirect_uri` | | ClientId | `client_id` | | ClientSecret | `client_secret` | | ClientAssertion | `client_assertion` | | ClientAssertionType | `client_assertion_type` | | Assertion | `assertion` | | Code | `code` | | RefreshToken | `refresh_token` | | Scope | `scope` | | UserName | `username` | | Password | `password` | | CodeVerifier | `code_verifier` | | TokenType | `token_type` | | Algorithm | `alg` | | Key | `key` | | DeviceCode | `device_code` | | Resource | `resource` | | Audience | `audience` | | RequestedTokenType | `requested_token_type` | | SubjectToken | `subject_token` | | SubjectTokenType | `subject_token_type` | | ActorToken | `actor_token` | | ActorTokenType | `actor_token_type` | | AuthenticationRequestId | `auth_req_id` | ### BackchannelAuthenticationRequest [Section titled “BackchannelAuthenticationRequest”](#backchannelauthenticationrequest) | Name | Value | | ----------------------- | --------------------------- | | Scope | `scope` | | ClientNotificationToken | `client_notification_token` | | AcrValues | `acr_values` | | LoginHintToken | `login_hint_token` | | IdTokenHint | `id_token_hint` | | LoginHint | `login_hint` | | BindingMessage | `binding_message` | | UserCode | `user_code` | | RequestedExpiry | `requested_expiry` | | Request | `request` | | Resource | `resource` | | DPoPKeyThumbprint | `dpop_jkt` | ### BackchannelAuthenticationRequestErrors [Section titled “BackchannelAuthenticationRequestErrors”](#backchannelauthenticationrequesterrors) | Name | Value | | --------------------- | -------------------------- | | InvalidRequestObject | `invalid_request_object` | | InvalidRequest | `invalid_request` | | InvalidScope | `invalid_scope` | | ExpiredLoginHintToken | `expired_login_hint_token` | | UnknownUserId | `unknown_user_id` | | UnauthorizedClient | `unauthorized_client` | | MissingUserCode | `missing_user_code` | | InvalidUserCode | `invalid_user_code` | | InvalidBindingMessage | `invalid_binding_message` | | InvalidClient | `invalid_client` | | AccessDenied | `access_denied` | | InvalidTarget | `invalid_target` | ### TokenRequestTypes [Section titled “TokenRequestTypes”](#tokenrequesttypes) | Name | Value | | ------ | -------- | | Bearer | `bearer` | | Pop | `pop` | ### TokenErrors [Section titled “TokenErrors”](#tokenerrors) | Name | Value | | ----------------------- | --------------------------- | | InvalidRequest | `invalid_request` | | InvalidClient | `invalid_client` | | InvalidGrant | `invalid_grant` | | UnauthorizedClient | `unauthorized_client` | | UnsupportedGrantType | `unsupported_grant_type` | | UnsupportedResponseType | `unsupported_response_type` | | InvalidScope | `invalid_scope` | | AuthorizationPending | `authorization_pending` | | AccessDenied | `access_denied` | | SlowDown | `slow_down` | | ExpiredToken | `expired_token` | | InvalidTarget | `invalid_target` | | InvalidDPoPProof | `invalid_dpop_proof` | | UseDPoPNonce | `use_dpop_nonce` | ### TokenResponse [Section titled “TokenResponse”](#tokenresponse) | Name | Value | | ---------------- | ------------------- | | AccessToken | `access_token` | | ExpiresIn | `expires_in` | | TokenType | `token_type` | | RefreshToken | `refresh_token` | | IdentityToken | `id_token` | | Error | `error` | | ErrorDescription | `error_description` | | BearerTokenType | `Bearer` | | DPoPTokenType | `DPoP` | | IssuedTokenType | `issued_token_type` | | Scope | `scope` | ### BackchannelAuthenticationResponse [Section titled “BackchannelAuthenticationResponse”](#backchannelauthenticationresponse) | Name | Value | | ----------------------- | ------------- | | AuthenticationRequestId | `auth_req_id` | | ExpiresIn | `expires_in` | | Interval | `interval` | ### PushedAuthorizationRequestResponse [Section titled “PushedAuthorizationRequestResponse”](#pushedauthorizationrequestresponse) | Name | Value | | ---------- | ------------- | | ExpiresIn | `expires_in` | | RequestUri | `request_uri` | ### TokenIntrospectionRequest [Section titled “TokenIntrospectionRequest”](#tokenintrospectionrequest) | Name | Value | | ------------- | ----------------- | | Token | `token` | | TokenTypeHint | `token_type_hint` | ### RegistrationResponse [Section titled “RegistrationResponse”](#registrationresponse) | Name | Value | | ----------------------- | --------------------------- | | Error | `error` | | ErrorDescription | `error_description` | | ClientId | `client_id` | | ClientSecret | `client_secret` | | RegistrationAccessToken | `registration_access_token` | | RegistrationClientUri | `registration_client_uri` | | ClientIdIssuedAt | `client_id_issued_at` | | ClientSecretExpiresAt | `client_secret_expires_at` | | SoftwareStatement | `software_statement` | ### ClientMetadata [Section titled “ClientMetadata”](#clientmetadata) | Name | Value | | ------------------------------------------- | -------------------------------------- | | RedirectUris | `redirect_uris` | | ResponseTypes | `response_types` | | GrantTypes | `grant_types` | | ApplicationType | `application_type` | | Contacts | `contacts` | | ClientName | `client_name` | | LogoUri | `logo_uri` | | ClientUri | `client_uri` | | PolicyUri | `policy_uri` | | TosUri | `tos_uri` | | JwksUri | `jwks_uri` | | Jwks | `jwks` | | SectorIdentifierUri | `sector_identifier_uri` | | Scope | `scope` | | PostLogoutRedirectUris | `post_logout_redirect_uris` | | FrontChannelLogoutUri | `frontchannel_logout_uri` | | FrontChannelLogoutSessionRequired | `frontchannel_logout_session_required` | | BackchannelLogoutUri | `backchannel_logout_uri` | | BackchannelLogoutSessionRequired | `backchannel_logout_session_required` | | SoftwareId | `software_id` | | SoftwareStatement | `software_statement` | | SoftwareVersion | `software_version` | | SubjectType | `subject_type` | | TokenEndpointAuthenticationMethod | `token_endpoint_auth_method` | | TokenEndpointAuthenticationSigningAlgorithm | `token_endpoint_auth_signing_alg` | | DefaultMaxAge | `default_max_age` | | RequireAuthenticationTime | `require_auth_time` | | DefaultAcrValues | `default_acr_values` | | InitiateLoginUri | `initiate_login_uri` | | RequestUris | `request_uris` | | IdentityTokenSignedResponseAlgorithm | `id_token_signed_response_alg` | | IdentityTokenEncryptedResponseAlgorithm | `id_token_encrypted_response_alg` | | IdentityTokenEncryptedResponseEncryption | `id_token_encrypted_response_enc` | | UserinfoSignedResponseAlgorithm | `userinfo_signed_response_alg` | | UserInfoEncryptedResponseAlgorithm | `userinfo_encrypted_response_alg` | | UserinfoEncryptedResponseEncryption | `userinfo_encrypted_response_enc` | | RequestObjectSigningAlgorithm | `request_object_signing_alg` | | RequestObjectEncryptionAlgorithm | `request_object_encryption_alg` | | RequestObjectEncryptionEncryption | `request_object_encryption_enc` | | RequireSignedRequestObject | `require_signed_request_object` | | AlwaysUseDPoPBoundAccessTokens | `dpop_bound_access_tokens` | | IntrospectionSignedResponseAlgorithm | `introspection_signed_response_alg` | | IntrospectionEncryptedResponseAlgorithm | `introspection_encrypted_response_alg` | | IntrospectionEncryptedResponseEncryption | `introspection_encrypted_response_enc` | ### TokenTypes [Section titled “TokenTypes”](#tokentypes) | Name | Value | | ------------- | --------------- | | AccessToken | `access_token` | | IdentityToken | `id_token` | | RefreshToken | `refresh_token` | ### TokenTypeIdentifiers [Section titled “TokenTypeIdentifiers”](#tokentypeidentifiers) | Name | Value | | ------------- | ------------------------------------------------ | | AccessToken | `urn:ietf:params:oauth:token-type:access_token` | | IdentityToken | `urn:ietf:params:oauth:token-type:id_token` | | RefreshToken | `urn:ietf:params:oauth:token-type:refresh_token` | | Saml11 | `urn:ietf:params:oauth:token-type:saml1` | | Saml2 | `urn:ietf:params:oauth:token-type:saml2` | | Jwt | `urn:ietf:params:oauth:token-type:jwt` | ### AuthenticationSchemes [Section titled “AuthenticationSchemes”](#authenticationschemes) | Name | Value | | ------------------------- | ------------------ | | AuthorizationHeaderBearer | `Bearer` | | AuthorizationHeaderDPoP | `DPoP` | | FormPostBearer | `access_token` | | QueryStringBearer | `access_token` | | AuthorizationHeaderPop | `PoP` | | FormPostPop | `pop_access_token` | | QueryStringPop | `pop_access_token` | ### GrantTypes [Section titled “GrantTypes”](#granttypes) | Name | Value | | ----------------- | ------------------------------------------------- | | Password | `password` | | AuthorizationCode | `authorization_code` | | ClientCredentials | `client_credentials` | | RefreshToken | `refresh_token` | | Implicit | `implicit` | | Saml2Bearer | `urn:ietf:params:oauth:grant-type:saml2-bearer` | | JwtBearer | `urn:ietf:params:oauth:grant-type:jwt-bearer` | | DeviceCode | `urn:ietf:params:oauth:grant-type:device_code` | | TokenExchange | `urn:ietf:params:oauth:grant-type:token-exchange` | | Ciba | `urn:openid:params:grant-type:ciba` | ### ClientAssertionTypes [Section titled “ClientAssertionTypes”](#clientassertiontypes) | Name | Value | | ---------- | ---------------------------------------------------------- | | JwtBearer | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | | SamlBearer | `urn:ietf:params:oauth:client-assertion-type:saml2-bearer` | ### ResponseTypes [Section titled “ResponseTypes”](#responsetypes) | Name | Value | | ---------------- | --------------------- | | Code | `code` | | Token | `token` | | IdToken | `id_token` | | IdTokenToken | `id_token token` | | CodeIdToken | `code id_token` | | CodeToken | `code token` | | CodeIdTokenToken | `code id_token token` | ### ResponseModes [Section titled “ResponseModes”](#responsemodes) | Name | Value | | -------- | ----------- | | FormPost | `form_post` | | Query | `query` | | Fragment | `fragment` | ### DisplayModes [Section titled “DisplayModes”](#displaymodes) | Name | Value | | ----- | ------- | | Page | `page` | | Popup | `popup` | | Touch | `touch` | | Wap | `wap` | ### PromptModes [Section titled “PromptModes”](#promptmodes) | Name | Value | | ------------- | ---------------- | | None | `none` | | Login | `login` | | Consent | `consent` | | SelectAccount | `select_account` | | Create | `create` | ### CodeChallengeMethods [Section titled “CodeChallengeMethods”](#codechallengemethods) | Name | Value | | ------ | ------- | | Plain | `plain` | | Sha256 | `S256` | ### ProtectedResourceErrors [Section titled “ProtectedResourceErrors”](#protectedresourceerrors) | Name | Value | | ----------------- | -------------------- | | InvalidToken | `invalid_token` | | ExpiredToken | `expired_token` | | InvalidRequest | `invalid_request` | | InsufficientScope | `insufficient_scope` | ### EndpointAuthenticationMethods [Section titled “EndpointAuthenticationMethods”](#endpointauthenticationmethods) | Name | Value | | ----------------------- | ----------------------------- | | PostBody | `client_secret_post` | | BasicAuthentication | `client_secret_basic` | | PrivateKeyJwt | `private_key_jwt` | | TlsClientAuth | `tls_client_auth` | | SelfSignedTlsClientAuth | `self_signed_tls_client_auth` | ### AuthenticationMethods [Section titled “AuthenticationMethods”](#authenticationmethods) | Name | Value | | ----------------------------------- | -------- | | FacialRecognition | `face` | | FingerprintBiometric | `fpt` | | Geolocation | `geo` | | ProofOfPossessionHardwareSecuredKey | `hwk` | | IrisScanBiometric | `iris` | | KnowledgeBasedAuthentication | `kba` | | MultipleChannelAuthentication | `mca` | | MultiFactorAuthentication | `mfa` | | OneTimePassword | `otp` | | PersonalIdentificationOrPattern | `pin` | | ProofOfPossessionKey | `pop` | | Password | `pwd` | | RiskBasedAuthentication | `rba` | | RetinaScanBiometric | `retina` | | SmartCard | `sc` | | ConfirmationBySms | `sms` | | ProofOfPossessionSoftwareSecuredKey | `swk` | | ConfirmationByTelephone | `tel` | | UserPresenceTest | `user` | | VoiceBiometric | `vbm` | | WindowsIntegratedAuthentication | `wia` | ### Algorithms [Section titled “Algorithms”](#algorithms) #### Symmetric [Section titled “Symmetric”](#symmetric) | Name | Value | | ----- | ------- | | HS256 | `HS256` | | HS384 | `HS384` | | HS512 | `HS512` | #### Asymmetric [Section titled “Asymmetric”](#asymmetric) | Name | Value | | ----- | ------- | | RS256 | `RS256` | | RS384 | `RS384` | | RS512 | `RS512` | | ES256 | `ES256` | | ES384 | `ES384` | | ES512 | `ES512` | | PS256 | `PS256` | | PS384 | `PS384` | | PS512 | `PS512` | ### Discovery [Section titled “Discovery”](#discovery) | Name | Value | | ------------------------------------------- | -------------------------------------------------- | | Issuer | `issuer` | | AuthorizationEndpoint | `authorization_endpoint` | | DeviceAuthorizationEndpoint | `device_authorization_endpoint` | | TokenEndpoint | `token_endpoint` | | UserInfoEndpoint | `userinfo_endpoint` | | IntrospectionEndpoint | `introspection_endpoint` | | RevocationEndpoint | `revocation_endpoint` | | DiscoveryEndpoint | `.well-known/openid-configuration` | | JwksUri | `jwks_uri` | | EndSessionEndpoint | `end_session_endpoint` | | CheckSessionIframe | `check_session_iframe` | | RegistrationEndpoint | `registration_endpoint` | | MtlsEndpointAliases | `mtls_endpoint_aliases` | | PushedAuthorizationRequestEndpoint | `pushed_authorization_request_endpoint` | | FrontChannelLogoutSupported | `frontchannel_logout_supported` | | FrontChannelLogoutSessionSupported | `frontchannel_logout_session_supported` | | BackChannelLogoutSupported | `backchannel_logout_supported` | | BackChannelLogoutSessionSupported | `backchannel_logout_session_supported` | | GrantTypesSupported | `grant_types_supported` | | CodeChallengeMethodsSupported | `code_challenge_methods_supported` | | ScopesSupported | `scopes_supported` | | SubjectTypesSupported | `subject_types_supported` | | ResponseModesSupported | `response_modes_supported` | | ResponseTypesSupported | `response_types_supported` | | ClaimsSupported | `claims_supported` | | TokenEndpointAuthenticationMethodsSupported | `token_endpoint_auth_methods_supported` | | ClaimsLocalesSupported | `claims_locales_supported` | | ClaimsParameterSupported | `claims_parameter_supported` | | ClaimTypesSupported | `claim_types_supported` | | DisplayValuesSupported | `display_values_supported` | | AcrValuesSupported | `acr_values_supported` | | IdTokenEncryptionAlgorithmsSupported | `id_token_encryption_alg_values_supported` | | IdTokenEncryptionEncValuesSupported | `id_token_encryption_enc_values_supported` | | IdTokenSigningAlgorithmsSupported | `id_token_signing_alg_values_supported` | | OpPolicyUri | `op_policy_uri` | | OpTosUri | `op_tos_uri` | | RequestObjectEncryptionAlgorithmsSupported | `request_object_encryption_alg_values_supported` | | RequestObjectEncryptionEncValuesSupported | `request_object_encryption_enc_values_supported` | | RequestObjectSigningAlgorithmsSupported | `request_object_signing_alg_values_supported` | | RequestParameterSupported | `request_parameter_supported` | | RequestUriParameterSupported | `request_uri_parameter_supported` | | RequireRequestUriRegistration | `require_request_uri_registration` | | ServiceDocumentation | `service_documentation` | | TokenEndpointAuthSigningAlgorithmsSupported | `token_endpoint_auth_signing_alg_values_supported` | | UILocalesSupported | `ui_locales_supported` | | UserInfoEncryptionAlgorithmsSupported | `userinfo_encryption_alg_values_supported` | | UserInfoEncryptionEncValuesSupported | `userinfo_encryption_enc_values_supported` | | UserInfoSigningAlgorithmsSupported | `userinfo_signing_alg_values_supported` | | TlsClientCertificateBoundAccessTokens | `tls_client_certificate_bound_access_tokens` | | AuthorizationResponseIssParameterSupported | `authorization_response_iss_parameter_supported` | | PromptValuesSupported | `prompt_values_supported` | | IntrospectionSigningAlgorithmsSupported | `introspection_signing_alg_values_supported` | | IntrospectionEncryptionAlgorithmsSupported | `introspection_encryption_alg_values_supported` | | IntrospectionEncryptionEncValuesSupported | `introspection_encryption_enc_values_supported` | ### BackchannelTokenDeliveryModes [Section titled “BackchannelTokenDeliveryModes”](#backchanneltokendeliverymodes) | Name | Value | | ---- | ------ | | Poll | `poll` | | Ping | `ping` | | Push | `push` | ### Events [Section titled “Events”](#events) | Name | Value | | ----------------- | ---------------------------------------------------- | | BackChannelLogout | `http://schemas.openid.net/event/backchannel-logout` | ### BackChannelLogoutRequest [Section titled “BackChannelLogoutRequest”](#backchannellogoutrequest) | Name | Value | | ----------- | -------------- | | LogoutToken | `logout_token` | ### StandardScopes [Section titled “StandardScopes”](#standardscopes) | Name | Value | Description | | ------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | OpenId | `openid` | REQUIRED. Indicates the Client is making an OpenID Connect request. The behavior is unspecified if this is not included. | | Profile | `profile` | OPTIONAL. Requests access to End-User’s default profile Claims such as `name`, `family_name`, `given_name`, etc. | | Email | `email` | OPTIONAL. Requests access to the `email` and `email_verified` Claims. | | Address | `address` | OPTIONAL. Requests access to the `address` Claim. | | Phone | `phone` | OPTIONAL. Requests access to `phone_number` and `phone_number_verified` Claims. | | OfflineAccess | `offline_access` | MUST NOT be used with the OpenID Connect Implicit Client Implementer’s Guide. Used in accordance with the OpenID Connect Basic Client Implementer’s Guide. | ### HttpHeaders [Section titled “HttpHeaders”](#httpheaders) | Name | Value | | --------- | ------------ | | DPoP | `DPoP` | | DPoPNonce | `DPoP-Nonce` | ## JWT Claim Types [Section titled “JWT Claim Types”](#jwt-claim-types) The `JwtClaimTypes` class has all standard claim types found in the OpenID Connect, JWT and OAuth 2.0 specs -many of them are also aggregated at [IANA](https://www.iana.org/assignments/jwt/jwt.xhtml). | Claim Type | Value | Description/Remarks | | :---------------------------------- | :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Subject | `sub` | Unique Identifier for the End-User at the Issuer. | | Name | `name` | End-User’s full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the End-User’s locale and preferences. | | GivenName | `given_name` | Given name(s) or first name(s) of the End-User. Note that in some cultures, people can have multiple given names; all can be present, with the names being separated by space characters. | | FamilyName | `family_name` | Surname(s) or last name(s) of the End-User. Note that in some cultures, people can have multiple family names or no family name; all can be present, with the names being separated by space characters. | | MiddleName | `middle_name` | Middle name(s) of the End-User. Note that in some cultures, people can have multiple middle names; all can be present, with the names being separated by space characters. Also note that in some cultures, middle names are not used. | | NickName | `nickname` | Casual name of the End-User that may or may not be the same as the given\_name. For instance, a nickname value of Mike might be returned alongside a given\_name value of Michael. | | PreferredUserName | `preferred_username` | Shorthand name by which the End-User wishes to be referred to at the RP, such as janedoe or j.doe. This value MAY be any valid JSON string including special characters. **Remarks:** The relying party MUST NOT rely upon this value being unique, as discussed in the OpenID Connect specification. | | Profile | `profile` | URL of the End-User’s profile page. The contents of this Web page SHOULD be about the End-User. | | Picture | `picture` | URL of the End-User’s profile picture. This URL MUST refer to an image file (e.g., PNG, JPEG, or GIF image file). **Remarks:** This URL SHOULD specifically reference a profile photo of the End-User rather than an arbitrary photo. | | WebSite | `website` | URL of the End-User’s Web page or blog. This Web page SHOULD contain information published by the End-User or an organization related to the End-User. | | Email | `email` | End-User’s preferred e-mail address. Its value MUST conform to the RFC 5322 syntax. The relying party MUST NOT rely upon this value being unique. | | EmailVerified | `email_verified` | `"true"` if the End-User’s e-mail address has been verified; otherwise `"false"`. **Remarks:** Verification methods vary depending on trust frameworks or agreements. | | Gender | `gender` | End-User’s gender. Allowed values include `"female"` and `"male"`, with additional values permissible when the predefined ones are not applicable. | | BirthDate | `birthdate` | End-User’s birthday in ISO 8601 format (e.g., YYYY-MM-DD). The year MAY be `0000`, indicating it is omitted. | | ZoneInfo | `zoneinfo` | String representing the End-User’s time zone, e.g., `Europe/Paris` or `America/Los_Angeles`. | | Locale | `locale` | End-User’s locale represented as a BCP47 language tag (e.g., `en-US`, `fr-CA`). Compatibility notes suggest some implementations may use underscores instead of dashes. | | PhoneNumber | `phone_number` | End-User’s preferred telephone number. E.164 format is recommended, including extensions. | | PhoneNumberVerified | `phone_number_verified` | `"true"` if the End-User’s phone number has been verified; otherwise `"false"`. **Remarks:** Applies to numbers in E.164 format. | | Address | `address` | End-User’s preferred postal address. Contains a JSON structure with predefined fields from the OpenID Connect specification. | | Audience | `aud` | Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client\_id of the Relying Party. | | Issuer | `iss` | Issuer Identifier for the Issuer of the response in the form of a URL. | | NotBefore | `nbf` | The time before which the JWT MUST NOT be accepted, specified in seconds since 1970-01-01T00:00:00Z. | | Expiration | `exp` | The token’s expiration time in seconds since 1970-01-01T00:00:00Z. | | UpdatedAt | `updated_at` | Time of last update for the End-User’s information, measured in seconds since 1970-01-01T00:00:00Z. | | IssuedAt | `iat` | Time at which the JWT was issued, specified in seconds since 1970-01-01T00:00:00Z. | | AuthenticationMethod | `amr` | JSON array of strings identifying the authentication method(s) used. | | SessionId | `sid` | Session identifier representing an OP session at an RP for a logged-in End-User. | | AuthenticationContextClassReference | `acr` | Specifies the Authentication Context Class Reference value satisfied during authentication. **Remarks:** Example: `"level 0"` indicates authentication did not meet ISO/IEC 29115 level 1. | | AuthenticationTime | `auth_time` | Time of the End-User’s authentication, measured in seconds since 1970-01-01T00:00:00Z. | | AuthorizedParty | `azp` | Authorized party to which the ID Token was issued. | | AccessTokenHash | `at_hash` | Access token hash value derived using a specific hash algorithm. | | AuthorizationCodeHash | `c_hash` | Authorization code hash value derived using a specific hash algorithm. | | StateHash | `s_hash` | State hash value derived using a specific hash algorithm. | | Nonce | `nonce` | Value used to mitigate replay attacks between a Client session and an ID Token. | | JwtId | `jti` | A unique identifier for the token to prevent reuse. | | Events | `events` | Defines a set of event statements to describe a logical event that has occurred. | | ClientId | `client_id` | OAuth 2.0 Client Identifier valid at the Authorization Server. | | Scope | `scope` | OpenID Connect “openid” scope value. Additional scope values can be included. | | Actor | `act` | Identifies the acting party to whom authority has been delegated. | | MayAct | `may_act` | Statement asserting that a party is authorized to act on behalf of another party. | | Id | `id` | An identifier. | | IdentityProvider | `idp` | The identity provider. | | Role | `role` | The role. | | Roles | `roles` | The roles. | | ReferenceTokenId | `reference_token_id` | Reference token identifier. | | Confirmation | `cnf` | The confirmation. | | Algorithm | `alg` | The algorithm. | | JsonWebKey | `jwk` | JSON web key. | | TokenType | `typ` | The token type. | | DPoPHttpMethod | `htm` | DPoP HTTP method. | | DPoPHttpUrl | `htu` | DPoP HTTP URL. | | DPoPAccessTokenHash | `ath` | DPoP access token hash. | ### JwtTypes [Section titled “JwtTypes”](#jwttypes) `JwtTypes` is a nested class that provides a set of constants for confirmation methods. It can be found under the `JwtConstants` class. | Type | Value | Description | | :----------------------- | :-------------------------- | :---------------------------------------------------------- | | AccessToken | `at+jwt` | OAuth 2.0 access token. | | AuthorizationRequest | `oauth-authz-req+jwt` | JWT secured authorization request. | | DPoPProofToken | `dpop+jwt` | DPoP proof token. | | IntrospectionJwtResponse | `token-introspection+jwt` | Token introspection JWT response. | | ClientAuthentication | `client-authentication+jwt` | Client authentication JWT (for use with private\_key\_jwt). | ### ConfirmationMethods [Section titled “ConfirmationMethods”](#confirmationmethods) `ConfirmationMethods` is a nested class that provides a set of constants for confirmation methods. It can be found under the `JwtConstants` class. | Method | Value | Description | | :------------------- | :--------- | :----------------------------------------- | | JsonWebKey | `jwk` | JSON web key. | | JwkThumbprint | `jkt` | JSON web key thumbprint. | | X509ThumbprintSha256 | `x5t#S256` | X.509 certificate thumbprint using SHA256. | ----- # Epoch Time Conversion > Learn about converting between DateTime and Unix/Epoch time formats in Duende IdentityModel for JWT tokens JSON Web Token (JWT) tokens use so-called [Epoch or Unix time](https://en.wikipedia.org/wiki/Unix_time) to represent date/times, which is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT). In .NET, you can convert `DateTimeOffset` to Unix/Epoch time via the two methods of `ToUnixTimeSeconds` and `ToUnixTimeMilliseconds`: EpochTimeExamples.cs ```csharp var seconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var milliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); ``` ----- # Creating Authorize and EndSession URLs > Helper utilities for creating OAuth 2.0/OpenID Connect authorization and end session URLs with query parameters The *RequestUrl* class is a helper for creating URLs with query string parameters, e.g.: ```csharp var ru = new RequestUrl("https://server/endpoint"); // produces https://server/endpoint?foo=foo&bar=bar var url = ru.Create(new { foo: "foo", bar: "bar" }); ``` As a parameter to the *Create* method you can either pass in an object, or a string dictionary. In both cases the properties/values will be serialized to key/value pairs. ## Authorization Endpoint [Section titled “Authorization Endpoint”](#authorization-endpoint) For most cases, the [OAuth 2.0](https://tools.ietf.org/html/rfc6749#section-3.1) and [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html#authorizationendpoint) authorization endpoint expects a GET request with a number of query string parameters. The *CreateAuthorizeUrl* extension method creates URLs for the authorize endpoint - it has support the most common parameters: ```csharp /// /// Creates an authorize URL. /// /// The request. /// The client identifier. /// The response type. /// The scope. /// The redirect URI. /// The state. /// The nonce. /// The login hint. /// The acr values. /// The prompt. /// The response mode. /// The code challenge. /// The code challenge method. /// The display option. /// The max age. /// The ui locales. /// The id_token hint. /// Extra parameters. /// public static string CreateAuthorizeUrl(this RequestUrl request, string clientId, string responseType, string scope = null, string redirectUri = null, string state = null, string nonce = null, string loginHint = null, string acrValues = null, string prompt = null, string responseMode = null, string codeChallenge = null, string codeChallengeMethod = null, string display = null, int? maxAge = null, string uiLocales = null, string idTokenHint = null, object extra = null) { ... } ``` Example: ```csharp var ru = new RequestUrl("https://demo.duendesoftware.com/connect/authorize"); var url = ru.CreateAuthorizeUrl( clientId: "client", responseType: "implicit", redirectUri: "https://app.com/callback", nonce: "xyz", scope: "openid"); ``` ## EndSession Endpoint [Section titled “EndSession Endpoint”](#endsession-endpoint) The *CreateEndSessionUrl* extensions methods supports the most common parameters: ```csharp /// /// Creates a end_session URL. /// /// The request. /// The id_token hint. /// The post logout redirect URI. /// The state. /// The extra parameters. /// public static string CreateEndSessionUrl(this RequestUrl request, string idTokenHint = null, string postLogoutRedirectUri = null, string state = null, object extra = null) { ... } ``` ----- # Time-Constant String Comparison > Learn about implementing secure string comparison to prevent timing attacks in security-sensitive contexts using TimeConstantComparer When comparing strings in a security context (e.g. comparing keys), you should avoid leaking timing information. Standard string comparison algorithms are optimized to stop comparing characters as soon as a difference is found. An attacker can exploit this by making many requests with strings that all differ in the first character. The strings that begin with an incorrect first character will make a single character comparison and stop. However, the strings that begin with a correct first character will need to make additional string comparisons, and thus take more time before they stop. Sophisticated attackers can measure this difference and use it to deduce the characters that their input is being compared to. ## Time-Constant String Comparison [Section titled “Time-Constant String Comparison”](#time-constant-string-comparison) ```csharp using System.Security.Cryptography; // Simulated sensitive data (e.g., a secure token or password hash) var storedHash = Convert.FromBase64String("HJG3+eXAIoQsNI1ASD2i+If7xhQAEjZLefBWo5pcuDE="); // Incoming hash to validate (e.g., provided by the user) var providedHash = Convert.FromBase64String("HJG3+eXAIoQsNI1ASD2i+If7xhQAEjZLefBWo5pcuDE="); // Compare the two byte sequences using FixedTimeEquals var isEqual = CryptographicOperations.FixedTimeEquals(storedHash, providedHash); var result = isEqual ? "the hashes match!" : "the hashes do not match!"; Console.WriteLine(result); ``` ## TimeConstantComparer [Section titled “TimeConstantComparer”](#timeconstantcomparer) The *TimeConstantComparer* class defends against these timing attacks by implementing a constant-time string comparison. The string comparison is a constant-time operation in the sense that comparing strings of equal length always performs the same amount of work. Usage example: ```csharp using Duende.IdentityModel; // Simulated sensitive data (e.g., a secure token or password hash) var storedHash = "HJG3+eXAIoQsNI1ASD2i+If7xhQAEjZLefBWo5pcuDE="; // Incoming hash to validate (e.g., provided by the user) var providedHash = "HJG3+eXAIoQsNI1ASD2i+If7xhQAEjZLefBWo5pcuDE="; // Compare the two byte sequences using FixedTimeEquals var isEqual = TimeConstantComparer.IsEqual(storedHash, providedHash); var result = isEqual ? "the hashes match!" : "the hashes do not match!"; Console.WriteLine(result); ``` ----- # Fluent X.509 Certificate Store API > Provides a simplified, fluent API for accessing and managing X.509 certificates in a certificate store. A common place to store X.509 certificates is within a host’s X.509 certificate store. With .NET APIs, this is done using the `X509Store` class. ```csharp using System.Security.Cryptography.X509Certificates; // with .NET APIs using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); using var certificate = store.Certificates .Find(X509FindType.FindBySubjectDistinguishedName, "CN=localhost", false)[0]; if (certificate == null) throw new InvalidOperationException("Certificate not found"); Console.WriteLine(certificate); ``` The *X509* class in the IdentityModel library is a simplified API to load certificates from a certificate store. The following code loads a certificate by name from the personal machine store: ```csharp using Duende.IdentityModel; using var certificate = X509.CurrentUser .My .SubjectDistinguishedName .Find("CN=localhost", false) .FirstOrDefault(); if (certificate == null) throw new InvalidOperationException("Certificate not found"); Console.WriteLine(certificate); ``` ### Certificate Store Locations [Section titled “Certificate Store Locations”](#certificate-store-locations) You can load certificates from the following machine or user stores: * *My* * *AddressBook* * *TrustedPeople* * *CertificateAuthority* * *TrustedPublisher* ### Certificate Search Options [Section titled “Certificate Search Options”](#certificate-search-options) You can search for a certificate by the following attributes: * Subject name, * Thumbprint * Issuer name * Serial number. ### Debugging Certificates in a Store [Section titled “Debugging Certificates in a Store”](#debugging-certificates-in-a-store) When finding it difficult to find a certificate by name, you can use the following code to list all certificates in a store for debugging purposes: ```csharp using System.Security.Cryptography.X509Certificates; using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); var certificates = store.Certificates; foreach (var certificate in certificates) { Console.WriteLine($"{certificate.Subject} ({certificate.Thumbprint})"); } ``` ----- # Duende IdentityServer > Overview of Duende IdentityServer framework for OpenID Connect and OAuth 2.x protocols, covering extensibility, security scenarios, licensing, and support. Duende IdentityServer is a highly extensible, standards-compliant framework for implementing the OpenID Connect, OAuth 2.x and SAML protocols in .NET and ASP.NET Core. It offers deep flexibility for handling authentication, authorization, and token issuance and can be adapted to fit complex custom security scenarios. [GitHub Repository ](https://github.com/DuendeSoftware/products/tree/main/identity-server/)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.IdentityServer/)View the package on NuGet.org. ## Extensibility Points [Section titled “Extensibility Points”](#extensibility-points) * **Customizable User Experience**: Go beyond simple branding to fully customizable user interfaces. * **Core Engine Customization**: The engine itself is modular and built from services that can be extended or overridden. ## Advanced Security Scenarios [Section titled “Advanced Security Scenarios”](#advanced-security-scenarios) Duende IdentityServer supports a wide range of security scenarios for modern applications: * **Federation**: Easily integrate with external identity providers or other authentication services using [federation](/identityserver/ui/federation/). * **Token Exchange**: Enable secure token exchange between clients and services with [Token Exchange](/identityserver/tokens/extension-grants/#token-exchange). * **Audience Constrained Tokens**: Restrict tokens to specific audiences, increasing security in multi-service architectures. Learn more about [audience-constrained tokens](/identityserver/fundamentals/resources/isolation/). * **Sender Constrained Tokens**: Implement Proof of Possession (PoP) tokens with [DPoP or mTLS](/identityserver/tokens/pop/), which bind tokens to the client, adding another layer of protection. * **Pushed Authorization Requests (PAR)**: Support [Pushed Authorization Requests](/identityserver/tokens/par/) to enhance the security of the authorization flow. * **FAPI 2.0**: protect APIs in high-value scenarios with the [FAPI 2.0 Security profile](/identityserver/tokens/fapi-2-0-specification/). ## Licensing [Section titled “Licensing”](#licensing) Duende IdentityServer is source-available, but **requires a paid [license](https://duendesoftware.com/products/identityserver) for production use.** * **Development and Testing**: You are free to use and explore the code for development, testing, or personal projects without a license. * **Production**: A license is required for production environments. * **Free Community Edition**: A free Community Edition license is available for qualifying companies and non-profit organizations. Learn more [here](https://duendesoftware.com/products/communityedition). ## Reporting Issues and Getting Support [Section titled “Reporting Issues and Getting Support”](#reporting-issues-and-getting-support) * For bug reports or feature requests, [use our developer community forum](https://github.com/DuendeSoftware/community). * For security-related concerns, please contact us privately at: ****. ----- # Protecting APIs > Learn how to secure and protect your APIs using Duende IdentityServer's token-based authentication and authorization Duende IdentityServer issues tokens for accessing resources. These resources are very often HTTP-based APIs, but could be also other “invocable” functionality like messaging endpoints, gRPC services or even good old XML Web Services. See the [issuing tokens](/identityserver/tokens/) section on more information on access tokens and how to request them. ## Adding API Endpoints to IdentityServer [Section titled “Adding API Endpoints to IdentityServer”](#adding-api-endpoints-to-identityserver) It’s a common scenario to add additional API endpoints to the application hosting IdentityServer. These endpoints are typically protected by IdentityServer itself. For simple scenarios, we give you some helpers. See the advanced section to understand more of the internal plumbing. Start by registering your API as an `ApiScope`, (or resource) e.g.: ```csharp var scopes = new List { // local API new ApiScope(IdentityServerConstants.LocalApi.ScopeName), }; ``` …and give your clients access to this API, e.g.: ```csharp new Client { // rest omitted AllowedScopes = { IdentityServerConstants.LocalApi.ScopeName }, } ``` To enable token validation for local APIs, add the following to your IdentityServer startup: Program.cs ```csharp builder.Services.AddLocalApiAuthentication(); ``` To protect an API endpoint, call `RequireAuthorization` with the `LocalApi.PolicyName` policy: ```csharp app.MapGet("/localApi", () => { // omitted }).RequireAuthorization(LocalApi.PolicyName); ``` To protect an API controller, decorate it with an `Authorize` attribute using the `LocalApi.PolicyName` policy: ```csharp [Route("localApi")] [Authorize(LocalApi.PolicyName)] public class LocalApiController : ControllerBase { public IActionResult Get() { // omitted } } ``` Authorized clients can then request a token for the `IdentityServerApi` scope and use it to call the API. ## Discovery [Section titled “Discovery”](#discovery) You can also add your endpoints to the discovery document if you want, e.g.like this:: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Discovery.CustomEntries.Add("local_api", "~/localapi"); }) ``` ## Advanced [Section titled “Advanced”](#advanced) Under the hood, the `AddLocalApiAuthentication` helper does a couple of things: * adds an authentication handler that validates incoming tokens using IdentityServer’s built-in token validation engine (the name of this handler is `IdentityServerAccessToken` or `IdentityServerConstants.LocalApi.AuthenticationScheme` * configures the authentication handler to require a scope claim inside the access token of value `IdentityServerApi` * sets up an authorization policy that checks for a scope claim of value `IdentityServerApi` This covers the most common scenarios. You can customize this behavior in the following ways: * Add the authentication handler yourself by calling `services.AddAuthentication().AddLocalApi(...)`. This way you can specify the required scope name yourself, or (by specifying no scope at all) accept any token from the current IdentityServer instance * Do your own scope validation/authorization in your controllers using custom policies or code, e.g.: Program.cs ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy => { policy.AddAuthenticationSchemes(IdentityServerConstants.LocalApi.AuthenticationScheme); policy.RequireAuthenticatedUser(); // custom requirements }); }); ``` ## Claims Transformation [Section titled “Claims Transformation”](#claims-transformation) You can provide a callback to transform the claims of the incoming token after validation. Either use the helper method, e.g.: Program.cs ```csharp builder.Services.AddLocalApiAuthentication(principal => { principal.Identities.First().AddClaim(new Claim("additional_claim", "additional_value")); return Task.FromResult(principal); }); ``` …or implement the event on the options if you add the authentication handler manually. ## DPoP Support v8.0 [Section titled “DPoP Support ”v8.0](#dpop-support) The local API authentication handler supports [DPoP](/identityserver/tokens/pop/) proof validation. When a client sends a DPoP-bound access token to a local API, the handler validates the accompanying proof token automatically. Requests must include exactly one `DPoP` header. If multiple `DPoP` headers are present, the handler rejects the request with an `invalid_dpop_proof` error. ----- # Authorization based on Scopes and Claims > Guide for implementing authorization using scope claims and ASP.NET Core authorization policies with IdentityServer access tokens The access token will include additional claims that can be used for authorization, e.g. the `scope` claim will reflect the scope the client requested (and was granted) during the token request. In ASP.NET core, the contents of the JWT payload get transformed into claims and packaged up in a `ClaimsPrincipal`. So you can always write custom validation or authorization logic in C#: ```csharp public IActionResult Get() { var isAllowed = User.HasClaim("scope", "read"); // rest omitted } ``` For better encapsulation and re-use, consider using the ASP.NET Core [authorization policy](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) feature. With this approach, you would first turn the claim requirement(s) into a named policy: ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy("read_access", policy => policy.RequireClaim("scope", "read")); }); ``` …and then enforce it, e.g. using the routing table: ```csharp app.MapControllers().RequireAuthorization("read_access"); ``` …or imperatively inside the endpoint handler: ```csharp app.MapGet("/", async (IAuthorizationService authz, ClaimsPrincipal user) => { var allowed = await authz.AuthorizeAsync(user, "read_access"); if (!allowed.Succeeded) { return Results.Forbid(); } // rest omitted }); ``` … or declaratively: ```csharp app.MapGet("/", () => { // rest omitted }).RequireAuthorization("read_access"); ``` #### Scope Claim Format [Section titled “Scope Claim Format”](#scope-claim-format) Historically, Duende IdentityServer emitted the `scope` claims as an array in the JWT. This works very well with the .NET deserialization logic, which turns every array item into a separate claim of type `scope`. The newer *JWT Profile for OAuth* [spec](/identityserver/overview/specs/) mandates that the scope claim is a single space delimited string. You can switch the format by setting the `EmitScopesAsSpaceDelimitedStringInJwt` on the [options](/identityserver/reference/v8/options/). But this means that the code consuming access tokens might need to be adjusted. The following code can do a conversion to the *multiple claims* format that .NET prefers: ```csharp namespace IdentityModel.AspNetCore.AccessTokenValidation; /// /// Logic for normalizing scope claims to separate claim types /// public static class ScopeConverter { /// /// Logic for normalizing scope claims to separate claim types /// /// /// public static ClaimsPrincipal NormalizeScopeClaims(this ClaimsPrincipal principal) { var identities = new List(); foreach (var id in principal.Identities) { var identity = new ClaimsIdentity(id.AuthenticationType, id.NameClaimType, id.RoleClaimType); foreach (var claim in id.Claims) { if (claim.Type == "scope") { if (claim.Value.Contains(' ')) { var scopes = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var scope in scopes) { identity.AddClaim(new Claim("scope", scope, claim.ValueType, claim.Issuer)); } } else { identity.AddClaim(claim); } } else { identity.AddClaim(claim); } } identities.Add(identity); } return new ClaimsPrincipal(identities); } } ``` The above code could then be called as an extension method or as part of [claims transformation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.iclaimstransformation). ----- # Validating Proof-of-Possession > Guide for validating Proof-of-Possession (PoP) access tokens in ASP.NET Core using mTLS or DPoP mechanisms IdentityServer can [bind tokens to clients](/identityserver/tokens/pop/#proof-of-possession-styles) using either mTLS or DPoP, creating a `Proof-of-Possession` (PoP) access token. When one of these mechanisms is used, APIs that use those access tokens for authorization need to validate the binding between the client and token. This document describes how to perform such validation, depending on which mechanism was used to produce a PoP token. ### Validating mTLS [Section titled “Validating mTLS”](#validating-mtls) If you are using a [mutual TLS connection](/identityserver/tokens/pop/#mutual-tls) to establish proof-of-possession, the resulting access token will contain a `cnf` claim containing the client’s certificate thumbprint. APIs validate such tokens by comparing this thumbprint to the thumbprint of the client certificate in the mTLS connection. This validation should be performed early in the pipeline, ideally immediately after the standard validation of the access token. You can do so with custom middleware like this: ```csharp // normal token validation happens here app.UseAuthentication(); // This adds custom middleware to validate cnf claim app.UseConfirmationValidation(); app.UseAuthorization(); ``` Here, `UseConfirmationValidation` is an extension method that registers the middleware that performs the necessary validation: ```csharp public static class ConfirmationValidationExtensions { public static IApplicationBuilder UseConfirmationValidation(this IApplicationBuilder app, ConfirmationValidationMiddlewareOptions options = default) { return app.UseMiddleware(options ?? new ConfirmationValidationMiddlewareOptions()); } } ``` And this is the actual middleware that validates the `cnf` claim: ```csharp // this middleware validates the cnf claim (if present) against the thumbprint of the X.509 client certificate for the current client public class ConfirmationValidationMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly ConfirmationValidationMiddlewareOptions _options; public ConfirmationValidationMiddleware( RequestDelegate next, ILogger logger, ConfirmationValidationMiddlewareOptions options = null) { _next = next; _logger = logger; _options ??= new ConfirmationValidationMiddlewareOptions(); } public async Task Invoke(HttpContext ctx) { if (ctx.User.Identity.IsAuthenticated) { // read the cnf claim from the validated token var cnfJson = ctx.User.FindFirst("cnf")?.Value; if (!String.IsNullOrWhiteSpace(cnfJson)) { // if present, make sure a valid certificate was presented as well var certResult = await ctx.AuthenticateAsync(_options.CertificateSchemeName); if (!certResult.Succeeded) { await ctx.ChallengeAsync(_options.CertificateSchemeName); return; } // get access to certificate from transport var certificate = await ctx.Connection.GetClientCertificateAsync(); var thumbprint = Base64UrlTextEncoder.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256)); // retrieve value of the thumbprint from cnf claim var cnf = JObject.Parse(cnfJson); var sha256 = cnf.Value("x5t#S256"); // compare thumbprint claim with thumbprint of current TLS client certificate if (String.IsNullOrWhiteSpace(sha256) || !thumbprint.Equals(sha256, StringComparison.OrdinalIgnoreCase)) { _logger.LogError("certificate thumbprint does not match cnf claim."); await ctx.ChallengeAsync(_options.JwtBearerSchemeName); return; } _logger.LogDebug("certificate thumbprint matches cnf claim."); } } await _next(ctx); } } public class ConfirmationValidationMiddlewareOptions { public string CertificateSchemeName { get; set; } = CertificateAuthenticationDefaults.AuthenticationScheme; public string JwtBearerSchemeName { get; set; } = JwtBearerDefaults.AuthenticationScheme; } ``` ### Validating DPoP [Section titled “Validating DPoP”](#validating-dpop) When using [DPoP](/identityserver/tokens/pop/#enabling-dpop-in-identityserver) for proof-of-possession, validating the `cnf` claim requires several steps: 1. Validating the access token as normal 2. Validating the DPoP proof token from the `DPoP` HTTP request header 3. Ensuring the authorization header uses the DPoP scheme 4. Validating the JWT format of the proof token 5. Verifying the `cnf` claim matches between tokens 6. Validating the HTTP method and URL match the request 7. Detecting replay attacks using storage 8. Managing nonce generation and validation 9. Handling clock skew between systems 10. Returning appropriate error response headers when validation fails This comprehensive validation process requires careful implementation to ensure security. Luckily for developers, we’ve implemented these steps into an easy-to-use library. You can use the `Duende.AspNetCore.Authentication.JwtBearer` NuGet package to implement this validation. ```bash dotnet add package Duende.AspnetCore.Authentication.JwtBearer ``` With this package, the configuration necessary in your startup can be as simple as this: ```csharp // adds the normal JWT bearer validation builder.Services.AddAuthentication("token") .AddJwtBearer("token", options => { options.Authority = Constants.Authority; options.TokenValidationParameters.ValidateAudience = false; options.MapInboundClaims = false; options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; }); // extends the "token" scheme above with DPoP processing and validation builder.Services.ConfigureDPoPTokensForScheme("token"); ``` You will also typically need a distributed cache, used to perform replay detection of DPoP proofs. `Duende.AspNetCore.Authentication.JwtBearer` relies on `IDistributedCache` for this, so you can supply the cache implementation of your choice. See the [Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-8.0) for more details on setting up distributed caches, along with many examples, including Redis, CosmosDB, and Sql Server. A full sample [using the default in memory caching](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/DPoP) is available on GitHub. ----- # Using JSON Web Tokens (JWTs) > Guide for validating JWT bearer tokens in ASP.NET Core applications using the JWT authentication handler On ASP.NET Core, you typically use the [JWT authentication handler](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer) for validating JWT bearer tokens. ## Validating A JWT [Section titled “Validating A JWT”](#validating-a-jwt) First you need to add a reference to the authentication handler in your API project: ```xml ``` If all you care about is making sure that an access token comes from your trusted IdentityServer, the following snippet shows the typical JWT validation configuration for ASP.NET Core: ```csharp builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // base-address of your identityserver options.Authority = "https://demo.duendesoftware.com"; // audience is optional, make sure you read the following paragraphs // to understand your options options.TokenValidationParameters.ValidateAudience = false; // it's recommended to check the type header to avoid "JWT confusion" attacks options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; }); ``` ## Adding Audience Validation [Section titled “Adding Audience Validation”](#adding-audience-validation) Simply making sure that the token is coming from a trusted issuer is not good enough for most cases. In more complex systems, you will have multiple resources and multiple clients. Not every client might be authorized to access every resource. In OAuth there are two complementary mechanisms to embed more information about the “functionality” that the token is for - `audience` and `scope` (see [defining resources](/identityserver/fundamentals/resources/api-resources/) for more information). If you designed your APIs around the concept of [API resources](/identityserver/fundamentals/resources/api-resources/), your IdentityServer will emit the `aud` claim by default (`api1` in this example): ```text { "typ": "at+jwt", "kid": "123" }. { "aud": "api1", "client_id": "mobile_app", "sub": "123", "scope": "read write delete" } ``` If you want to express in your API, that only access tokens for the `api1` audience (aka API resource name) are accepted, change the above code snippet to: ```csharp builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://demo.duendesoftware.com"; options.Audience = "api1"; options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; }); ``` ----- # Reference Tokens > Guide for implementing reference token validation in ASP.NET Core APIs using OAuth 2.0 token introspection If you are using [reference tokens](/identityserver/tokens/reference/), you need an authentication handler that implements the back-channel validation via the [OAuth 2.0 token introspection](https://tools.ietf.org/html/rfc7662) protocol, e.g. [Duende.AspNetCore.Authentication.OAuth2Introspection](/introspection/): Program.cs ```csharp builder.Services.AddAuthentication("token") .AddOAuth2Introspection("token", options => { options.Authority = Constants.Authority; // this maps to the API resource name and secret options.ClientId = "resource1"; options.ClientSecret = "secret"; }); ``` ## Supporting Both JWTs And Reference Tokens [Section titled “Supporting Both JWTs And Reference Tokens”](#supporting-both-jwts-and-reference-tokens) It is not uncommon to use the same API with both JWTs and reference tokens. In this case you set up two authentication handlers, make one the default handler and provide some forwarding logic, e.g.: Program.cs ```csharp builder.Services.AddAuthentication("token") // JWT tokens .AddJwtBearer("token", options => { options.Authority = Constants.Authority; options.Audience = "resource1"; options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; // if token does not contain a dot, it is a reference token options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection"); }) // reference tokens .AddOAuth2Introspection("introspection", options => { options.Authority = Constants.Authority; options.ClientId = "resource1"; options.ClientSecret = "secret"; }); ``` The logic of the forward selector looks like this: IntrospectionUtilities.cs ```csharp /// /// Provides a forwarding func for JWT vs reference tokens (based on existence of dot in token) /// /// Scheme name of the introspection handler /// public static Func ForwardReferenceToken(string introspectionScheme = "introspection") { string Select(HttpContext context) { var (scheme, credential) = GetSchemeAndCredential(context); if (scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase) && !credential.Contains(".")) { return introspectionScheme; } return null; } return Select; } /// /// Extracts scheme and credential from Authorization header (if present) /// /// /// public static (string, string) GetSchemeAndCredential(HttpContext context) { var header = context.Request.Headers["Authorization"].FirstOrDefault(); if (string.IsNullOrEmpty(header)) { return ("", ""); } var parts = header.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) { return ("", ""); } return (parts[0], parts[1]); } ``` ----- # ASP.NET Identity Integration > Guide to integrating ASP.NET Identity with IdentityServer for user management, including setup instructions and configuration options An ASP.NET Identity-based implementation is provided for managing the identity database for users of IdentityServer. This implementation implements the extensibility points in IdentityServer needed to load identity data for your users to emit claims into tokens. To use the ASP.NET Identity-based implementation, ensure that you have the NuGet package for the ASP.NET Identity integration. It is called `Duende.IdentityServer.AspNetIdentity`: Terminal ```bash dotnet add package Duende.IdentityServer.AspNetIdentity ``` Next, configure ASP.NET Identity normally in your IdentityServer host with the standard calls to `AddIdentity` and any other related configuration. Then in your `Program.cs`, use the `AddAspNetIdentity` extension method after the call to `AddIdentityServer`: Program.cs ```csharp builder.Services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); builder.Services.AddIdentityServer() .AddAspNetIdentity(); ``` `AddAspNetIdentity` requires as a generic parameter the class that models your user for ASP.NET Identity (and the same one passed to `AddIdentity` to configure ASP.NET Identity). This configures IdentityServer to use the ASP.NET Identity implementations of [IUserClaimsPrincipalFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.iuserclaimsprincipalfactory-1) to convert the user data into claims, `IResourceOwnerPasswordValidator` to support the [password grant type](/identityserver/tokens/password-grant/), and `IProfileService`, which uses the `IUserClaimsPrincipalFactory` to add [claims](/identityserver/fundamentals/claims/) to tokens. It also configures some of ASP.NET Identity’s options for use with IdentityServer (such as claim types to use and authentication cookie settings). If you need to use your own implementation of `IUserClaimsPrincipalFactory`, then that is supported. Our implementation of the `IUserClaimsPrincipalFactory` will use the decorator pattern to encapsulate yours. For this to work correctly, ensure that your implementation is registered in the ASP.NET Core service provider before calling the IdentityServer `AddAspNetIdentity` extension method. The `IUserProfileService` interface has two methods that IdentityServer uses to interact with the user store. The profile service added for ASP.NET Identity implements `GetProfileDataAsync` by invoking the `IUserClaimsPrincipalFactory` implementation registered in the dependency injection container. The other method on `IProfileService` is `IsActiveAsync`, which is used in various places in IdentityServer to validate that the user is ( still) active. There is no built-in concept in ASP.NET Identity to inactive users, so our implementation is hard-coded to return `true`. If you extend the ASP.NET Identity user with enabled/disabled functionality, you should derive from our `ProfileService` and override `IsUserActiveAsync(TUser user)` to check your custom enabled/disabled flags. ## Template [Section titled “Template”](#template) You can use the `duende-is-aspid` [template](/identityserver/overview/packaging/#templates) to create a starter IdentityServer host project configured to use ASP.NET Identity. See the [Quickstart Documentation](/identityserver/quickstarts/5-aspnetid/) for a detailed walkthrough. ## User Management Pages [Section titled “User Management Pages”](#user-management-pages) The IdentityServer templates only include pages necessary for the authentication flow (login, logout, consent, error). User management pages, such as forgot password, password reset, or two-factor authentication setup, are not part of the IdentityServer templates because they are specific to your user store implementation. Since ASP.NET Core Identity provides built-in support for these features, you can add them to your IdentityServer host by [scaffolding Identity into your project](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity). This gives you ready-made pages for password reset, email confirmation, two-factor authentication, and more — all integrated with the ASP.NET Core Identity user store you’ve already configured. ----- # Authentication Schemes and Cookies > Understanding the authentication schemes and cookies used by Duende IdentityServer, especially when integrated with ASP.NET Identity. Authentication in ASP.NET Core is organized into [authentication schemes](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/#authentication-scheme). A scheme is a name that corresponds to an authentication handler and its configuration options. IdentityServer relies on several specific schemes for different purposes, and understanding them is crucial, especially when integrating with ASP.NET Identity. ## Cookie Schemes [Section titled “Cookie Schemes”](#cookie-schemes) When a user logs in, their identity is established and persisted across requests using a cookie. IdentityServer uses a primary authentication cookie to track the user’s session. ### Standalone IdentityServer [Section titled “Standalone IdentityServer”](#standalone-identityserver) When using IdentityServer without ASP.NET Identity, the default cookie scheme is named `"idsrv"`, though we recommend using the constant `IdentityServerConstants.DefaultCookieAuthenticationScheme` in your code if you ever need it. The default cookie scheme is configured by default in `AddIdentityServer()`, which sets up the cookie authentication handler with this scheme name. This cookie is essential for: * maintaining the user’s authenticated session * supporting single sign-on (SSO) * managing sign-out ### With ASP.NET Identity [Section titled “With ASP.NET Identity”](#with-aspnet-identity) When you integrate ASP.NET Identity, for example using `AddAspNetIdentity()`, the configuration changes to align with ASP.NET Identity’s defaults. In this scenario, the main authentication cookie scheme is not `"idsrv"`. Instead, it uses the ASP.NET Identity default scheme name: `"Identity.Application"` (or the `IdentityConstants.ApplicationScheme` constant). This is a common point of confusion. ASP.NET Identity registers its own cookie handlers, and `AddAspNetIdentity` configures IdentityServer to use them. This means: 1. **Login UI:** When you call `HttpContext.SignInAsync`, you must use the correct scheme. If you use the `SignInManager` provided by ASP.NET Identity, it automatically uses `"Identity.Application"`. 2. **Configuration:** If you need to configure cookie options (like expiration or sliding expiration), you must configure the options for `"Identity.Application"`, not `"idsrv"`. Program.cs ```csharp services.ConfigureApplicationCookie(options => { // The default ("Identity.Application") options.Cookie.Name = IdentityConstants.ApplicationScheme; // Configure other options here... options.ExpireTimeSpan = TimeSpan.FromHours(1); options.SlidingExpiration = true; }); ``` ## Other Important Schemes [Section titled “Other Important Schemes”](#other-important-schemes) Besides the main application cookie, IdentityServer uses other schemes for specific features. ### External Authentication (e.g., Google, OIDC) [Section titled “External Authentication (e.g., Google, OIDC)”](#external-authentication-eg-google-oidc) When a user signs in with an external provider (like Google or another OIDC provider), the result of that remote authentication is temporarily stored in an “external” cookie. This allows your login logic to read the claims from the external provider before fully signing the user into your main local session. IdentityServer always uses the `"idsrv.external"` scheme here, available in the `IdentityServerConstants.ExternalCookieAuthenticationScheme` constant. ### Check Session Cookie [Section titled “Check Session Cookie”](#check-session-cookie) IdentityServer session management requires a separate cookie to monitor the session state without sending the large authentication cookie. The [User Session Service](/identityserver/reference/v8/services/user-session-service/) manages this cookie. * **Default Name:** `"idsrv.session"` (Constant: `IdentityServerConstants.DefaultCheckSessionCookieName`). Note this cookie is not marked as `HttpOnly`, so it can be accessed in client-side code. The JavaScript code that is required to check user sessions in the background also requires access to this cookie, and needs it to be `HttpOnly`. ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) * **Mixing Schemes:** Attempting to `SignOutAsync("idsrv")` when ASP.NET Identity is in use will have no effect on the actual `"Identity.Application"` cookie, leaving the user logged in. Always use the constants or the helper services (like `SignInManager`) that match your configuration. * **Cookie Configuration:** Setting options on the default authentication scheme (which might differ from the effective cookie scheme) or configuring the wrong named options instance will result in settings (like `Cookie.SameSite` or `ExpireTimeSpan`) being ignored. ----- # Configuration API > Documentation for the Configuration API endpoints that enable management and configuration of IdentityServer implementations The Configuration API is a collection of endpoints that allow for management and configuration of an IdentityServer implementation. The Configuration API can be hosted either separately or within the IdentityServer implementation, and is distributed through the separate [Duende.IdentityServer.Configuration NuGet package](https://www.nuget.org/packages/Duende.IdentityServer.Configuration). Currently, the Configuration API supports the [Dynamic Client Registration](/identityserver/configuration/dcr/) protocol. The Configuration API source code is available [on GitHub](https://github.com/DuendeSoftware/products/tree/main/identity-server/src/Configuration). Samples of the Configuration API are available [here](/identityserver/samples/configuration/). ----- # Dynamic Client Registration (DCR) > Learn how to configure and use Dynamic Client Registration (DCR) to automatically register OAuth clients with IdentityServer Dynamic Client Registration (DCR) is the process of registering OAuth clients dynamically. It allows OAuth client applications to programmatically register themselves with an authorization server at runtime, rather than requiring manual configuration. The client provides information about itself and specifies its desired configuration in an HTTP request to the configuration endpoint. If the request is authorized and valid, the endpoint will then create the necessary client configuration and return an HTTP response describing the new client. DCR eliminates the need for a manual registration process, making it more efficient and less time-consuming to register new clients. It can help automate the onboarding of new applications in large-scale OAuth ecosystems, such as microservices, mobile apps, and partner APIs. ## Installation And Hosting [Section titled “Installation And Hosting”](#installation-and-hosting) DCR in Duende IdentityServer is provided as a separate NuGet package, [`Duende.IdentityServer.Configuration`](https://www.nuget.org/packages/Duende.IdentityServer.Configuration), which contains the Configuration API and endpoints required to support DCR. The Configuration API can be installed in a separate host from IdentityServer, or in the same host. In many cases, it is desirable to host the configuration API and IdentityServer separately. This facilitates the ability to restrict access to the configuration API at the network level separately from IdentityServer and keeps IdentityServer’s access to the configuration data read-only. In other cases, you may find that hosting the two systems together better fits your needs. ### Separate Host For Configuration API [Section titled “Separate Host For Configuration API”](#separate-host-for-configuration-api) To host the Configuration API separately from IdentityServer, you will need to create a new ASP.NET Core Web application which will host the Configuration API. 1. **Create a new project of type “Empty Web Application”** Terminal ```bash dotnet new web -n Configuration ``` 2. **Add the `Duende.IdentityServer.Configuration` package** Terminal ```bash cd Configuration dotnet add package Duende.IdentityServer.Configuration ``` 3. **Configure services to include the Configuration API** Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = ""; ); ``` 4. **Add and configure the client configuration store** The Configuration API uses the `IClientConfigurationStore` abstraction to persist new clients to the configuration store. Your Configuration API host needs an implementation of this interface. You can either use the Entity Framework Core-based implementation, or implement the interface yourself. See [the IClientConfigurationStore reference](/identityserver/reference/v8/stores/) for more details. If you wish to use the built-in implementation, install its NuGet package and add it to the ASP.NET Core service provider. Terminal ```bash dotnet add package Duende.IdentityServer.Configuration.EntityFramework ``` The `AddClientConfigurationStore()` extension method registers the built-in implementation of the `IClientConfigurationStore` interface with the service provider. Make sure to also configure the connection string to the [configuration store](/identityserver/data/ef/#configuration-store-support): Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = "" ).AddClientConfigurationStore(); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddConfigurationDbContext(options => { options.ConfigureDbContext = builder => builder.UseSqlite(connectionString); }); ``` 5. **Map the Configuration API endpoints** Program.cs ```csharp app.MapDynamicClientRegistration() .RequireAuthorization("DCR"); ``` The `MapDynamicClientRegistration` extension method registers the DCR endpoints and returns an `IEndpointConventionBuilder` which you can use to define authorization requirements for your DCR endpoint. See [Authorization](#authorization) for more details about implementing authorization for the DCR endpoint. ### Shared Host For Configuration API and IdentityServer [Section titled “Shared Host For Configuration API and IdentityServer”](#shared-host-for-configuration-api-and-identityserver) The Configuration API can be hosted by your Duende IdentityServer host. You’ll need to add the Configuration API’s services to the service collection, and configure the store implementation. 1. **Add the `Duende.IdentityServer.Configuration` package** Terminal ```bash cd Configuration dotnet add package Duende.IdentityServer.Configuration ``` 2. **Configure services to include the Configuration API** Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = ""; ); ``` 3. **Add and configure the client configuration store** The Configuration API uses the `IClientConfigurationStore` abstraction to persist new clients to the configuration store. Your Configuration API host needs an implementation of this interface. You can either use the Entity Framework Core-based implementation, or implement the interface yourself. See [the IClientConfigurationStore reference](/identityserver/reference/v8/stores/) for more details. If you wish to use the built-in implementation, install its NuGet package and add it to the ASP.NET Core service provider. Terminal ```bash dotnet add package Duende.IdentityServer.Configuration.EntityFramework ``` The `AddClientConfigurationStore()` extension method registers the built-in implementation of the `IClientConfigurationStore` interface with the service provider. Make sure to also configure the connection string to the [configuration store](/identityserver/data/ef/#configuration-store-support) if you haven’t already as part of your IdentityServer host: Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = "" ).AddClientConfigurationStore(); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddConfigurationDbContext(options => { options.ConfigureDbContext = builder => builder.UseSqlite(connectionString); }); ``` 4. **Map the Configuration API endpoints** Program.cs ```csharp app.MapDynamicClientRegistration() .RequireAuthorization("DCR"); ``` The `MapDynamicClientRegistration` extension method registers the DCR endpoints and returns an `IEndpointConventionBuilder` which you can use to define authorization requirements for your DCR endpoint. See [Authorization](#authorization) for more details about implementing authorization for the DCR endpoint. ### Adding the Registration Endpoint to the Discovery Document [Section titled “Adding the Registration Endpoint to the Discovery Document”](#adding-the-registration-endpoint-to-the-discovery-document) By default, the Dynamic Client Registration (DCR) endpoint is not included in the [discovery document](/identityserver/reference/v8/endpoints/discovery/) of Duende IdentityServer. To include it, change the Discovery Document options when registering IdentityServer in the service collection: Program.cs ```csharp builder.Services.AddIdentityServer(options => { // Either use a static URL for the registration endpoint, when hosted outside of IdentityServer: options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Static; options.Discovery.DynamicClientRegistration.StaticRegistrationEndpoint = new Uri("https://my-configuration-api/connect/dcr"); // Or use inferred when the registration endpoint is hosted within IdentityServer: options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Inferred; }); ``` Cross-host Registration Endpoint When using `RegistrationEndpointMode.Static` with a registration endpoint on a different host than the authority, clients that consume the discovery document may reject the cross-host endpoint by default due to endpoint validation in their discovery policy. This applies to [Duende IdentityModel](/identitymodel/endpoints/discovery/#cross-host-endpoints), and may also apply to other client libraries that validate discovery document endpoints. You will need to configure the discovery policy on any downstream service that fetches the discovery document. ## Authorization [Section titled “Authorization”](#authorization) When implementing Dynamic Client Registration (DCR), it is important to consider authentication and authorization for the Configuration API endpoint. While not strictly required, it is recommended that you implement some form of authentication and authorization for the DCR endpoint. You don’t want anyone with access to the DCR endpoint to be able to register clients! The specifications that define DCR allow both open registration, where authentication and authorization are absent and all client software can register with the authorization server, and protected registration, where an initial access token is required to register. The Configuration API creates standard ASP.NET endpoints that can be protected through traditional ASP.NET authorization. Alternatively, the Dynamic Client Registration `software_statement` parameter can be used to authenticate requests. ### Traditional ASP.NET Authorization [Section titled “Traditional ASP.NET Authorization”](#traditional-aspnet-authorization) You can authorize access to the Configuration API Endpoints using [authorization policies](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies), just like any other endpoint created in an ASP.NET Web application. That authorization policy can use any criteria that an authorization policy might enforce, such as checking for particular claims or scopes. One possibility is to authenticate the provisioning system, that is, the system making the DCR call, using OAuth. The resulting access token could include a scope that grants access to the Configuration API. For example, you might protect the Configuration APIs with a JWT-bearer authentication scheme and an authorization policy that requires a particular scope to be present in the JWTs. You could choose any name for the scope that gives access to the Configuration APIs. Let’s use the name `IdentityServer.Configuration` for this example. You would then define the `IdentityServer.Configuration` scope as an [ApiScope](/identityserver/reference/v8/models/api-scope/) in your IdentityServer and allow the appropriate clients to access it. An automated process running in a CI pipeline could be configured as an OAuth client that uses the client credentials flow and is allowed to request the `IdentityServer.Configuration` scope. It could obtain a token using its client id and secret and then present that token when it calls the Configuration API. You might also have an interactive web application with a user interface that makes calls to the Configuration API. Again, you would define the application as an OAuth client allowed to request the appropriate scope, but this time, you’d use the authorization code flow. ### Software Statement [Section titled “Software Statement”](#software-statement) The metadata within requests to the Configuration API can be bundled together into a JWT and sent in the [`software_statement` parameter](https://datatracker.ietf.org/doc/html/rfc7591#section-2.3). If you can establish a trust relationship between the Configuration API and the issuer of the software statement, then that can be used to decide if you want to accept registration requests. To use a software statement in this way, you would need to design the specific semantics of your software statements. How you will issue them, how you will create the necessary trust relationship between the issuer and your Configuration API, and how the Configuration API will validate the software statements are all aspects to consider. The configuration API doesn’t make any assumptions about the software statement design. By default, it does nothing with the `software_statement` parameter. To make use of software statements, customize the `DynamicClientRegistrationValidator.ValidateSoftwareStatementAsync` extension point and add your validation logic. ## Calling The Registration Endpoint [Section titled “Calling The Registration Endpoint”](#calling-the-registration-endpoint) The registration endpoint is invoked by making an HTTP POST request to the `/connect/dcr` endpoint with a JSON payload containing metadata describing the desired client as described in [RFC 7591](https://datatracker.ietf.org/doc/rfc7591/) and [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html). The supported metadata properties are listed in the reference section on the [`DynamicClientRegistrationRequest` model](/identityserver/reference/v8/dcr/models/#dynamicclientregistrationrequest). A mixture of standardized and IdentityServer-specific properties are supported. Most standardized properties that are applicable to the client credentials or code flow grants are supported. Where IdentityServer’s configuration model includes important properties that are not standardized, we have included those properties as extensions. For example, there are no standardized properties describing token lifetimes, so the dynamic client registration endpoint adds `absolute_refresh_token_lifetime`, `access_token_lifetime`, `identity_token_lifetime`, etc. ## Customization [Section titled “Customization”](#customization) The behavior of the Configuration API can be customized through the use of several extension points that control the steps that occur when a dynamic client registration request arrives. First, the incoming request is validated to ensure that it is syntactically valid and semantically correct. The result of the validation process is a model which will either contain error details or a validated `Client` model. When validation succeeds, the validated request is passed on to the request processor. The request processor is responsible for generating properties of the `Client` that are not specified in the request. For example, the `client_id` is not normally specified in the request and is instead generated by the processor. When the processor is finished generating values, it passes the final client object to the store and returns an `IDynamicClientRegistrationResponse` indicating success or failure. This response object is finally used by the response generator to generate an HTTP response. Each of the validation and processing steps might also encounter an error. When that occurs, errors are conveyed using the `DynamicClientRegistrationError` class. ### Validation [Section titled “Validation”](#validation) To customize the validation process, you can implement the `IDynamicClientRegistrationValidator` interface, or extend the default implementation, `DynamicClientRegistrationValidator`. The default implementation includes many virtual methods, allowing you to use most of the base functionality and add your customization in a targeted manner. Each virtual method is responsible for validating a small number of parameters in the request and setting corresponding values on the client. A context object is passed to each virtual method. It contains the client object that is being built up, the original request, the claims principal that made the request, and a dictionary of additional items that can be used to pass state between customized steps. Each step should update the client in the context and return an `IStepResult` to indicate success or failure. For more details, see the [reference section on DCR validation](/identityserver/reference/v8/dcr/validation/). ### Processing [Section titled “Processing”](#processing) The request processor can be customized by implementing the `IDynamicClientRegistrationRequestProcessor` interface, or by extending the default `DynamicClientRegistrationRequestProcessor`. The default request processor contains virtual methods that allow you to override (part of) its functionality. For more details, see the [reference section on DCR request processing](/identityserver/reference/v8/dcr/processing/). ### Response Generation [Section titled “Response Generation”](#response-generation) To customize the HTTP responses of the Configuration API, you can implement the `IDynamicClientRegistrationResponseGenerator` interface, or extend the default `DynamicClientRegistrationResponseGenerator`. For more details, see the [reference section on DCR response generation](/identityserver/reference/v8/dcr/response/). ----- # Data Stores and Persistence > Overview of IdentityServer data stores types, including configuration and operational data, and their implementation options Duende IdentityServer is backed by two kinds of data: * [Configuration Data](/identityserver/data/configuration/) * [Operational Data](/identityserver/data/operational/) Data access is abstracted by store interfaces that are registered in the ASP.NET Core service provider. These store interfaces allow IdentityServer to access the data it needs at runtime when processing requests. You can implement these interfaces yourself and thus can use any database you wish. If you prefer a relational database for this data, then we provide [EntityFramework Core](/identityserver/data/ef/) implementations. ----- # Configuration Data > Documentation about configuration data models and stores in Duende IdentityServer, including client, resource, and identity provider stores Configuration data models the information for [Clients](/identityserver/fundamentals/clients/) and [Resources](/identityserver/fundamentals/resources). ## Stores [Section titled “Stores”](#stores) Store interfaces are designed to abstract accessing the configuration data. The stores used in Duende IdentityServer are: * [Client store](/identityserver/reference/v8/stores/client-store/) for `Client` data. * [CORS policy service](/identityserver/reference/v8/stores/cors-policy-service/) for [CORS support](/identityserver/tokens/cors/). Given that this is so closely tied to the `Client` configuration data, the CORS policy service is considered one of the configuration stores. * [Resource store](/identityserver/reference/v8/stores/resource-store/) for `IdentityResource`, `ApiResource`, and `ApiScope` data. * [Identity Provider store](/identityserver/reference/v8/stores/idp-store/) for `IdentityProvider` data. ## Registering Custom Stores [Section titled “Registering Custom Stores”](#registering-custom-stores) Custom implementations of the stores must be registered in the ASP.NET Core service provider. There are [convenience methods](/identityserver/reference/v8/di/#configuration-stores) for registering these. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddIdentityProviderStore(); ``` ## Caching Configuration Data [Section titled “Caching Configuration Data”](#caching-configuration-data) Configuration data is used frequently during request processing. If this data is loaded from a database or other external store, then it might be expensive to frequently re-load the same data. * v8.0+ Duende IdentityServer provides [convenience methods](/identityserver/reference/v8/di#caching-configuration-data) to enable caching data from the various stores. The caching implementation is built on Microsoft’s [`HybridCache`](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid) from the `Microsoft.Extensions.Caching.Hybrid` package, registered as a [keyed service](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#keyed-services) under `ServiceProviderKeys.ConfigurationStoreCache`. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache() .AddIdentityProviderStoreCache(); ``` For Entity Framework users, there is a convenience method `AddConfigurationStoreCache()` that enables caching for all configuration stores at once: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConfigurationStore(...) .AddConfigurationStoreCache(); ``` The duration of the data in the default cache is configurable on the [`IdentityServerOptions`](/identityserver/reference/v8/options#caching). For example: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(5); options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(5); }) .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache(); ``` Further customization of the cache is possible: * The caching stores use a keyed `HybridCache` instance registered under `ServiceProviderKeys.ConfigurationStoreCache`. You can customize the `HybridCache` behavior by configuring the keyed service registration (e.g., adding a distributed cache backend via `IDistributedCache`). * By default, only the L1 (in-memory) cache tier is used. To enable L2 (distributed) caching, register an `IDistributedCache` implementation (e.g., Redis via `AddStackExchangeRedisCache`). `HybridCache` will automatically use it as the L2 tier. * v7.0 Duende IdentityServer provides [convenience methods](/identityserver/reference/v8/di/#caching-configuration-data) to enable caching data from the various stores. The caching implementation relies upon an `ICache` service and must also be added to the ASP.NET Core service provider. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache() .AddIdentityProviderStoreCache(); ``` The duration of the data in the default cache is configurable on the [`IdentityServerOptions`](/identityserver/reference/v7/options#caching). For example: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(5); options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(5); }) .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache(); ``` Further customization of the cache is possible: * If you wish to customize the caching behavior for the specific configuration objects, you can replace the `ICache` service implementation in the dependency injection system. * The default implementation of the `ICache` itself relies upon the `IMemoryCache` interface (and `MemoryCache` implementation) provided by .NET. If you wish to customize the in-memory caching behavior, you can replace the `IMemoryCache` implementation in the dependency injection system. ## In-Memory Stores [Section titled “In-Memory Stores”](#in-memory-stores) The various [in-memory configuration APIs](/identityserver/reference/v8/di/#configuration-stores) allow for configuring IdentityServer from an in-memory list of the various configuration objects. These in-memory collections can be hard-coded in the hosting application, or could be loaded dynamically from a configuration file or a database. By design, though, these collections are only created when the hosting application is starting up. Use of these configuration APIs are designed for use when prototyping, developing, and/or testing where it is not necessary to dynamically consult database at runtime for the configuration data. This style of configuration might also be appropriate for production scenarios if the configuration rarely changes, or it is not inconvenient to require restarting the application if the value must be changed. ----- # Entity Framework Core Integration > Documentation for using Entity Framework with IdentityServer to store configuration and operational data in any EF-supported database An EntityFramework-based implementation is provided for the configuration and operational data extensibility points in IdentityServer. The use of EntityFramework allows any EF-supported database to be used with this library. The features provided by this library are broken down into two main areas: configuration store and operational store support. These two different areas can be used independently or together, based upon the needs of the hosting application. To use this library, ensure that you have the NuGet package for the EntityFramework integration. It is called `Duende.IdentityServer.EntityFramework`. You can install it with: ```plaintext dotnet add package Duende.IdentityServer.EntityFramework ``` ## Configuration Store Support [Section titled “Configuration Store Support”](#configuration-store-support) For storing [configuration data](/identityserver/configuration/), the configuration store can be used. This support provides implementations of the `IClientStore`, `IResourceStore`, `IIdentityProviderStore`, and the `ICorsPolicyService` extensibility points. These implementations use a `DbContext`-derived class called `ConfigurationDbContext` to model the tables in the database. To use the configuration store support, in Program.cs use the `AddConfigurationStore` extension method after the call to `AddIdentityServer`: Program.cs ```csharp const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; builder.Services.AddIdentityServer() // this adds the config data from DB (clients, resources, CORS) .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }); ``` To configure the configuration store, use the `ConfigurationStoreOptions` options object passed to the configuration callback. ### ConfigurationStoreOptions [Section titled “ConfigurationStoreOptions”](#configurationstoreoptions) This options class contains properties to control the configuration store and `ConfigurationDbContext`. `ConfigureDbContext` Delegate of type `Action` used as a callback to configure the underlying `ConfigurationDbContext`. The delegate can configure the `ConfigurationDbContext` in the same way if EF were being used directly with `AddDbContext`, which allows any EF-supported database to be used. `DefaultSchema` Allows setting the default database schema name for all the tables in the `ConfigurationDbContext` ```csharp options.DefaultSchema = "myConfigurationSchema"; ``` If you need to change the schema for the Migration History Table, you can chain another action to the `UseSqlServer`: ```csharp options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly) .MigrationsHistoryTable("MyConfigurationMigrationTable", "myConfigurationSchema")); ``` ### Enabling Caching For Configuration Store [Section titled “Enabling Caching For Configuration Store”](#enabling-caching-for-configuration-store) To enable caching for the EF configuration store implementation, use the `AddConfigurationStoreCache` extension method: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConfigurationStore(options => { // ... }) // this is something you will want in production to reduce load on and requests to the DB .AddConfigurationStoreCache(); ``` ## Operational Store [Section titled “Operational Store”](#operational-store) For storing [operational data](/identityserver/data/operational/) then the operational store can be used. This support provides implementations of the `IPersistedGrantStore`, `IDeviceFlowStore`, `IServerSideSessionStore`, and `ISigningKeyStore` extensibility points. The implementation uses a `DbContext`-derived class called `PersistedGrantDbContext` to model the table in the database. To use the operational store support, in Program.cs use the `AddOperationalStore` extension method after the call to `AddIdentityServer`: Program.cs ```csharp const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; builder.Services.AddIdentityServer() // this adds the operational data from DB (codes, tokens, consents) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); // this enables automatic token cleanup. this is optional. options.EnableTokenCleanup = true; options.TokenCleanupInterval = 3600; // interval in seconds (default is 3600) }); ``` To configure the operational store, use the `OperationalStoreOptions` options object passed to the configuration callback. ### OperationalStoreOptions [Section titled “OperationalStoreOptions”](#operationalstoreoptions) This options class contains properties to control the operational store and `PersistedGrantDbContext`. `ConfigureDbContext` Delegate of type `Action` used as a callback to configure the underlying `PersistedGrantDbContext`. The delegate can configure the `PersistedGrantDbContext` in the same way if EF were being used directly with `AddDbContext`, which allows any EF-supported database to be used. `DefaultSchema` Allows setting the default database schema name for all the tables in the `PersistedGrantDbContext`. `EnableTokenCleanup` Indicates whether expired grants and pushed authorization requests will be automatically cleaned up from the database. The default is `false`. `RemoveConsumedTokens` added >=5.1 Indicates whether consumed grants will be automatically cleaned up from the database. The default is `false`. `TokenCleanupInterval` The token cleanup interval (in seconds). The default is 3600 (1 hour). `ConsumedTokenCleanupDelay` added >=6.3 The consumed token cleanup delay (in seconds). The default is 0. This delay is the amount of time that must elapse before tokens marked as consumed can be deleted. Note that only refresh tokens with OneTime usage can be marked as consumed. `FuzzTokenCleanupStart` added >=7.0 The background token cleanup job runs at a configured interval. If multiple nodes run the cleanup job at the same time, update conflicts might occur in the store. To reduce the probability of that happening, the startup time can be fuzzed. When enabled, the first run is scheduled at a random time between the host startup and the configured TokenCleanupInterval. Subsequent runs are run on the configured TokenCleanupInterval. Defaults to `true`. ## Database Creation And Schema Changes Across Different IdentityServer Versions [Section titled “Database Creation And Schema Changes Across Different IdentityServer Versions”](#database-creation-and-schema-changes-across-different-identityserver-versions) It is very likely that across different versions of IdentityServer (and the EF support) that the database schema will change to accommodate new and changing features. We do not provide any support for creating your database or migrating your data from one version to another. You are expected to manage the database creation, schema changes, and data migration in any way your organization sees fit. Using EF migrations is one possible approach to this. If you do wish to use migrations, then see the [EF quickstart](/identityserver/quickstarts/4-entity-framework/) for samples on how to get started, or consult the Microsoft [documentation on EF migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/index). We publish a [sample app](https://github.com/DuendeSoftware/products/tree/main/identity-server/migrations/IdentityServerDb) that we use internally for creating databases to test the latest database schema (this is SQL Server specific). ----- # Operational Data > Documentation for managing dynamic operational data in IdentityServer including grants, keys, and server-side sessions For certain operations, IdentityServer needs a persistence store to keep dynamically created state. This data is collectively called *operational data*, and includes: * [Grants](#grants) for authorization and device codes, reference and refresh tokens, and remembered user consent * [Keys](#keys) managing dynamically created signing keys * [Server Side Sessions](#server-side-sessions) for storing authentication session data for interactive users server-side ## Grants [Section titled “Grants”](#grants) Many protocol flows produce state that represents a grant of one type or another. These include authorization and device codes, reference and refresh tokens, and remembered user consent. ### Stores [Section titled “Stores”](#stores) The persistence for grants is abstracted behind two interfaces: * The [persisted grant store](/identityserver/reference/v8/stores/persisted-grant-store/) is a common store for most grants. * The [device flow store](/identityserver/reference/v8/stores/device-flow-store/) is a specialized store for device grants. ### Registering Custom Stores [Section titled “Registering Custom Stores”](#registering-custom-stores) Custom implementations of `IPersistedGrantStore`, and/or `IDeviceFlowStore` must be registered in the ASP.NET Core service provider. For example: Program.cs ```csharp builder.Services.AddIdentityServer(); builder.Services.AddTransient(); builder.Services.AddTransient(); ``` ### Grant Expiration and Consumption [Section titled “Grant Expiration and Consumption”](#grant-expiration-and-consumption) The presence of the record in the store without a `ConsumedTime` and while still within the `Expiration` represents the validity of the grant. Setting either of these two values, or removing the record from the store effectively revokes the grant. Some grant types are one-time use only (either by definition or configuration). Once they are “used”, rather than deleting the record, the `ConsumedTime` value is set in the database marking them as having been used. This “soft delete” allows for custom implementations to either have flexibility in allowing a grant to be re-used (typically within a short window of time), or to be used in risk assessment and threat mitigation scenarios (where suspicious activity is detected) to revoke access. For refresh tokens, this sort of custom logic would be performed in the [IRefreshTokenService](/identityserver/reference/v8/services/refresh-token-service/). ### Grant Data [Section titled “Grant Data”](#grant-data) The `Data` property of the model contains the authoritative copy of the values in the store. This data is protected at rest using the ASP.NET Data Protection API. Except for `ConsumedTime`, the other properties of the model should be treated as read-only. ### Persisted Grant Service [Section titled “Persisted Grant Service”](#persisted-grant-service) Working with the grants store directly might be too low level. As such, a higher level service called the [IPersistedGrantService](/identityserver/reference/v8/services/persisted-grant-service/) is provided. It abstracts and aggregates the different grant types into one concept, and allows querying and revoking the persisted grants for a user. ## Keys [Section titled “Keys”](#keys) The [automatic key management](/identityserver/fundamentals/key-management/#automatic-key-management) feature in Duende IdentityServer requires a store to persist keys that are dynamically created. ### Signing Key Store [Section titled “Signing Key Store”](#signing-key-store) By default, the file system is used, but the storage of these keys is abstracted behind an extensible store interface. The [ISigningKeyStore](/identityserver/reference/v8/stores/signing-key-store/) is that storage interface. ### Registering a custom signing key store [Section titled “Registering a custom signing key store”](#registering-a-custom-signing-key-store) To register a custom signing key store in the ASP.NET Core service provider, there is a `AddSigningKeyStore` helper on the `IIdentityServerBuilder`. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddSigningKeyStore(); ``` ### Key Lifecycle [Section titled “Key Lifecycle”](#key-lifecycle) When keys are required, `LoadKeysAsync` will be called to load them all from the store. They are then cached automatically for some amount of time based on [configuration](/identityserver/reference/v8/options/#key-management). Periodically a new key will be created, and `StoreKeyAsync` will be used to persist the new key. Once a key is past its retirement, `DeleteKeyAsync` will be used to purge the key from the store. ### Serialized Key [Section titled “Serialized Key”](#serialized-key) The [SerializedKey](/identityserver/reference/v8/stores/signing-key-store/#serializedkey) is the model that contains the key data to persist. It is expected that the `Id` is the unique identifier for the key in the store. The `Data` property is the main payload of the key and contains a copy of all the other values. Some of the properties affect how the `Data` is processed (e.g. `DataProtected`), and the other properties are considered read-only and thus can’t be changed to affect the behavior (e.g. changing the `Created` value will not affect the key lifetime, nor will changing `Algorithm` change which signing algorithm the key is used for). ## Server Side Sessions [Section titled “Server Side Sessions”](#server-side-sessions) The [server-side sessions](/identityserver/ui/server-side-sessions/) feature in Duende IdentityServer requires a store to persist a user’s session data. ### Server-Side Session Store [Section titled “Server-Side Session Store”](#server-side-session-store) The [IServerSideSessionStore](/identityserver/reference/v8/stores/server-side-sessions/) abstracts storing the server-side session data. [ServerSideSession](/identityserver/reference/v8/stores/server-side-sessions/#serversidesession) objects act as the storage entity, and provide several properties used as metadata for the session. The `Ticket` property contains the actual serialized data used by the ASP.NET Cookie Authentication handler. By default, this serialized data is stored in an encrypted state using ASP.NET Core Data Protection. The methods on the [IServerSideSessionStore](/identityserver/reference/v8/stores/server-side-sessions/) are used to orchestrate the various management functions needed by the [server-side sessions](/identityserver/ui/server-side-sessions/#session-management) feature. ### Registering a custom store [Section titled “Registering a custom store”](#registering-a-custom-store) To register a custom server-side session store in the ASP.NET Core service provider, there is a `AddServerSideSessionStore` helper on the `IIdentityServerBuilder`. It is still necessary to call `AddServerSideSessions` to enable the server-side session feature. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddServerSideSessions() .AddServerSideSessionStore(); ``` There is also an overloaded version of a `AddServerSideSessions` that will perform both registration steps in one call. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddServerSideSessions(); ``` ### EntityFramework Store Implementation [Section titled “EntityFramework Store Implementation”](#entityframework-store-implementation) An EntityFramework Core implementation of the server-side session store is included in the [Entity Framework Integration](/identityserver/data/ef/#operational-store) operational store. When using the EntityFramework Core operational store, it will be necessary to indicate that server-side sessions need to be used with the call to the `AddServerSideSessions` fluent API. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddServerSideSessions() .AddOperationalStore(options => { // ... }); ``` ----- # IdentityServer Deployment > Comprehensive guide covering key aspects of deploying IdentityServer including proxy configuration, data protection, data stores, caching, and health monitoring. Because IdentityServer is made up of middleware and services that you use within an ASP.NET Core application, it can be hosted and deployed with the same diversity of technology as any other ASP.NET Core application. You have the choice about * where to host your IdentityServer (on-prem or in the cloud, and if in the cloud, which one?) * which web server to use (IIS, Kestrel, Nginx, Apache, etc.) * how you’ll scale and load-balance the deployment * what kind of deployment artifacts you’ll publish (files in a folder, containers, etc.) * how you’ll manage the environment (a managed app service in the cloud, a Kubernetes cluster, etc.) While this is a lot of decisions to make, this also means that your IdentityServer implementation can be built, deployed, hosted, and managed with the same technology that you’re using for any other ASP.NET applications that you have. Microsoft publishes extensive [advice and documentation](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) about deploying ASP.NET Core applications, and it is applicable to IdentityServer implementations. We’re not attempting to replace that documentation - or the documentation for other tools that you might be using in your environment. Rather, this section of our documentation focuses on IdentityServer-specific deployment and hosting considerations. ## Proxy Servers and Load Balancers [Section titled “Proxy Servers and Load Balancers”](#proxy-servers-and-load-balancers) In typical deployments, your IdentityServer will be hosted behind a load balancer or reverse proxy. These and other network appliances often obscure information about the request before it reaches the host. Some of the behavior of IdentityServer and the ASP.NET authentication handlers depend on that information, most notably the scheme (HTTP vs HTTPS) of the request and the originating client IP address. Requests to your IdentityServer that come through a proxy will appear to come from that proxy instead of its true source on the Internet or corporate network. If the proxy performs TLS termination (that is, HTTPS requests are proxied over HTTP), the original HTTPS scheme will also no longer be present in the proxied request. Then, when the IdentityServer middleware and the ASP.NET authentication middleware process these requests, they will have incorrect values for the scheme and originating IP address. Common symptoms of this problem are * HTTPS requests get downgraded to HTTP * HTTP issuer is being published instead of HTTPS in `.well-known/openid-configuration` * Host names are incorrect in the discovery document or on redirect * Cookies are not sent with the secure attribute, which can especially cause problems with the samesite cookie attribute. In almost all cases, these problems can be solved by adding the ASP.NET `ForwardedHeaders` middleware to your pipeline. Most network infrastructure that proxies requests will set the [`X-Forwarded-For`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) and [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) HTTP headers to describe the original request’s IP address and scheme. The `ForwardedHeaders` middleware reads the information in these headers on incoming requests and makes it available to the rest of the ASP.NET pipeline by updating the [`HttpContext.HttpRequest`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/use-http-context?view=aspnetcore-7.0#httprequest). This transformation should be done early in the pipeline, certainly before the IdentityServer middleware and ASP.NET authentication middleware process requests, so that the presence of a proxy is abstracted away first. The appropriate configuration for the forwarded headers middleware depends on your environment. In general, you need to configure which headers it should respect, the IP address or IP address range of your proxy, and the number of proxies you expect (when there are multiple proxies, each one is captured in the `X-Forwarded-*` headers). There are two ways to configure this middleware: 1. Enable the environment variable `ASPNETCORE_FORWARDEDHEADERS_ENABLED`. This is the simplest option, but doesn’t give you as much control. It automatically adds the forwarded headers middleware to the pipeline, and configures it to accept forwarded headers from any single proxy, respecting the `X-Forwarded-For` and `X-Forwarded-Proto` headers. This is often the right choice for cloud hosted environments and Kubernetes clusters. 2. Configure the `ForwardedHeadersOptions` in DI, and use the `ForwardedHeaders` middleware explicitly in your pipeline. The advantage of configuring the middleware explicitly is that you can configure it in a way that is appropriate for your environment, if the defaults used by `ASPNETCORE_FORWARDEDHEADERS_ENABLED` are not what you need. Most notably, you can use the `KnownNetworks` or `KnownProxies` options to only accept headers sent by a known proxy, and you can set the `ForwardLimit` to allow for multiple proxies in front of your IdentityServer. This is often the right choice when you have more complex proxying going on, or if your proxy has a stable IP address. By default, `KnownNetworks` and `KnownProxies` support localhost with values of `127.0.0.1/8` and `::1` respectively. This is useful (and secure!) for local development environments and for solutions where the reverse proxy and the .NET web host runs on the same machine. In production environments when operating behind a proxy, you’ll need to configure the `ForwardedHeadersOptions`. Be sure to correctly set values for `KnownNetworks` and `KnownProxies` for your environments, as otherwise requests may be blocked. ```csharp builder.Services.Configure(options => { // you may need to change these ForwardedHeaders // values based on your network architecture options.ForwardedHeaders = ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto; // exact Addresses of known proxies to accept forwarded headers from. options.KnownProxies.Add(IPAddress.Parse("203.0.113.42")); // <-- change this value to the IP Address of the proxy // if the proxies could use any address from a block, that can be configured too: // var network = new IPNetwork(IPAddress.Parse("198.51.100.0"), 24); // options.KnownNetworks.Add(network); // default is 1 options.ForwardLimit = 1; }); ``` Please consult the [Microsoft documentation on configuring ASP.NET Core to work with proxy servers and load balancers](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer) for more details. ## ASP.NET Core Data Protection [Section titled “ASP.NET Core Data Protection”](#aspnet-core-data-protection) Duende IdentityServer makes extensive use of ASP.NET’s [data protection](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/) feature. It is crucial that you configure data protection correctly before you start using your IdentityServer in production. The recommended practices for setting up and using ASP.NET Core Data Protection for Duende IdentityServer are the same as for other server-side products, like BFF. See the [general ASP.NET Core Data Protection page](/general/data-protection). ### ASP.NET Data Protection Keys and IdentityServer Signing Keys [Section titled “ASP.NET Data Protection Keys and IdentityServer Signing Keys”](#aspnet-data-protection-keys-and-identityserver-signing-keys) ASP.NET’s data protection keys are sometimes confused with IdentityServer’s signing keys, but the two are completely separate keys with different purposes. IdentityServer implementations need both to function correctly. #### ASP.NET Data Protection Keys [Section titled “ASP.NET Data Protection Keys”](#aspnet-data-protection-keys) Data protection is a cryptographic library that is part of ASP.NET Core. Data protection uses private key cryptography to encrypt and sign sensitive data to ensure that it is only written and read by the application. The framework uses data protection to secure data that is commonly used by IdentityServer implementations, such as authentication cookies and anti-forgery tokens. In addition, IdentityServer itself uses data protection to protect sensitive data at rest, such as persisted grants, and sensitive data passed through the browser, such as the context objects passed to pages in the UI. The data protection keys are critical secrets for an IdentityServer implementation because they encrypt a great deal of sensitive data at rest and prevent sensitive data that is round-tripped through the browser from being tampered with. #### The IdentityServer Signing Key [Section titled “The IdentityServer Signing Key”](#the-identityserver-signing-key) Separately, IdentityServer needs cryptographic keys, called [signing keys](/identityserver/fundamentals/key-management/), to sign tokens such as JWT access tokens and id tokens. The signing keys use public key cryptography to allow client applications and APIs to validate token signatures using the public keys, which are published by IdentityServer through [discovery](/identityserver/reference/v8/endpoints/discovery/). The private key component of the signing keys are also critical secrets for IdentityServer because a valid signature provides integrity and non-repudiation guarantees that allow client applications and APIs to trust those tokens. ### IdentityServer Data Stores [Section titled “IdentityServer Data Stores”](#identityserver-data-stores) IdentityServer itself is stateless and does not require server affinity - but there is data that needs to be shared between in multi-instance deployments. ### Configuration Data [Section titled “Configuration Data”](#configuration-data) This typically includes: * resources * clients * startup configuration, e.g. key material, external provider settings etc… The way you store that data depends on your environment. In situations where configuration data rarely changes we recommend using the in-memory stores and code or configuration files. In highly dynamic environments (e.g. Saas) we recommend using a database or configuration service to load configuration dynamically. ### Operational Data [Section titled “Operational Data”](#operational-data) For certain operations, IdentityServer needs a persistence store to keep state, this includes: * issuing authorization codes * issuing reference and refresh tokens * storing consent * automatic management for signing keys You can either use a traditional database for storing operational data, or use a cache with persistence features like Redis. Duende IdentityServer includes storage implementations for above data using EntityFramework, and you can build your own. See the [data stores](/identityserver/data) section for more information. ### IdentityServer Features Using Data Protection [Section titled “IdentityServer Features Using Data Protection”](#identityserver-features-using-data-protection) Duende IdentityServer’s features that rely on data protection include: * protecting signing keys at rest (if [automatic key management](/identityserver/fundamentals/key-management/#automatic-key-management) is used and enabled) * protecting [persisted grants](/identityserver/data/operational/#persisted-grant-service) at rest (if enabled) * protecting [server-side session](/identityserver/ui/server-side-sessions/) data at rest (if enabled) * protecting [the state parameter](/identityserver/ui/login/external/#state-url-length-and-isecuredataformat) for external OIDC providers (if enabled) * protecting message payloads sent between pages in the UI (e.g. [logout context](/identityserver/ui/logout/logout-context/) and [error context](/identityserver/ui/error/)). * session management (because the ASP.NET Core cookie authentication handler requires it) ## Distributed Caching [Section titled “Distributed Caching”](#distributed-caching) Some optional features rely on ASP.NET Core distributed caching: * [State data formatter for OpenID Connect](/identityserver/ui/login/external/#state-url-length-and-isecuredataformat) * Replay cache (e.g. for [JWT client credentials](/identityserver/tokens/client-authentication/#setting-up-a-private-key-jwt-secret)) * [Device flow](/identityserver/reference/v8/stores/device-flow-store/) throttling service * Authorization parameter store In order to work in a multi-server environment, this needs to be set up correctly. Please consult the Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more details. ## Health Checks [Section titled “Health Checks”](#health-checks) You can use ASP.NET’s [health checks](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks) to monitor the health of your IdentityServer deployment. Health checks can contain arbitrary logic to test various conditions of a system. One common strategy for checking the health of IdentityServer is to make discovery requests. Successful discovery responses indicate not just that the IdentityServer host is running and able to receive requests and generate responses, but also that it was able to communicate with the configuration store. The following example code creates a health check that makes requests to the discovery endpoint. It finds the discovery endpoint’s handler by name, which requires IdentityServer `v6.3`. ```csharp public class DiscoveryHealthCheck : IHealthCheck { private readonly IEnumerable _endpoints; private readonly IHttpContextAccessor _httpContextAccessor; public DiscoveryHealthCheck(IEnumerable endpoints, IHttpContextAccessor httpContextAccessor) { _endpoints = endpoints; _httpContextAccessor = httpContextAccessor; } public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { try { var endpoint = _endpoints.FirstOrDefault(x => x.Name == IdentityServerConstants.EndpointNames.Discovery); if (endpoint != null) { var handler = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService(endpoint.Handler) as IEndpointHandler; if (handler != null) { var result = await handler.ProcessAsync(_httpContextAccessor.HttpContext); if (result is DiscoveryDocumentResult) { return HealthCheckResult.Healthy(); } } } } catch { } return new HealthCheckResult(context.Registration.FailureStatus); } } ``` Another health check that you can perform is to request the public keys that IdentityServer uses to sign tokens - the JWKS (JSON Web Key Set). Doing so demonstrates that IdentityServer is able to communicate with the signing key store, a critical dependency. The following example code creates such a health check. Just as with the previous health check, it finds the endpoint’s handler by name, which requires IdentityServer `v6.3`. ```csharp public class DiscoveryKeysHealthCheck : IHealthCheck { private readonly IEnumerable _endpoints; private readonly IHttpContextAccessor _httpContextAccessor; public DiscoveryKeysHealthCheck(IEnumerable endpoints, IHttpContextAccessor httpContextAccessor) { _endpoints = endpoints; _httpContextAccessor = httpContextAccessor; } public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { try { var endpoint = _endpoints.FirstOrDefault(x => x.Name == IdentityServerConstants.EndpointNames.Jwks); if (endpoint != null) { var handler = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService(endpoint.Handler) as IEndpointHandler; if (handler != null) { var result = await handler.ProcessAsync(_httpContextAccessor.HttpContext); if (result is JsonWebKeysResult) { return HealthCheckResult.Healthy(); } } } } catch { } return new HealthCheckResult(context.Registration.FailureStatus); } } ``` ----- # Federal Information Processing Standard (FIPS) compliance > Explains Duende IdentityServer Federal Information Processing Standard (FIPS) compliance. The Federal Information Processing Standard (FIPS) Publication 140-2 is a U.S. government standard that defines minimum security requirements for cryptographic modules in information technology products. IdentityServer does not provide built-in FIPS enforcement or a configuration option to enable FIPS compliance. There is no toggle switch or configuration profile that will automatically make your solution FIPS-compliant. You are solely responsible for ensuring FIPS compliance in your application and infrastructure. This includes: * Configuring your operating system for FIPS mode * Selecting and using only FIPS-validated cryptographic algorithms * Properly managing and storing cryptographic key material * Validating that your complete solution meets FIPS requirements Duende IdentityServer does not contain its own cryptographic algorithm implementations. Instead, it relies on cryptographic primitives provided by: * The underlying .NET runtime * The operating system When IdentityServer signs tokens or protects cookies, it uses the cryptographic modules provided by these underlying platforms. However, IdentityServer does not restrict or enforce which algorithms or key sizes you use. This is your responsibility to configure correctly. To build a FIPS-compliant solution with Duende IdentityServer, here is some guidance: 1. **Configure your operating system and .NET Core codebase** for FIPS mode following the guidance in the [Microsoft documentation on .NET Core FIPS compliance](https://learn.microsoft.com/en-us/dotnet/standard/security/fips-compliance) 2. **Select only FIPS-validated algorithms** in your IdentityServer configuration: * **Do not use:** `RS256`, `RS384`, or `RS512` * **Use instead:** `PS*` or `ES*` token signing algorithms 3. **Use secure key storage** for private key material, such as: * Azure Key Vault Hardware Security Module (HSM) * Other FIPS 140-2 validated hardware security modules 4. **Configure ASP.NET Core Data Protection** appropriately: * Use FIPS-compliant algorithms for generating data protection keys * Store data protection keys securely in a FIPS-validated module Remember, it is your responsibility to validate that your complete solution meets FIPS compliance requirements for your specific use case and regulatory environment. ----- # Diagnostics > Overview of IdentityServer's diagnostic capabilities including logging, OpenTelemetry integration, and event system for monitoring and troubleshooting ## Logging [Section titled “Logging”](#logging) IdentityServer offers multiple diagnostics possibilities. The logs contains detailed information and are your best friend when troubleshooting. For security reasons the error messages returned to the UI/client are very brief - the logs always have all the details of what went wrong. [Read More](/identityserver/diagnostics/logging/) ## OpenTelemetry [Section titled “OpenTelemetry”](#opentelemetry) OpenTelemetry is a standard way of emitting diagnostics information from a process and IdentityServer supports Traces (.NET Activities), Metrics and Logs. [Read More](/identityserver/diagnostics/otel/) ## Events [Section titled “Events”](#events) The eventing system was created as an extension point to integrate with application monitoring systems (APM). They used to have their own different APIs so IdentityServer only provided events that could be used to call the APM’s APIs. Thanks to OpenTelemetry there is now a standardized way to emit diagnostic information from a process. The events may eventually be deprecated and removed. [Read More](/identityserver/diagnostics/events/) ## Conformance Report [Section titled “Conformance Report”](#conformance-report) IdentityServer can generate a conformance report that assesses your configuration against OAuth 2.1 and FAPI 2.0 specifications. [Read More](/identityserver/diagnostics/conformance-report/) ----- # Financial-Grade Security and Conformance Report > How to install, configure, and use the IdentityServer Financial-Grade Security and Conformance report to assess OAuth 2.1 and FAPI 2.0 compliance. Added in 8.0 Part of Financial-Grade Security and Conformance, the conformance report assesses your IdentityServer deployment against [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) and [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile.html) specifications, generating an HTML report accessible via a protected endpoint. ## Installation [Section titled “Installation”](#installation) Install the NuGet package: Terminal ```bash dotnet add package Duende.IdentityServer.ConformanceReport ``` ## Setup [Section titled “Setup”](#setup) ### 1. Register the Financial-Grade Security and Conformance Report [Section titled “1. Register the Financial-Grade Security and Conformance Report”](#1-register-the-financial-grade-security-and-conformance-report) Call `AddConformanceReport()` on the IdentityServer builder: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConformanceReport(options => { options.Enabled = true; }); ``` ### 2. Map the Endpoint [Section titled “2. Map the Endpoint”](#2-map-the-endpoint) Add the Financial-Grade Security and Conformance report endpoint to your middleware pipeline: Program.cs ```csharp app.MapConformanceReport(); ``` ### 3. Access the Report [Section titled “3. Access the Report”](#3-access-the-report) Navigate to: `https://your-server/_duende/conformance-report` The endpoint requires an authenticated user by default (see [Authorization](#authorization) below). ## Configuration Options [Section titled “Configuration Options”](#configuration-options) `ConformanceReportOptions` controls the Financial-Grade Security and Conformance report feature: * **`Enabled`** Enable or disable the conformance report endpoint. Defaults to `false`. * **`EnableOAuth21Assessment`** Include OAuth 2.1 profile assessment in the report. Defaults to `true`. * **`EnableFapi2SecurityAssessment`** Include FAPI 2.0 Security Profile assessment in the report. Defaults to `true`. * **`PathPrefix`** URL path prefix for the conformance endpoint (no leading slash). Defaults to `"_duende"`. * **`ConfigureAuthorization`** Authorization policy for the HTML report endpoint. Defaults to require an authenticated user. * **`AuthorizationPolicyName`** ASP.NET Core authorization policy name used internally. Defaults to `"ConformanceReport"`. * **`HostCompanyName`** Optional company name shown in the report header. Defaults to `null`. * **`HostCompanyLogoUrl`** Optional company logo URL shown in the report header. Defaults to `null`. ## Authorization [Section titled “Authorization”](#authorization) By default, the report endpoint requires an authenticated user. Customize the policy using `ConfigureAuthorization`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConformanceReport(options => { options.Enabled = true; // Require a specific role options.ConfigureAuthorization = policy => policy.RequireRole("Admin"); // Or require multiple conditions // options.ConfigureAuthorization = policy => policy // .RequireRole("Admin") // .RequireClaim("department", "IT"); // Or allow anonymous (development/testing only) // options.ConfigureAuthorization = policy => // policy.RequireAssertion(_ => builder.Environment.IsDevelopment()); }); ``` Caution If you set `ConfigureAuthorization = null`, you must manually register an ASP.NET Core authorization policy with the name specified in `AuthorizationPolicyName` (default: `"ConformanceReport"`). Otherwise, the endpoint will fail at runtime with a “policy not found” error. ## Understanding the Report [Section titled “Understanding the Report”](#understanding-the-report) The HTML report displays: * **Server Configuration** — a matrix of server-level conformance rules and their status * **Client Configurations** — a matrix of per-client conformance rules and their status * **Rule Legend** — explanation of each rule identifier * **Notes** — detailed messages for warnings and failures ### Status Indicators [Section titled “Status Indicators”](#status-indicators) | Symbol | Meaning | | ------- | -------------------------------------------------------- | | Pass | Requirement is met | | Fail | Requirement is not met (configuration is non-conformant) | | Warning | Recommended practice is not followed | | N/A | Rule is not applicable to this configuration | ## Requirements [Section titled “Requirements”](#requirements) The conformance report uses `IClientStore.GetAllClientsAsync` to enumerate all clients for assessment. Custom `IClientStore` implementations must implement this method (added in v8.0). See the [upgrade guide](/identityserver/upgrades/v7_4-to-v8_0/#iclientstoregetallclientsasync-now-required) for details. ## Full Example [Section titled “Full Example”](#full-example) Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) .AddConformanceReport(options => { options.Enabled = true; options.EnableOAuth21Assessment = true; options.EnableFapi2SecurityAssessment = true; options.HostCompanyName = "Acme Corp"; options.ConfigureAuthorization = policy => policy.RequireRole("ComplianceTeam"); }); // ... app.MapConformanceReport(); app.UseIdentityServer(); ``` ----- # Diagnostics Data Added in 7.3 To make troubleshooting easier, newer versions of IdentityServer can collect important configuration and operational diagnostics data from your IdentityServer host. Diagnostics data is [written to logs periodically](/identityserver/reference/v8/options/#diagnostics), and can be used by your operations team to help analyze your IdentityServer configuration. Diagnostics information is never automatically shared with Duende. In support scenarios, you can choose to manually share this diagnostics data with [Duende priority support](/general/support-and-issues/#priority-support) to provide additional context. If needed, you can redact/remove entries before doing so. ## Diagnostics Data Contents [Section titled “Diagnostics Data Contents”](#diagnostics-data-contents) Diagnostics data contains information that is relevant to the configuration and behavior of your IdentityServer instance. The diagnostics data contains the following information: * Assembly information for [IdentityServer-related assemblies](https://github.com/DuendeSoftware/products/blob/main/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/AssemblyInfoDiagnosticEntry.cs#L17) * .NET runtime version * IdentityServer version * Assembly name and version * Registered authentication schemes (does not include [dynamic providers](/identityserver/ui/login/dynamicproviders/)) * Name of the scheme and authentication handler type * Registered non-default implementations of Duende IdentityServer extension points * Extension point type, implementation type, assembly name and version * [`IdentityServerOptions`](/identityserver/reference/v8/options/) configuration * [Data Protection](/identityserver/deployment/#aspnet-core-data-protection) configuration * `ApplicationDiscriminator`, `XmlEncryptor` and `XmlRepository` * Basic server information * Host name * [License Usage Summary](/identityserver/reference/v8/models/license-usage-summary/) data * Token issue counts (for various token types) * Endpoint usage (only for IdentityServer endpoints) * Clients configuration (limited to first 100 clients, excluding sensitive information/secrets) * Resources configuration (limited to the first 100 resources) * Identity resources * API resources * API scopes Diagnostics data [is formatted as JSON](#diagnostics-data-format). ## Capturing Diagnostics Data [Section titled “Capturing Diagnostics Data”](#capturing-diagnostics-data) The IdentityServer diagnostics data is [written to logs periodically](/identityserver/reference/v8/options/#diagnostics). By default, you will see log entries similar to the following in your IdentityServer logs ```log info: Duende.IdentityServer.Diagnostics.Summary[7000] Diagnostic data (1 of 2): { ... info: Duende.IdentityServer.Diagnostics.Summary[7000] Diagnostic data (2 of 2): ... } ``` Diagnostics data [may be chunked](/identityserver/reference/v8/options/#diagnostics), and you will need to concatenate chunks to collect the full diagnostics JSON data. To capture diagnostics data from your IdentityServer instance, you can log entries written to the `Duende.IdentityServer.Diagnostics.Summary` log category. You may want to set up your IdentityServer logging to filter diagnostics data and emit these to a separate log provider/sink. Let’s look at some examples of how you can filter diagnostics data and write it to a separate log file. Note that to read the contents of this log file, you will need access to your IdentityServer host storage (or use another provider/sink to extract log data). ### .NET Core Default Logger [Section titled “.NET Core Default Logger”](#net-core-default-logger) To write log entries to a file using the default [.NET Core `ILogger` API](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging), you will need a log provider that supports doing this. In the example below, we are using the [`NReco.Logging.File`](https://www.nuget.org/packages/NReco.Logging.File) package. * In code Use the `AddFile()` extension method to add a file logger to your `ILoggingBuilder` instance. The `FilterLogEntry` property on the file logger can be used to filter log entries based on the log category, which is what we are using to filter the `Duende.IdentityServer.Diagnostics.Summary` log category. Program.cs ```csharp // ... builder.Services.AddLogging(configure => { configure.AddFile("diagnostics.log", options => { options.Append = true; options.FilterLogEntry = entry => entry.LogName == "Duende.IdentityServer.Diagnostics.Summary"; }); }); ``` * With configuration pattern The file logger will need to be registered in your application. Use the `AddFile()` extension method to add a file logger to your `ILoggingBuilder` instance. Note the `NReco.Logging.File` requires a file name to be specified. Program.cs ```csharp // ... builder.Services.AddLogging(configure => { configure.AddFile("diagnostics.log", append: true); }); ``` In your `appsettings.json`, you can configure the file logger to filter log entries based on the log category. appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Duende.IdentityServer.Diagnostics.Summary": "None" }, "File": { "LogLevel": { "Default": "None", "Duende.IdentityServer.Diagnostics.Summary": "Information" } } } } ``` ### Serilog [Section titled “Serilog”](#serilog) When using [Serilog](https://serilog.net/), you can configure a separate file logger sink to write `Duende.IdentityServer.Diagnostics.Summary` log entries to. * In code In the `AddSerilog()` extension method’s configuration builder, you can add a file logger that filters log messages and only emits those from the `Duende.IdentityServer.Diagnostics.Summary` category. The console logger (or another default logger you are using) can be configured to exclude this category. Program.cs ```csharp // ... builder.Services.AddSerilog((services, configuration) => { configuration .ReadFrom.Configuration(builder.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) .MinimumLevel.Override("System", LogEventLevel.Warning) .WriteTo.Logger(fileLogger => { fileLogger .WriteTo.File("./diagnostics/diagnostic.log", rollingInterval: RollingInterval.Day, fileSizeLimitBytes: 1024 * 1024 * 10, // 10 MB rollOnFileSizeLimit: true, outputTemplate: "[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") .Filter.ByIncludingOnly(Matching.FromSource("Duende.IdentityServer.Diagnostics.Summary")); }) .WriteTo.Logger(consoleLogger => { consoleLogger .WriteTo.Console( outputTemplate: "[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") .Filter.ByExcluding(Matching.FromSource("Duende.IdentityServer.Diagnostics.Summary")); }); ``` * With configuration pattern When using the configuration pattern, you can configure the file logger to filter log entries based on the log category. Note that you will need the [`Serilog.Expressions`](https://github.com/serilog/serilog-expressions) package installed and configured in your IdentityServer host In your `appsettings.json`, you can configure Serilog to filter log entries based on the log category. appsettings.json ```json { "Serilog":{ "Using":[ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "Enrich":[ "FromLogContext" ], "MinimumLevel":{ "Default":"Debug", "Override":{ "Microsoft":"Warning", "Microsoft.Hosting.Lifetime":"Information", "Microsoft.AspNetCore.Authentication":"Debug", "System":"Warning" } }, "WriteTo":[ { "Name":"Logger", "Args":{ "configureLogger":{ "WriteTo":[ { "Name":"File", "Args":{ "path":"diagnostics/identity-server-diagnostics.log", "outputTemplate":"[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", "rollingInterval":"Day", "fileSizeLimitBytes":10000000, "rollOnFileSizeLimit":true } } ], "Filter":[ { "Name":"ByIncludingOnly", "Args":{ "expression":"StartsWith(SourceContext, 'Duende.IdentityServer.Diagnostics.Summary')" } } ] } } }, { "Name":"Logger", "Args":{ "configureLogger":{ "WriteTo":[ { "Name":"Console", "Args":{ "outputTemplate":"[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}" } } ], "Filter":[ { "Name":"ByExcluding", "Args":{ "expression":"SourceContext = 'Duende.IdentityServer.Diagnostics.Summary'" } } ] } } } ] } } ``` ### log4net [Section titled “log4net”](#log4net) When using [log4net](https://logging.apache.org/log4net/index.html), you can use the `log4net.config` configuration file to configure a file appender that writes `Duende.IdentityServer.Diagnostics.Summary` log entries to a separate file. log4net.config ```xml ``` ### NLog [Section titled “NLog”](#nlog) When using [NLog](https://nlog-project.org/) and the [`NLog.Extensions.Logging`](https://www.nuget.org/packages/NLog.Extensions.Logging) package, you can use the configuration pattern to configure a file logger that writes `Duende.IdentityServer.Diagnostics.Summary` log entries to a separate file. appsettings.json ```json { "NLog": { "ThrowConfigExceptions": true, "Targets": { "file": { "type": "File", "fileName": "${basedir}/diagnostics/${shortdate}.log", "layout": "${longdate} ${level:uppercase=true} ${logger} ${message} ${exception:format=ToString}" }, "console": { "type": "ColoredConsole", "layout": "${longdate} ${level:uppercase=true} ${logger} ${message} ${exception:format=ToString}" } }, "Rules": [ { "logger": "Duende.IdentityServer.Diagnostics.Summary", "writeTo": "file", "final": true, "maxLevel": "Info" }, { "logger": "*", "minLevel": "Info", "writeTo": "console" } ] } } ``` ## Diagnostics Data Format [Section titled “Diagnostics Data Format”](#diagnostics-data-format) Diagnostics data is written to logs in one or more chunks containing data formatted as JSON. ----- # Events > Documentation about IdentityServer's event system for structured logging and monitoring of important operations While logging is more low level “printf” style - events represent higher level information about certain operations in IdentityServer. Events are structured data and include event IDs, success/failure information, categories and details. This makes it easy to query and analyze them and extract useful information that can be used for further processing. Events work great with structured logging stores like [ELK](https://www.elastic.co/webinars/introduction-elk-stack), [Seq](https://getseq.net) or [Splunk](https://www.splunk.com/). ### Emitting events [Section titled “Emitting events”](#emitting-events) Events are not turned on by default - but can be globally configured when `AddIdentityServer` is called, e.g.: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Events.RaiseSuccessEvents = true; options.Events.RaiseFailureEvents = true; options.Events.RaiseErrorEvents = true; }); ``` To emit an event use the `IEventService` from the ASP.NET Core service provider and call the `RaiseAsync` method, e.g.: ```csharp public async Task Login(LoginInputModel model) { if (_users.ValidateCredentials(model.Username, model.Password)) { // issue authentication cookie with subject ID and username var user = _users.FindByUsername(model.Username); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); } else { await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); } } ``` ### Custom sinks [Section titled “Custom sinks”](#custom-sinks) Our default event sink will serialize the event class to JSON and forward it to the ASP.NET Core logging system. If you want to connect to a custom event store, implement the `IEventSink` interface and register it with the ASP.NET Core service provider. The following example uses [Seq](https://getseq.net) to emit events: ```csharp public class SeqEventSink : IEventSink { private readonly Logger _log; public SeqEventSink() { _log = new LoggerConfiguration() .WriteTo.Seq("http://localhost:5341") .CreateLogger(); } public Task PersistAsync(Event evt) { if (evt.EventType == EventTypes.Success || evt.EventType == EventTypes.Information) { _log.Information("{Name} ({Id}), Details: {@details}", evt.Name, evt.Id, evt); } else { _log.Error("{Name} ({Id}), Details: {@details}", evt.Name, evt.Id, evt); } return Task.CompletedTask; } } ``` Add the `Serilog.Sinks.Seq` package to your host to make the above code work. ## Built-in events [Section titled “Built-in events”](#built-in-events) The following events are defined in IdentityServer: * **`ApiAuthenticationFailureEvent`** & **`ApiAuthenticationSuccessEvent`** Gets raised for successful/failed API authentication at the introspection endpoint. * **`ClientAuthenticationSuccessEvent`** & **`ClientAuthenticationFailureEvent`** Gets raised for successful/failed client authentication at the token endpoint. * **`TokenIssuedSuccessEvent`** & **`TokenIssuedFailureEvent`** Gets raised for successful/failed attempts to request identity tokens, access tokens, refresh tokens and authorization codes. * **`TokenIntrospectionSuccessEvent`** & **`TokenIntrospectionFailureEvent`** Gets raised for successful token introspection requests. * **`TokenRevokedSuccessEvent`** Gets raised for successful token revocation requests. * **`UserLoginSuccessEvent`** & **`UserLoginFailureEvent`** Gets raised by the quickstart UI for successful/failed user logins. * **`UserLogoutSuccessEvent`** Gets raised for successful logout requests. * **`ConsentGrantedEvent`** & **`ConsentDeniedEvent`** Gets raised in the consent UI. * **`UnhandledExceptionEvent`** Gets raised for unhandled exceptions. * **`DeviceAuthorizationFailureEvent`** & **`DeviceAuthorizationSuccessEvent`** Gets raised for successful/failed device authorization requests. ### SAML Events v8.0 [Section titled “SAML Events ”v8.0](#saml-events) The following events are raised by SAML components: * **`SamlSsoSuccessEvent`** Raised when a SAML single sign-on request completes successfully. * **`SamlSsoFailureEvent`** Raised when a SAML single sign-on request fails. * **`SamlSloSuccessEvent`** Raised when a SAML single logout request completes successfully. * **`SamlSloFailureEvent`** Raised when a SAML single logout request fails. * **`SamlAuthnRequestValidationFailureEvent`** Raised when validation of an incoming SAML authentication request fails. * **`SamlLogoutRequestValidationFailureEvent`** Raised when validation of an incoming SAML logout request fails. ### Custom events [Section titled “Custom events”](#custom-events) You can create your own events and emit them via our infrastructure. You need to derive from our base `Event` class which injects contextual information like activity ID, timestamp, etc. Your derived class can then add arbitrary data fields specific to the event context:: ```csharp public class UserLoginFailureEvent : Event { public UserLoginFailureEvent(string username, string error) : base(EventCategories.Authentication, "User Login Failure", EventTypes.Failure, EventIds.UserLoginFailure, error) { Username = username; } public string Username { get; set; } } ``` ----- # Logging > Documentation for logging configuration and usage in Duende IdentityServer, including log levels and Serilog setup Duende IdentityServer uses the standard logging facilities provided by ASP.NET Core. You don’t need to do any extra configuration to benefit from rich logging functionality. For general information on how to configure logging, setting up Serilog, and understanding log levels in Duende products, see our [Logging Fundamentals](/general/logging/) guide. ## Configuration [Section titled “Configuration”](#configuration) Logs are typically written under the `Duende.IdentityServer` category. We are roughly following the Microsoft guidelines for usage of log levels: * **`Trace`** For information that is valuable only to a developer troubleshooting an issue. These messages may contain sensitive application data like tokens and should not be enabled in a production environment. * **`Debug`** For following the internal flow and understanding why certain decisions are made. Has short-term usefulness during development and debugging. * **`Information`** For tracking the general flow of the application. These logs typically have some long-term value. * **`Warning`** For abnormal or unexpected events in the application flow. These may include errors or other conditions that do not cause the application to stop, but which may need to be investigated. * **`Error`** For errors and exceptions that cannot be handled. Examples: failed validation of a protocol request. * **`Critical`** For failures that require immediate attention. Examples: missing store implementation, invalid key material… To get detailed logs from IdentityServer, you can configure your `appsettings.json` to enable `Debug` or `Information` level logs for the `Duende.IdentityServer` namespace: appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Duende.IdentityServer": "Information" } } } ``` ### Filtering Exceptions [Section titled “Filtering Exceptions”](#filtering-exceptions) The `LoggingOptions` class allows developers to filter out any exceptions that could potentially lead to log bloat. For example, in a web application, developers should expect to see `OperationCanceledException` as clients end HTTP requests abruptly for many reasons. It’s such a common occurrence to see this exception that the default filter included with IdentityServer excludes it by default. ```csharp /// /// Called when the IdentityServer middleware detects an unhandled exception, and is used to determine if the exception is logged. /// Returns true to emit the log, false to suppress. /// public Func UnhandledExceptionLoggingFilter = (context, exception) => { var result = !(context.RequestAborted.IsCancellationRequested && exception is OperationCanceledException); return result; }; ``` To apply custom filtering, you can set the `UnhandledExceptionLoggingFilter` property on the `LoggingOptions` for your `IdentityServerOptions`. ```csharp var isBuilder = builder.Services.AddIdentityServer(options => { options.Logging.UnhandledExceptionLoggingFilter = (ctx, ex) => { if (ctx.User is { Identity.Name: "Jeff" }) { // Oh Jeff... return false; } if (ex.Message.Contains("Oops")) { // ignore this exception return false; } // this is a real exception return true; }; }) .AddTestUsers(TestUsers.Users) .AddLicenseSummary(); ``` Returning `true` means the exception will be logged, while returning `false` indicates the exception should not be logged. ## OpenTelemetry [Section titled “OpenTelemetry”](#opentelemetry) Logs written to the standard `ILogger` system in .NET 8+ can be exported to OpenTelemetry traces at runtime. This helps visualize when the log statement occurred in relation to the entire request. The logs are augmented with trace ids and correlated with traces. Have a look at [logs in OpenTelemetry](/identityserver/diagnostics/otel/#logs) for setup details. ----- # OpenTelemetry > Documentation for OpenTelemetry integration in IdentityServer, covering metrics, traces and logs collection for monitoring and diagnostics [OpenTelemetry](https://opentelemetry.io) (OTel) is a collection of tools, APIs, and SDKs for generating and collecting telemetry data (metrics, logs, and traces). This is very useful for analyzing software performance and behavior, especially in highly distributed systems. ## OpenTelemetry Signals [Section titled “OpenTelemetry Signals”](#opentelemetry-signals) OpenTelemetry signals are the information collected and processed to describe the internal activity of the system. The most common signals are traces, metrics, and logs. .NET 8+ comes with first class support for OpenTelemetry. IdentityServer emits traces, metrics, and logs you can collect. ### Metrics [Section titled “Metrics”](#metrics) Metrics are high level statistic counters. They provide an aggregated overview and can be used to set monitoring rules. ### Traces [Section titled “Traces”](#traces) Traces shows individual requests and dependencies. The output is very useful for visualizing the control flow and finding performance bottlenecks. This is an example of distributed traces from a web application calling an API (displayed using our [Aspire sample](/identityserver/samples/diagnostics/)). The web application uses a refresh token to call IdentityServer to get a new access token and then calls the API. The API reads the discovery endpoint, finds the jwks url and then gets the keys from jwks endpoint. ![.NET Aspire dashboard showing Duende IdentityServer traces](/_astro/aspire_traces.C5IYKs1g_8l8HE.webp) ### Logs [Section titled “Logs”](#logs) OpenTelemetry in .NET 8+ can export logs written to the standard `ILogger` system. The logs are augmented with trace ids and correlated with traces. This is an example of a structured log message from a web application calling an API (also displayed using our [Aspire sample](/identityserver/samples/diagnostics/)). ![.NET Aspire dashboard showing Duende IdentityServer Structured Logs](/_astro/aspire_structured_logs.C4_GEVBr_Z2uoiap.webp) Here is an example of that same log message appearing in the trace. Aspire displays the log entry details as dots on the trace timeline. ![.NET Aspire dashboard showing Duende IdentityServer a trace with a log entry](/_astro/aspire_structured_logs_in_trace.DDvSbnq__27frWP.webp) ## Setup [Section titled “Setup”](#setup) To start emitting OpenTelemetry tracing and metrics information you need to: * add the OpenTelemetry libraries to your IdentityServer and client applications * start collecting traces and metrics from the various IdentityServer sources (and other sources e.g. ASP.NET Core) * add the OpenTelemetry configuration to your service setup For development a simple option is to export the tracing information to the console and use the Prometheus exporter to create a human-readable `/metrics` endpoint for the metrics. ```bash dotnet add package OpenTelemetry dotnet add package OpenTelemetry.Extensions.Hosting dotnet add package OpenTelemetry.Instrumentation.AspNetCore dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol ``` Program.cs ```csharp using OpenTelemetry.Resources; // Add OpenTelemetry logging infrastructure // to correlate logs with traces builder.Logging.AddOpenTelemetry(); // Enable OpenTelemetry var openTelemetry = builder.Services.AddOpenTelemetry(); openTelemetry.ConfigureResource(r => r .AddService(builder.Environment.ApplicationName)); openTelemetry.WithMetrics(m => m .AddMeter(Telemetry.ServiceName) .AddMeter(Pages.Telemetry.ServiceName) .AddPrometheusExporter()); openTelemetry.WithTracing(t => t .AddSource(IdentityServerConstants.Tracing.Basic) .AddSource(IdentityServerConstants.Tracing.Cache) .AddSource(IdentityServerConstants.Tracing.Services) .AddSource(IdentityServerConstants.Tracing.Stores) .AddSource(IdentityServerConstants.Tracing.Validation) .AddAspNetCoreInstrumentation() .AddConsoleExporter()); ``` Add the Prometheus exporter to the pipeline Program.cs ```csharp // Map /metrics that displays OpenTelemetry data in human-readable form. app.UseOpenTelemetryPrometheusScrapingEndpoint(); ``` This setup will write the tracing information to the console and provide metrics on the /metrics endpoint. ## Metrics [Section titled “Metrics”](#metrics-1) OpenTelemetry metrics are run-time measurements that are intended to provide an indication of overall health and are typically used to show graphs on a dashboard or to set up monitoring rules. When that monitoring reveals issues, traces and logs are used to investigate further. OpenTelemetry monitoring tools often provide features to find the traces and logs corresponding to certain metrics. IdentityServer emits metrics from the IdentityServer middleware and services. Our quick start for the UI also [contains metrics](#metrics-in-the-ui) that can be used as a starting point for monitoring UI events. The metric counters that IdentityServer emits are designed to not contain any sensitive information. They are often tagged to indicate the source of the events. ### High level Metrics [Section titled “High level Metrics”](#high-level-metrics) These metrics are instrumented by the IdentityServer middleware and services and are intended to describe the overall usage and health of the system. They could provide the starting point for building a metrics dashboard. The high level metrics are created by the meter named “Duende.IdentityServer”, which is the value of the `Duende.IdentityServer.Telemetry.ServiceName` constant. #### Telemetry.Metrics.Counters.Operation [Section titled “Telemetry.Metrics.Counters.Operation”](#telemetrymetricscountersoperation) Counter name: `tokenservice.operation` Aggregated counter of failed and successful operations. The result tag indicates if an operation succeeded, failed, or caused an internal error. It is expected to have some failures during normal operations. In contrast, operations tagged with a result of internal\_error are abnormal and indicate an unhandled exception. The error/success ratio can be used as a very high level health metric. | Tag | Description | | ------ | ---------------------------------------------------- | | error | Error label on errors | | result | Success, error or internal\_error | | client | Id of client requesting the operation. May be empty. | #### Telemetry.Metrics.Counters.ActiveRequests [Section titled “Telemetry.Metrics.Counters.ActiveRequests”](#telemetrymetricscountersactiverequests) Counter name: `active_requests` Gauge/up-down counter that shows current active requests that are processed by any IdentityServer endpoint. Note that the pages in the user interface are not IdentityServer endpoints and are not included in this count. | Tag | Description | | -------- | ---------------------------------------- | | endpoint | The type name for the endpoint processor | | path | The path of the request | ### Detailed Metrics [Section titled “Detailed Metrics”](#detailed-metrics) These detailed metrics are instrumented by the IdentityServer middleware and services and track usage of specific flows and features. #### Telemetry.Metrics.Counters.ApiSecretValidation [Section titled “Telemetry.Metrics.Counters.ApiSecretValidation”](#telemetrymetricscountersapisecretvalidation) Counter name: `tokenservice.api.secret_validation` Number of successful/failed validations of API Secrets. | Tag | Description | | ------------ | -------------------------- | | api | The Api Id | | auth\_method | Authentication method used | | error | Error label on errors | #### Telemetry.Metrics.Counters.BackchannelAuthentication [Section titled “Telemetry.Metrics.Counters.BackchannelAuthentication”](#telemetrymetricscountersbackchannelauthentication) Counter name: `tokenservice.backchannel_authentication` Number of successful/failed back channel authentications (CIBA). | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.ClientConfigValidation [Section titled “Telemetry.Metrics.Counters.ClientConfigValidation”](#telemetrymetricscountersclientconfigvalidation) Counter name: `tokenservice.client.config_validation` Number of successful/failed client validations. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.ClientSecretValidation [Section titled “Telemetry.Metrics.Counters.ClientSecretValidation”](#telemetrymetricscountersclientsecretvalidation) Counter name: `tokenservice.client.secret_validation` Number of successful/failed client secret validations. | Tag | Description | | ------------ | ------------------------------------ | | client | The client Id | | auth\_method | The authentication method on success | | error | Error label on errors | #### Telemetry.Metrics.Counters.DeviceAuthentication [Section titled “Telemetry.Metrics.Counters.DeviceAuthentication”](#telemetrymetricscountersdeviceauthentication) Counter name: `tokenservice.device_authentication` Number of successful/failed device authentications. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.DynamicIdentityProviderValidation [Section titled “Telemetry.Metrics.Counters.DynamicIdentityProviderValidation”](#telemetrymetricscountersdynamicidentityprovidervalidation) Counter name: `tokenservice.dynamic_identityprovider.validation` Number of successful/failed validations of dynamic identity providers. | Tag | Description | | ------ | ------------------------------- | | scheme | The scheme name of the provider | | error | Error label on errors | #### Telemetry.Metrics.Counters.Introspection [Section titled “Telemetry.Metrics.Counters.Introspection”](#telemetrymetricscountersintrospection) Counter name: `tokenservice.introspection` Number of successful/failed token introspections. | Tag | Description | | ------ | -------------------------------------------------- | | caller | The caller of the endpoint, a client id or api id. | | active | Was the token active? Only sent on success | | error | Error label on errors | #### Telemetry.Metrics.Counters.PushedAuthorizationRequest [Section titled “Telemetry.Metrics.Counters.PushedAuthorizationRequest”](#telemetrymetricscounterspushedauthorizationrequest) Counter name: `tokenservice.pushed_authorization_request` Number of successful/failed pushed authorization requests. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.ResourceOwnerAuthentication [Section titled “Telemetry.Metrics.Counters.ResourceOwnerAuthentication”](#telemetrymetricscountersresourceownerauthentication) Counter name: `tokenservice.resourceowner_authentication` Number of successful/failed resource owner authentications. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.Revocation [Section titled “Telemetry.Metrics.Counters.Revocation”](#telemetrymetricscountersrevocation) Counter name: `tokenservice.revocation` Number of successful/failed token revocations. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.TokenIssued [Section titled “Telemetry.Metrics.Counters.TokenIssued”](#telemetrymetricscounterstokenissued) Counter name: `tokenservice.token_issued` Number of successful/failed token issuance attempts. Note that a token issuance might include multiple actual tokens (id\_token, access token, refresh token). | Tag | Description | | ------------------------ | ---------------------------------------------------------------- | | client | The client Id | | grant\_type | The grant type used | | authorize\_request\_type | The authorize request type, if information about it is available | | error | Error label on errors | #### Telemetry.Metrics.Counters.SamlSso v8.0 [Section titled “Telemetry.Metrics.Counters.SamlSso ”v8.0](#telemetrymetricscounterssamlsso) Counter name: `tokenservice.saml.sso` Number of SAML SSO attempts, both successful and failed. On success, the counter is tagged with the service provider entity ID and the SAML binding used. On failure, the binding tag is replaced with an error code so you can quickly see what went wrong without flooding your metrics system with high-cardinality data. On success: | Tag | Description | | -------------- | ------------------------------------------------------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | | binding | The SAML binding used (for example, `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST`) | On failure: | Tag | Description | | -------------- | ------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | | error | Bounded error code (see below) | The `error` tag uses a bounded set of values to prevent cardinality explosion in your metrics backend: `invalid`, `unknown`, `sp_not_found`, `sp_disabled`, `invalid_acs_url`, `access_denied`, `interaction_error` #### Telemetry.Metrics.Counters.SamlSlo [Section titled “Telemetry.Metrics.Counters.SamlSlo”](#telemetrymetricscounterssamlslo) Counter name: `tokenservice.saml.slo` Number of SAML Single Logout (SLO) attempts, both successful and failed. Like the SSO counter, error codes are kept to a bounded set to keep your metrics cardinality under control. On success: | Tag | Description | | -------------- | ------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | On failure: | Tag | Description | | -------------- | ------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | | error | Bounded error code (see below) | The `error` tag uses a bounded set of values to prevent cardinality explosion in your metrics backend: `invalid`, `unknown`, `sp_not_found`, `sp_disabled`, `invalid_acs_url`, `access_denied`, `partial_logout`, `interaction_error` ### Metrics In The UI [Section titled “Metrics In The UI”](#metrics-in-the-ui) The [UI in your IdentityServer host](/identityserver/ui/) can instrument these events to measure activities that occur during interactive flows, such as user login and logout. These events are not instrumented by the IdentityServer middleware or services because they are the responsibility of the UI. Our templated UI does instrument these events, and you can alter and add metrics as needed to the UI in your context. #### Telemetry.Metrics.Counters.Consent [Section titled “Telemetry.Metrics.Counters.Consent”](#telemetrymetricscountersconsent) Counter name: `tokenservice.consent` Consent requests granted or denied. The counters are per scope, so if a user consents to multiple scopes, the counter is increased multiple times, one for each scope. This allows the scope name to be included as a tag without causing an explosion of combination of tags. | Tag | Description | | ------- | ----------------- | | client | The client Id | | scope | The scope names | | consent | granted or denied | #### Telemetry.Metrics.Counters.GrantsRevoked [Section titled “Telemetry.Metrics.Counters.GrantsRevoked”](#telemetrymetricscountersgrantsrevoked) Counter name: `tokenservice.grants_revoked` Revocation of grants. | Tag | Description | | ------ | --------------------------------------------------------------------------------------------------------- | | client | The client Id, if grants are revoked only for one client. If not set, the revocation was for all clients. | #### Telemetry.Metrics.Counters.UserLogin [Section titled “Telemetry.Metrics.Counters.UserLogin”](#telemetrymetricscountersuserlogin) Counter names: `tokenservice.user_login` Successful and failed user logins. | Tag | Description | | ------ | ----------------------------------------------------------------- | | client | The client Id, if the login was caused by a request from a client | | idp | The idp (ASP.NET Core Scheme name) used to log in | | error | Error label on errors | #### Telemetry.Metrics.Counters.UserLogout [Section titled “Telemetry.Metrics.Counters.UserLogout”](#telemetrymetricscountersuserlogout) Counter name: `user_logout` User logout. Note that this is only raised on explicit user logout, not if the session times out. The number of logouts will typically be lower than the number of logins. | Tag | Description | | --- | ---------------------------------------------- | | idp | The idp (ASP.NET scheme name) logging out from | ### .NET Authentication And Authorization Metrics [Section titled “.NET Authentication And Authorization Metrics”](#net-authentication-and-authorization-metrics) Starting with .NET 10, metrics are available for certain authentication and authorization events in ASP.NET Core. You can get metrics for the following events: * [Authentication](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/built-in?view=aspnetcore-10.0#microsoftaspnetcoreauthentication) * Authenticated request duration (`aspnetcore.authentication.authenticate.duration`) * Challenge count (`aspnetcore.authentication.challenges`) * Forbid count (`aspnetcore.authentication.forbids`) * Sign in count (`aspnetcore.authentication.sign_ins`) * Sign out count (`aspnetcore.authentication.sign_outs`) * [Authorization](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/built-in?view=aspnetcore-10.0#microsoftaspnetcoreauthorization) * Count of requests requiring authorization (`aspnetcore.authorization.attempts`) Refer to the [ASP.NET Core documentation](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/built-in?view=aspnetcore-10.0) for more information about ASP.NET Core built-in metrics. ### ASP.NET Core Identity metrics [Section titled “ASP.NET Core Identity metrics”](#aspnet-core-identity-metrics) When using ASP.NET Identity, metrics are available for key user and sign-in operation metrics. These let you monitor user management activities like creating users, changing passwords, etc. It’s also possible to track login attempts, sign-ins, sign-outs, and two-factor authentication usage. The `Microsoft.AspNetCore.Identity` meter provides the following metrics: * User management metrics * Duration of user creation operations (`aspnetcore.identity.user.create.duration`) * Duration of user update operations (`aspnetcore.identity.user.update.duration`) * Duration of user deletion operations (`aspnetcore.identity.user.delete.duration`) * Number of password verification attempts (`aspnetcore.identity.user.check_password_attempts`) * Number of tokens generated for users, such as password reset tokens (`aspnetcore.identity.user.generated_tokens`) * Number of token verification attempts (`aspnetcore.identity.user.verify_token_attempts`) * Authentication metrics * Duration of authentication operations (`aspnetcore.identity.sign_in.authenticate.duration`) * Number of password check attempts at sign-in (`aspnetcore.identity.sign_in.check_password_attempts`) * Number of successful sign-ins (`aspnetcore.identity.sign_in.sign_ins`) * Number of sign-outs (`aspnetcore.identity.sign_in.sign_outs`) * Number of remembered two-factor authentication (2FA) clients (`aspnetcore.identity.sign_in.two_factor_clients_remembered`) * Number of forgotten two-factor authentication (2FA) clients (`aspnetcore.identity.sign_in.two_factor_clients_forgotten`) ## Traces [Section titled “Traces”](#traces-1) Here’s e.g. the output for a request to the discovery endpoint: ![Honeycomb UI showing traces for discovery document endpoint](/_astro/otel_disco.BBgm8ly2_RcRU3.webp) When multiple applications send their traces to the same OpenTelemetry server, this becomes super useful for following e.g. authentication flows over service boundaries. The following screenshot shows the ASP.NET Core OpenID Connect authentication handler redeeming the authorization code: ![HoneyComb UI showing traces for the OpenID Connect authentication handler](/_astro/otel_flow_1.BBYe6Iu9_1HRa9h.webp) …and then contacting the userinfo endpoint: ![Honeycomb UI showing traces for the userinfo endpoint](/_astro/otel_flow_2.DcVRg6r2_Z1FNh6z.webp) *The above screenshots are from .* ### Tracing Sources [Section titled “Tracing Sources”](#tracing-sources) IdentityServer can emit very fine-grained traces which is useful for performance troubleshooting and general exploration of the control flow. This might be too detailed in production. You can select which information you are interested in by selectively listening to various traces: * *`IdentityServerConstants.Tracing.Basic`* High level request processing like request validators and response generators * *`IdentityServerConstants.Tracing.Cache`* Caching related tracing * *`IdentityServerConstants.Tracing.Services`* Services related tracing * *`IdentityServerConstants.Tracing.Stores`* Store related tracing * *`IdentityServerConstants.Tracing.Validation`* More detailed tracing related to validation ## OpenTelemetry From 3rd Party Logging Frameworks [Section titled “OpenTelemetry From 3rd Party Logging Frameworks”](#opentelemetry-from-3rd-party-logging-frameworks) If you’re unable to use the `ILogger` system in .NET, your choice of logging framework may be able to push log messages to traces. You can view their documentation to set that up. ### OpenTelemetry with Serilog [Section titled “OpenTelemetry with Serilog”](#opentelemetry-with-serilog) If you are logging with Serilog and want to use that framework’s native API to push log messages to traces, you need to: * Add the Serilog OpenTelemetry sink library * Instruct the Serilog logger object to write to the OpenTelemetry sink Note: See the Serilog [OpenTelemetry sink](https://github.com/serilog/serilog-sinks-opentelemetry) documentation for the most up to date information. ```bash dotnet add package Serilog.Sinks.OpenTelemetry ``` ```csharp Log.Logger = new LoggerConfiguration() .WriteTo.OpenTelemetry() .CreateLogger(); ``` ----- # Claims > Learn about how IdentityServer emits and manages claims for users and clients, including claim emission strategies and serialization IdentityServer emits claims about users and clients into tokens. You are in full control of which claims you want to emit, in which situations you want to emit those claims, and where to retrieve those claims from. ## User Claims [Section titled “User Claims”](#user-claims) User claims can be emitted in both identity and access tokens and in the [userinfo endpoint](/identityserver/reference/v8/endpoints/userinfo/). The central extensibility point to implement to emit claims is called the [profile service](/identityserver/reference/v8/services/profile-service/). The profile service is responsible for both gathering claim data and deciding which claims should be emitted. Whenever IdentityServer needs the claims for a user, it invokes the registered profile service with a [context](/identityserver/reference/v8/services/profile-service/#duendeidentityservermodelsprofiledatarequestcontext) that presents detailed information about the current request, including * the client that is making the request * the identity of the user * the type of the request (access token, id token, or userinfo) * the requested claim types, which are the claims types associated with requested scopes and resources ### Strategies For Emitting Claims [Section titled “Strategies For Emitting Claims”](#strategies-for-emitting-claims) You can use different strategies to determine which claims to emit based on the information in the profile context. * emit claims based on the requested claim types * emit claims based on user or client identity * always emit certain claims #### Emit Claims Based On The Client’s Request [Section titled “Emit Claims Based On The Client’s Request”](#emit-claims-based-on-the-clients-request) You can filter the claims you emit to only include the claim types requested by the client. If your client requires consent, this will also give end users the opportunity to approve or deny sharing those claims with the client. Clients can request claims in several ways: * Requesting an [IdentityResource](/identityserver/fundamentals/resources/identity/) by including the scope parameter for the `IdentityResource` requests the claims associated with the `IdentityResource` in its `UserClaims` collection. * Requesting an [ApiScope](/identityserver/fundamentals/resources/api-scopes/) by including the scope parameter for the `ApiScope` requests the claims associated with the `ApiScope` in its `UserClaims` collection. * Requesting an [ApiResource](/identityserver/fundamentals/resources/api-resources/) by including the resource indicator parameter for the `ApiResource` requests the claims associated with the `ApiResource` in its `UserClaims` collection. The `RequestedClaimTypes` property of the `ProfileDataRequestContext` contains the collection of claims requested by the client. If your profile service extends the `DefaultProfileService`, you can use its `AddRequestedClaims` method to add only requested and approved claims. The intent is that your profile service can retrieve claim data and then filter that claim data based on what was requested by the client. For example: ```csharp public class SampleProfileService : DefaultProfileService { public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context) { var claims = await GetClaimsAsync(context); context.AddRequestedClaims(claims); } private async Task> GetClaimsAsync(ProfileDataRequestContext context) { // Your implementation that retrieves claims goes here } } ``` #### Always Emit Claims [Section titled “Always Emit Claims”](#always-emit-claims) We generally recommend emitting claims based on the requested claim types, as that respects the scopes and resources requested by the client and gives the end user an opportunity to consent to this sharing of information. However, if you have claims that don’t need to follow such rules, such as claims that are an integral part of the user’s identity and that are needed in most scenarios, they can be added by directly updating the `context.IssuedClaims` collection. For example: ```csharp public class SampleProfileService : DefaultProfileService { public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context) { var claims = await GetClaimsAsync(context); context.IssuedClaims.AddRange(claims); } private async Task GetClaimsAsync(ProfileDataRequestContext context) { // Your implementation that retrieves claims goes here } } ``` #### Emit Claims Based On The User Or Client Identity [Section titled “Emit Claims Based On The User Or Client Identity”](#emit-claims-based-on-the-user-or-client-identity) Finally, you might have claims that are only appropriate for certain users or clients. Your `ProfileService` can add whatever filtering or logic that you like. ### The Subject Of The ProfileDataRequestContext [Section titled “The Subject Of The ProfileDataRequestContext”](#the-subject-of-the-profiledatarequestcontext) When the profile service is invoked to add claims to tokens, the `Subject` property on the `ProfileDataRequestContext` contains the principal that was issued during user sign-in. Typically, the profile service will source some claims from the `Subject` and others from databases or other data sources. When the profile service is called for requests to the [userinfo endpoint](/identityserver/reference/v8/endpoints/userinfo/), the `Subject` property will not contain the principal issued during user sign-in, since userinfo calls don’t happen as part of a session. Instead, the `Subject` property will contain a claims principal populated with the claims in the access token used to authorize the userinfo call. You can check the caller of the profile service by querying the `Caller` property on the context. ## Client Claims [Section titled “Client Claims”](#client-claims) Client claims are a set of pre-defined claims that are emitted in access tokens. They are defined on a per-client basis, meaning that each client can have its own unique set of client claims. The following shows an example of a client that is associated with a certain customer in your system: ```csharp var client = new Client { ClientId = "client", // rest omitted Claims = { new ClientClaim("customer_id", "123") } }; ``` To avoid accidental collision with user claims, client claims are prefixed with `client_`. For example, the above `ClientClaim` would be emitted as the `client_customer_id` claim type in access tokens. You can change or remove this prefix by setting the `ClientClaimsPrefix` on the [client definition](/identityserver/reference/v8/models/client/#token). ### Setting Client Claims Dynamically [Section titled “Setting Client Claims Dynamically”](#setting-client-claims-dynamically) If you want to set client claims dynamically, you could either do that at client load time (via a client [store](/identityserver/data) implementation), or using a [custom token request validator](/identityserver/tokens/dynamic-validation/). ## Claim Serialization [Section titled “Claim Serialization”](#claim-serialization) Claim values are serialized based on the `ClaimValueType` of the claim. Claims that don’t specify a `ClaimValueType` are serialized as strings. Claims that specify a `ClaimValueType` of `System.Security.Claims.ClaimValueTypes.Integer`, `System.Security.Claims.ClaimValueTypes.Integer32`, `System.Security.Claims.ClaimValueTypes.Integer64`, `System.Security.Claims.ClaimValueTypes.Double`, or `System.Security.Claims.ClaimValueTypes.Boolean` are parsed as the corresponding type, while those that specify `IdentityServerConstants.ClaimValueTypes.Json` are serialized to JSON using `System.Text.Json`. ----- # Clients > Learn about configuring and managing client applications that can request tokens from IdentityServer [Clients](/identityserver/overview/terminology/#client), or [connected applications](/general/glossary/#connected-application), represent applications that can request tokens from your IdentityServer. The details vary, but you typically define the following common settings for a client: * a unique client ID * a secret if needed * the allowed interactions with the token service (called a grant type) * a network location where identity and/or access token gets sent to (called a redirect URI) * a list of scopes (aka resources) the client is allowed to access ## Defining A Client For Server To Server Communication [Section titled “Defining A Client For Server To Server Communication”](#defining-a-client-for-server-to-server-communication) In this scenario no interactive user is present - a service (i.e. the client) wants to communicate with an API (i.e. the resource that supports the scope): ```csharp public class Clients { public static IEnumerable Get() { return new List { new Client { ClientId = "service.client", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = { "api1", "api2.read_only" } } }; } } ``` ## Defining An Interactive Application: Authentication And Delegated API Access [Section titled “Defining An Interactive Application: Authentication And Delegated API Access”](#defining-an-interactive-application-authentication-and-delegated-api-access) Interactive applications (e.g. web applications or native desktop/mobile applications) use the authorization code flow. This flow gives you the best security because the access tokens are transmitted via back-channel calls only (and gives you access to refresh tokens): ```csharp var interactiveClient = new Client { ClientId = "interactive", AllowedGrantTypes = GrantTypes.Code, AllowOfflineAccess = true, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:21402/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:21402/" }, FrontChannelLogoutUri = "http://localhost:21402/signout-oidc", AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, "api1", "api2.read_only" }, }; ``` ## Defining Clients In `appsettings.json` [Section titled “Defining Clients In appsettings.json”](#defining-clients-in-appsettingsjson) The `AddInMemoryClients` extensions method also supports adding clients from the ASP.NET Core configuration file. This allows you to define static clients directly from the appsettings.json file: appsettings.json ```json { "IdentityServer": { "Clients": [ { "Enabled": true, "ClientId": "local-dev", "ClientName": "Local Development", "ClientSecrets": [ { "Value": "" } ], "AllowedGrantTypes": [ "client_credentials" ], "AllowedScopes": [ "api1" ] } ] } } ``` Then pass the configuration section to the `AddInMemoryClients` method: Program.cs ```csharp AddInMemoryClients(configuration.GetSection("IdentityServer:Clients")) ``` ----- # Hosting > Learn how to host and configure Duende IdentityServer in ASP.NET Core applications by adding services and middleware to the pipeline You add the Duende IdentityServer engine to any ASP.NET Core application by adding the relevant services to the dependency injection (DI) system and adding the middleware to the processing pipeline. ## Dependency Injection System [Section titled “Dependency Injection System”](#dependency-injection-system) You add the necessary services to the ASP.NET Core service provider by calling `AddIdentityServer` at application startup: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { // ... }); ``` Many of the fundamental configuration settings can be set on the options. See the [`IdentityServerOptions`](/identityserver/reference/v8/options/) reference for more details. The builder object has a number of extension methods to add additional services to the ASP.NET Core service provider. You can see the full list in the [reference](/identityserver/reference/v8/di/) section, but very commonly you start by adding the configuration stores for clients and resources, e.g.: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) ``` The above is using the in-memory stores, but we also support EntityFramework-based implementations and custom stores. See [here](/identityserver/data) for more information. ----- # Key Management > Learn how to manage cryptographic keys for token signing in IdentityServer using automatic or static key management Duende IdentityServer issues several types of tokens that are cryptographically signed, including identity tokens, JWT access tokens, and logout tokens. To create those signatures, IdentityServer needs key material. That key material can be configured automatically, by using the Automatic Key Management feature, or manually, by loading the keys from a secured location with static configuration. IdentityServer supports [signing](https://tools.ietf.org/html/rfc7515) tokens using the `RS`, `PS` and `ES` family of cryptographic signing algorithms. ## Automatic Key Management [Section titled “Automatic Key Management”](#automatic-key-management) Duende IdentityServer can manage signing keys for you using the Automatic Key Management feature. Automatic Key Management follows best practices for handling signing key material, including * automatic rotation of keys * secure storage of keys at rest using data protection * announcement of upcoming new keys * maintenance of retired keys ### Configuration [Section titled “Configuration”](#configuration) Automatic Key Management is configured by the options in the `KeyManagement` property on the [`IdentityServerOptions`](/identityserver/reference/v8/options/#key-management). ### Managed Key Lifecycle [Section titled “Managed Key Lifecycle”](#managed-key-lifecycle) Keys created by Automatic Key Management move through several phases. First, new keys are announced, that is, they are added to the list of keys in discovery, but not yet used for signing. After a configurable amount of `PropagationTime`, keys are promoted to be signing credentials, and will be used by IdentityServer to sign tokens. Eventually, enough time will pass that the key is older than the configurable `RotationTime`, at which point the key is retired, but kept in discovery for a configurable `RetentionDuration`. After the `RetentionDuration` has passed, keys are removed from discovery, and optionally deleted. The default is to rotate keys every 90 days, announce new keys with 14 days of propagation time, retain old keys for a duration of 14 days, and to delete keys when they are retired. ``` --- config: theme: default gantt: useWidth: 800 useMaxWidth: false --- gantt title 90 Day Key Rotation Schedule per Signing Algorithm todayMarker off section RS256 Signing :active, rsa_s, 2025-01-01, 76d Retire :rsa_r, after rsa_s, 14d Delete :crit, rsa_d, after rsa_r, 1d Announce :rsa_na, 2025-03-03, 14d Signing :active, rsa_ns, after rsa_na, 62d Retire :rsa_nr, after rsa_ns, 14d Delete :crit, rsa_nd, after rsa_nr, 1d section ES256 Signing :active, es_s, 2025-01-01, 76d Retire :es_r, after es_s, 14d Delete :crit, :es_d, after es_r, 1d Announce :es_na, 2025-03-03, 14d Signing :active, es_ns, after es_na, 62d Retire :es_nr, after es_ns, 14d Delete :crit, es_nd, after es_nr, 1d ``` All of these options are configurable in the `KeyManagement` options. For example: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { // new key every 30 days options.KeyManagement.RotationInterval = TimeSpan.FromDays(30); // announce new key 2 days in advance in discovery options.KeyManagement.PropagationTime = TimeSpan.FromDays(2); // keep old key for 7 days in discovery for validation of tokens options.KeyManagement.RetentionDuration = TimeSpan.FromDays(7); // don't delete keys after their retention period is over options.KeyManagement.DeleteRetiredKeys = false; }); ``` ### Key Storage [Section titled “Key Storage”](#key-storage) Automatic Key Management stores keys through the abstraction of the [`ISigningKeyStore`](/identityserver/data/operational/#keys). You can implement this extensibility point to customize the storage of your keys (perhaps using a key vault of some kind), or use one of the two implementations of the `ISigningKeyStore` that we provide: * the default `FileSystemKeyStore`, which writes keys to the file system. * the [EntityFramework operational store](/identityserver/data/ef/#operational-store) which writes keys to a database using EntityFramework. The default `FileSystemKeyStore` writes keys to the `KeyPath` directory configured in your IdentityServer host, which defaults to the directory `~/keys`. This directory should be excluded from source control. If you are deploying in a load balanced environment and wish to use the `FileSystemKeyStore`, all instances of IdentityServer will need read/write access to the `KeyPath`. Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { // set path to store keys options.KeyManagement.KeyPath = "/home/shared/keys"; }); ``` ### Encryption Of Keys at Rest [Section titled “Encryption Of Keys at Rest”](#encryption-of-keys-at-rest) The keys created by Automatic Key Management are sensitive cryptographic secrets that should be encrypted at rest. By default, keys managed by Automatic Key Management are protected at rest using ASP.NET Core Data Protection. This is controlled with the `DataProtectKeys` flag, which is on by default. We recommend leaving this flag on unless you are using a custom `ISigningKeyStore` to store your keys in a secure location that will ensure keys are encrypted at rest. For example, if you implement the `ISigningKeyStore` to store your keys in Azure Key Vault, you could safely disabled `DataProtectKeys`, relying on Azure Key Vault to encrypt your signing keys at rest. See the [deployment](/identityserver/deployment/) section for more information about setting up data protection. ### Manage Multiple Keys [Section titled “Manage Multiple Keys”](#manage-multiple-keys) By default, Automatic Key Management will maintain a signing credential and validation keys for a single cryptographic algorithm (`RS256`). You can specify multiple keys, algorithms, and if those keys should additionally get wrapped in an X.509 certificate. Automatic key management will create and rotate keys for each signing algorithm you specify. ```csharp options.KeyManagement.SigningAlgorithms = new[] { // RS256 for older clients (with additional X.509 wrapping) new SigningAlgorithmOptions(SecurityAlgorithms.RsaSha256) { UseX509Certificate = true }, // PS256 new SigningAlgorithmOptions(SecurityAlgorithms.RsaSsaPssSha256), // ES256 new SigningAlgorithmOptions(SecurityAlgorithms.EcdsaSha256) }; ``` ## Static Key Management [Section titled “Static Key Management”](#static-key-management) Instead of using [Automatic Key Management](#automatic-key-management), IdentityServer’s signing keys can be set manually. Automatic Key Management is generally recommended, but if you want to explicitly control your keys statically, or you have a license that does not include the feature, you will need to manually manage your keys. With static configuration you are responsible for secure storage, loading and rotation of keys. ## Disabling Key Management [Section titled “Disabling Key Management”](#disabling-key-management) The automatic key management feature can be disabled by setting the `Enabled` flag to `false` on the `KeyManagement` property of [`IdentityServerOptions`](/identityserver/reference/v8/options/#key-management): Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); ``` ## Key Creation [Section titled “Key Creation”](#key-creation) Without automatic key management, you are responsible for creating your own cryptographic keys. Such keys can be created with many tools. Some options include: * Use the PowerShell commandlet [New-SelfSignedCertificate](https://learn.microsoft.com/en-us/powershell/module/pki/new-selfsignedcertificate?view=windowsserver2022-ps) to self-sign your own certificate * Create certificates using [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/certificates/certificate-scenarios) * Create certificates using your Public Key Infrastructure. * Create certificates using C# (see below) ```csharp var name = "MySelfSignedCertificate"; // Generate a new key pair using var rsa = RSA.Create(keySizeInBits: 2048); // Create a certificate request var request = new CertificateRequest( subjectName: $"CN={name}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1 ); // Self-sign the certificate var certificate = request.CreateSelfSigned( DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1) ); // Export the certificate to a PFX file var pfxBytes = certificate.Export( // TODO: pick a format X509ContentType.Pfx, // TODO: change the password password: "password" ); File.WriteAllBytes($"{name}.pfx", pfxBytes); Console.Write(certificate); Console.WriteLine("Self-signed certificate created successfully."); Console.WriteLine($"Certificate saved to {name}.pfx"); ``` ## Adding Keys [Section titled “Adding Keys”](#adding-keys) Signing keys are added with the [`AddSigningCredential`](/identityserver/reference/v8/di/#signing-keys) configuration method: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(); var key = LoadKeyFromVault(); // (Your code here) idsvrBuilder.AddSigningCredential(key, SecurityAlgorithms.RsaSha256); ``` You can call `AddSigningCredential` multiple times if you want to register more than one signing key. When you register multiple signing algorithms, the first one added will be the default used for signing tokens. Client and API resource definitions both have an `AllowedTokenSigningAlgorithm` property to override the default on a per resource and client basis. Another configuration method called `AddValidationKey` can be called to register public keys that should be accepted for token validation. ## Key Storage [Section titled “Key Storage”](#key-storage-1) With automatic key management disabled, secure storage of the key material is left to you. This key material should be treated as highly sensitive. Key material should be encrypted at rest, and access to it should be restricted. Loading a key from disk into memory can be done using the `X509CertificateLoader` found in .NET assuming your hosting environment has proper security practices in place. ```csharp // load certificate from disk var bytes = File.ReadAllBytes("mycertificate.pfx"); var importedCertificate = X509CertificateLoader.LoadPkcs12(bytes, "password"); ``` You may also choose to load a certificate from the current environment’s key store using the `X509Store` class. ```csharp // Pick the appropriate StoreName and StoreLocation var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); var certificate = store .Certificates .First(c => c.Thumbprint == ""); ``` If you’re generating self-signed certificates using C#, you can use the `X509Store` to store the certificate into the current hosting environment as well. ```csharp // Pick the appropriate StoreName and StoreLocation var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); // push certificate into store var certificate = CreateCertificate(); store.Add(certificate); ``` ## Manual Key Rotation [Section titled “Manual Key Rotation”](#manual-key-rotation) With automatic key management disabled, you will need to rotate your keys manually. The rotation process must be done carefully for two reasons: 1. Client applications and APIs cache key material. If you begin using a new key too quickly, new tokens will be signed with a key that is not yet in their caches. This will cause clients to not be able to validate the signatures of new id tokens which will prevent users from logging in, and APIs will not be able to validate signatures of access tokens, which will prevent authorization of calls to those APIs. 2. Tokens signed with the old key material probably exist. If you tell APIs to stop using the old key too quickly, APIs will reject the signatures of old tokens, again causing authorization failures at your APIs. There are two solutions to these problems. Which one is right for you depends on the level of control you have over client applications, the amount of downtime that is acceptable, and the degree to which invalidating old tokens matters to you. ### Solution 1: Invalidate All Caches When Keys Are Rotated [Section titled “Solution 1: Invalidate All Caches When Keys Are Rotated”](#solution-1-invalidate-all-caches-when-keys-are-rotated) One solution to these problems is to invalidate the caches in all the client applications and APIs immediately after the key is rotated. In ASP.NET, the simplest way to do so is to restart the hosting process, which clears the cached signing keys of the authentication middleware. This is only appropriate if all the following are true: * You have control over the deployment of all the client applications. * You can tolerate a maintenance window in which your services are all restarted. * You don’t mind that users will need to log in again after the key is rotated. ### Solution 2: Phased Rotation [Section titled “Solution 2: Phased Rotation”](#solution-2-phased-rotation) A more robust solution is to gradually transition from the old to the new key. This requires three phases. #### Phase 1: Announce The New Key [Section titled “Phase 1: Announce The New Key”](#phase-1-announce-the-new-key) First, announce a new key that will be used for signing in the future. During this phase, continue to sign tokens with the old key. The idea is to allow for all the applications and APIs to update their caches without any interruption in service. Configure IdentityServer for phase 1 by registering the new key as a validation key. Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var oldKey = LoadOldKeyFromVault(); var newKey = LoadNewKeyFromVault(); idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); idsvrBuilder.AddValidationKey(newKey, SecurityAlgorithms.RsaSha256) ``` Once IdentityServer is updated with the new key as a validation key, wait to proceed to phase 2 until all the applications and services have updated their signing key caches. The default cache duration in .NET is 24 hours, but this is customizable. You may also need to support clients or APIs built with other platforms or that were customized to use a different value. Ultimately you have to decide how long to wait to proceed to phase 2 in order to ensure that all clients and APIs have updated their caches. #### Phase 2: Start Signing With The New Key [Section titled “Phase 2: Start Signing With The New Key”](#phase-2-start-signing-with-the-new-key) Next, start signing tokens with the new key, but continue to publish the public key of the old key so that tokens that were signed with that key can continue to be validated. The IdentityServer configuration change needed is to swap the signing credential and validation key. Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var oldKey = LoadOldKeyFromVault(); var newKey = LoadNewKeyFromVault(); idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256) ``` Again, you need to wait to proceed to phase 3. The delay here is typically shorter, because the reason for the delay is to ensure that tokens signed with the old key remain valid until they expire. IdentityServer’s token lifetime defaults to 1 hour, though it is configurable. #### Phase 3: Remove The Old Key [Section titled “Phase 3: Remove The Old Key”](#phase-3-remove-the-old-key) Once enough time has passed that there are no unexpired tokens signed with the old key, it is safe to completely remove the old key. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var newKey = LoadNewKeyFromVault(); idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); ``` ## Migrating From Static Keys To Automatic Key Management [Section titled “Migrating From Static Keys To Automatic Key Management”](#migrating-from-static-keys-to-automatic-key-management) To migrate from static to automatic key management, you can set keys manually and enable automatic key management at the same time. This allows the automatic key management feature to begin creating keys and announce them in discovery, while you continue to use the old statically configured key. Eventually you can transition from the statically configured key to the automatically managed keys. A signing key registered with `AddSigningCredential` will take precedence over any keys created by the automatic key management feature. IdentityServer will sign tokens with the credential specified in `AddSigningCredential`, but also automatically create and manage validation keys. Validation keys registered manually with `AddValidationKey` are added to the collection of validation keys along with the keys produced by automatic key management. When automatic key management is enabled and there are keys statically specified with `AddValidationkey`, the set of validation keys will include: * new keys created by automatic key management that are not yet used for signing * old keys created by automatic key management that are retired * the keys added explicitly with calls to `AddValidationKey`. The migration path from manual to automatic keys is a three-phase process, similar to the phased approach to [manual key rotation](#manual-key-rotation). The difference here is that you are phasing out the old key and allowing the automatically generated keys to phase in. ### Phase 1: Announce New (Automatic) Key [Section titled “Phase 1: Announce New (Automatic) Key”](#phase-1-announce-new-automatic-key) First, enable automatic key management while continuing to register your old key as the signing credential. In this phase, the new automatically managed key will be announced so that as client apps and APIs update their caches, they get the new key. IdentityServer will continue to sign keys with your old static key. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); var oldKey = LoadOldKeyFromVault(); idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); ``` Wait until all APIs and applications have updated their signing key caches, and then proceed to phase 2. ### Phase 2: Start Signing With The New (Automatic) Key [Section titled “Phase 2: Start Signing With The New (Automatic) Key”](#phase-2-start-signing-with-the-new-automatic-key) Next, switch to using the new automatically managed keys for signing, but still keep the old key for validation purposes. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); var oldKey = LoadOldKeyFromVault(); idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256); ``` Keep the old key as a validation key until all tokens signed with that key are expired, and then proceed to phase 3. ### Phase 3: Drop the old key [Section titled “Phase 3: Drop the old key”](#phase-3-drop-the-old-key) Now the static key configuration can be removed entirely. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); ``` ----- # ASP.NET Core OpenID Connect Handler Events > ASP.NET Core's OpenID Connect handler events, what they are, and why you might want to use them. The ASP.NET Core [OpenID Connect handler](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnecthandler?view=aspnetcore-9.0) exposes events that a client can subscribe to intercept the OpenID Connect protocol flow. Understanding these events is important to understanding how to customize the OpenID Connect protocol flow from the client. We’ll cover each of the events, what they are, and why you might want to subscribe to them. To use the `OpenIdConnectHandler` in your client applications, you will first need to install the `Microsoft.AspNetCore.Authentication.OpenIdConnect` NuGet package. ```bash dotnet package add Microsoft.AspNetCore.Authentication.OpenIdConnect ``` Followed by adding the `OpenIdConnectHandler` to your application. Program.cs ```csharp builder.Services.AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("cookie", options => { options.Cookie.Name = "__Host-bff"; options.Cookie.SameSite = SameSiteMode.Strict; }) .AddOpenIdConnect("oidc", options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }); ``` From here you can use the `options.Events` property to subscribe to the events you want to use. Let’s look at each of the events in more detail. ## OpenID Connect Events [Section titled “OpenID Connect Events”](#openid-connect-events) All events either occur before a request is sent to the identity provider, or after a response is received from the identity provider. Understanding the direction of these events can help you determine when to subscribe to them. Let’s call events coming from the identity provider **incoming** and events going to the identity provider **outgoing** for an easier understanding. | **Event Name** | **Usage** | | ---------------------------------------- | ------------ | | `OnAuthenticationFailed` | **Incoming** | | `OnAuthorizationCodeReceived` | **Incoming** | | `OnMessageReceived` | **Incoming** | | `OnRedirectToIdentityProvider` | **Outgoing** | | `OnRedirectToIdentityProviderForSignOut` | **Outgoing** | | `OnSignedOutCallbackRedirect` | **Outgoing** | | `OnRemoteSignOut` | **Incoming** | | `OnTokenResponseReceived` | **Incoming** | | `OnTokenValidated` | **Incoming** | | `OnUserInformationReceived` | **Incoming** | | `OnTicketReceived` | **Incoming** | | `OnPushAuthorization` (**.NET 9+ only**) | **Outgoing** | ## Commonly Subscribed Events [Section titled “Commonly Subscribed Events”](#commonly-subscribed-events) While there are many events available in the `OpenIdConnectEvents` class, only a few are commonly subscribed. We suggest you start with the most commonly subscribed events and then subscribe to the remaining events as needed. For ASP.NET Core developers, the most commonly subscribed events are: 1. **`OnRedirectToIdentityProvider`**: Useful for customizing login requests (e.g., appending extra parameters). 2. **`OnRedirectToIdentityProviderForSignOut`**: Often required to customize the behavior of sign-out requests. 3. **`OnTokenValidated`**: Frequently used to customize the claims processing or validate custom claims included in the ID token. 4. **`OnUserInformationReceived`**: Sometimes used to process additional user data retrieved from the UserInfo endpoint (if enabled). ## Descriptions [Section titled “Descriptions”](#descriptions) ### OnAuthenticationFailed [Section titled “OnAuthenticationFailed”](#onauthenticationfailed) * **When called**: Triggered whenever an exception occurs during the authentication process. This event provides an opportunity to handle or log errors. * **How often**: Only called when an authentication error happens. * **Example use case**: Use this event to log detailed error messages or display a custom error page to the user instead of the default behavior. * **Commonly subscribed**: No, unless you need specific error-handling logic. ### OnAuthorizationCodeReceived [Section titled “OnAuthorizationCodeReceived”](#onauthorizationcodereceived) * **When called**: Invoked after an authorization code is received and before it is redeemed for tokens. * **How often**: Called once per successful authorization code flow request. * **Example use case**: Validate the authorization code or add extra functionality (e.g., logging or monitoring) when the code is received. * **Commonly subscribed**: Rarely, unless custom logic is required before token redemption. ### OnMessageReceived [Section titled “OnMessageReceived”](#onmessagereceived) * **When called**: Triggered when a protocol message (e.g., an authorization response, logout request) is first received. * **How often**: Called once per incoming protocol message. * **Example use case**: Inspect or modify protocol messages for debugging or to handle additional query parameters passed by the identity provider. * **Commonly subscribed**: No, unless advanced customization is needed. ### OnRedirectToIdentityProvider [Section titled “OnRedirectToIdentityProvider”](#onredirecttoidentityprovider) * **When called**: Invoked when redirecting the user to the identity provider for authentication. You can modify the outgoing authentication request. * **How often**: Called once per user authentication attempt (e.g., a “login”). * **Example use case**: Add custom query parameters to the request or modify the state parameter. * **Commonly subscribed**: Yes—often used to customize the authentication request. ### OnRedirectToIdentityProviderForSignOut [Section titled “OnRedirectToIdentityProviderForSignOut”](#onredirecttoidentityproviderforsignout) * **When called**: Triggered before redirecting the user to the identity provider to start the sign-out process. * **How often**: Called once per user sign-out request. * **Example use case**: Modify the logout request, such as appending additional parameters. * **Commonly subscribed**: Yes, if signing out requires customization. ### OnSignedOutCallbackRedirect [Section titled “OnSignedOutCallbackRedirect”](#onsignedoutcallbackredirect) * **When called**: Invoked after a remote sign-out is completed and before redirecting the user to the `SignedOutRedirectUri`. * **How often**: Called once per remote sign-out. * **Example use case**: Log or perform business logic after the remote sign-out. * **Commonly subscribed**: Rarely, unless additional behavior is needed. ### OnRemoteSignOut [Section titled “OnRemoteSignOut”](#onremotesignout) * **When called**: Called when a remote sign-out request is received on the `RemoteSignOutPath` endpoint. * **How often**: Called once per incoming remote sign-out request. * **Example use case**: Perform cleanup tasks such as clearing local session data upon receiving a sign-out request from the identity provider. * **Commonly subscribed**: Rarely, but important in distributed or multi-tenant systems. ### OnTokenResponseReceived [Section titled “OnTokenResponseReceived”](#ontokenresponsereceived) * **When called**: Triggered after an authorization code exchange is completed and the token endpoint returns tokens. * **How often**: Called once per token request. * **Example use case**: Log or debug the token response, or inspect additional data included in the token response. * **Commonly subscribed**: No, unless debugging or inspection of tokens is required. ### OnTokenValidated [Section titled “OnTokenValidated”](#ontokenvalidated) * **When called**: Invoked after the ID token has been validated and an `AuthenticationTicket` has been created. * **How often**: Called once per token validation process. * **Example use case**: Add or modify claims in the `ClaimsPrincipal` or validate custom claims included in the token. * **Commonly subscribed**: Yes—this is one of the most commonly used events for customizing claims. ### OnUserInformationReceived [Section titled “OnUserInformationReceived”](#onuserinformationreceived) * **When called**: Triggered when retrieving user information from the UserInfo endpoint (if `GetClaimsFromUserInfoEndpoint = true`). * **How often**: Called once per user information fetch (e.g., per login). * **Example use case**: Extend or modify user claims based on the additional information retrieved from the UserInfo endpoint. * **Commonly subscribed**: Sometimes, if extra claims processing is required. ### OnTicketReceived [Section titled “OnTicketReceived”](#onticketreceived) * **When called**: Invoked after the OpenID Connect authentication flow is complete and before the authentication ticket is returned. * **How often**: Called once per successful authentication flow completion. * **Example use case**: Modify the final authentication ticket, perform additional validation, or execute custom logic before completing the authentication process. * **Commonly subscribed**: Sometimes, when final authentication customization is needed before completing the flow or for diagnostics and troubleshooting purposes. ### OnPushAuthorization [Section titled “OnPushAuthorization”](#onpushauthorization) * **When called**: Invoked before sending authorization parameters using the Pushed Authorization Request (PAR) mechanism. * **How often**: Called once per outgoing PAR-based authorization request. * **Example use case**: Modify or log pushed authorization parameters. * **Commonly subscribed**: Rarely, as this is used mainly in advanced scenarios. ----- # Resources > Overview of resource types in Duende IdentityServer including API resources, identity resources, API scopes, and resource isolation concepts The ultimate job of Duende IdentityServer is to control access to resources. ## API Resources [Section titled “API Resources”](#api-resources) In Duende IdentityServer, the *ApiResource* class allows for some additional organization and grouping and isolation of scopes and providing some common settings. [Read More](/identityserver/fundamentals/resources/api-resources/) ## Identity Resources [Section titled “Identity Resources”](#identity-resources) An identity resource is a named group of claims about a user that can be requested using the *scope* parameter. The OpenID Connect specification [suggests](https://openid.net/specs/openid-connect-core-1_0.html#scopeclaims) a couple of standard scope name to claim type mappings that might be useful to you for inspiration, but you can freely design them yourself. [Read More](/identityserver/fundamentals/resources/identity/) ## API Scopes [Section titled “API Scopes”](#api-scopes) Designing your API surface can be a complicated task. Duende IdentityServer provides a couple of primitives to help you with that. The original OAuth 2.0 specification has the concept of scopes, which is just defined as *the scope of access* that the client requests. Technically speaking, the *scope* parameter is a list of space delimited values - you need to provide the structure and semantics of it. In more complex systems, often the notion of a *resource* is introduced. This might be e.g. a physical or logical API. In turn each API can potentially have scopes as well. Some scopes might be exclusive to that resource, and some scopes might be shared. [Read More](/identityserver/fundamentals/resources/api-scopes/) ## Resources Isolation [Section titled “Resources Isolation”](#resources-isolation) OAuth itself only knows about scopes - the (API) resource concept does not exist from a pure protocol point of view. This means that all the requested scope and audience combination get merged into a single access token. This has a couple of downsides, e.g. * tokens can become very powerful (and big) * if such a token leaks, it allows access to multiple resources * resources within that single token might have conflicting settings, e.g. * user claims of all resources share the same token * resource specific processing like signing or encryption algorithms conflict * without sender-constraints, a resource could potentially re-use (or abuse) a token to call another contained resource directly To solve this problem [RFC 8707](https://tools.ietf.org/html/rfc8707) adds another request parameter for the authorize and token endpoint called *resource*. This allows requesting a token for a specific resource (in other words - making sure the audience claim has a single value only, and all scopes belong to that single resource). [Read More](/identityserver/fundamentals/resources/isolation/) ----- # API Resources > Learn how API Resources in Duende IdentityServer help organize and group scopes, manage token claims, and control access token properties When the API/resource surface gets larger, a flat list of scopes might become hard to manage. In Duende IdentityServer, the `ApiResource` class allows for some additional organization and grouping and isolation of scopes and providing some common settings. Let’s use the following scope definition as an example: ```csharp public static IEnumerable GetApiScopes() { return new List { // invoice API specific scopes new ApiScope(name: "invoice.read", displayName: "Reads your invoices."), new ApiScope(name: "invoice.pay", displayName: "Pays your invoices."), // customer API specific scopes new ApiScope(name: "customer.read", displayName: "Reads you customers information."), new ApiScope(name: "customer.contact", displayName: "Allows contacting one of your customers."), // shared scopes new ApiScope(name: "manage", displayName: "Provides administrative access."), new ApiScope(name: "enumerate", displayName: "Allows enumerating data.") }; } ``` With `ApiResource` you can now create two logical APIs and their corresponding scopes: ```csharp public static readonly IEnumerable GetApiResources() { return new List { new ApiResource("invoice", "Invoice API") { Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" } }, new ApiResource("customer", "Customer API") { Scopes = { "customer.read", "customer.contact", "manage", "enumerate" } } }; } ``` Using the API resource grouping gives you the following additional features * support for the JWT `aud` claim. The value(s) of the audience claim will be the name of the API resource(s) * support for adding common user claims across all contained scopes * support for introspection by assigning an API secret to the resource * support for configuring the access token signing algorithm for the resource Let’s have a look at some example access tokens for the above resource configuration. Client requests: *`invoice.read`* and *`invoice.pay`*: ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": "invoice", "scope": "invoice.read invoice.pay" } ``` Client requests: *`invoice.read`* and *`customer.read`*: ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": [ "invoice", "customer" ], "scope": "invoice.read customer.read" } ``` Client requests: *`manage`*: ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": [ "invoice", "customer" ], "scope": "manage" } ``` ### Adding User Claims [Section titled “Adding User Claims”](#adding-user-claims) You can specify that an access token for an API resource (regardless of which scope is requested) should contain additional user claims. ```csharp var customerResource = new ApiResource("customer", "Customer API") { Scopes = { "customer.read", "customer.contact", "manage", "enumerate" }, // additional claims to put into access token UserClaims = { "department_id", "sales_region" } } ``` If a client now requested a scope belonging to the `customer` resource, the access token would contain the additional claims (if provided by your [profile service](/identityserver/reference/v8/services/profile-service/)). ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": [ "invoice", "customer" ], "scope": "invoice.read customer.read", "department_id": 5, "sales_region": "south" } ``` ### Setting A Signing Algorithm [Section titled “Setting A Signing Algorithm”](#setting-a-signing-algorithm) Your APIs might have certain requirements for the cryptographic algorithm used to sign the access tokens for that resource. An example could be regulatory requirements, or that you are starting to migrate your system to higher security algorithms. The following sample sets `PS256` as the required signing algorithm for the `invoices` API: ```csharp var invoiceApi = new ApiResource("invoice", "Invoice API") { Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" }, AllowedAccessTokenSigningAlgorithms = { SecurityAlgorithms.RsaSsaPssSha256 } } ``` ### Resource Isolation [Section titled “Resource Isolation”](#resource-isolation) See [Resource Isolation](/identityserver/fundamentals/resources/isolation/) for more details on how to use the `resource` parameter to request a token with scopes for a specific resource. ----- # API Scopes > Learn about API scopes in IdentityServer, how to define and use them for access control, and how they work with OAuth 2.0 Designing your API surface can be a complicated task. Duende IdentityServer provides a couple of primitives to help you with that. The original OAuth 2.0 specification has the concept of scopes, which is just defined as *the scope of access* that the client requests. Technically speaking, the `scope` parameter is a list of space delimited values - you need to provide the structure and semantics of it. In more complex systems, often the notion of a `resource` is introduced. This might be e.g. a physical or logical API. In turn each API can potentially have scopes as well. Some scopes might be exclusive to that resource, and some scopes might be shared. Let’s start with simple scopes first, and then we’ll have a look how resources can help structure scopes. ### Scopes [Section titled “Scopes”](#scopes) Let’s model something very simple - a system that has three logical operations `read`, `write`, and `delete`. You can define them using the `ApiScope` class: ```csharp public static IEnumerable GetApiScopes() { return new List { new ApiScope(name: "read", displayName: "Read your data."), new ApiScope(name: "write", displayName: "Write your data."), new ApiScope(name: "delete", displayName: "Delete your data.") }; } ``` You can then assign the scopes to various clients, e.g.: ```csharp var webViewer = new Client { ClientId = "web_viewer", AllowedScopes = { "openid", "profile", "read" } }; var mobileApp = new Client { ClientId = "mobile_app", AllowedScopes = { "openid", "profile", "read", "write", "delete" } } ``` ### Authorization Based On Scopes [Section titled “Authorization Based On Scopes”](#authorization-based-on-scopes) When a client asks for a scope (and that scope is allowed via configuration and not denied via consent), the value of that scope will be included in the resulting access token as a claim of type `scope` (for both JWTs and introspection), e.g.: ```json { "typ": "at+jwt" }. { "client_id": "mobile_app", "sub": "123", "scope": "read write delete" } ``` The consumer of the access token can use that data to make sure that the client is actually allowed to invoke the corresponding functionality. See the [APIs](/identityserver/apis) section for more information on protecting APIs with access tokens. Caution Be aware, that scopes are purely for authorizing clients, not users. In other words, the `write` scope allows the client to invoke the functionality associated with the scope and is unrelated to the user’s permission to do so. This additional user-centric authorization is application logic and not covered by OAuth, yet still possibly important to implement in your API. ### Adding User Claims [Section titled “Adding User Claims”](#adding-user-claims) You can add more identity information about the user to the access token. The additional claims added are based on the scope requested. The following scope definition tells the configuration system that when a `write` scope gets granted the `user_level` claim should be added to the access token: ```csharp var writeScope = new ApiScope( name: "write", displayName: "Write your data.", userClaims: new[] { "user_level" }); ``` This will pass the `user_level` claim as a requested claim type to the profile service, so that the consumer of the access token can use this data as input for authorization decisions or business logic. ### Parameterized Scopes [Section titled “Parameterized Scopes”](#parameterized-scopes) Sometimes scopes have a certain structure, e.g. a scope name with an additional parameter: `transaction:id` or `read_patient:patientid`. In this case you would create a scope without the parameter part and assign that name to a client, but in addition provide some logic to parse the structure of the scope at runtime using the `IScopeParser` interface or by deriving from our default implementation, e.g.: ```csharp public class ParameterizedScopeParser : DefaultScopeParser { public ParameterizedScopeParser(ILogger logger) : base(logger) { } public override void ParseScopeValue(ParseScopeContext scopeContext) { const string transactionScopeName = "transaction"; const string separator = ":"; const string transactionScopePrefix = transactionScopeName + separator; var scopeValue = scopeContext.RawValue; if (scopeValue.StartsWith(transactionScopePrefix)) { // we get in here with a scope like "transaction:something" var parts = scopeValue.Split(separator, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2) { scopeContext.SetParsedValues(transactionScopeName, parts[1]); } else { scopeContext.SetError("transaction scope missing transaction parameter value"); } } else if (scopeValue != transactionScopeName) { // we get in here with a scope not like "transaction" base.ParseScopeValue(scopeContext); } else { // we get in here with a scope exactly "transaction", which is to say we're ignoring it // and not including it in the results scopeContext.SetIgnore(); } } } ``` You then have access to the parsed value throughout the pipeline, e.g. in the profile service: ```csharp public class HostProfileService : IProfileService { public override async Task GetProfileDataAsync(ProfileDataRequestContext context) { var transaction = context.RequestedResources.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction"); if (transaction?.ParsedParameter != null) { context.IssuedClaims.Add(new Claim("transaction_id", transaction.ParsedParameter)); } } } ``` ----- # Identity Resources > Learn about identity resources in Duende IdentityServer - named groups of claims about users that can be requested using scopes An identity resource is a named group of claims about a user that can be requested using the `scope` parameter. The OpenID Connect specification [suggests](https://openid.net/specs/openid-connect-core-1_0.html#scopeclaims) a couple of standard scope name to claim type mappings that might be useful to you for inspiration, but you can freely design them yourself. One of them is actually mandatory, the `openid` scope, which tells the provider to return the `sub` (subject id) claim in the identity token. This is how you could define the openid scope in code: ```csharp public static IEnumerable GetIdentityResources() { return new List { new IdentityResource( name: "openid", userClaims: new[] { "sub" }, displayName: "Your user identifier") }; } ``` But since this is one of the standard scopes from the spec you can shorten that to: ```csharp public static IEnumerable GetIdentityResources() { return new List { new IdentityResources.OpenId() }; } ``` The following example shows a custom identity resource called `profile` that represents the display name, email address and website claim: ```csharp public static IEnumerable GetIdentityResources() { return new List { new IdentityResource( name: "profile", userClaims: new[] { "name", "email", "website" }, displayName: "Your profile data") }; } ``` Once the resource is defined, you can give access to it to a client via the `AllowedScopes` option (other properties omitted): ```csharp var client = new Client { ClientId = "client", AllowedScopes = { "openid", "profile" } }; ``` The client can then request the resource using the scope parameter (other parameters omitted): ```plaintext https://demo.duendesoftware.com/connect/authorize?client_id=client&scope=openid profile ``` IdentityServer will then use the scope names to create a list of requested claim types, and present that to your implementation of the [profile service](/identityserver/reference/v8/services/profile-service/). ----- # Overview > Learn about isolating OAuth resources and using the resource parameter to control access token scope and audience OAuth itself only knows about scopes - the (API) resource concept does not exist from a pure protocol point of view. This means that all the requested scope and audience combinations get merged into a single access token. This has a couple of downsides: * Tokens can become very powerful (and large) * If such a token leaks, it allows access to multiple resources * Resources within that single token might have conflicting settings, e.g. * User claims of all resources share the same token * Resource-specific processing like signing or encryption algorithms conflict * Without sender-constraints, a resource could potentially re-use (or abuse) a token to call another contained resource directly ### Audience Ambiguity [Section titled “Audience Ambiguity”](#audience-ambiguity) In a system with multiple APIs (e.g., Shipping, Invoicing and Inventory APIs), a single token often lists all of them as valid audiences. ```json { "iss": "https://demo.duendesoftware.com", "aud": ["invoice_api", "shipping_api", "inventory_api"], "scope": ["invoice.read", "shipping.write", "inventory.read"] } ``` This violates the Principle of Least Privilege. If this token is leaked from the Inventory API, it can be used to call the Invoice API. To solve this problem [RFC 8707](https://tools.ietf.org/html/rfc8707) adds another request parameter for the authorize and token endpoint called `resource`. This allows requesting a token for a specific resource (in other words - making sure the audience claim has a single value only, and all scopes belong to that single resource). ## Using The Resource Parameter [Section titled “Using The Resource Parameter”](#using-the-resource-parameter) Let’s assume you have the following resource design and that the client is allowed access to all scopes: ApiResources.cs ```csharp var resources = new[] { new ApiResource("urn:invoices") { Scopes = { "read", "write" } }, new ApiResource("urn:products") { Scopes = { "read", "write" } } }; ``` If the client would request a token for the `read` scope, the resulting access token would contain the audience of both the invoice and the products API and thus be accepted at both APIs. ### Machine to Machine Scenarios [Section titled “Machine to Machine Scenarios”](#machine-to-machine-scenarios) If the client in addition passes the `resource` parameter specifying the name of the resource where it wants to use the access token, the token engine can `down-scope` the resulting access token to the single resource, e.g.: ```text POST /token grant_type=client_credentials& client_id=client& client_secret=...& scope=read& resource=urn:invoices ``` Thus resulting in an access token like this (some details omitted): ```json { "aud": ["urn:invoice"], "scope": "read", "client_id": "client" } ``` ### Interactive Applications [Section titled “Interactive Applications”](#interactive-applications) The authorize endpoint supports the `resource` parameter as well, e.g.: ```text GET /authorize?client_id=client&response_type=code&scope=read&resource=urn:invoices ``` Once the front-channel operations are done, the resulting code can be redeemed by passing the resource name on the token endpoint: ```text POST /token grant_type=authorization_code& client_id=client& client_secret=...& authorization_code=...& redirect_uri=...& resource=urn:invoices ``` ### Requesting Access To Multiple Resources [Section titled “Requesting Access To Multiple Resources”](#requesting-access-to-multiple-resources) It is also possible to request access to multiple resources. This will result in multiple access tokens - one for each request resource. ```text GET /authorize?client_id=client&response_type=code&scope=read offline_access&resource=urn:invoices&resource=urn:products ``` When you redeem the code, you need to specify for which resource you want to have an access token, e.g.: ```text POST /token grant_type=authorization_code& client_id=client& client_secret=...& authorization_code=...& redirect_uri=...& resource=urn:invoices ``` This will return an access token for the invoices API and a refresh token. If you want to also retrieve the access token for the products API, you use the refresh token and make another roundtrip to the token endpoint. ```text POST /token grant_type=refresh_token& client_id=client& client_secret=...& refresh_token=...& resource=urn:products ``` The end-result will be that the client has two access tokens - one for each resource and can manage their lifetime via the refresh token. ## Enforcing Resource Isolation [Section titled “Enforcing Resource Isolation”](#enforcing-resource-isolation) All examples so far used the `resource` parameter optionally. If you have API resources, where you want to make sure they are not sharing access tokens with other resources, you can enforce the resource indicator, e.g.: ApiResources.cs ```csharp var resources = new[] { new ApiResource("urn:invoices") { Scopes = { "read", "write" }, RequireResourceIndicator = true }, new ApiResource("urn:products") { Scopes = { "read", "write" }, RequireResourceIndicator = true } }; ``` The `RequireResourceIndicator` property **does not** mean that clients are forced to send the `resource` parameter when they request scopes associated with the API resource. You can still request those scopes without setting the `resource` parameter (or including the resource), and IdentityServer will issue a token as long as the client is allowed to request the scopes. Instead, `RequireResourceIndicator` controls **when** the resource’s URI is included in the **audience claim** (`aud`) of the issued access token. * When `RequireResourceIndicator` is `false` (the default): IdentityServer **automatically includes** the API’s resource URI in the token’s audience if any of the resource’s scopes are requested, even if the `resource` parameter was not sent in the request or didn’t contain the resource URI. * When `RequireResourceIndicator` is `true`: The API’s resource URI will **only** be included in the audience **if the client explicitly includes the resource URI** via the `resource` parameter when requesting the token. ## .NET Client Implementation [Section titled “.NET Client Implementation”](#net-client-implementation) While the examples above show the underlying HTTP protocol, .NET clients can use the Duende libraries to handle resource indicators easily. ### Machine-to-Machine (Worker) [Section titled “Machine-to-Machine (Worker)”](#machine-to-machine-worker) When using `Duende.IdentityModel` for client credentials, you can pass the `resource` parameter using the `Parameters` dictionary: ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "invoice_worker", ClientSecret = "secret", // The scope defines the permission Scope = "invoice.read", // The parameter defines the target (RFC 8707) Resource = [ "urn:invoices" ] }); ``` ### ASP.NET Core [Section titled “ASP.NET Core”](#aspnet-core) For interactive applications using the standard OpenID Connect handler, use the `Resource` property on `OpenIdConnectOptions`: ```csharp .AddOpenIdConnect(options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive_app"; options.Scope.Add("invoice.read"); // Explicitly set the target resource here options.Resource = "urn:invoices"; options.ResponseType = "code"; options.SaveTokens = true; }); ``` Note that while the RFC allows multiple `resource` parameters, the Microsoft OpenID Connect handler only supports a single resource value here. For dynamic scenarios (e.g. multi-tenant), you can set the resource parameter in the `OnRedirectToIdentityProvider` event: ```csharp options.Events.OnRedirectToIdentityProvider = context => { var tenantSpecificResource = DetermineResource(context); // Overwrite or set the 'resource' parameter context.ProtocolMessage.SetParameter("resource", tenantSpecificResource); return Task.CompletedTask; }; ``` ----- # Isolation Sample > Learn about isolating OAuth resources and using the resource parameter to control access token scope and audience Imagine a set of services with separate APIs for handling orders and tracking inventory, an Orders API and Inventory API. Each has their own distinct set of API scopes, plus a set of scopes shared between the APIs. In addition, there’s a global scope used by legacy systems that haven’t been updated yet to use Resource Isolation. The set of scopes used by each application are: | urn:orders | urn:inventory | Not Shared with any API Resource | | ------------ | --------------- | -------------------------------- | | orders.read | inventory.read | global.audit | | orders.write | inventory.write | | | shared.read | shared.read | | The below code creates in-memory scopes, API resources, and a single client (which knows about the aforementioned resources) inside a Duende IdentityServer application. Notice that all scopes are created in a single `Scopes` collection, then the `Resources` collection groups the scopes per `ApiResource`. Finally, the `Client` includes all scopes in its `AllowedScopes` property because the client will be requesting any combination of those scopes from Duende IdentityServer. The only grouping happening is when the `ApiResource` objects link an API resource to a scope. Config.cs ```csharp // All scopes used by all API Resources and Clients public static readonly IEnumerable Scopes = [ // resource specific scopes new ApiScope("orders.read"), new ApiScope("orders.write"), new ApiScope("inventory.read"), new ApiScope("inventory.write"), // a scope shared by multiple resources new ApiScope("shared.read"), // scopes without resource association new ApiScope("global.audit"), ]; // API resources with the scopes they use public static readonly IEnumerable Resources = [ new ApiResource("urn:orders", "Orders API") { Scopes = { "orders.read", "orders.write", "shared.read" } }, new ApiResource("urn:inventory", "Inventory API") { Scopes = { "inventory.read", "inventory.write", "shared.read" }, RequireResourceIndicator = true } ]; public static readonly IEnumerable Clients = [ new Client { ClientId = "resource.isolation.demo.client", ClientSecrets = { new Secret("my-secret".Sha256()) }, ClientClaimsPrefix = "", AllowedGrantTypes = GrantTypes.ClientCredentials, // Client is allowed to access all scopes for all ApiResources AllowedScopes = { "orders.read", "orders.write", "inventory.read", "inventory.write", "shared.read", "global.audit", } } ]; ``` When requesting an `ApiResource`, IdentityServer will create a token with scopes filtered to what is supported by that `ApiResource`. Scopes are not owned by any individual `ApiResource`, and are global across your applications because internally they’re an arbitrary string. An `ApiResource` doesn’t “own” scopes, it is allowed access to those scopes. The table below shows the resulting **audience claim** (`aud`) when making requests for a token with a specific scope/resource combination. | Scopes | Resource Api | Result **audience claim** (`aud`) | | ------------------------ | ------------- | --------------------------------- | | orders.read | null | urn:orders | | inventory.read | null | NOT SET | | inventory.read | urn:inventory | urn:inventory | | orders.read global.audit | null | urn:orders | | shared.read | null | urn:orders | | orders.read shared.read | null | urn:orders | ## Experimenting with a Code Sample [Section titled “Experimenting with a Code Sample”](#experimenting-with-a-code-sample) The code for the above scenario is written out in the two tabs below. Each tab is a [C# file-based app](https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/). One is a Duende IdentityServer application with scopes, API resources, and a client. The second app is a console client that makes requests to Duende IdentityServer, each request with different combinations of scopes and resources to show the result `aud` claim. To help understand how resource isolation works, feel free to run the two apps locally and make modifications as you see fit to experiment. * Duende IdentityServer IdentityServer.cs ```csharp // Run with `dotnet run IdentityServer.cs` #:sdk Microsoft.Net.Sdk.Web #:property PublishAot=false #:package Duende.IdentityServer@8.0.0 using Duende.IdentityServer.Models; var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseUrls("https://localhost:5001"); _ = builder.Services.AddIdentityServer(options => { // emits static audience if required options.EmitStaticAudienceClaim = false; // control format of scope claim options.EmitScopesAsSpaceDelimitedStringInJwt = true; }) .AddInMemoryApiScopes(InMemoryConfig.Scopes) .AddInMemoryApiResources(InMemoryConfig.Resources) .AddInMemoryClients(InMemoryConfig.Clients); var app = builder.Build(); app.UseIdentityServer(); app.Run(); public static class InMemoryConfig { // All scopes used by all API Resources and Clients public static readonly IEnumerable Scopes = [ // resource specific scopes new ApiScope("orders.read"), new ApiScope("orders.write"), new ApiScope("inventory.read"), new ApiScope("inventory.write"), // a scope shared by multiple resources new ApiScope("shared.read"), // scopes without resource association new ApiScope("global.audit"), ]; // API resources with the scopes they use public static readonly IEnumerable Resources = [ new ApiResource("urn:orders", "Orders API") { Scopes = { "orders.read", "orders.write", "shared.read" } }, new ApiResource("urn:inventory", "Inventory API") { Scopes = { "inventory.read", "inventory.write", "shared.read" }, RequireResourceIndicator = true }, ]; public static readonly IEnumerable Clients = [ new Client { ClientId = "resource.isolation.demo.client", ClientSecrets = { new Secret("my-secret".Sha256()) }, ClientClaimsPrefix = "", AllowedGrantTypes = GrantTypes.ClientCredentials, // Client is allowed to access all scopes for all ApiResources AllowedScopes = { "orders.read", "orders.write", "inventory.read", "inventory.write", "shared.read", "global.audit", } } ]; } ``` * Client ResourceIsolationClient.cs ```csharp // Run with `dotnet run ResourceIsolationClient.cs` #:property PublishAot=false // Choose your access package library // #:package Duende.IdentityModel@8.1.0 #:package Duende.AccessTokenManagement@4.2.0 using System.Buffers.Text; using System.Text; using System.Text.Json; using Duende.IdentityModel.Client; var cache = new DiscoveryCache("https://localhost:5001"); Console.WriteLine("Access Token for scope `orders.read`"); await RequestToken(cache, scope: "orders.read", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scope `inventory.read`"); await RequestToken(cache, scope: "inventory.read", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scope `inventory.read` and resource `urn:inventory`"); await RequestToken(cache, scope: "inventory.read", resource: "urn:inventory"); Console.WriteLine(); Console.WriteLine("Access Token for scopes `orders.read global.audit`"); await RequestToken(cache, scope: "orders.read global.audit", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scope `shared.read`"); await RequestToken(cache, scope: "shared.read", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scopes `orders.read and shared.read`"); await RequestToken(cache, scope: "orders.read shared.read", resource: null); static async Task RequestToken(DiscoveryCache cache, string scope, string? resource) { var client = new HttpClient(); var disco = await cache.GetAsync(); var request = new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "resource.isolation.demo.client", ClientSecret = "my-secret", Scope = scope, }; if (!string.IsNullOrEmpty(resource)) { request.Resource.Add(resource); } var response = await client.RequestClientCredentialsTokenAsync(request); Show(response); } static void Show(TokenResponse response) { if (!response.IsError) { if (response.AccessToken?.Contains('.') is true) { var parts = response.AccessToken.Split('.'); var claims = parts[1]; var raw = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(claims)); var doc = JsonDocument.Parse(raw).RootElement; var json = JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }); Console.WriteLine(json); } else { Console.WriteLine($"Token response: {response.Json}"); } } else if (response.ErrorType == ResponseErrorType.Http) { Console.WriteLine($"HTTP error: {response.Error} with HTTP status code: {response.HttpStatusCode}"); } else { Console.WriteLine($"Protocol error response: {response.Raw}"); } } ``` The code above outputs the token response for each request to Duende IdentityServer. Below is that output, but modified to be in a table to simplify | Scopes | Resource Api | Result **audience claim** (`aud`) | | ------------------------ | ------------- | --------------------------------- | | orders.read | null | urn:orders | | inventory.read | null | NOT SET | | inventory.read | urn:inventory | urn:inventory | | orders.read global.audit | null | urn:orders | | shared.read | null | urn:orders | | orders.read shared.read | null | urn:orders | ----- # Users and Logging In > Overview of user management, authentication workflows, and UI customization options in Duende IdentityServer ## Users And User Interface [Section titled “Users And User Interface”](#users-and-user-interface) The design of Duende IdentityServer allows you to use any user database and build any user interface (UI) workflow needed to satisfy your requirements. This means you have the ability to customize any UI page (registration, login, password reset, etc.), support any credential type (password, MFA, etc.), use any user database (greenfield or legacy), and/or use federated logins from any provider (social or enterprise). You have the ability to control the entire user experience while Duende IdentityServer provides the implementation of the security protocol (OpenID Connect and OAuth). ## Authorization Endpoint And Login Page Workflow [Section titled “Authorization Endpoint And Login Page Workflow”](#authorization-endpoint-and-login-page-workflow) The standard mechanism to allow users to login is for the client application to use a web browser. This is obvious if the client application is a web application, but it’s also the recommended practice for native and mobile applications. When a user must log in, the client application will redirect the user to the protocol endpoint called the [authorization endpoint](/identityserver/reference/v8/endpoints/authorize/) in your IdentityServer server to request authentication. As part of the authorize request, your IdentityServer will typically display a login page for the user to enter their credentials. Once the user has authenticated, your IdentityServer will redirect the user back to the application with the protocol response. Recall the diagram showing the relationship of your custom UI pages and the IdentityServer middleware in your IdentityServer host application: ![middleware diagram](/_astro/middleware.Cqu9tG8b_Z1LEIl7.svg) When your IdentityServer receives an authorize request, it will inspect it for a current authentication session for a user. This authentication session is based on ASP.NET Core’s authentication system and is ultimately determined by a cookie issued from your login page. If the user has never logged in there will be no cookie, and then the request to the authorize endpoint will result in a redirect to your login page. This is the entry point into your custom workflow that can take over to get the user logged in. ![sign in flow](/_astro/signin_flow.CJ2S0qPI_Zhu0yW.svg) Once the login page has finished logging in the user with the ASP.NET Core authentication system, it will redirect the user back to the authorize endpoint. This time the request to the authorize endpoint will have an authenticated session for the user, and it can then create the protocol response and redirect to the client application. ## Additional Pages [Section titled “Additional Pages”](#additional-pages) In addition to the login page, there are other pages that Duende IdentityServer expects (e.g. logout, error, consent), and you could implement custom pages as well (e.g. register, forgot password, etc.). Details about building these pages, and coverage of additional topics are in the [User Interaction](/identityserver/ui) section of this documentation. ----- # The Big Picture > An overview of modern application architecture patterns and how OpenID Connect and OAuth 2.0 protocols implemented by IdentityServer solve authentication and API access challenges Most modern applications look more or less like this: ![an architecture diagram for modern applications with clients and services](/_astro/application-architecture.BtlWYoIv_1ufBKP.svg) The most common interactions are: * Browsers communicate with web applications * Web applications communicate with web APIs (sometimes on their own, sometimes on behalf of a user) * Browser-based applications communicate with web APIs * Native applications communicate with web APIs * Server-based applications communicate with web APIs * Web APIs communicate with web APIs (sometimes on their own, sometimes on behalf of a user) Typically, each and every layer (front-end, middle-tier and back-end) has to protect resources and implement authentication and/or authorization – often against the same user store. Outsourcing these fundamental security functions to a security token service prevents duplicating that functionality across those applications and endpoints. Restructuring the application to support a security token service leads to the following architecture and protocols: ![an architecture diagram showing where OAuth 2.0 is used](/_astro/protocols.D9K09C-x_Z1VwovC.svg) Such a design divides security concerns into two parts: ## Authentication [Section titled “Authentication”](#authentication) Authentication is needed when an application needs to know the identity of the current user. Typically, these applications manage data on behalf of that user and need to make sure that this user can only access the data for which they are allowed. The most common example for that is (classic) web applications – but native and JS-based applications also have a need for authentication. The most common authentication protocols are SAML2p, WS-Federation and OpenID Connect – SAML2p being the most popular and the most widely deployed. OpenID Connect is the newest of the three, but is considered to be the future because it has the most potential for modern applications. It was built for mobile application scenarios right from the start and is designed to be API friendly. ## API Access [Section titled “API Access”](#api-access) Applications have two fundamental ways with which they communicate with APIs – using the application identity, or delegating the user’s identity. Sometimes both methods need to be combined. OAuth 2.0 is a protocol that allows applications to request access tokens from a security token service and use them to communicate with APIs. This delegation reduces complexity in both the client applications and the APIs since authentication and authorization can be centralized. ## OpenID Connect And OAuth 2.0 – Better Together! [Section titled “OpenID Connect And OAuth 2.0 – Better Together!”](#openid-connect-and-oauth-20--better-together) OpenID Connect and OAuth 2.0 are very similar – in fact OpenID Connect is an extension on top of OAuth 2.0. The two fundamental security concerns, authentication and API access, are combined into a single protocol - often with a single round trip to the security token service. We believe that the combination of OpenID Connect and OAuth 2.0 is the best approach to secure modern applications for the foreseeable future. Duende IdentityServer is an implementation of these two protocols and is highly optimized to solve the typical security problems of today’s mobile, native and web applications. ## How Duende IdentityServer Can Help [Section titled “How Duende IdentityServer Can Help”](#how-duende-identityserver-can-help) Duende IdentityServer is middleware that adds spec-compliant OpenID Connect and OAuth 2.0 endpoints to an arbitrary ASP.NET Core host. Typically, you build (or re-use) an application that contains login and logout pages (and optionally a consent page, depending on your needs) and add the IdentityServer middleware to that application. The middleware adds the necessary protocol heads to the application so that clients can talk to it using those standard protocols. ![IdentityServer middleware diagram and its relationship in the ASP.NET Core pipeline](/_astro/middleware.Cqu9tG8b_Z1LEIl7.svg) The hosting application can be as complex as you want, but we typically recommend to keep the attack surface as small as possible by including authentication/federation related UI only. ----- # Packaging and Builds > A guide to Duende IdentityServer packages, templates, UI components, and source code accessibility ## Product [Section titled “Product”](#product) The licensed and supported libraries can be accessed via NuGet: * [Duende IdentityServer](https://www.nuget.org/packages/Duende.IdentityServer) * [Duende IdentityServer EntityFramework Integration](https://www.nuget.org/packages/Duende.IdentityServer.EntityFramework) * [Duende IdentityServer ASP.NET Identity Integration](https://www.nuget.org/packages/Duende.IdentityServer.AspNetIdentity) ## Templates [Section titled “Templates”](#templates) Contains Duende templates for the `dotnet` CLI to help jump-start your Duende-powered solutions. You can install the templates using the following command: Terminal ```bash dotnet new install Duende.Templates ``` [Templates ](https://www.nuget.org/packages/Duende.Templates)NuGet Package for IdentityServer Templates [Source Code ](https://github.com/DuendeSoftware/IdentityServer.Templates)Source code for IdentityServer Templates Running the command `dotnet new list duende` should give you a list of the following templates ```bash Template Name Short Name Language Tags ---------------------------------------------------------- -------------------- -------- ------------------------- Duende BFF Host using a Remote API duende-bff-remoteapi [C#] Web/Duende/BFF Duende BFF using a Local API duende-bff-localapi [C#] Web/Duende/BFF Duende BFF with Blazor autorender duende-bff-blazor [C#] Web/Duende/BFF Duende IdentityServer Empty duende-is-empty [C#] Web/Duende/IdentityServer Duende IdentityServer Quickstart UI (UI assets only) duende-is-ui [C#] Web/IdentityServer Duende IdentityServer with ASP.NET Core Identity duende-is-aspid [C#] Web/Duende/IdentityServer Duende IdentityServer with Entity Framework Stores duende-is-ef [C#] Web/Duende/IdentityServer Duende IdentityServer with In-Memory Stores and Test Users duende-is-inmem [C#] Web/Duende/IdentityServer Duende IdentityServer duende-is [C#] Web/Duende/IdentityServer ``` ## Template Descriptions [Section titled “Template Descriptions”](#template-descriptions) In this section, we’ll discuss what each IdentityServer template offers and why you would choose to start with it. While there are similarities across templates, there are nuances that can make for better starting points depending on your particular use case. We’ll start with the simplest templates and then move to the most feature-rich ones. Many of these templates build on each other’s work, so moving from one to another is straightforward. All templates are provided as a starting point for your customization. Using the templates, you assume development responsibility for the choices, alterations, and inevitable deployment of your IdentityServer instance. ### Duende IdentityServer Empty [Section titled “Duende IdentityServer Empty”](#duende-identityserver-empty) You want to run the following command to start using the **Duende IdentityServer Empty** template. ```bash dotnet new duende-is-empty ``` Once created, this template has three essential files: `Config`, `HostingExtensions`, and `Program`. You can modify the `Config` file to add clients, scopes, and claims, as all configurations are from in-memory objects. ```csharp public static class Config { public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId() }; public static IEnumerable ApiScopes => new ApiScope[] { }; public static IEnumerable Clients => new Client[] { }; } ``` This template doesn’t include user interface elements, so it doesn’t support OpenID Connect unless you add those UI elements. You can do so by running the UI-only template of `duende-is-ui`. ```bash dotnet new duende-is-ui --project ``` The executed command will add Razor Pages to your web project. You will need to add Razor Pages to your `HostingExtensions` file. ```csharp using Serilog; internal static class HostingExtensions { public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { builder.Services.AddRazorPages(); builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) .AddLicenseSummary(); return builder.Build(); } public static WebApplication ConfigurePipeline(this WebApplication app) { app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthorization(); app.MapRazorPages().RequireAuthorization(); return app; } } ``` ### Duende IdentityServer with In-Memory Stores and Test Users [Section titled “Duende IdentityServer with In-Memory Stores and Test Users”](#duende-identityserver-with-in-memory-stores-and-test-users) The `duende-is-inmem` template is similar to the `duende-is-empty` and `duende-is-ui` templates combined into a single project template. ```bash dotnet new duende-is-inmem ``` This template differs from others in that we have defined some starting clients, scopes, and claims for common development scenarios and a speedier development experience. Config.cs ```csharp public static class Config { public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; public static IEnumerable ApiScopes => new ApiScope[] { new ApiScope("scope1"), new ApiScope("scope2"), }; public static IEnumerable Clients => new Client[] { // m2m client credentials flow client new Client { ClientId = "m2m.client", ClientName = "Client Credentials Client", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) }, AllowedScopes = { "scope1" } }, // interactive client using code flow + pkce new Client { ClientId = "interactive", ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, RedirectUris = { "https://localhost:44300/signin-oidc" }, FrontChannelLogoutUri = "https://localhost:44300/signout-oidc", PostLogoutRedirectUris = { "https://localhost:44300/signout-callback-oidc" }, AllowOfflineAccess = true, AllowedScopes = { "openid", "profile", "scope2" } }, }; } ``` This template is a great starting point for proof of concepts and a learning tool for developers experiencing OAuth 2.0 and OpenID Connect in the .NET space for the first time. ### Duende IdentityServer with Entity Framework Stores [Section titled “Duende IdentityServer with Entity Framework Stores”](#duende-identityserver-with-entity-framework-stores) For developers looking to quickly go to a production-like environment, starting with the `duende-is-ef` template is a great starting point. ```bash dotnet new duende-is-ef ``` This template stores all operational and configuration data of the IdentityServer instance in your chosen data storage, utilizing EF Core’s ability to target multiple database engines. The template targets SQLite by default, but we have included scripts to easily swap out and regenerate migrations for your database. [Read more about the Entity Framework Core setup here.](/identityserver/data/ef/) ### Duende IdentityServer [Section titled “Duende IdentityServer”](#duende-identityserver) The Duende IdentityServer template is our most feature-rich offering and a great starting point for developers who want a simple yet effective UI/UX experience. ```bash dotnet new duende-is ``` The template is built on the Entity Framework Core template but provides an administrative UI for managing clients, scopes, and claims against a database storage engine. It also has a diagnostics dashboard showing system information, including the licensing tier and features currently used in your IdentityServer deployment. #### Third-party Dependencies [Section titled “Third-party Dependencies”](#third-party-dependencies) This template includes several third-party dependencies: * [Serilog](https://serilog.net/) * [Bootstrap 5](https://getbootstrap.com) * [Bootstrap 5 tags](https://github.com/lekoala/bootstrap5-tags) * [JQuery](https://jquery.org) * [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) ### Duende IdentityServer with ASP.NET Core Identity [Section titled “Duende IdentityServer with ASP.NET Core Identity”](#duende-identityserver-with-aspnet-core-identity) The **Duende IdentityServer with ASP.NET Core Identity** template integrates with ASP.NET Identity to provide you with an instance of Duende IdentityServer that has a user store powered by the Microsoft library. [Please read our ASP.NET Identity documentation](/identityserver/aspnet-identity/), to learn more about this integration. ### BFF Templates [Section titled “BFF Templates”](#bff-templates) For Duende BFF template description, refer the [Duende BFF project templates](/bff/getting-started/templates/). ----- # More Reading Resources > Collection of learning resources including demo server access, OAuth fundamentals, and ASP.NET security guides ## Demo Server [Section titled “Demo Server”](#demo-server) You can try Duende IdentityServer with your favourite client library. We have a test instance at [demo.duendesoftware.com](https://demo.duendesoftware.com). On the main page you can find instructions on how to configure your client and how to call an API. [IdentityServer Demo Server ](https://demo.duendesoftware.com)Visit https\://demo.duendesoftware.com ## OAuth and OIDC Fundamentals [Section titled “OAuth and OIDC Fundamentals”](#oauth-and-oidc-fundamentals) * [OAuth the good Parts (video from NDC Porto 2022)](https://www.youtube.com/watch?v=Ps8ep-glDfc) * [Securing SPAs and Blazor Applications using the BFF (video from NDC Porto 2022)](https://www.youtube.com/watch?v=xzRhabmlc8M) * [Automated OAuth Access Token Management for .NET Workers and ASP.NET Web Applications](https://www.youtube.com/watch?v=zr-LAYg5BCE) ## ASP.NET Security [Section titled “ASP.NET Security”](#aspnet-security) * [Introduction to ASP.NET Core Authentication and Authorization](https://www.youtube.com/watch?v=02Yh3sxzAYI) * [ASP.NET Cookie Authentication Documentation](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie) * [Supporting External Authentication Providers Part 1](https://www.youtube.com/watch?v=HH_tw7dFhpg) * [Supporting External Authentication Providers Part 2](https://www.youtube.com/watch?v=daeVaU5CmPw) ## End-User Authorization [Section titled “End-User Authorization”](#end-user-authorization) * [Authorization for Modern Applications (video from DevConf 2018)](https://www.youtube.com/watch?v=Dlrf85NTuAU) ----- # Supported Specifications > A comprehensive list of supported OpenID Connect and OAuth 2.x specifications implemented in Duende IdentityServer Duende IdentityServer implements the following specifications: ## OpenID Connect [Section titled “OpenID Connect”](#openid-connect) * OpenID Connect Core 1.0 ([spec](https://openid.net/specs/openid-connect-core-1_0.html)) * OpenID Connect Discovery 1.0 ([spec](https://openid.net/specs/openid-connect-discovery-1_0.html)) * OpenID Connect RP-Initiated Logout 1.0 ([spec](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)) * OpenID Connect Session Management 1.0 ([spec](https://openid.net/specs/openid-connect-session-1_0.html)) * OpenID Connect Front-Channel Logout 1.0 ([spec](https://openid.net/specs/openid-connect-frontchannel-1_0.html)) * OpenID Connect Back-Channel Logout 1.0 ([spec](https://openid.net/specs/openid-connect-backchannel-1_0.html)) * Multiple Response Types ([spec](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html)) * Form Post Response Mode ([spec](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html)) * Enterprise Edition: OpenID Connect Client-Initiated Backchannel Authentication (CIBA) ([spec](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)) * FAPI 2.0 Security Profile ([spec](https://openid.net/specs/fapi-security-profile-2_0-final.html)) ## OAuth 2.x [Section titled “OAuth 2.x”](#oauth-2x) * OAuth 2.0 ([RFC 6749](https://tools.ietf.org/html/rfc6749)) * OAuth 2.0 Bearer Token Usage ([RFC 6750](https://tools.ietf.org/html/rfc6750)) * JSON Web Token ([RFC 7519](https://tools.ietf.org/html/rfc7519)) * OAuth 2.0 Token Revocation ([RFC 7009](https://tools.ietf.org/html/rfc7009)) * OAuth 2.0 Token Introspection ([RFC 7662](https://tools.ietf.org/html/rfc7662)) * Proof Key for Code Exchange by OAuth Public Clients ([RFC 7636](https://tools.ietf.org/html/rfc7636)) * OAuth 2.0 JSON Web Tokens for Client Authentication ([RFC 7523](https://tools.ietf.org/html/rfc7523)) * OAuth 2.0 Device Authorization Grant ([RFC 8628](https://tools.ietf.org/html/rfc8628)) * Proof-of-Possession Key Semantics for JSON Web Tokens ([RFC 7800](https://tools.ietf.org/html/rfc7800)) * OAuth 2.0 Mutual TLS Client Authentication and Certificate-Bound Access Tokens ([RFC 8705](https://tools.ietf.org/html/rfc8705)) * OAuth 2.0 Token Exchange ([RFC 8693](https://tools.ietf.org/html/rfc8693)) * JWT Secured Authorization Request / JAR ([RFC 9101](https://datatracker.ietf.org/doc/html/rfc9101)) * JWT Profile for OAuth 2.0 Access Tokens ([RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068)) * OAuth 2.0 Authorization Server Issuer Identifier in Authorization Response ([RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207)) * OAuth 2.0 Step-up Authentication Challenge Protocol ([RFC 9470](https://datatracker.ietf.org/doc/html/rfc9470)) * Business (legacy), Enterprise (legacy), Standard, Advanced, and Custom Edition: OAuth 2.0 Dynamic Client Registration Protocol ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)) * Business (legacy), Enterprise (legacy), Standard, Advanced, and Custom Edition: OAuth 2.0 Pushed Authorization Requests ([RFC 9126](https://www.rfc-editor.org/rfc/rfc9126)) * Enterprise (legacy), Standard, Advanced, and Custom Edition: Resource Indicators for OAuth 2.0 ([RFC 8707](https://tools.ietf.org/html/rfc8707)) * Enterprise (legacy), Standard, Advanced, and Custom Edition: OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer / DPoP ([RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)) * JSON Web Token (JWT) Response for OAuth Token Introspection ([RFC 9701](https://www.rfc-editor.org/rfc/rfc9701.html)) * OAuth 2.0 Authorization Server Metadata ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) ----- # Terminology > Learn about the key terms and concepts used in IdentityServer, including clients, resources, tokens, and user authentication flows. The specs, documentation and object model use a certain terminology that you should be aware of. ![a basic diagrams showing the relationship between users, clients, identityserver, and resources](/_astro/terminology.Du5MR-aA_vpczY.svg) ## Duende IdentityServer [Section titled “Duende IdentityServer”](#duende-identityserver) Duende IdentityServer is an OpenID Connect & OAuth engine - it implements the OpenID Connect and OAuth 2.0 family of [protocols](/identityserver/overview/specs/). Different literature uses different terms for the same role - you probably also find the terms security token service, identity provider, authorization server, IP-STS and more. But they are in a nutshell all the same: a piece of software that issues security tokens to clients. A typical implementation of Duende IdentityServer has a number of jobs and features - including: * manage access to resources * authenticate users using a local account store or via an external identity provider * provide session management and single sign-on * manage and authenticate clients * issue identity and access tokens to clients ## User [Section titled “User”](#user) A user is a human that is using a registered client to access resources. ## Client [Section titled “Client”](#client) A [client](/identityserver/fundamentals/clients/) is a piece of software that requests tokens from your IdentityServer - either for authenticating a user (requesting an identity token) or for accessing a resource (requesting an access token). A client must be first registered with your IdentityServer before it can request tokens. While there are many different client types, e.g. web applications, native mobile or desktop applications, SPAs, server processes etc., they can all be put into two high-level categories. ### Machine to Machine Communication [Section titled “Machine to Machine Communication”](#machine-to-machine-communication) In this scenario two machines talk to each other (e.g. background processes, batch jobs, server daemons), and there is no interactive user present. To authorize this communication, your IdentityServer issues a token to the caller. In protocol terms, this scenario is called *Client Credentials Flow* and you can learn more about it in the issuing tokens [section](/identityserver/tokens/requesting/#machine-to-machine-communication) and in our [Quickstart](/identityserver/quickstarts/1-client-credentials/). ### Interactive Applications [Section titled “Interactive Applications”](#interactive-applications) This is the most common type of client scenario: web applications, SPAs or native/mobile apps with interactive users. This scenario typically involves a browser for user interaction (e.g. for authentication or consent). In protocol terms, this scenario is called *Authorization Code Flow* and you can learn more about it in the issuing tokens [section](/identityserver/tokens/requesting/#interactive-applications) and in our [Quickstart](/identityserver/quickstarts/2-interactive/). ## Resources [Section titled “Resources”](#resources) [Resources](/identityserver/fundamentals/resources) are something you want to protect with your IdentityServer - either identity data of your users, or APIs. Every resource has a unique name - and clients use this name to specify to which resources they want to get access to. **Identity data** Identity information (aka claims) about a user, e.g. name or email address. *`APIs`* APIs resources represent functionality a client wants to invoke - typically modelled as Web APIs, but not necessarily. ## Identity Token [Section titled “Identity Token”](#identity-token) An identity token represents the outcome of an authentication process. It contains at a bare minimum an identifier for the user (called the `sub` aka subject claim) and information about how and when the user authenticated. It can contain additional identity data. ## Access Token [Section titled “Access Token”](#access-token) An access token allows access to an API resource. Clients request access tokens and forward them to the API. Access tokens contain information about the client and the user (if present). APIs use that information to authorize access to their data and functionality. ----- # IdentityServer Quickstarts > Step-by-step tutorials for implementing common Duende IdentityServer scenarios, from basic setup to advanced features. The quickstarts provide step-by-step instructions for various common Duende IdentityServer scenarios. They start with the absolute basics and become more complex - it is recommended you do them in order. * adding Duende IdentityServer to an ASP.NET Core application * configuring Duende IdentityServer * issuing tokens for various clients * securing web applications and APIs * adding support for EntityFramework based configuration * adding support for ASP.NET Identity Every quickstart has a reference solution. You can find the code in the [samples](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts) folder. ## Preparation [Section titled “Preparation”](#preparation) The first thing you should do is install our templates: Terminal ```bash dotnet new install Duende.Templates ``` They will be used as a starting point for the various tutorials. [YouTube video player](https://www.youtube.com/embed/cxYmODQHErM) ----- # Protecting An API With Client Credentials > Learn how to set up IdentityServer to protect an API using client credentials, implementing server-to-server authentication with access tokens. Welcome to the first quickstart for IdentityServer! To see the full list of quickstarts, please see [Quickstarts Overview](/identityserver/quickstarts/0-overview/). This first quickstart provides step-by-step instructions to set up IdentityServer in the most basic scenario: protecting APIs for server-to-server communication. You will create a solution containing three projects: * An Identity Server * An API that requires authentication * A client that accesses that API The client will request an access token from IdentityServer using its client ID and secret and then use the token to gain access to the API. ## Source Code [Section titled “Source Code”](#source-code) Finished source code for each quickstart in this series is available in the [Samples](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts) repository, and a reference implementation of this quickstart is available [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials). ## Video [Section titled “Video”](#video) In addition to the written steps below there’s also a YouTube video available: [YouTube video player](https://www.youtube.com/embed/EhuCpbH7Ad0) ## Preparation [Section titled “Preparation”](#preparation) The IdentityServer templates for the dotnet CLI are a good starting point for the quickstarts. To install the templates open a console window and type the following command: ```console dotnet new install Duende.Templates ``` ## Create The Solution And IdentityServer Project [Section titled “Create The Solution And IdentityServer Project”](#create-the-solution-and-identityserver-project) In this section, you will create a directory for the solution and use the `isempty` (IdentityServer Empty) template to create an ASP.NET Core application that includes a basic IdentityServer setup. Back in the console, run the following commands to create the directory structure for the solution. ```console mkdir quickstart cd quickstart mkdir src dotnet new sln -n Quickstart ``` This will create a quickstart directory that will serve as the root of the solution, a src subdirectory to hold your source code, and a solution file to organize your projects. Throughout the rest of the quickstart series, paths will be written relative to the quickstart directory. From the new quickstart directory, run the following commands to use the `isempty` template to create a new project. The template creates a web project named IdentityServer with the IdentityServer package installed and minimal configuration added for it. ```console cd src dotnet new duende-is-empty -n IdentityServer ``` This will create the following files within a new `src/IdentityServer` directory: * `Properties/launchSettings.json` file - launch profile * `appsettings.json` - run time settings * `Config.cs` - definitions for [resources](/identityserver/overview/terminology/#resources) and [clients](/identityserver/overview/terminology/#client) used by IdentityServer * `HostingExtensions.cs` - configuration for ASP.NET pipeline and services Notably, the IdentityServer services are configured here and the IdentityServer middleware is added to the pipeline here. * `IdentityServer.csproj` - project file with the IdentityServer NuGet package added * `Program.cs` - main application entry point Next, add the IdentityServer project to the solution. Back in the console, navigate up to the quickstart directory and add the IdentityServer project to the solution. ```console cd .. dotnet sln add ./src/IdentityServer ``` ### Defining An API Scope [Section titled “Defining An API Scope”](#defining-an-api-scope) Scope is a core feature of OAuth that allows you to express the extent or scope of access. Clients request scopes when they initiate the protocol, declaring what scope of access they want. IdentityServer then has to decide which scopes to include in the token. Just because the client has asked for something doesn’t mean they should get it! There are built-in abstractions and extensibility points that you can use to make this decision. Ultimately, IdentityServer issues a token to the client, which then uses the token to access APIs. APIs can check the scopes that were included in the token to make authorization decisions. Scopes don’t have structure imposed by the protocols - they are just space-separated strings. This allows for flexibility when designing the scopes used by a system. In this quickstart, you will create a scope that represents complete access to an API that will be created later in this quickstart. Scope definitions can be loaded in many ways. This quickstart shows how to use a “code as configuration” approach. A minimal Config.cs was created by the template at `src/IdentityServer/Config.cs`. Open it and add an `ApiScope` to the `ApiScopes` property: ```csharp public static IEnumerable ApiScopes => new ApiScope[] { new ApiScope(name: "api1", displayName: "My API") }; ``` See the full file [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/Config.cs). ### Defining The client [Section titled “Defining The client”](#defining-the-client) The next step is to configure a client application that you will use to access the API. You’ll create the client application project later in this quickstart. First, you’ll add configuration for it to your IdentityServer project. In this quickstart, the client will not have an interactive user and will authenticate with IdentityServer using a client secret. Add this client definition to `Config.cs`: ```csharp public static IEnumerable Clients => new Client[] { new Client { ClientId = "client", // no interactive user, use the clientid/secret for authentication AllowedGrantTypes = GrantTypes.ClientCredentials, // secret for authentication ClientSecrets = { new Secret("secret".Sha256()) }, // scopes that client has access to AllowedScopes = { "api1" } } }; ``` Again, see the full file [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/Config.cs). Clients can be configured with many options. Your minimal machine-to-machine client here contains: * A ClientId, which identifies the application to IdentityServer so that it knows which client is trying to connect to it. * A Secret, which you can think of as the password for the client. * The list of scopes that the client is allowed to ask for. Notice that the allowed scope here matches the name of the ApiScope above. ### Configuring IdentityServer [Section titled “Configuring IdentityServer”](#configuring-identityserver) The scope and client definitions are loaded in [HostingExtensions.cs](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/HostingExtensions.cs). The template created a ConfigureServices method there that is already loading the scopes and clients. You can take a look to see how it is done. Note that the template adds a few things that are not used in this quickstart. Here’s the minimal ConfigureServices method that is needed: Startup.cs ```csharp public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { // Can also be found in Program.cs builder.Services.AddIdentityServer() .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); return builder.Build(); } ``` That’s it - your IdentityServer is now configured. If you run the project and then navigate to `https://localhost:5001/.well-known/openid-configuration` in your browser, you should see the [discovery document](/identityserver/reference/v8/endpoints/discovery/). The discovery document is a standard endpoint in [OpenID Connect](https://openid.net/specs/openid-connect-discovery-1_0.html) and [OAuth](https://datatracker.ietf.org/doc/html/rfc8414). It is used by your clients and APIs to retrieve configuration data needed to request and validate tokens, login and logout, etc. ![Browser showing discovery endpoint JSON](/_astro/1_discovery.CglV0FSW_uefJN.webp) ## Create An API Project [Section titled “Create An API Project”](#create-an-api-project) Next, add an API project to your solution. This API will serve protected resources that will be secured by IdentityServer. You can either use the ASP.NET Core Web API template from Visual Studio or use the .NET CLI to create the API project. To use the CLI, run the following commands: ```console cd src dotnet new webapi -n Api --no-openapi ``` Then navigate back up to the root quickstart directory and add it to the solution by running the following commands: ```console cd .. dotnet sln add ./src/Api ``` ### Add JWT Bearer Authentication [Section titled “Add JWT Bearer Authentication”](#add-jwt-bearer-authentication) Now you will add JWT Bearer Authentication to the API’s ASP.NET pipeline. The goal is to authorize calls to your API using tokens issued by the IdentityServer project. To that end, you will add authentication middleware to the pipeline from the `Microsoft.AspNetCore.Authentication.JwtBearer` NuGet package. This middleware will: * Find and parse a JWT sent with incoming requests as an *Authorization: Bearer* header. * Validate the JWT’s signature to ensure that it was issued by IdentityServer. * Validate that the JWT is not expired. Run this command to add the middleware package to the API: ```console dotnet add ./src/Api package Microsoft.AspNetCore.Authentication.JwtBearer ``` Now add the authentication and authorization services to the Service Collection, and configure the JWT Bearer authentication provider as the default [Authentication Scheme](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-8.0#authentication-scheme). Program.cs ```csharp builder.Services.AddAuthentication() .AddJwtBearer(options => { options.Authority = "https://localhost:5001"; options.TokenValidationParameters.ValidateAudience = false; }); builder.Services.AddAuthorization(); ``` ### Add An Endpoint [Section titled “Add An Endpoint”](#add-an-endpoint) Replace the templated weather forecast endpoint with a new endpoint: ```csharp app.MapGet("identity", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) .RequireAuthorization(); ``` This endpoint will be used to test authorization and to display the claims identity through the eyes of the API. ### Configure API To Listen On Port 6001 [Section titled “Configure API To Listen On Port 6001”](#configure-api-to-listen-on-port-6001) Configure the API to run on `https://localhost:6001` only. You can do this by editing the [launchSettings.json](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/Api/Properties/launchSettings.json) file in the `src/Api/Properties` directory. Change these settings for the `https` profile: ```json { "launchUrl": "identity", "applicationUrl": "https://localhost:6001" } ``` ### Test The Identity Endpoint [Section titled “Test The Identity Endpoint”](#test-the-identity-endpoint) Run the API project using the `https` profile and then navigate to the identity controller at `https://localhost:6001/identity` in a browser. This should return a 401 status code, which means your API requires a credential and is now protected by IdentityServer. ## Create The Client Project [Section titled “Create The Client Project”](#create-the-client-project) The last step is to create a client that requests an access token and then uses that token to access the API. Your client will be a console project in your solution. Run the following commands: ```console cd src dotnet new console -n Client ``` Then as before, add it to your solution using: ```console cd .. dotnet sln add ./src/Client ``` ### Add The IdentityModel NuGet Package [Section titled “Add The IdentityModel NuGet Package”](#add-the-identitymodel-nuget-package) The token endpoint at IdentityServer implements the OAuth protocol, and you could use raw HTTP to access it. However, we have a client library called IdentityModel that encapsulates the protocol interaction in an easy-to-use API. Add the \*Duende.IdentityModel \* NuGet package to your client by running the following command: ```console dotnet add ./src/Client package Duende.IdentityModel ``` ### Retrieve The Discovery Document [Section titled “Retrieve The Discovery Document”](#retrieve-the-discovery-document) IdentityModel includes a client library to use with the discovery endpoint. This way you only need to know the base address of IdentityServer - the actual endpoint addresses can be read from the metadata. Add the following to the client’s Program.cs in the `src/Client/Program.cs` directory: ```csharp using Duende.IdentityModel.Client; // discovery endpoints from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); if (disco.IsError) { Console.WriteLine(disco.Error); Console.WriteLine(disco.Exception); return 1; } ``` ### Request A Token From IdentityServer [Section titled “Request A Token From IdentityServer”](#request-a-token-from-identityserver) Next you can use the information from the discovery document to request a token from `IdentityServer` to access `api1`: ```csharp // request token var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Scope = "api1" }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); Console.WriteLine(tokenResponse.ErrorDescription); return 1; } Console.WriteLine(tokenResponse.AccessToken); ``` ### Calling The API [Section titled “Calling The API”](#calling-the-api) To send the access token to the API you typically use the HTTP Authorization header. This is done using the `SetBearerToken` extension method: ```csharp // call api var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken!); // AccessToken is always non-null when IsError is false var response = await apiClient.GetAsync("https://localhost:6001/identity"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); return 1; } var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true })); return 0; ``` The completed `Program.cs` file can be found [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/Client/Program.cs). To test the flow, start the IdentityServer and API projects. Once they are running, run the Client project. The output should look like this: ![Windows console showing claims for a bearer token](/_astro/1_client_screenshot.DapOaE8B_Z21a1sv.webp) If you’re using Visual Studio, here’s how to start everything up: 1. Right-click the solution and select *Configure Startup Projects…* 2. Choose *Multiple Startup Projects* and set the action for Api and IdentityServer to Start 3. Run the solution and wait a moment for both the API and IdentityServer to start 4. Right-click the `Client` project and select Debug -> Start Without Debugging. #### Authorization At The API [Section titled “Authorization At The API”](#authorization-at-the-api) Right now, the API accepts any access token issued by your IdentityServer. In this section, you will add an [Authorization Policy](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-8.0) to the API that will check for the presence of the “api1” scope in the access token. The protocol ensures that this scope will only be in the token if the client requests it and IdentityServer allows the client to have that scope. You configured IdentityServer to allow this access by [including it in the allowedScopes property](#defining-the-client). Add the following to the `Program.cs` file of the API: Program.cs ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy("ApiScope", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "api1"); }); }); ``` You can now enforce this policy at various levels, e.g.: * globally * for all endpoints * for specific controllers, actions, or endpoints Add the policy to the identity endpoint in `src/Api/Program.cs`: ```csharp app.MapGet("identity", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) .RequireAuthorization("ApiScope"); ``` Now you can run the API again, and it will enforce that the api1 scope is present in the access token. ## Further Experiments [Section titled “Further Experiments”](#further-experiments) This quickstart focused on the success path: * The client was able to request a token. * The client could use the token to access the API. You can now try to provoke errors to learn how the system behaves, e.g.: * Try to connect to IdentityServer when it is not running (unavailable). * Try to use an invalid client id or secret to request the token. * Try to ask for an invalid scope during the token request. * Try to call the API when it is not running (unavailable). * Don’t send the token to the API. * Configure the API to require a different scope than the one in the token. ----- # Interactive Applications With ASP.NET Core > Learn how to add interactive user authentication to an ASP.NET Core application using OpenID Connect and IdentityServer, including configuring the UI, managing user login/logout, and accessing claims. Welcome to Quickstart 2 for Duende IdentityServer! In this quickstart, you will add support for interactive user authentication via the OpenID Connect protocol to the IdentityServer you built in [Quickstart 1](/identityserver/quickstarts/1-client-credentials/). Once that is in place, you will create an ASP.NET Razor Pages application that will use IdentityServer for authentication. ## Video [Section titled “Video”](#video) In addition to the written steps below there’s also a YouTube video available: [YouTube video player](https://www.youtube.com/embed/4aYj4xb7_Cg) ## Enable OIDC In IdentityServer [Section titled “Enable OIDC In IdentityServer”](#enable-oidc-in-identityserver) To enable OIDC in IdentityServer you need: * An interactive UI * Configuration for OIDC scopes * Configuration for an OIDC client * Users to log in with ### Add The UI [Section titled “Add The UI”](#add-the-ui) Support for the OpenID Connect protocol is already built into IdentityServer. You need to provide the User Interface for login, logout, consent, and error. While the look & feel and workflows will differ in each implementation, we provide a Razor Pages-based UI that you can use as a starting point. You can use the .NET CLI to add the quickstart UI to a project. Run the following command from the `src/IdentityServer` directory: ```console dotnet new duende-is-ui ``` ### Enable The UI [Section titled “Enable The UI”](#enable-the-ui) Once you have added the UI, you will need to register its services and enable it in the pipeline. In `src/IdentityServer/HostingExtensions.cs` you will find commented out code in the `ConfigureServices` and `ConfigurePipeline` methods that enable the UI. Note that there are three places to comment in - two in `ConfigurePipeline` and one in `ConfigureServices`. Comment in the service registration and pipeline configuration, run the `IdentityServer` project, and navigate to `https://localhost:5001`. You should now see a home page. Spend some time reading the pages and models, especially those in the `src/IdentityServer/Pages/Account` directory. These pages are the main UI entry points for login and logout. The better you understand them, the easier it will be to make future modifications. ### Configure OIDC Scopes [Section titled “Configure OIDC Scopes”](#configure-oidc-scopes) Similar to OAuth, OpenID Connect uses scopes to represent something you want to protect and that clients want to access. In contrast to OAuth, scopes in OIDC represent identity data like user id, name or email address rather than APIs. Add support for the standard `openid` (subject id) and `profile` (first name, last name, etc.) scopes by declaring them in `src/IdentityServer/Config.cs`: ```csharp public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; ``` Then register the identity resources in `src/IdentityServer/HostingExtensions.cs`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); ``` ### Add Test Users [Section titled “Add Test Users”](#add-test-users) The sample UI also comes with an in-memory “user database”. You can enable this by calling `AddTestUsers` in `src/IdentityServer/HostingExtensions.cs`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) .AddTestUsers(TestUsers.Users); ``` In the `TestUsers` class, you can see that two users called `alice` and `bob` are defined with some identity claims. You can use those users to login. Note that the test users’ passwords match their usernames. ### Register An OIDC client [Section titled “Register An OIDC client”](#register-an-oidc-client) The last step in the `IdentityServer` project is to add a new configuration entry for a client that will use OIDC to log in. You will create the application code for this client in the next section. For now, you will register its configuration. OpenID Connect-based clients are very similar to the OAuth clients we added in [Quickstart 1](/identityserver/quickstarts/1-client-credentials/). But since the flows in OIDC are always interactive, we need to add some redirect URLs to our configuration. The `Clients` list in `src/IdentityServer/Config.cs` should look like this: ```csharp public static IEnumerable Clients => new List { // machine to machine client (from quickstart 1) new Client { ClientId = "client", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, // scopes that client has access to AllowedScopes = { "api1" } }, // interactive ASP.NET Core Web App new Client { ClientId = "web", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } } }; ``` ## Create The OIDC client [Section titled “Create The OIDC client”](#create-the-oidc-client) Next you will create an ASP.NET web application that will allow interactive users to log in using OIDC. Use the webapp template to create the project. Run the following commands from the `src` directory: ```console dotnet new webapp -n WebClient cd .. dotnet sln add ./src/WebClient ``` ### Install The OIDC NuGet Package [Section titled “Install The OIDC NuGet Package”](#install-the-oidc-nuget-package) To add support for OpenID Connect authentication to the `WebClient` project, you need to add the NuGet package containing the OpenID Connect handler. From the `src/WebClient` directory, run the following command: ```console dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect ``` ### Configure Authentication Services [Section titled “Configure Authentication Services”](#configure-authentication-services) Then add the authentication service and register the cookie and OpenIdConnect authentication providers in `src/WebClient/Program.cs`: Program.cs ```csharp builder.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5001"; options.ClientId = "web"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.MapInboundClaims = false; // Don't rename claim types options.SaveTokens = true; }); ``` `AddAuthentication` registers the authentication services. Notice that in its options, the DefaultChallengeScheme is set to “oidc”, and the DefaultScheme is set to “Cookies”. The DefaultChallengeScheme is used when an unauthenticated user must log in. This begins the OpenID Connect protocol, redirecting the user to `IdentityServer`. After the user has logged in and been redirected back to the client, the client creates its own local cookie. Subsequent requests to the client will include this cookie and be authenticated with the default Cookie scheme. After the call to `AddAuthentication`, `AddCookie` adds the handler that can process the local cookie. Finally, `AddOpenIdConnect` is used to configure the handler that performs the OpenID Connect protocol. The `Authority` indicates where the trusted token service is located. The `ClientId` and the `ClientSecret` identify this client. The `Scope` is the collection of scopes that the client will request. By default, it includes the openid and profile scopes, but clear the collection and add them back for explicit clarity. `SaveTokens` is used to persist the tokens in the cookie (as they will be needed later). ### Configure The Pipeline [Section titled “Configure The Pipeline”](#configure-the-pipeline) Now add `UseAuthentication` to the ASP.NET pipeline in `src/WebClient/Program.cs`. Also chain a call to `RequireAuthorization` onto `MapRazorPages` to disable anonymous access for the entire application. ```csharp app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages().RequireAuthorization(); ``` ### Display The Auth Cookie [Section titled “Display The Auth Cookie”](#display-the-auth-cookie) Modify `src/WebClient/Pages/Index.cshtml` to display the claims of the user and the cookie properties: ```csharp @page @model IndexModel @using Microsoft.AspNetCore.Authentication

Claims

@foreach (var claim in User.Claims) {
@claim.Type
@claim.Value
}

Properties

@foreach (var prop in (await HttpContext.AuthenticateAsync()).Properties!.Items) {
@prop.Key
@prop.Value
}
``` ### Configure WebClient’s Port [Section titled “Configure WebClient’s Port”](#configure-webclients-port) Update the client’s applicationUrl in `src/WebClient/Properties/launchSettings.json` to use port 5002. ```json { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "WebClient": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:5002", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ``` ## Test The client [Section titled “Test The client”](#test-the-client) Now everything should be in place to log in to `WebClient` using OIDC. Run `IdentityServer` and `WebClient` and then trigger the authentication handshake by navigating to the protected home page. You should see a redirect to the login page in `IdentityServer`. ![login screen for IdentityServer](/_astro/2_login.CED0TwDu_xcUgm.webp) After you log in, `IdentityServer` will redirect back to `WebClient`, where the OpenID Connect authentication handler will process the response and sign-in the user locally by setting a cookie. Finally, the `WebClient`’s page will show the contents of the cookie. ![ASP.NET Core application showing ClaimsPrincipal\'s claims](/_astro/2_claims.iXvcLYaR_ZRP40f.webp) As you can see, the cookie has two parts: the claims of the user and some metadata in the properties. This metadata also contains the original access and id tokens issued by `IdentityServer`. Feel free to copy these tokens to [jwt.me](https://jwt.me) to inspect their contents. ## Adding Sign-out [Section titled “Adding Sign-out”](#adding-sign-out) Next you will add sign-out to `WebClient`. To sign out, you need to * Clear local application cookies * Make a roundtrip to `IdentityServer` using the OIDC protocol to clear its session The cookie auth handler will clear the local cookie when you sign out from its authentication scheme. The OpenId Connect handler will perform the protocol steps for the roundtrip to `IdentityServer` when you sign out of its scheme. Create a page to trigger sign-out of both schemes by running the following command from the `src/WebClient/Pages` directory: ```console dotnet new page -n Signout ``` Update the new page’s model (`src/WebClient/Pages/Signout.cshtml.cs`) with the following code: ```csharp public class SignoutModel : PageModel { public IActionResult OnGet() { return SignOut("Cookies", "oidc"); } } ``` This will clear the local cookie and then redirect to the IdentityServer. The IdentityServer will clear its cookies and then give the user a link to return back to the web application. Create a link to the logout page in `src/WebClient/Pages/Shared/_Layout.cshtml` within the navbar-nav list: ```html ``` Run the application again, and try logging out. Observe that you get redirected to the end session endpoint, and that both session cookies are cleared. ## Getting Claims From The UserInfo Endpoint [Section titled “Getting Claims From The UserInfo Endpoint”](#getting-claims-from-the-userinfo-endpoint) You might have noticed that even though you’ve configured the client to be allowed to retrieve the `profile` identity scope, the claims associated with that scope (such as `name`, `given_name`, `family_name`, etc.) don’t appear in the returned token. You need to tell the client to retrieve those claims from the userinfo endpoint by specifying scopes that the client application needs to access and setting the `GetClaimsFromUserInfoEndpoint` option. Add the following to `ConfigureServices` in `src/WebClient/Program.cs`: Program.cs ```csharp .AddOpenIdConnect("oidc", options => { // ... options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.GetClaimsFromUserInfoEndpoint = true; // ... }); ``` After restarting the client app and logging back in, you should see additional user claims associated with the `profile` identity scope displayed on the page. ![ASP.NET Core page showing additional claims](/_astro/2_additional_claims.D7m0XRhG_Z1I6mLe.webp) ## Further Experiments [Section titled “Further Experiments”](#further-experiments) This quickstart created a client with interactive login using OIDC. To experiment further you can * Add additional claims to the identity * Add support for external authentication ### Add More Claims [Section titled “Add More Claims”](#add-more-claims) To add more claims to the identity: * Add a new identity resource to the list in `src/IdentityServer/Config.cs`. Name it and specify which claims should be returned when it is requested. The `Name` property of the resource is the scope value that clients can request to get the associated `UserClaims`. For example, you could add an `IdentityResource` named “verification” which would include the `email` and `email_verified` claims. ```csharp public static IEnumerable IdentityResources => new List { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResource() { Name = "verification", UserClaims = new List { JwtClaimTypes.Email, JwtClaimTypes.EmailVerified } } }; ``` * Give the client access to the resource via the `AllowedScopes` property on the client configuration in `src/IdentityServer/Config.cs`. The string value in `AllowedScopes` must match the `Name` property of the resource. ```csharp new Client { ClientId = "web", //... AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "verification" } } ``` * Request the resource by adding it to the `Scopes` collection on the OpenID Connect handler configuration in `src/WebClient/Program.cs`, and add a [ClaimAction](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions.claimactions?view=aspnetcore-8.0) to map the new claim returned from the userinfo endpoint onto a user claim. Program.cs ```csharp .AddOpenIdConnect("oidc", options => { // ... options.Scope.Add("verification"); options.ClaimActions.MapJsonKey("email_verified", "email_verified"); // ... } ``` IdentityServer uses the `IProfileService` to retrieve claims for tokens and the userinfo endpoint. You can provide your own implementation of `IProfileService` to customize this process with custom logic, data access, etc. Since you are using `AddTestUsers`, the `TestUserProfileService` is used automatically. It will automatically include requested claims from the test users added in `src/IdentityServer/TestUsers.cs`. ### Add Support for External Authentication [Section titled “Add Support for External Authentication”](#add-support-for-external-authentication) Adding support for external authentication to your IdentityServer can be done with very little code; all that is needed is an authentication handler. ASP.NET Core ships with handlers for OpenID Connect, and provides [integrations for Google, Facebook, Microsoft Account, Entra ID, and more](/identityserver/ui/login/external/#third-party-aspnet-core-authentication-handlers). In this section, you’ll register the Duende IdentityServer demo instance at `demo.duendesoftware.com` as an external provider. Since no other configuration is required apart from your IdentityServer, it is a good starting point. You’ll also see [how to add Google authentication support](#add-google-support). #### Adding An Additional OpenID Connect-Based External Provider [Section titled “Adding An Additional OpenID Connect-Based External Provider”](#adding-an-additional-openid-connect-based-external-provider) A cloud-hosted [demo instance of Duende IdentityServer](https://demo.duendesoftware.com) can be added as an additional external provider. Register and configure the services for the OpenId Connect handler in`src/IdentityServer/HostingExtensions.cs`: HostingExtensions.cs ```csharp builder.Services.AddAuthentication() .AddOpenIdConnect("oidc", "Sign-in with demo.duendesoftware.com", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.SignOutScheme = IdentityServerConstants.SignoutScheme; options.SaveTokens = true; options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" }; }); ``` Now if you try to authenticate, you should see an additional *Sign-in with demo.duendesoftware.com* button to log in to the cloud-hosted demo IdentityServer. If you click that button, you will be redirected to . Check that the page’s location has changed and then log in using the `alice` or `bob` users (their passwords are their usernames, just as they are for the local test users). You should land back at `WebClient`, authenticated with a demo user. The demo users are logically distinct entities from the local test users, even though they happen to have identical usernames. Inspect their claims in `WebClient` and note the differences between them, such as the distinct `sub` claims. #### Add Google Support [Section titled “Add Google Support”](#add-google-support) To use Google for authentication, you need to: * Add the `Google.Apis.Auth.AspNetCore3` NuGet package to the IdentityServer project. * Register with Google and [set up a client](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-9.0#create-the-google-oauth-20-client-id-and-secret). * Store the client id and secret securely with `dotnet user-secrets`. * Add the Google authentication handler to the middleware pipeline and configure it. See [Microsoft’s guide](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-9.0#create-the-google-oauth-20-client-id-and-secret) for details on how to register with Google, create the client, and store the secrets in user secrets. **Stop before adding the authentication middleware and Google authentication handler to the pipeline.** You will need an IdentityServer specific option. Add the following to `ConfigureServices` in `src/IdentityServer/HostingExtensions.cs`: HostingExtensions.cs ```csharp builder.Services.AddAuthentication() .AddGoogleOpenIdConnect( authenticationScheme: GoogleOpenIdConnectDefaults.AuthenticationScheme, displayName: "Google", configureOptions: options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = builder.Configuration["Authentication:Google:ClientId"]; options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]; }); ``` When authenticating with Google, there are again two [authentication schemes](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/#authentication-scheme). `AddGoogleOpenIdConnect` adds the `GoogleOpenIdConnect` scheme, which handles the protocol flow back and forth with Google. After successful login, the application needs to sign in to an additional scheme that can authenticate future requests without needing a roundtrip to Google - typically by issuing a local cookie. The `SignInScheme` tells the Google handler to use the scheme named `IdentityServerConstants.ExternalCookieAuthenticationScheme`, which is a cookie authentication handler automatically created by IdentityServer that is intended for external logins. Now run `IdentityServer` and `WebClient` and try to authenticate (you may need to log out and log back in) You will see a *Google* button on the login page. ![IdentityServer login page showing Google as an external login option](/_astro/2_google_login.BG4lBuSl_Z2fAHBr.webp) Click on *Google* and authenticate with a Google account. You should land back on the `WebClient` home page, showing that the user is now coming from Google with claims sourced from Google’s data. ----- # ASP.NET Core And API access > Learn how to combine user authentication with API access by requesting both identity and API scopes during the OpenID Connect login flow. Welcome to Quickstart 3 for Duende IdentityServer! The previous quickstarts introduced [API access](/identityserver/quickstarts/1-client-credentials/) and [user authentication](/identityserver/quickstarts/2-interactive/). This quickstart will bring the two together. In addition to the written steps below a YouTube video is available: [YouTube video player](https://www.youtube.com/embed/zHVmzgPUImc) OpenID Connect and OAuth combine elegantly; you can achieve both user authentication and api access in a single exchange with the token service. In Quickstart 2, the token request in the login process asked for only identity resources, that is, only scopes such as *profile* and *openid*. In this quickstart, you will add scopes for API resources to that request. *IdentityServer* will respond with two tokens: 1. the identity token, containing information about the authentication process and session, and 2. the access token, allowing access to APIs on behalf of the logged on user ## Modifying The Client Configuration [Section titled “Modifying The Client Configuration”](#modifying-the-client-configuration) The client configuration in IdentityServer requires one straightforward update. We should add the *api1* resource to the allowed scopes list so that the client will have permission to access it. Update the *Client* in *src/IdentityServer/Config.cs* as follows: ```csharp new Client { ClientId = "web", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "verification", "api1" } } ``` ## Modifying The Web client [Section titled “Modifying The Web client”](#modifying-the-web-client) Now configure the client to ask for access to api1 by requesting the *api1* scope. This is done in the OpenID Connect handler configuration in *src/WebClient/Program.cs*: Program.cs ```csharp builder.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5001"; options.ClientId = "web"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api1"); options.Scope.Add("verification"); options.ClaimActions.MapJsonKey("email_verified", "email_verified"); options.GetClaimsFromUserInfoEndpoint = true; options.MapInboundClaims = false; // Don't rename claim types options.SaveTokens = true; }); ``` Since *SaveTokens* is enabled, ASP.NET Core will automatically store the id and access tokens in the properties of the authentication cookie. If you run the solution and authenticate, you will see the tokens on the page that displays the cookie claims and properties created in quickstart 2. ## Using The Access Token [Section titled “Using The Access Token”](#using-the-access-token) Now you will use the access token to authorize requests from the *WebClient* to the *Api*. Create a page that will 1. Retrieve the access token from the session using the *GetTokenAsync* method from *Microsoft.AspNetCore.Authentication* 2. Set the token in an *Authentication: Bearer* HTTP header 3. Make an HTTP request to the *API* 4. Display the results Create the Page by running the following command from the *src/WebClient/Pages* directory: ```console dotnet new page -n CallApi ``` Update *src/WebClient/Pages/CallApi.cshtml.cs* as follows: ```csharp public class CallApiModel : PageModel { public string Json = string.Empty; public async Task OnGet() { var accessToken = await HttpContext.GetTokenAsync("access_token"); var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var content = await client.GetStringAsync("https://localhost:6001/identity"); var parsed = JsonDocument.Parse(content); var formatted = JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true }); Json = formatted; } } ``` And update *src/WebClient/Pages/CallApi.cshtml* as follows: ```html @page @model MyApp.Namespace.CallApiModel
@Model.Json
``` Also add a link to the new page in *src/WebClient/Shared/\_Layout.cshtml* with the following: ```html ``` Make sure the *IdentityServer* and *Api* projects are running, start the *WebClient* and request */CallApi* after authentication. ----- # Token Management > Learn how to manage access tokens in interactive applications, including requesting refresh tokens, caching, and automatic token refresh using Duende.AccessTokenManagement. Welcome to this Quickstart for Duende IdentityServer! The previous quickstart introduced [API access](/identityserver/quickstarts/3-api-access/) with interactive applications, but by far the most complex task for a typical client is to manage the access token. In addition to the written steps below a YouTube video is available: [YouTube video player](https://www.youtube.com/embed/W8jtc2Ou1d4) Given that the access token has a finite lifetime, you typically want to * request a refresh token in addition to the access token at login time * cache those tokens * use the access token to call APIs until it expires * use the refresh token to get a new access token * repeat the process of caching and refreshing with the new token ASP.NET Core has built-in facilities that can help you with some of those tasks (like caching or sessions), but there is still quite some work left to do. [Duende.AccessTokenManagement](/accesstokenmanagement) can help. It provides abstractions for storing tokens, automatic refresh of expired tokens, etc. ## Requesting A Refresh Token [Section titled “Requesting A Refresh Token”](#requesting-a-refresh-token) To allow the *web* client to request a refresh token set the *AllowOfflineAccess* property to true in the client configuration. Update the *Client* in *src/IdentityServer/Config.cs* as follows: ```csharp new Client { ClientId = "web", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, AllowOfflineAccess = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "verification", "api1" } } ``` To get the refresh token the *offline\_access* scope has to be requested by the client. In *src/WebClient/Program.cs* add the scope to the scope list: ```csharp options.Scope.Add("offline_access"); ``` When running the solution the refresh token should now be visible under *Properties* on the landing page of the client. ## Automatically Refreshing An Access Token [Section titled “Automatically Refreshing An Access Token”](#automatically-refreshing-an-access-token) In the WebClient project add a reference to the NuGet package `Duende.AccessTokenManagement.OpenIdConnect` and in *Program.cs* add the needed types to dependency injection: Program.cs ```csharp builder.Services.AddOpenIdConnectAccessTokenManagement(); ``` In *CallApi.cshtml.cs* update the method body of `OnGet` as follows: CallApi.cshtml.cs ```csharp public async Task OnGet() { var tokenInfo = await HttpContext.GetUserAccessTokenAsync(); var client = new HttpClient(); client.SetBearerToken(tokenInfo.AccessToken!); var content = await client.GetStringAsync("https://localhost:6001/identity"); var parsed = JsonDocument.Parse(content); var formatted = JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true }); Json = formatted; } ``` There are two changes here that utilize the AccessTokenManagement NuGet package: * An object called tokenInfo containing all stored tokens is returned by the *GetUserAccessTokenAsync* extension method. This will make sure the access token is *automatically refreshed* using the refresh token if needed. * The *SetBearerToken* extension method on HttpClient is used for convenience to place the access token in the needed HTTP header. ## Using A Named HttpClient [Section titled “Using A Named HttpClient”](#using-a-named-httpclient) On each call to OnGet in *CallApi.cshtml.cs* a new HttpClient is created in the code above. Recommended however is to use the [HttpClientFactory](https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory) pattern so that instances can be reused. `Duende.AccessTokenManagement.OpenIdConnect` builds on top of *HttpClientFactory* to create HttpClient instances that automatically retrieve the needed access token and refresh if needed. In the client in *Program.cs* under the call to *AddOpenIdConnectAccessTokenManagement* register the HttpClient: Program.cs ```csharp builder.Services.AddUserAccessTokenHttpClient("apiClient", configureClient: client => { client.BaseAddress = new Uri("https://localhost:6001"); }); ``` Now the *OnGet* method in *CallApi.cshtml.cs* can be even more straightforward: ```csharp public class CallApiModel(IHttpClientFactory httpClientFactory) : PageModel { public string Json = string.Empty; public async Task OnGet() { var client = httpClientFactory.CreateClient("apiClient"); var content = await client.GetStringAsync("https://localhost:6001/identity"); var parsed = JsonDocument.Parse(content); var formatted = JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true }); Json = formatted; } } ``` Note that: * The httpClientFactory is injected using a primary constructor. The type was registered when *AddOpenIdConnectAccessTokenManagement* was called in *Program.cs*. * The client is created using the factory passing in the name of the client that was registered in *program.cs*. * No additional code is needed. The client will automatically retrieve the access token and refresh it if needed. ----- # Entity Framework Core: Configuration & Operational Data > Learn how to configure IdentityServer to use Entity Framework Core for storing configuration and operational data in a persistent database. Welcome to Quickstart 4 for Duende IdentityServer! In this quickstart you will move configuration and other temporary data into a database using Entity Framework. In addition to the written steps below a YouTube video is available: [YouTube video player](https://www.youtube.com/embed/GKSp3StwaVA) In the previous quickstarts, you configured clients and scopes with code. IdentityServer loaded this configuration data into memory on startup. Modifying the configuration required a restart. IdentityServer also generates temporary data, such as authorization codes, consent choices, and refresh tokens. Up to this point in the quickstarts, this data was also stored in memory. To move this data into a database that is persistent between restarts and across multiple IdentityServer instances, you will use the `Duende.IdentityServer.EntityFramework` library. ## Configure IdentityServer [Section titled “Configure IdentityServer”](#configure-identityserver) #### Install Duende.IdentityServer.EntityFramework [Section titled “Install Duende.IdentityServer.EntityFramework”](#install-duendeidentityserverentityframework) IdentityServer’s Entity Framework integration is provided by the `Duende.IdentityServer.EntityFramework` NuGet package. Run the following commands from the `src/IdentityServer` directory to replace the `Duende.IdentityServer` package with it. Replacing packages prevents any dependency issues with version mismatches. ```console dotnet remove package Duende.IdentityServer dotnet add package Duende.IdentityServer.EntityFramework ``` #### Install Microsoft.EntityFrameworkCore.Sqlite [Section titled “Install Microsoft.EntityFrameworkCore.Sqlite”](#install-microsoftentityframeworkcoresqlite) `Duende.IdentityServer.EntityFramework` can be used with any Entity Framework database provider. In this quickstart, you will use Sqlite. To add Sqlite support to your IdentityServer project, install the Entity framework Sqlite NuGet package by running the following command from the `src/IdentityServer` directory: ```console dotnet add package Microsoft.EntityFrameworkCore.Sqlite ``` #### Configuring The Stores [Section titled “Configuring The Stores”](#configuring-the-stores) `Duende.IdentityServer.EntityFramework` stores configuration and operational data in separate stores, each with their own DbContext. * ConfigurationDbContext: used for configuration data such as clients, resources, and scopes * PersistedGrantDbContext: used for dynamic operational data such as authorization codes and refresh tokens To use these stores, replace the existing calls to `AddInMemoryClients`, `AddInMemoryIdentityResources`, and `AddInMemoryApiScopes` in your `ConfigureServices` method in `src/IdentityServer/HostingExtensions.cs` with `AddConfigurationStore` and `AddOperationalStore`, like this: HostingExtensions.cs ```csharp public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { builder.Services.AddRazorPages(); var migrationsAssembly = typeof(Program).Assembly.GetName().Name; const string connectionString = @"Data Source=Duende.IdentityServer.Quickstart.EntityFramework.db"; builder.Services.AddIdentityServer() .AddConfigurationStore(options => { options.ConfigureDbContext = b => b.UseSqlite(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) .AddOperationalStore(options => { options.ConfigureDbContext = b => b.UseSqlite(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) .AddTestUsers(TestUsers.Users); //... } ``` ## Managing Database Schema [Section titled “Managing Database Schema”](#managing-database-schema) The `Duende.IdentityServer.EntityFramework.Storage` NuGet package (installed as a dependency of `Duende.IdentityServer.EntityFramework`) contains entity classes that map onto IdentityServer’s models. These entities are maintained in sync with IdentityServer’s models - when the models are changed in a new release, corresponding changes are made to the entities. As you use IdentityServer and upgrade over time, you are responsible for your database schema and changes necessary to that schema. One approach for managing those changes is to use [EF migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/index), which is what this quickstart will use. If migrations are not your preference, then you can manage the schema changes in any way you see fit. #### Adding Migrations [Section titled “Adding Migrations”](#adding-migrations) To create migrations, you will need to install the Entity Framework Core CLI tool on your machine and the `Microsoft.EntityFrameworkCore.Design` NuGet package in IdentityServer. Run the following commands from the `src/IdentityServer` directory: ```console dotnet tool install --global dotnet-ef dotnet add package Microsoft.EntityFrameworkCore.Design ``` #### Handle Expected Exception [Section titled “Handle Expected Exception”](#handle-expected-exception) The Entity Framework CLI internally starts up `IdentityServer` for a short time in order to read your database configuration. After it has read the configuration, it shuts `IdentityServer` down by throwing a `HostAbortedException` exception. We expect this exception to be unhandled and therefore stop `IdentityServer`. Since it is expected, you do not need to log it as a fatal error. Update the error logging code in `src/IdentityServer/Program.cs` as follows: ```csharp // See https://github.com/dotnet/runtime/issues/60600 re StopTheHostException catch (Exception ex) when (ex.GetType().Name is not "StopTheHostException") { Log.Fatal(ex, "Unhandled exception"); } ``` Now run the following two commands from the `src/IdentityServer` directory to create the migrations: ```console dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb ``` You should now see a `src/IdentityServer/Data/Migrations/IdentityServer` directory in your project containing the code for your newly created migrations. #### Initializing Database [Section titled “Initializing Database”](#initializing-database) Now that you have the migrations, you can write code to create the database from them and seed the database with the same configuration data used in the previous quickstarts. In `src/IdentityServer/HostingExtensions.cs`, add this method to initialize the database: ```csharp private static void InitializeDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetService()!.CreateScope()) { serviceScope.ServiceProvider.GetRequiredService().Database.Migrate(); var context = serviceScope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); if (!context.Clients.Any()) { foreach (var client in Config.Clients) { context.Clients.Add(client.ToEntity()); } context.SaveChanges(); } if (!context.IdentityResources.Any()) { foreach (var resource in Config.IdentityResources) { context.IdentityResources.Add(resource.ToEntity()); } context.SaveChanges(); } if (!context.ApiScopes.Any()) { foreach (var resource in Config.ApiScopes) { context.ApiScopes.Add(resource.ToEntity()); } context.SaveChanges(); } } } ``` Call `InitializeDatabase` from the `ConfigurePipeline` method: ```csharp public static WebApplication ConfigurePipeline(this WebApplication app) { app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } InitializeDatabase(app); //... } ``` Now if you run the IdentityServer project, the database should be created and seeded with the quickstart configuration data. You should be able to use a tool like SQL Lite Studio to connect and inspect the data. ![SQLiteStudio showing the contents of an IdentityServer database](/_astro/ef_database.CgdRJRsh_ZtgcXf.webp) ## Run The Client Applications [Section titled “Run The Client Applications”](#run-the-client-applications) You should now be able to run any of the existing client applications and sign-in, get tokens, and call the API — all based upon the database configuration. ----- # ASP.NET Core Identity > Learn how to integrate ASP.NET Core Identity with IdentityServer to manage user authentication and storage using Entity Framework Core. Welcome to Quickstart 5 for Duende IdentityServer! In this quickstart you will integrate IdentityServer with ASP.NET Core Identity. IdentityServer’s flexible design allows you to use any database you want to store users and their data, including password hashes, multi-factor authentication details, roles, claims, profile data, etc. If you are starting with a new user database, then ASP.NET Core Identity is one option you could choose. This quickstart shows how to use ASP.NET Core Identity with IdentityServer. The approach this quickstart takes to using ASP.NET Core Identity is to create a new project for the IdentityServer host. This new project will replace the IdentityServer project you built up in the previous quickstarts. You will create a new project because it is a convenient way to get the UI assets that are needed to login and logout with ASP.NET Core Identity. All the other projects in this solution (for the clients and the API) will remain the same. In addition to the written steps below a YouTube video is available: [YouTube video player](https://www.youtube.com/embed/blvZzYsr8uI) ## New Project For ASP.NET Core Identity [Section titled “New Project For ASP.NET Core Identity”](#new-project-for-aspnet-core-identity) The first step is to add a new project for ASP.NET Core Identity to your solution. We provide a template that contains the minimal UI assets needed to use ASP.NET Core Identity with IdentityServer. You will eventually delete the old project for IdentityServer, but there are some items that you will need to migrate over. Start by creating a new IdentityServer project that will use ASP.NET Core Identity. Run the following commands from the `src` directory: ```console dotnet new duende-is-aspid -n IdentityServerAspNetIdentity cd .. dotnet sln add ./src/IdentityServerAspNetIdentity ``` When prompted to “seed” the user database, choose “Y” for “yes”. This populates the user database with our “alice” and “bob” users. Their passwords are “Pass123$”. ## Inspect The New Project [Section titled “Inspect The New Project”](#inspect-the-new-project) Open the new project in the editor of your choice, and inspect the generated code. Much of it is the same from the prior quickstarts and templates. The following sections will describe some key differences and guide you through migrating configuration from the old IdentityServer Project, including: * The project file (`IdentityServerAspNetIdentity.csproj`) * Pipeline and service configuration (`HostingExtensions.cs`) * Resource and client configuration (Config.cs) * Entry point and seed data (`Program.cs` and `SeedData.cs`) * Login and logout pages (Pages in `Pages/Account`) #### IdentityServerAspNetIdentity.csproj [Section titled “IdentityServerAspNetIdentity.csproj”](#identityserveraspnetidentitycsproj) Notice the reference to `Duende.IdentityServer.AspNetIdentity`. This NuGet package contains the ASP.NET Core Identity integration components for IdentityServer. #### HostingExtensions.cs [Section titled “HostingExtensions.cs”](#hostingextensionscs) In `ConfigureServices` notice the necessary `AddDbContext()` and *AddIdentity\()* calls are done to configure ASP.NET Core Identity. Also notice that much of the same IdentityServer configuration you did in the previous quickstarts is already done. The template uses the in-memory style for clients and resources, which are defined in `Config.cs`. Finally, notice the addition of the new call to `AddAspNetIdentity()`. `AddAspNetIdentity()` adds the integration layer to allow IdentityServer to access the user data for the ASP.NET Core Identity user database. This is needed when IdentityServer must add claims for the users into tokens. Note that *AddIdentity\()* must be invoked before `AddIdentityServer()`. #### Config.cs [Section titled “Config.cs”](#configcs) `Config.cs` contains the hard-coded in-memory clients and resource definitions. To keep the same clients and API working as the prior quickstarts, we need to copy over the configuration data from the old IdentityServer project into this one. Do that now, and afterwards `Config.cs` should look like this: ```csharp public static class Config { public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResource() { Name = "verification", UserClaims = new List { JwtClaimTypes.Email, JwtClaimTypes.EmailVerified } } }; public static IEnumerable ApiScopes => new ApiScope[] { new ApiScope(name: "api1", displayName: "My API") }; public static IEnumerable Clients => new Client[] { new Client { ClientId = "client", // no interactive user, use the clientid/secret for authentication AllowedGrantTypes = GrantTypes.ClientCredentials, // secret for authentication ClientSecrets = { new Secret("secret".Sha256()) }, // scopes that client has access to AllowedScopes = { "api1" } }, // interactive ASP.NET Core Web App new Client { ClientId = "web", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, AllowOfflineAccess = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "verification", "api1" } } }; } ``` At this point, you no longer need the old IdentityServer project and can remove it from the solution. From the quickstart directory, run the following commands: ```console dotnet sln remove ./src/IdentityServer rm -r ./src/IdentityServer ``` #### Program.cs and SeedData.cs [Section titled “Program.cs and SeedData.cs”](#programcs-and-seeddatacs) The application entry point in `Program.cs` is a little different than most ASP.NET Core projects. Notice that it looks for a command line argument called `/seed` which is used as a flag to seed the users in the ASP.NET Core Identity database. This seed process is invoked during template creation and already ran when you were prompted to seed the database. Look at the `SeedData` class’ code to see how the database is created and the first users are created. #### Account Pages [Section titled “Account Pages”](#account-pages) Finally, take a look at the pages in the `src/IdentityServerAspNetIdentity/Pages/Account` directory. These pages contain slightly different login and logout code than the prior quickstart and templates because the login and logout processes now rely on ASP.NET Core Identity. Notice the use of the `SignInManager` and `UserManager` types from ASP.NET Core Identity to validate credentials and manage the authentication session. Much of the rest of the code is the same from the prior quickstarts and templates. ## Logging In With The Web client [Section titled “Logging In With The Web client”](#logging-in-with-the-web-client) At this point, you should be able to run all the existing clients and samples. Launch the Web client application, and you should be redirected to IdentityServer to log in. Login with one of the users created by the seed process (e.g., alice/Pass123$), and after that you will be redirected back to the Web client application where your user’s claims should be listed. ![ASP.NET Core application showing properties and claims on a ClaimsPrincipal](/_astro/aspid_claims.CuAZLh_S_2tSuUE.webp) You should also be able to go to the call api page at `https://localhost:5002/callapi` to invoke the API on behalf of the user: ![Showing claims retrieved from an API in an ASP.NET Core application](/_astro/aspid_api_claims.CdpDd3zD_Z2WLLd.webp) Congratulations, you’re using users from ASP.NET Core Identity in IdentityServer! ## Adding Custom Profile Data [Section titled “Adding Custom Profile Data”](#adding-custom-profile-data) Next you will add a custom property to your user model and include it as a claim when the appropriate Identity Resource is requested. First, add a `FavoriteColor` property in `src/IdentityServerAspNetIdentity/ApplicationUser.cs`. ```csharp public class ApplicationUser : IdentityUser { public string FavoriteColor { get; set; } } ``` Then, set the FavoriteColor of one of your test users in `SeedData.cs` ```csharp alice = new ApplicationUser { UserName = "alice", Email = "AliceSmith@email.com", EmailConfirmed = true, FavoriteColor = "red", }; ``` In the same file, add code to recreate the database when you re-seed the data, by calling `EnsureDeleted` just before `Migrate`: ```csharp var context = scope.ServiceProvider.GetService(); context.Database.EnsureDeleted(); context.Database.Migrate(); ``` Next, create an ef migration for the CustomProfileData and reseed your user database. Run the following commands from the `src/IdentityServerAspNetIdentity` directory: ```sh dotnet ef migrations add CustomProfileData dotnet run /seed ``` Now that you have more data in the database, you can use it to set claims. IdentityServer contains an extensibility point called the `IProfileService` that is responsible for retrieval of user claims. The ASP.NET Identity Integration includes an implementation of `IProfileService` that retrieves claims from ASP.NET Identity. You can extend that implementation to use the custom profile data as a source of claims data. [See here](/identityserver/reference/v8/services/profile-service/) for more details on the profile service. Create a new file called `src/IdentityServerAspNetIdentity/CustomProfileService.cs` and add the following code to it: ```csharp using Duende.IdentityServer.AspNetIdentity; using Duende.IdentityServer.Models; using IdentityServerAspNetIdentity.Models; using Microsoft.AspNetCore.Identity; using System.Security.Claims; namespace IdentityServerAspNetIdentity { public class CustomProfileService : ProfileService { public CustomProfileService(UserManager userManager, IUserClaimsPrincipalFactory claimsFactory) : base(userManager, claimsFactory) { } protected override async Task GetProfileDataAsync(ProfileDataRequestContext context, ApplicationUser user) { var principal = await GetUserClaimsAsync(user); var id = (ClaimsIdentity)principal.Identity; if (!string.IsNullOrEmpty(user.FavoriteColor)) { id.AddClaim(new Claim("favorite_color", user.FavoriteColor)); } context.AddRequestedClaims(principal.Claims); } } } ``` Register the `CustomProfileService` in `HostingExtensions.cs`: HostingExtensions.cs ```csharp builder.Services .AddIdentityServer(options => { // ... }) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) .AddAspNetIdentity() .AddProfileService(); ``` Finally, you need to configure your application to make a request for the favorite\_color, and include that claim in your client’s configuration. Add a new `IdentityResource` in `src/IdentityServerAspNetIdentity/Config.cs` that will map the color scope onto the favorite\_color claim type: ```csharp public static IEnumerable IdentityResources => new IdentityResource[] { // ... new IdentityResource("color", new [] { "favorite_color" }) }; ``` Allow the web client to request the color scope (also in `Config.cs`): ```csharp new Client { ClientId = "web", // ... AllowedScopes = new List { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1", "color" } } ``` Finally, update the `WebClient` project so that it will request the color scope. In its `src/WebClient/Program.cs` file, add the color scope to the requested scopes, and add a claim action to map the favorite\_color into the principal: Program.cs ```csharp .AddOpenIdConnect("oidc", options => { // ... options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("offline_access"); options.Scope.Add("api1"); options.Scope.Add("color"); options.GetClaimsFromUserInfoEndpoint = true; options.ClaimActions.MapUniqueJsonKey("favorite_color", "favorite_color"); }); ``` Now restart the `IdentityServerAspNetIdentity` and `WebClient` projects, sign out and sign back in as alice, and you should see the favorite color claim. ## What’s Missing? [Section titled “What’s Missing?”](#whats-missing) The rest of the code in this template is similar to the other quickstarts and templates we provide. You will notice that this template does not include UI code for user registration, password reset, and other things you might expect from Microsoft’s templates that include ASP.NET Core Identity. Given the variety of requirements and different approaches to using ASP.NET Core Identity, our template deliberately does not provide those features. The intent of this template is to be a starting point to which you can add the features you need from ASP.NET Core Identity, customized according to your requirements. Alternatively, you can [create a new project based on the ASP.NET Core Identity template](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-8.0\&tabs=netcore-cli#create-a-web-app-with-authentication) and add the IdentityServer features you have learned about in these quickstarts to that project. With that approach, you may need to configure IdentityServer so that it knows the paths to pages for user interactions. Set the LoginUrl, LogoutUrl, ConsentUrl, ErrorUrl, and DeviceVerificationUrl as needed in your `IdentityServerOptions`. ----- # Building Blazor WASM Client Applications > Learn how to build secure Blazor WebAssembly applications using the Duende BFF security framework and integrate them with IdentityServer. Blazor applications can be set up using different interactivity modes: * Static * Server * WebAssembly * Auto Projects using the static or server modes can be configured just like any other ASP.NET Core application. We covered that in the [interactive applications](/identityserver/quickstarts/2-interactive/) quickstart. Similar to JavaScript SPAs, you can build Blazor WebAssembly applications with and without a backend. Not having a backend has all the security disadvantages we discussed already in the [JavaScript quickstart](/identityserver/quickstarts/javascript-clients/). So in this quickstart we will focus on how to build a Blazor WebAssembly application using our Duende.BFF security framework. You can find the full source code [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/7_Blazor). The “auto” interactivity mode requires a mix of server-side authentication and authentication with a BFF. This is more complex than we want this quickstart to be. But we have a [template with annotations](https://github.com/DuendeSoftware/products/tree/main/bff/templates/src/BffBlazorAutoRenderMode) that helps with that. Before diving into that however, we recommend you first follow this quickstart first. ## Setting Up The Project [Section titled “Setting Up The Project”](#setting-up-the-project) The .NET CLI includes a template that sets up a standalone Blazor WebAssembly project. Create the directory where you want to work in, and run the following command: ```plaintext dotnet new blazorwasm -n BlazorWasm ``` Now create a backend that will host the BFF. ```plaintext dotnet new web -n BFF ``` And if you’re using Visual Studio or Rider, create a solution file and add the projects: ```plaintext dotnet new sln -n BlazorQuickstart dotnet sln add BlazorWasm/BlazorWasm.csproj dotnet sln add BFF/BFF.csproj ``` Open the solution in your IDE or if you use Visual Studio Code open the directory where you created the solution file. ## Configuring The BFF [Section titled “Configuring The BFF”](#configuring-the-bff) In the BFF project, add a reference to the BlazorWasm project and modify `Program.cs` as follows: Program.cs ```csharp var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapStaticAssets(); app.MapFallbackToFile("index.html"); app.Run(); ``` When you run just the BFF project now, you should see the Blazor application running. The call to `MapFallbackToFile` renders the entry point of the Blazor application in the browser. It’s important that both projects run on the same site because the session cookie we’ll use has the samesite=strict flag to protect against CSRF attacks. Add the following package references to the BFF project: * [Microsoft.AspNetCore.Authentication.OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect) * [Duende.BFF](https://www.nuget.org/packages/Duende.BFF) Next, we will add OpenID Connect and OAuth support to the BFF. For this we are adding the Microsoft OpenID Connect authentication handler for the protocol interactions with IdentityServer, and the cookie authentication handler for managing the resulting authentication session. See [here](/bff/fundamentals/session/handlers/) for more background information. The BFF services provide the logic to invoke the authentication plumbing from the frontend (more about this later). Add the following snippet to your `Program.cs` just before the call to `builder.Build();` * Duende BFF v4 Program.cs ```csharp builder.Services.AddAuthorization(); builder.Services.AddCascadingAuthenticationState(); builder.Services .AddBff() .ConfigureOpenIdConnect(options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.MapInboundClaims = false; options.ClaimActions.MapAll(); options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }) .ConfigureCookies(options => { options.Cookie.Name = "__Host-blazor"; options.Cookie.SameSite = SameSiteMode.Strict; }); ``` * Duende BFF v3 Program.cs ```csharp builder.Services.AddAuthorization(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddBff(); builder.Services .AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("cookie", options => { options.Cookie.Name = "__Host-blazor"; options.Cookie.SameSite = SameSiteMode.Strict; }) .AddOpenIdConnect("oidc", options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.MapInboundClaims = false; options.ClaimActions.MapAll(); options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }); ``` The last step is to add the required middleware for authentication, authorization and BFF session management. Add the following snippet before the call to `MapStaticAssets`: Program.cs ```csharp app.UseAuthentication(); app.UseBff(); app.UseAuthorization(); app.MapBffManagementEndpoints(); ``` Now run the BFF project again. **Be sure to use https.** Try to manually invoke the BFF login endpoint on `/bff/login` - this should bring you to the demo IdentityServer. After login (e.g. using bob/bob), the browser will return to the Blazor application. In other words, the fundamental authentication plumbing is already working. Now we need to make the frontend aware of it. ## Modifying The Frontend (Part 1) [Section titled “Modifying The Frontend (Part 1)”](#modifying-the-frontend-part-1) A couple of steps are necessary to add the security and identity plumbing to the Blazor application. *`a)`* Install the NuGet package [“Microsoft.AspNetCore.Components.WebAssembly.Authentication”](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.WebAssembly.Authentication/). *`b)`* Add a using statement to `_Imports.razor` in the BlazorWasm project: ```csharp @using Microsoft.AspNetCore.Components.Authorization ``` *`c)`* To propagate the current authentication state to all pages in the Blazor client, a component called `CascadingAuthenticationState` is used. Wrap the Router component in the file `App.razor` with it: ```razor ``` *`d)`* Last but not least, we will add some conditional rendering to the layout page to be able to trigger login/logout and displaying the current user name when logged in. This is achieved by using the `AuthorizeView` component in `MainLayout.razor`. Replace the contents of the `
` with this: ```razor
Hello, @context.User.Identity.Name! Log out Log in
@Body
``` When you now run the Blazor application, you will see the following error in your browser console: ```plaintext crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Cannot provide a value for property 'AuthenticationStateProvider' on type 'Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState'. There is no registered service of type 'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'. ``` `CascadingAuthenticationState` is an abstraction over an arbitrary authentication system. It internally relies on a service called `AuthenticationStateProvider` to return the required information about the current authentication state and the information about the currently logged on user. A special version of this component, aware of the BFF, has to be added, and that’s what we’ll do next. ## Modifying The Frontend (Part 2) [Section titled “Modifying The Frontend (Part 2)”](#modifying-the-frontend-part-2) The BFF library we just configured includes an endpoint that allows the Blazor application to query the current authentication session and state (see [here](/bff/fundamentals/session/management/user/)). We will now add a Blazor `AuthenticationStateProvider` that will internally use this endpoint. It is included in our NuGet package “Duende.BFF.Blazor.Client”. In the BlazorWasm.Client project: * Add the NuGet package [“Duende.BFF.Blazor.Client”](https://www.nuget.org/packages/Duende.BFF.Blazor.Client/). * In `Program.cs`, just before the call to `builder.Build().RunAsync();`, add the following code: ```csharp builder.Services.AddBffBlazorClient(); ``` If you restart the application again, the logon/logoff logic should work now. In addition, you can display the contents of the session on the main page by replacing the code in `Home.razor` with this: ```razor @page "/" Home

Hello, Blazor BFF!

@foreach (var claim in @context.User.Claims) {
@claim.Type
@claim.Value
}
``` The claims you see on the page are coming from the user endpoint on the BFF and the `AuthenticationStateProvider` we just registered with the call to `AddBffBlazorClient` takes care of polling the endpoint. ## Securing a Local API Endpoint [Section titled “Securing a Local API Endpoint”](#securing-a-local-api-endpoint) Right now the BFF project doesn’t contain any endpoints. Let’s create a simple one that will be used by the Blazor application. *`a)`* Observe the `Weather.razor` page in the BlazorWasm project. In `OnInitializedAsync`, it fetches data from a file. *`b)`* Move the file wwwwroot/sample-data/weather.json to the root of the BFF project. *`c)`* In the `Program.cs` file of the BFF project, just above the `MapFallbackToFile` call, add the following code: Program.cs ```csharp app.MapGet("/api/data", async () => { var json = await File.ReadAllTextAsync("weather.json"); return Results.Content(json, "application/json"); }).RequireAuthorization().AsBffApiEndpoint(); ``` `RequireAuthorization` is ASP.NET Core’s standard way to make sure a user is authenticated before accessing a given endpoint. `AsBffApiEndpoint` is an extension method provided by the BFF library that adds anti-forgery protection to the endpoint and returns the expected 401 response when the user is not authenticated. The anti-forgery protection consists of the requirement to include an `X-CSRF` HTTP header with each request. *`d)`* In the `Program.cs` file of the BlazorWasm project, replace the registration of the `HttpClient` with the following: Program.cs ```csharp builder.Services.AddTransient(sp => { var client = new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }; client.DefaultRequestHeaders.Add("X-CSRF", "1"); return client; }); ``` Alternatively, a [handler](https://duendesoftware.com/blog/20250902-dotnet-httpclient-and-delegating-handlers) can be created and used with the `HttpClient` instance. And with this in place, the application should be able to fetch data from the API endpoint when the Weather page is shown. ## Setting Up A Blazor BFF client In IdentityServer [Section titled “Setting Up A Blazor BFF client In IdentityServer”](#setting-up-a-blazor-bff-client-in-identityserver) In essence, a BFF client is “just” a normal authorization code flow client: * use the code grant type * set a client secret * enable `AllowOfflineAccess` if you want to use refresh tokens * enable the required identity and resource scopes * set the redirect URIs for the OIDC handler Below is a typical code snippet for the client definition: ```csharp var bffClient = new Client { ClientId = "bff", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, RedirectUris = { "https://bff_host/signin-oidc" }, FrontChannelLogoutUri = "https://bff_host/signout-oidc", PostLogoutRedirectUris = { "https://bff_host/signout-callback-oidc" }, AllowOfflineAccess = true, AllowedScopes = { "openid", "profile", "remote_api" } }; ``` ----- # Building Browser-Based Client Applications > Overview of browser-based client application patterns and security considerations when implementing JavaScript clients with IdentityServer When building browser-based or SPA applications using javascript, there are two main styles: those with a backend and those without. Browser-based applications **with a backend** are more secure, making it the recommended style. This style uses the [“Backend For Frontend” pattern](https://duendesoftware.com/blog/20210326-bff), or “BFF” for short, which relies on the backend host to implement all the security protocol interactions with the token server. The `Duende.BFF` library is used in [this quickstart](/identityserver/quickstarts/javascript-clients/js-with-backend/) to easily support the BFF pattern. Browser-based applications **without a backend** need to do all the security protocol interactions on the client-side, including driving user authentication and token requests, session and token management, and token storage. This leads to more complex JavaScript, cross-browser incompatibilities, and a considerably higher attack surface. Since this style inherently needs to store security sensitive artifacts (like tokens) in JavaScript reachable locations, **this style is not recommended**. As the [“OAuth 2.0 for Browser-Based Apps” IETF/OAuth working group BCP document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps) says: > there is no browser API that allows to store tokens in a completely secure way. Additionally, modern browsers have recently added or are planning to add privacy features that can break some front-channel protocol interactions. See [here](/bff/#3rd-party-cookies) for more details. ----- # Browser-Based Applications With A BFF > Guide to building secure browser-based JavaScript applications using the Backend For Frontend (BFF) pattern with Duende.BFF library In this quickstart, you will build a browser-based JavaScript client application with a backend. This means your application will have server-side code that supports the frontend application code. This is known as the Backend For Frontend (BFF) pattern. You will implement the BFF pattern with the help of the `Duende.BFF` library. The backend will implement all the security protocol interactions with the token server and will be responsible for management of the tokens. The client-side JavaScript authenticates with the BFF using traditional cookie authentication. This simplifies the JavaScript in the client-side, and reduces the attack surface of the application. The features that will be shown in this quickstart will allow the user to log in with IdentityServer, invoke a local API hosted in the backend (secured with cookie authentication), invoke a remote API running in a different host (secured with an access token), and logout of IdentityServer. ## New Project For The JavaScript Client And BFF [Section titled “New Project For The JavaScript Client And BFF”](#new-project-for-the-javascript-client-and-bff) Begin by creating a new project to host the JavaScript application and its BFF. A single project containing the front-end and its BFF facilitates cookie authentication - the front end and BFF need to be on the same host so that cookies will be sent from the front end to the BFF. Create a new ASP.NET Core web application and add it to the solution by running the following commands from the `src` directory: Terminal ```bash dotnet new web -n JavaScriptClient cd .. dotnet sln add ./src/JavaScriptClient ``` ### Add Additional NuGet Packages [Section titled “Add Additional NuGet Packages”](#add-additional-nuget-packages) Install NuGet packages to add BFF and OIDC support to the new project by running the following commands from the `src/JavaScriptClient` directory: Terminal ```bash dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect dotnet add package Duende.BFF dotnet add package Duende.BFF.Yarp ``` ### Modify Hosting [Section titled “Modify Hosting”](#modify-hosting) Modify the `JavaScriptClient` project to run on `https://localhost:5003`. Its `Properties/launchSettings.json` should look like this: ```json { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "JavaScriptClient": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:5003", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ``` ### Add Services [Section titled “Add Services”](#add-services) In the BFF pattern, the server-side code triggers and receives OpenID Connect requests and responses. To do that, it needs the same services configured as the WebClient did in the prior [web application quickstart](/identityserver/quickstarts/3-api-access/). Additionally, the BFF services need to be added with `AddBff()`. In addition, the offline\_access scope is requested that will result in a refresh token that will be used by the BFF library to automatically refresh the access token for the remote API if needed. Add the following to `src/JavaScriptClient/Program.cs`: * Duende BFF v4 Program.cs ```csharp using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Duende.Bff.Yarp; using Microsoft.AspNetCore.Authorization; var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthorization(); builder.Services .AddBff() .ConfigureOpenIdConnect(options => { options.Authority = "https://localhost:5001"; options.ClientId = "bff"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.Scope.Add("api1"); options.Scope.Add("offline_access"); options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.MapInboundClaims = false; }) .ConfigureCookies(options => options.Cookie.SameSite = SameSiteMode.Strict) .AddRemoteApis(); var app = builder.Build(); ``` * Duende BFF v3 Program.cs ```csharp using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Duende.Bff.Yarp; using Microsoft.AspNetCore.Authorization; var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthorization(); builder.Services .AddBff() .AddRemoteApis(); builder.Services .AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5001"; options.ClientId = "bff"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.Scope.Add("api1"); options.Scope.Add("offline_access"); options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.MapInboundClaims = false; }); var app = builder.Build(); ``` ### Add Middleware [Section titled “Add Middleware”](#add-middleware) Similarly, the middleware pipeline for this application will resemble the WebClient, with the addition of the BFF middleware and the BFF endpoints. Continue by adding the following to `src/JavaScriptClient/Program.cs`: Program.cs ```csharp var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseDefaultFiles(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseBff(); app.UseAuthorization(); app.MapBffManagementEndpoints(); app.Run(); ``` ### Add HTML And JavaScript Files [Section titled “Add HTML And JavaScript Files”](#add-html-and-javascript-files) Next, add HTML and JavaScript files for your client-side application to the `wwwroot` directory in the `JavaScriptClient` project. Create that directory (`src/JavaScriptClient/wwwroot`) and add an `index.html` and an `app.js` file to it. *`index.html`* The index.html file will be the main page in your application. It contains * buttons for the user to login, logout, and call the APIs * a `
` container used to show messages to the user * a `   ``` *`app.js`* The app.js file will contain the client-side code for your application. First, add a helper function to display messages in the `
`: ```js function log() { document.getElementById("results").innerText = ""; Array.prototype.forEach.call(arguments, function (msg) { if (typeof msg !== "undefined") { if (msg instanceof Error) { msg = "Error: " + msg.message; } else if (typeof msg !== "string") { msg = JSON.stringify(msg, null, 2); } document.getElementById("results").innerText += msg + "\r\n"; } }); } ``` Next, you can use the BFF `user` management endpoint to query if the user is logged in or not. Notice the `userClaims` variable is global; it will be needed elsewhere. ```js let userClaims = null; (async function () { var req = new Request("/bff/user", { headers: new Headers({ "X-CSRF": "1", }), }); try { var resp = await fetch(req); if (resp.ok) { userClaims = await resp.json(); log("user logged in", userClaims); } else if (resp.status === 401) { log("user not logged in"); } } catch (e) { log("error checking user status"); } })(); ``` Next, register `click` event handlers on the buttons: ```js document.getElementById("login").addEventListener("click", login, false); document.getElementById("local").addEventListener("click", localApi, false); document.getElementById("remote").addEventListener("click", remoteApi, false); document.getElementById("logout").addEventListener("click", logout, false); ``` Next, implement the `login` and `logout` functions. Login is simple - just redirect the user to the BFF `login` endpoint. ```js function login() { window.location = "/bff/login"; } ``` Logout is more involved, as you need to redirect the user to the BFF `logout` endpoint, which requires an anti-forgery token to prevent cross site request forgery attacks. The `userClaims` that you populated earlier contain that token and the full logout URL in its `bff:logout_url` claim, so redirect to that url: ```plaintext function logout() { if (userClaims) { var logoutUrl = userClaims.find( (claim) => claim.type === "bff:logout_url" ).value; window.location = logoutUrl; } else { window.location = "/bff/logout"; } } ``` Finally, add empty stubs for the other button event handler functions. You will implement those after you get login and logout working. ```js async function localApi() {} async function remoteApi() {} ``` ## Add JavaScript Client Registration To IdentityServer [Section titled “Add JavaScript Client Registration To IdentityServer”](#add-javascript-client-registration-to-identityserver) Now that the client application is ready to go, you need to define a configuration entry in IdentityServer for the new JavaScript client. In the IdentityServer project locate the client configuration in `src/IdentityServer/Config.cs`. Add a new `Client` to the list for your new JavaScript application. Because this client uses the BFF pattern, the configuration will be very similar to the Web client. In addition, requesting the offline\_access scope should be allowed for this client. It should have the configuration listed below: ```csharp // JavaScript BFF client new Client { ClientId = "bff", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5003/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" }, AllowOfflineAccess = true, AllowedScopes = new List { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" } } ``` ## Run And Test Login And Logout [Section titled “Run And Test Login And Logout”](#run-and-test-login-and-logout) At this point, you should be able to run the `JavaScriptClient` application. You should see that the user is not logged in initially. ![A simple javascript client with multiple action buttons](/_astro/jsbff_not_logged_in.CdU2CJD4_ZC2tC7.webp) When you click the login button, you’ll be redirected to IdentityServer to login. After you log in, you’ll be redirected back to the `JavaScriptClient` application, where you’ll be signed in with the Cookies authentication scheme with your tokens saved in the session. The app loads again, but this time it has a session cookie. So, when it makes the HTTP request to get userClaims, that cookie is included in the request. This allows the BFF middleware to authenticate the user and return user info. Once the `JavaScriptClient` application receives the response, the user should appear logged in and their claims should be displayed. ![showing claims after the login action is invoked](/_astro/jsbff_logged_in.Dmunfv6t_peHEJ.webp) Finally, the logout button should successfully get the user logged out. ![showing the logout view on IdentityServer](/_astro/jsbff_signed_out.C6LecfKJ_Z2vDWkI.webp) ## Add API Support [Section titled “Add API Support”](#add-api-support) Now that you have login and logout working, you will add support to invoke both local and remote APIs. A local API is an endpoint that is hosted in the same backend as the `JavaScriptClient` application. Local APIs are intended to be APIs that only exist to support the JavaScript frontend, typically by providing UI specific data or aggregating data from other sources. Local APIs are authenticated with the user’s session cookie. A remote API is an API running in some other host than the `JavaScriptClient` application. This is useful for APIs that are shared by many different applications (e.g. mobile app, other web apps, etc.). Remote APIs are authenticated with an access token. Fortunately, the `JavaScriptClient` application has an access token stored in the user’s session. You will use the BFF proxy feature to accept a call from the JavaScript running in the browser authenticated with the user’s session cookie, retrieve the access token for the user from the user’s session, and then proxy the call to the remote API, sending the access token for authentication. ### Define A Local API [Section titled “Define A Local API”](#define-a-local-api) Local APIs can be defined using controllers or with [Minimal API Route Handlers](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-8.0#route-handlers). For simplicity, this quickstart uses a minimal API with its handler defined directly in `Program.cs`, but you can organize your Local APIs however you like. Add a handler to `src/JavaScriptClient/Program.cs` for the local API: ```csharp [Authorize] static IResult LocalIdentityHandler(ClaimsPrincipal user) { var name = user.FindFirst("name")?.Value ?? user.FindFirst("sub")?.Value; return Results.Json(new { message = "Local API Success!", user = name }); } ``` ### Update Routing To Accept Local And Remote API Calls [Section titled “Update Routing To Accept Local And Remote API Calls”](#update-routing-to-accept-local-and-remote-api-calls) Next, you need to register both the local API and the BFF proxy for the remote API in the ASP.NET Core routing system. Add the code below to the endpoint configuration code in `src/JavaScriptClient/Program.cs`. ```csharp app.MapBffManagementEndpoints(); // Uncomment this for Controller support // app.MapControllers() // .AsBffApiEndpoint(); app.MapGet("/local/identity", LocalIdentityHandler) .AsBffApiEndpoint(); app.MapRemoteBffApiEndpoint("/remote", new Uri("https://localhost:6001")) .WithAccessToken(RequiredTokenType.User); ``` The call to the `AsBffApiEndpoint()` fluent helper method adds BFF support to the local APIs. This includes anti-forgery protection and suppressing login redirects on authentication failures and instead returning 401 and 403 status codes under the appropriate circumstances. `MapRemoteBffApiEndpoint()` registers the BFF proxy for the remote API and configures it to pass the user’s access token. ### Call The APIs From JavaScript [Section titled “Call The APIs From JavaScript”](#call-the-apis-from-javascript) Back in `src/JavaScriptClient/wwwroot/app.js`, implement the two API button event handlers like this: ```js async function localApi() { var req = new Request("/local/identity", { headers: new Headers({ "X-CSRF": "1", }), }); try { var resp = await fetch(req); let data; if (resp.ok) { data = await resp.json(); } log("Local API Result: " + resp.status, data); } catch (e) { log("error calling local API"); } } async function remoteApi() { var req = new Request("/remote/identity", { headers: new Headers({ "X-CSRF": "1", }), }); try { var resp = await fetch(req); let data; if (resp.ok) { data = await resp.json(); } log("Remote API Result: " + resp.status, data); } catch (e) { log("error calling remote API"); } } ``` The path for the local API is exactly what you set in the call to `MapGet` in `src/JavaScriptClient/Program.cs`. The path for the remote API uses a “/remote” prefix to indicate that the BFF proxy should be used, and the remaining path is what’s then passed when invoking the remote API (“/identity” in this case). Notice both API calls require an *‘X-CSRF’: ‘1’* header, which acts as the anti-forgery token. ## Run And Test The API Calls [Section titled “Run And Test The API Calls”](#run-and-test-the-api-calls) At this point, you should be able to run the `JavaScriptClient` application and invoke the APIs. The local API should return something like this: ![showing a successful JavaScript fetch call to an API endpoint](/_astro/jsbff_local_api.D6JHzKis_2gRn2k.webp) And the remote API should return something like this: ![showing a successful JavaScript call to a remote api hosted in BFF](/_astro/jsbff_remote_api.BmDJGtvt_Z20u7nQ.webp) You now have the start of a JavaScript client application that uses IdentityServer for sign-in, sign-out, and authenticating calls to local and remote APIs, using `Duende.BFF`.
-----
# JavaScript Applications Without A Backend

> Learn how to build a client-side JavaScript application that interacts directly with IdentityServer for authentication and API access without a backend server.

This quickstart will show how to build a browser-based JavaScript client application without a backend. This means your application has no server-side code that can support the frontend application code, and thus all OpenID Connect/OAuth protocol interactions occur from the JavaScript code running in the browser. Also, invoking the API will be performed directly from the JavaScript in the browser. **This design has security concerns. It is no longer recommended.** See [overview](/identityserver/quickstarts/javascript-clients/) for details. The current best practice uses the [“BFF” pattern](/identityserver/quickstarts/javascript-clients/js-with-backend/). In this quickstart the user will log in to IdentityServer, invoke an API with an access token issued by IdentityServer, and logout of IdentityServer. All of this will be driven from the JavaScript running in the browser. ## New Project For The JavaScript Client [Section titled “New Project For The JavaScript Client”](#new-project-for-the-javascript-client) Create a new project for the JavaScript application. Beyond being able to serve your application’s HTML and javascript, there are no requirements on the backend. You could use anything from an empty ASP.NET Core application to a Node.js application. This quickstart will use an ASP.NET Core application. Create a new ASP.NET Core web application and add it to the solution by running the following commands from the `src` directory: ```console dotnet new web -n JavaScriptClient cd .. dotnet sln add ./src/JavaScriptClient ``` ### Modify Hosting [Section titled “Modify Hosting”](#modify-hosting) Modify the `JavaScriptClient` project to run on `https://localhost:5003`. Its `Properties/launchSettings.json` should look like this: ```json { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "JavaScriptClient": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:5003", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ``` ### Add Static File Middleware [Section titled “Add Static File Middleware”](#add-static-file-middleware) Given that this project is designed to run client-side, all we need ASP.NET Core to do is to serve up the static HTML and JavaScript files that will make up our application. The static file middleware is designed to do this. Register the static file middleware in `src/JavaScriptClient/Program.cs`. The entire file should look like this: ```csharp var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles(); app.Run(); ``` This middleware will now serve up static files from the application’s `src/JavaScriptClient/wwwroot` directory. This is where we will put our HTML and JavaScript files. If that directory does not exist in your project, create it now. ### Reference oidc-client [Section titled “Reference oidc-client”](#reference-oidc-client) In the prior [web application quickstart](/identityserver/quickstarts/3-api-access/), we used a .NET library to handle the OpenID Connect protocol. In this quickstart, we need a similar library in the `JavaScriptClient` project, except one that works in JavaScript and is designed to run in the browser. The [oidc-client library](https://github.com/IdentityModel/oidc-client-js) is one such library. It is available via [NPM](https://github.com/IdentityModel/oidc-client-js), or as a [direct download](https://github.com/IdentityModel/oidc-client-js/tree/release/dist) from GitHub. *`NPM`* If you want to use NPM to download `oidc-client`, then run these commands from the `src/JavaScriptClient` directory: ```console npm i oidc-client copy node_modules/oidc-client/dist/* wwwroot ``` This downloads the latest `oidc-client` package locally, and then copies the relevant JavaScript files into `src/JavaScriptClient/wwwroot` so they can be served by your application. **Manual download** If you want to download the `oidc-client` JavaScript files manually, browse to [the GitHub repository](https://github.com/IdentityModel/oidc-client-js/tree/release/dist) and download the JavaScript files. Once downloaded, copy them into `src/JavaScriptClient/wwwroot` so they can be served by your application. ### Add HTML And JavaScript Files [Section titled “Add HTML And JavaScript Files”](#add-html-and-javascript-files) Next, add HTML and JavaScript files to the `src/JavaScriptClient/wwwroot` directory. You will need two HTML files and one JavaScript file (in addition to the `oidc-client.js` library). Add `index.html`, `callback.html`, and `app.js` to `wwwroot`. *`index.html`* This will be the main page in your application. It contains * buttons for the user to login, logout, and call the API * a `
` container used to show messages to the user * `    ``` *`app.js`* This will contain the main code for your application. First, add a helper function to display messages in the `
`: ```js function log() { document.getElementById("results").innerText = ""; Array.prototype.forEach.call(arguments, function (msg) { if (typeof msg !== "undefined") { if (msg instanceof Error) { msg = "Error: " + msg.message; } else if (typeof msg !== "string") { msg = JSON.stringify(msg, null, 2); } document.getElementById("results").innerText += msg + "\r\n"; } }); } ``` Next, add code to register `click` event handlers to the three buttons: ```js document.getElementById("login").addEventListener("click", login, false); document.getElementById("api").addEventListener("click", api, false); document.getElementById("logout").addEventListener("click", logout, false); ``` Next, you will set up the `UserManager` class from the `oidc-client` library to manage the OpenID Connect protocol. It requires similar configuration that was necessary in the `WebClient` (albeit with different values). Add this code to configure and instantiate the `UserManager`: ```js var config = { authority: "https://localhost:5001", client_id: "js", redirect_uri: "https://localhost:5003/callback.html", response_type: "code", scope: "openid profile api1", post_logout_redirect_uri: "https://localhost:5003/index.html", }; var mgr = new Oidc.UserManager(config); ``` Next, use the `UserManager.getUser` function to determine if the user is logged into the JavaScript application. It uses a JavaScript `Promise` to return the results asynchronously. The returned `User` object has a `profile` property which contains the claims for the user. There’s also an event called `UserSignedOut` that can be handled to detect if the user signs out of the token server while the SPA application is being used (presumably in a different tab). Add this code to detect the user’s session status in the JavaScript application: ```js mgr.events.addUserSignedOut(function () { log("User signed out of IdentityServer"); }); mgr.getUser().then(function (user) { if (user) { log("User logged in", user.profile); } else { log("User not logged in"); } }); ``` Next, implement the `login`, `api`, and `logout` functions. The `UserManager` provides a `signinRedirect` to log the user in, and a `signoutRedirect` to log the user out. The `User` object that we obtained above also has an `access_token` property which can be used to authenticate to a web API. The `access_token` will be passed to the web API via the `Authorization` header with the `Bearer` scheme. Add this code to implement those three functions in your application: ```js function login() { mgr.signinRedirect(); } function api() { mgr.getUser().then(function (user) { var url = "https://localhost:6001/identity"; var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function () { log(xhr.status, JSON.parse(xhr.responseText)); }; xhr.setRequestHeader("Authorization", "Bearer " + user.access_token); xhr.send(); }); } function logout() { mgr.signoutRedirect(); } ``` *`callback.html`* This HTML file is the designated `redirect_uri` page once the user has logged into IdentityServer. It will complete the OpenID Connect protocol sign-in handshake with IdentityServer. The code for this is all provided by the `UserManager` class we used earlier. Once the sign-in is complete, we can then redirect the user back to the main `index.html` page. Add this code to complete the signin process: ```html            ``` ## Add JavaScript Client Registration To IdentityServer [Section titled “Add JavaScript Client Registration To IdentityServer”](#add-javascript-client-registration-to-identityserver) Now that the client application is ready to go, you need to define a configuration entry in IdentityServer for the new JavaScript client. In the IdentityServer project locate the client configuration in `src/IdentityServer/Config.cs`. Add a new `Client` to the list for your new JavaScript application. It should have the configuration listed below: ```csharp // JavaScript Client new Client { ClientId = "js", ClientName = "JavaScript Client", AllowedGrantTypes = GrantTypes.Code, RequireClientSecret = false, RedirectUris = { "https://localhost:5003/callback.html" }, PostLogoutRedirectUris = { "https://localhost:5003/index.html" }, AllowedCorsOrigins = { "https://localhost:5003" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" } } ``` ## Allowing Ajax Calls To The Web API With CORS [Section titled “Allowing Ajax Calls To The Web API With CORS”](#allowing-ajax-calls-to-the-web-api-with-cors) One last bit of configuration that is necessary is to configure CORS in the `Api` project. This will allow Ajax calls to be made from `https://localhost:5003` to `https://localhost:6001`. **Configure CORS** Add the CORS service to the dependency injection system in `src/Api/Program.cs`: Program.cs ```csharp builder.Services.AddCors(options => { // this defines a CORS policy called "default" options.AddPolicy("default", policy => { policy.WithOrigins("https://localhost:5003") .AllowAnyHeader() .AllowAnyMethod(); }); }); ``` Then add the CORS middleware to the pipeline in `src/Api/Program.cs`. Program.cs ```csharp app.UseHttpsRedirection(); app.UseCors("default"); ``` ## Run The JavaScript Application [Section titled “Run The JavaScript Application”](#run-the-javascript-application) Now you should be able to run the JavaScript client application: ![Showing a user is not logged in to a JavaScript client](/_astro/jsclient_not_logged_in.Cf2cwMl__Z1P5MVv.webp) Click the “Login” button to sign the user in. Once the user is returned back to the JavaScript application, you should see their profile information: ![Showing claims after a client has logged in](/_astro/jsclient_logged_in.4rNpJI6d_jlHrN.webp) And click the “API” button to invoke the web API: ![Showing the API results from a JavaScript fetch](/_astro/jsclient_api_results.rd6LEavw_ZJdfFC.webp) And finally click “Logout” to sign the user out. ![Showing the IdentityServer Logged Out view](/_astro/jsclient_signed_out.DI52Y-Hj_HinB.webp) You now have the start of a JavaScript client application that uses IdentityServer for sign-in, sign-out, and authenticating calls to web APIs.
-----
# Models

> Reference documentation for the models and interfaces used in Dynamic Client Registration (DCR), including request/response objects and validation context.

## DynamicClientRegistrationRequest [Section titled “DynamicClientRegistrationRequest”](#dynamicclientregistrationrequest) Represents a dynamic client registration request. The parameters that are supported include a subset of the parameters [defined by IANA](https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#client-metadata), and custom properties needed by IdentityServer. ```csharp public class DynamicClientRegistrationRequest ``` #### Public Members [Section titled “Public Members”](#public-members) | name | description | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `AbsoluteRefreshTokenLifetime { get; set; }` | The absolute lifetime of refresh tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | | `AccessTokenLifetime { get; set; }` | The lifetime of access tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | | `AccessTokenType { get; set; }` | The type of access tokens this client will create. Either “Jwt” or “Reference”. This property is an extension to the Dynamic Client Registration Protocol. | | `AllowedCorsOrigins { get; set; }` | List of allowed CORS origins for JavaScript clients. This property is an extension to the Dynamic Client Registration Protocol. | | `AllowedIdentityTokenSigningAlgorithms { get; set; }` | List of signing algorithms to use when signing identity tokens. If not set, will use the server’s default signing algorithm. This property is an extension to the Dynamic Client Registration Protocol. | | `AllowRememberConsent { get; set; }` | Boolean value specifying whether a user’s consent can be remembered in flows initiated by this client. This property is an extension to the Dynamic Client Registration Protocol. | | `AuthorizationCodeLifetime { get; set; }` | The lifetime of authorization codes, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | | `BackChannelLogoutSessionRequired { get; set; }` | Boolean value specifying whether the RP requires that a `sid` (session ID) claim should be included in the Logout Token to identify the RP session with the OP when using the `backchannel_logout_uri`. | | `BackChannelLogoutUri { get; set; }` | RP URL that will cause the RP to log itself out when receiving a Logout Token from the OP. | | `ClientName { get; set; }` | Human-readable string name of the client to be presented to the end-user during authorization. | | `ClientUri { get; set; }` | Web page providing information about the client. | | `ConsentLifetime { get; set; }` | The lifetime of consent, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | | `CoordinateLifetimeWithUserSession { get; set; }` | When enabled, the client’s token lifetimes (e.g. refresh tokens) will be tied to the user’s session lifetime. This means when the user logs out, any revokable tokens will be removed. When using server-side sessions, expired sessions will also remove any revokable tokens, and backchannel logout will be triggered. This client’s setting overrides the global `CoordinateClientLifetimesWithUserSession` configuration setting. This property is an extension to the Dynamic Client Registration Protocol. | | `DefaultMaxAge { get; set; }` | Default maximum authentication age. This is stored as the `UserSsoLifetime` property of the IdentityServer client model. | | `EnableLocalLogin { get; set; }` | Boolean value specifying if local logins are enabled when this client uses interactive flows. This property is an extension to the Dynamic Client Registration Protocol. | | `Extensions { get; set; }` | Custom client metadata fields to include in the serialization. | | `FrontChannelLogoutSessionRequired { get; set; }` | Boolean value specifying whether the RP requires that a `sid` (session ID) query parameter should be included to identify the RP session with the OP when using the `frontchannel_logout_uri`. | | `FrontChannelLogoutUri { get; set; }` | RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. | | `GrantTypes { get; set; }` | List of OAuth 2.0 grant type strings that the client can use at the token endpoint. Valid values are `"authorization_code"`, `"client_credentials"`, `"refresh_token"`. | | `IdentityProviderRestrictions { get; set; }` | List of external IdPs that can be used with this client. If the list is empty, all IdPs are allowed. Defaults to empty. This property is an extension to the Dynamic Client Registration Protocol. | | `IdentityTokenLifetime { get; set; }` | The lifetime of identity tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | | `InitiateLoginUri { get; set; }` | URI using the https scheme that a third party can use to initiate a login by the relying party. | | `Jwks { get; set; }` | JWK Set document which contains the client’s public keys. The `JwksUri` and `Jwks` parameters MUST NOT both be present in the same request or response. | | `JwksUri { get; set; }` | URL to a JWK Set document which contains the client’s public keys. The `JwksUri` and `Jwks` parameters MUST NOT both be present in the same request or response. The default validator must be extended to make use of the `JwksUri`. The default implementation ignores this property. | | `LogoUri { get; set; }` | Logo for the client. If present, the server should display this image to the end-user during approval. | | `PostLogoutRedirectUris { get; set; }` | List of post-logout redirection URIs for use in the end session endpoint. | | `RedirectUris { get; set; }` | List of redirection URI strings for use in redirect-based flows such as the authorization code flow. Clients using flows with redirection must register their redirection URI values. | | `RefreshTokenExpiration { get; set; }` | The type of expiration for refresh tokens. Either `"sliding"` or `"absolute"`. This property is an extension to the Dynamic Client Registration Protocol. | | `RefreshTokenUsage { get; set; }` | The usage type for refresh tokens. Either `"OneTimeOnly"` or `"ReUse"`. This property is an extension to the Dynamic Client Registration Protocol. | | `RequireClientSecret { get; set; }` | Boolean value specifying if a client secret is needed to request tokens at the token endpoint. This property is an extension to the Dynamic Client Registration Protocol. | | `RequireConsent { get; set; }` | Boolean value specifying whether consent is required in user-centric flows initiated by this client. This property is an extension to the Dynamic Client Registration Protocol. | | `RequireSignedRequestObject { get; set; }` | Boolean value specifying whether authorization requests must be protected as signed request objects and provided through either the request or request\_uri parameters. | | `Scope { get; set; }` | String containing a space-separated list of scope values that the client can use when requesting access tokens. If omitted, the configuration API will register a client with the scopes set by the `DynamicClientRegistrationValidator.SetDefaultScopes` method, which defaults to no scopes. | | `SlidingRefreshTokenLifetime { get; set; }` | The sliding lifetime of refresh tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | | `SoftwareId { get; set; }` | A unique identifier string (e.g., a Universally Unique Identifier (UUID)) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically registered. Unlike `"client_id"`, which is issued by the authorization server and SHOULD vary between instances, the `"software_id"` SHOULD remain the same for all instances of the client software. The `"software_id"` SHOULD remain the same across multiple updates or versions of the same piece of software. The value of this field is not intended to be human-readable and is usually opaque to the client and authorization server. The default validator must be extended to make use of the `SoftwareId`. The default implementation ignores this property. | | `SoftwareStatement { get; set; }` | A software statement containing client metadata values about the client software as claims. This is a string value containing the entire signed JWT. The default validator must be extended to make use of the software statement. The default implementation ignores this property. | | `SoftwareVersion { get; set; }` | A version identifier string for the client software identified by `"software_id"`. The value of the `"software_version"` SHOULD change on any update to the client software identified by the same `"software_id"`. The value of this field is intended to be compared using string equality matching and no other comparison semantics are defined by this specification. The default validator must be extended to make use of the `SoftwareVersion`. The default implementation ignores this property. | | `TokenEndpointAuthenticationMethod { get; set; }` | Requested Client Authentication method for the Token Endpoint. The supported options are `"client_secret_post"`, `"client_secret_basic"`, `"client_secret_jwt"`, `"private_key_jwt"`. | | `UpdateAccessTokenClaimsOnRefresh { get; set; }` | Boolean value specifying whether access token claims are updated during token refresh. This property is an extension to the Dynamic Client Registration Protocol. | ## DynamicClientRegistrationResponse [Section titled “DynamicClientRegistrationResponse”](#dynamicclientregistrationresponse) Represents the response to a successful dynamic client registration request. This class extends the registration request by adding additional properties that are generated server side and not set by the client. ```csharp public class DynamicClientRegistrationResponse : DynamicClientRegistrationRequest, IDynamicClientRegistrationResponse ``` #### Public Members [Section titled “Public Members”](#public-members-1) | name | description | | ------------------------------------- | -------------------------------------------------------------------------------------------------- | | `ClientId { get; set; }` | Gets or sets the client ID. | | `ClientSecret { get; set; }` | Gets or sets the client secret. | | `ClientSecretExpiresAt { get; set; }` | Gets or sets the expiration time of the client secret. | | `ResponseTypes { get; set; }` | List of the OAuth 2.0 response type strings that the client can use at the authorization endpoint. | ## DynamicClientRegistrationContext [Section titled “DynamicClientRegistrationContext”](#dynamicclientregistrationcontext) Represents the context of a dynamic client registration request, including the original DCR request, the client model that is built up through validation and processing, the caller who made the DCR request, and other contextual information. ```csharp public class DynamicClientRegistrationContext ``` #### Public Members [Section titled “Public Members”](#public-members-2) | name | description | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Caller { get; set; }` | The ClaimsPrincipal that made the DCR request. | | `Client { get; set; }` | The client model that is built up through validation and processing. | | `Items { get; set; }` | A collection where additional contextual information may be stored. This is intended as a place to pass additional custom state between validation steps. | | `Request { get; set; }` | The original dynamic client registration request. | ## DynamicClientRegistrationError [Section titled “DynamicClientRegistrationError”](#dynamicclientregistrationerror) Represents an error that occurred during validation of a dynamic client registration request. This class implements the appropriate [marker interfaces](#marker-interfaces) so that it can be returned from various points in the validator or processor. ```csharp public class DynamicClientRegistrationValidationError : IStepResult, IDynamicClientRegistrationResponse, IDynamicClientRegistrationValidationResult ``` #### Public Members [Section titled “Public Members”](#public-members-3) | name | description | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Error { get; set; }` | Gets or sets the error code for the error that occurred during validation. Error codes defined by RFC 7591 are defined as constants in the `DynamicClientRegistrationErrors` class. | | `ErrorDescription { get; set; }` | Gets or sets a human-readable description of the error that occurred during validation. | ## Marker Interfaces [Section titled “Marker Interfaces”](#marker-interfaces) #### IDynamicClientRegistrationResponse [Section titled “IDynamicClientRegistrationResponse”](#idynamicclientregistrationresponse) Marker interface for the response to a dynamic client registration request. This interface has two implementations; [`DynamicClientRegistrationResponse`](#dynamicclientregistrationresponse) indicates success, while [`DynamicClientRegistrationError`](#dynamicclientregistrationerror) indicates failure. #### IDynamicClientRegistrationValidationResult [Section titled “IDynamicClientRegistrationValidationResult”](#idynamicclientregistrationvalidationresult) Marker interface for the result of validating a dynamic client registration request. This interface has two implementations; [`DynamicClientRegistrationValidatedRequest`](#successfulstep) indicates success, while [`DynamicClientRegistrationError`](#dynamicclientregistrationerror) indicates failure. Note that the `DynamicClientRegistrationError` implements multiple interfaces and can be used throughout the pipeline to convey errors. #### IStepResult [Section titled “IStepResult”](#istepresult) Marker interface for the result of a step in the dynamic client registration validator or processor. This interface has two implementations; [`SuccessfulStep`](#successfulstep) indicates success, while [`DynamicClientRegistrationError`](#dynamicclientregistrationerror) indicates failure. Note that the `DynamicClientRegistrationError` implements multiple interfaces and can be used throughout the pipeline to convey errors. ### IStepResult Convenience Functions [Section titled “IStepResult Convenience Functions”](#istepresult-convenience-functions) Your validation or processing steps can return a call to convenience functions in the static class `StepResult` to conveniently construct a success or failure from a step wrapped in a task. | name | description | | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `static Task Success()` | Indicates that the validation step was completed was completed successfully | | `static Task Failure(string errorDescription)` | Indicates that the validation step failed with the specified error description and the default error code of invalid\_client\_metadata | | `static Task Failure(string errorDescription, string error)` | Indicates that the validation step failed with the specified error description and error code | ## DynamicClientRegistrationValidatedRequest [Section titled “DynamicClientRegistrationValidatedRequest”](#dynamicclientregistrationvalidatedrequest) Represents a successfully validated dynamic client registration request. ```csharp public class DynamicClientRegistrationValidatedRequest : DynamicClientRegistrationValidationResult ``` ## SuccessfulStep [Section titled “SuccessfulStep”](#successfulstep) Represents a successful validation step. ```csharp public class SuccessfulStep : IStepResult ```
-----
# Options

> Reference documentation for the IdentityServer configuration options related to dynamic client registration and secret lifetimes.

The page describes the `IdentityServerConfigurationOptions` class, which provides top-level configuration options for IdentityServer, including the `DynamicClientRegistrationOptions` class for managing dynamic client registration and secret lifetimes. ## IdentityServerConfigurationOptions [Section titled “IdentityServerConfigurationOptions”](#identityserverconfigurationoptions) Top-level options for IdentityServer configuration. ```csharp public class IdentityServerConfigurationOptions ``` ### Public Members [Section titled “Public Members”](#public-members) | name | description | | ------------------------------------------------------------------------------ | --------------------------------------- | | [`DynamicClientRegistration { get; set; }`](#dynamicclientregistrationoptions) | Options for Dynamic Client Registration | ## DynamicClientRegistrationOptions [Section titled “DynamicClientRegistrationOptions”](#dynamicclientregistrationoptions) Options for dynamic client registration. ```csharp public class DynamicClientRegistrationOptions ``` ### Public Members [Section titled “Public Members”](#public-members-1) | name | description | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SecretLifetime { get; set; }` | Gets or sets the lifetime of secrets generated for clients. If unset, generated secrets will have no expiration. Defaults to null (secrets never expire). |
-----
# Request Processing

> Understand how dynamic client registration requests are processed, including client ID and secret generation, through the IDynamicClientRegistrationRequestProcessor contract and its default implementation.

The page explains the `IDynamicClientRegistrationRequestProcessor` contract, its default implementation ( `DynamicClientRegistrationRequestProcessor`), and the steps involved in processing a dynamic client registration request, including methods for generating client IDs, secrets, and customizing secret generation. ## IDynamicClientRegistrationRequestProcessor [Section titled “IDynamicClientRegistrationRequestProcessor”](#idynamicclientregistrationrequestprocessor) The `IDynamicClientRegistrationValidator` is the contract for the service that processes a dynamic client registration request. It contains a single `ProcessAsync(...)` method. Conceptually, the request processing step is responsible for setting properties on the `Client` model that are generated by the Configuration API itself. In contrast, the `IDynamicClientRegistrationRequestProcessor` is responsible for checking the validity of the metadata supplied in the registration request, and using that metadata to set properties of a `Client` model. The request processor is also responsible for passing the finished `Client` to the [store](/identityserver/reference/v7/dcr/store/) ### Members [Section titled “Members”](#members) | name | description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessAsync(…)` | Processes a valid dynamic client registration request, setting properties of the client that are not specified in the request, and storing the new client in the IClientConfigurationStore. | ## DynamicClientRegistrationRequestProcessor [Section titled “DynamicClientRegistrationRequestProcessor”](#dynamicclientregistrationrequestprocessor) The `DynamicClientRegistrationRequestProcessor` is the default implementation of the `IDynamicClientRegistrationRequestProcessor`. If you need to customize some aspect of Dynamic Client Registration request processing, we recommend that you extend this class and override the appropriate virtual methods. ```csharp public class DynamicClientRegistrationRequestProcessor : IDynamicClientRegistrationRequestProcessor ``` ## Request Processing Steps [Section titled “Request Processing Steps”](#request-processing-steps) Each of these virtual methods represents one step of request processing. Each step is passed a [DynamicClientRegistrationContext](/identityserver/reference/v7/dcr/models/#dynamicclientregistrationcontext) and returns a task that returns an [`IStepResult`](/identityserver/reference/v7/dcr/models/#istepresult). The `DynamicClientRegistrationContext` includes the client model that will have its properties set, the DCR request, and other contextual information. The `IStepResult` either represents that the step succeeded or failed. | name | description | | ------------------------- | ------------------------------------------------------------------------- | | `virtual AddClientId` | Generates a client ID and adds it to the validatedRequest’s client model. | | `virtual AddClientSecret` | Adds a client secret to a dynamic client registration request. | ## Secret Generation [Section titled “Secret Generation”](#secret-generation) The `AddClientSecret` method is responsible for adding the client’s secret and plaintext of that secret to the context’s `Items` dictionary for later use. If you want to customize secret generation, you can override the GenerateSecret method, which only needs to return a tuple containing the secret and its plaintext. | name | description | | ------------------------ | ------------------------------------------------------------- | | `virtual GenerateSecret` | Generates a secret for a dynamic client registration request. |
-----
# Response Generation

> Reference documentation for dynamic client registration response generation, including interfaces and implementations for handling HTTP responses in the registration process.

## IDynamicClientRegistrationResponseGenerator [Section titled “IDynamicClientRegistrationResponseGenerator”](#idynamicclientregistrationresponsegenerator) The `IDynamicClientRegistrationResponseGenerator` interface defines the contract for a service that generates dynamic client registration responses. ```csharp public interface IDynamicClientRegistrationResponseGenerator ``` ### Members [Section titled “Members”](#members) | name | description | | -------------------------- | ------------------------------------------------------------------------ | | `WriteBadRequestError(…)` | Writes a bad request error to the HTTP context. | | `WriteContentTypeError(…)` | Writes a content type error to the HTTP response. | | `WriteProcessingError(…)` | Writes a processing error to the HTTP context. | | `WriteResponse(…)` | Writes a response object to the HTTP context with the given status code. | | `WriteSuccessResponse(…)` | Writes a success response to the HTTP context. | | `WriteValidationError(…)` | Writes a validation error to the HTTP context. | ## DynamicClientRegistrationResponseGenerator [Section titled “DynamicClientRegistrationResponseGenerator”](#dynamicclientregistrationresponsegenerator) The `DynamicClientRegistrationResponseGenerator` is the default implementation of the `IDynamicClientRegistrationResponseGenerator`. If you wish to customize a particular aspect of response generation, you can extend this class and override the appropriate methods. You can also set JSON serialization options by overriding its `SerializerOptions` property. ### Members [Section titled “Members”](#members-1) | name | description | | --------------------------------- | --------------------------------------------------- | | `SerializerOptions { get; set; }` | The options used for serializing json in responses. |
-----
# Store

> Reference documentation for the Dynamic Client Registration (DCR) store interfaces and implementations used to manage client configurations in IdentityServer

## IClientConfigurationStore [Section titled “IClientConfigurationStore”](#iclientconfigurationstore) The `IClientConfigurationStore` interface defines the contract for a service that communicates with the client configuration data store. It contains a single `AddAsync` method. ```csharp public interface IClientConfigurationStore ``` ### Members [Section titled “Members”](#members) | name | description | | ------------- | ----------------------------------------- | | `AddAsync(…)` | Adds a client to the configuration store. | ## ClientConfigurationStore [Section titled “ClientConfigurationStore”](#clientconfigurationstore) The `ClientConfigurationStore` is the default implementation of the `IClientConfigurationStore`. It uses Entity Framework to communicate with the client configuration store, and is intended to be used when IdentityServer is configured to use the Entity Framework based configuration stores.
-----
# Validation

> Reference documentation for Dynamic Client Registration (DCR) validation process, including validation steps, interfaces, and client property configuration.

## IDynamicClientRegistrationValidator [Section titled “IDynamicClientRegistrationValidator”](#idynamicclientregistrationvalidator) The `IDynamicClientRegistrationValidator` is the contract for the service that validates a dynamic client registration request. It contains a single `ValidateAsync(...)` method. Conceptually, the validation step is responsible for checking the validity of the metadata supplied in the registration request, and using that metadata to set properties of a `Client` model. In contrast, the `IDynamicClientRegistrationRequestProcessor` is responsible for setting properties on the `Client` model that are generated by the Configuration API itself. ### IDynamicClientRegistrationValidator.ValidateAsync [Section titled “IDynamicClientRegistrationValidator.ValidateAsync”](#idynamicclientregistrationvalidatorvalidateasync) Validates a dynamic client registration request. ```csharp public Task ValidateAsync( DynamicClientRegistrationContext context) ``` | parameter | description | | --------- | --------------------------------------------- | | `context` | Contextual information about the DCR request. | ### Return Value [Section titled “Return Value”](#return-value) A task that returns an [`IDynamicClientRegistrationValidationResult`](/identityserver/reference/v7/dcr/models/#idynamicclientregistrationvalidationresult), indicating success or failure. ## DynamicClientRegistrationValidator [Section titled “DynamicClientRegistrationValidator”](#dynamicclientregistrationvalidator) ```csharp public class DynamicClientRegistrationValidator : IDynamicClientRegistrationValidator ``` The `DynamicClientRegistrationValidator` class is the default implementation of the `IDynamicClientRegistrationValidator`. If you need to customize some aspect of Dynamic Client Registration validation, we recommend that you extend this class and override the appropriate methods. ## Validation Steps [Section titled “Validation Steps”](#validation-steps) Each of these methods represents one step in the validation process. Each step is passed a [`DynamicClientRegistrationContext`](/identityserver/reference/v7/dcr/models/#dynamicclientregistrationcontext) and returns a task that returns an [`IStepResult`](/identityserver/reference/v7/dcr/models/#istepresult). The `DynamicClientRegistrationContext` includes the client model that will have its properties set, the DCR request, and other contextual information. The `IStepResult` either represents that the step succeeded or failed. The steps are invoked in the same order as they appear in this table. | name | description | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ValidateSoftwareStatementAsync(…)` | Validates the software statement of the request. The default implementation does nothing, and is included as an extension point. | | `SetGrantTypesAsync(…)` | Validates requested grant types and uses them to set the allowed grant types of the client. | | `SetRedirectUrisAsync(…)` | Validates requested redirect URIs and uses them to set the redirect URIs of the client. | | `SetScopesAsync(…)` | Validates requested scopes and uses them to set the scopes of the client. | | `SetDefaultScopes(…)` | Sets scopes on the client when no scopes are requested. The default implementation sets no scopes and is intended as an extension point. | | `SetSecretsAsync(…)` | Validates the requested JSON Web Key set to set the secrets of the client. | | `SetClientNameAsync(…)` | Validates the requested client name uses it to set the name of the client. | | `SetLogoutParametersAsync(…)` | Validates the requested client parameters related to logout and uses them to set the corresponding properties in the client. Those parameters include the post logout redirect URIs, front channel and back channel URIs, and flags for the front and back channel URIs indicating if they require session ids. | | `SetMaxAgeAsync(…)` | Validates the requested default max age and uses it to set the user SSO lifetime of the client. | | `SetUserInterfaceProperties(…)` | Validates details of the request that control the user interface, including the logo URI, client URI, initiate login URI, enable local login flag, and identity provider restrictions, and uses them to set the corresponding client properties. | | `SetPublicClientProperties(…)` | Validates the requested client parameters related to public clients and uses them to set the corresponding properties in the client. Those parameters include the require client secret flag and the allowed CORS origins. | | `SetAccessTokenProperties(…)` | Validates the requested client parameters related to access tokens and uses them to set the corresponding properties in the client. Those parameters include the allowed access token type and access token lifetime. | | `SetIdTokenProperties(…)` | Validates the requested client parameters related to id tokens and uses them to set the corresponding properties in the client. Those parameters include the id token lifetime and the allowed id token signing algorithms. | | `SetServerSideSessionProperties(…)` | Validates the requested client parameters related to server side sessions and uses them to set the corresponding properties in the client. Those parameters include the coordinate lifetime with user session flag. |
-----
# Dependency Injection Extension Methods

> A comprehensive guide to IdentityServer's dependency injection extension methods for configuring services, stores, caching, signing keys and other features.

`AddIdentityServer` return a builder object that provides many extension methods to add IdentityServer specific services to the ASP.NET Core service provider. Here’s a list grouped by feature areas. Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(); ``` ## Configuration Stores [Section titled “Configuration Stores”](#configuration-stores) Several convenience methods are provided for registering custom stores: * **`AddClientStore`** Registers a custom `IClientStore` implementation. * **`AddCorsPolicyService`** Registers a custom `ICorsPolicyService` implementation. * **`AddResourceStore`** Registers a custom `IResourceStore` implementation. * **`AddIdentityProviderStore`** Registers a custom `IIdentityProviderStore` implementation. The [in-memory configuration stores](/identityserver/data/configuration/#in-memory-stores) can be registered in DI with the following extension methods. * **`AddInMemoryClients`** Registers `IClientStore` and `ICorsPolicyService` implementations based on the in-memory collection of `Client` configuration objects. * **`AddInMemoryIdentityResources`** Registers `IResourceStore` implementation based on the in-memory collection of `IdentityResource` configuration objects. * **`AddInMemoryApiScopes`** Registers `IResourceStore` implementation based on the in-memory collection of `ApiScope` configuration objects. * **`AddInMemoryApiResources`** Registers `IResourceStore` implementation based on the in-memory collection of `ApiResource` configuration objects. ## Caching Configuration Data [Section titled “Caching Configuration Data”](#caching-configuration-data) Extension methods to enable [caching for configuration data](/identityserver/data/configuration/#caching-configuration-data): * **`AddInMemoryCaching`** To use any of the caches described below, an implementation of `ICache` must be registered in the ASP.NET Core service provider. This API registers a default in-memory implementation of `ICache` that’s based on ASP.NET Core’s `MemoryCache`. * **`AddClientStoreCache`** Registers a `IClientStore` decorator implementation which will maintain an in-memory cache of `Client` configuration objects. The cache duration is configurable on the `Caching` configuration options on the `IdentityServerOptions`. * **`AddResourceStoreCache`** Registers a `IResourceStore` decorator implementation which will maintain an in-memory cache of `IdentityResource` and `ApiResource` configuration objects. The cache duration is configurable on the `Caching` configuration options on the `IdentityServerOptions`. * **`AddCorsPolicyCache`** Registers a `ICorsPolicyService` decorator implementation which will maintain an in-memory cache of the results of the CORS policy service evaluation. The cache duration is configurable on the `Caching` configuration options on the `IdentityServerOptions`. * **`AddIdentityProviderStoreCache`** Registers a `IIdentityProviderStore` decorator implementation which will maintain an in-memory cache of `IdentityProvider` configuration objects. The cache duration is configurable on the `Caching` configuration options on the `IdentityServerOptions`. ## Test Stores [Section titled “Test Stores”](#test-stores) The `TestUser` class models a user, their credentials, and claims in IdentityServer. Use of `TestUser` is similar to the use of the “in-memory” stores in that it is intended for when prototyping, developing, and/or testing. The use of `TestUser` is not recommended in production. * **`AddTestUsers`** Registers `TestUserStore` based on a collection of `TestUser` objects. `TestUserStore` is e.g. used by the default quickstart UI. Also registers implementations of `IProfileService` and `IResourceOwnerPasswordValidator` that uses the test users as a backing store. ## Signing keys [Section titled “Signing keys”](#signing-keys) Duende IdentityServer needs key material to sign tokens. This key material can either be created and [managed automatically](/identityserver/fundamentals/key-management/#automatic-key-management) or [configured statically](/identityserver/fundamentals/key-management/#static-key-management). Duende IdentityServer supports X.509 certificates (both raw files and a reference to the certificate store), RSA keys and EC keys for token signatures and validation. Each key can be configured with a (compatible) signing algorithm, e.g. RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or ES512. You can configure the key material with the following methods: * **`AddSigningCredential`** Adds a signing key that provides the specified key material to the various token creation/validation services. * **`AddDeveloperSigningCredential`** Creates temporary key material at startup time. This is for dev scenarios. The generated key will be persisted in the local directory by default (or just kept in memory). * **`AddValidationKey`** Adds a key for validating tokens. They will be used by the internal token validator and will show up in the discovery document. ## Additional services [Section titled “Additional services”](#additional-services) The following are convenient to add additional features to your IdentityServer. * **`AddExtensionGrantValidator`** Adds an `IExtensionGrantValidator` implementation for use with extension grants. * **`AddSecretParser`** Adds an `ISecretParser` implementation for parsing client or API resource credentials. * **`AddSecretValidator`** Adds an `ISecretValidator` implementation for validating client or API resource credentials against a credential store. * **`AddResourceOwnerValidator`** Adds an `IResourceOwnerPasswordValidator` implementation for validating user credentials for the resource owner password credentials grant type. * **`AddProfileService`** Adds an`IProfileService` implementation. The default implementation (found in `DefaultProfileService`) relies upon the authentication cookie as the only source of claims for issuing in tokens. * **`AddAuthorizeInteractionResponseGenerator`** Adds an `IAuthorizeInteractionResponseGenerator` implementation to customize logic at authorization endpoint for when a user must be shown a UI for error, login, consent, or any other custom page. The default implementation can be found in the `AuthorizeInteractionResponseGenerator` class, so consider deriving from this existing class if you need to augment the existing behavior. * **`AddCustomAuthorizeRequestValidator`** Adds an `ICustomAuthorizeRequestValidator` implementation to customize request parameter validation at the authorization endpoint. * **`AddCustomTokenRequestValidator`** Adds an `ICustomTokenRequestValidator` implementation to customize request parameter validation at the token endpoint. * **`AddRedirectUriValidator`** Adds an `IRedirectUriValidator` implementation to customize redirect URI validation. * **`AddAppAuthRedirectUriValidator`** Adds an “AppAuth” (OAuth 2.0 for Native Apps) compliant redirect URI validator (does strict validation but also allows `http://127.0.0.1` with random port). * **`AddJwtBearerClientAuthentication`** Adds support for client authentication using JWT bearer assertions. * **`AddMutualTlsSecretValidators`** Adds the X509 secret validators for mutual TLS. * **`AddIdentityProviderConfigurationValidator`** Adds an IdentityProvider configuration validator. * **`AddBackchannelAuthenticationUserValidator`** Adds the backchannel login user validator. * **`AddBackchannelAuthenticationUserNotificationService`** Adds the backchannel login user validator.
-----
# Entity Framework Core Options

> Configuration options available when using Entity Framework Core as the storage implementation for IdentityServer.

If using the [Entity Framework Core store implementation](/identityserver/data/ef/), you might need to configure those specific options.
-----
# Configuration Options

> Configuration options available when using Entity Framework Core as the configuration store in IdentityServer

## Duende.IdentityServer.EntityFramework.Options.ConfigurationStoreOptions [Section titled “Duende.IdentityServer.EntityFramework.Options.ConfigurationStoreOptions”](#duendeidentityserverentityframeworkoptionsconfigurationstoreoptions) These options are configurable when using the Entity Framework Core for the [configuration store](/identityserver/data/configuration/): You set the options at startup time in your `AddConfigurationStore` method: Program.cs ```csharp var builder = services.AddIdentityServer() .AddConfigurationStore(options => { // configure options here.. }) ``` ### Pooling [Section titled “Pooling”](#pooling) Settings that affect the DbContext pooling feature of Entity Framework Core. * **`EnablePooling`** Gets or set if EF DbContext pooling is enabled. Defaults to `false`. * **`PoolSize`** Gets or set the pool size to use when DbContext pooling is enabled. If not set, the EF default is used. ### Schema [Section titled “Schema”](#schema) Settings that affect the database schema and table names. * **`DefaultSchema`** Gets or sets the default schema. Defaults to `null`. `TableConfiguration` settings for each individual table (schema and name) managed by this feature: Identity Resource related tables: * **`IdentityResource`** * **`IdentityResourceClaim`** * **`IdentityResourceProperty`** API Resource related tables: * **`ApiResource`** * **`ApiResourceSecret`** * **`ApiResourceScope`** * **`ApiResourceClaim`** * **`ApiResourceProperty`** Client related tables: * **`Client`** * **`ClientGrantType`** * **`ClientRedirectUri`** * **`ClientPostLogoutRedirectUri`** * **`ClientScopes`** * **`ClientSecret`** * **`ClientClaim`** * **`ClientIdPRestriction`** * **`ClientCorsOrigin`** * **`ClientProperty`** API Scope related tables: * **`ApiScope`** * **`ApiScopeClaim`** * **`ApiScopeProperty`** Identity provider related tables: * **`IdentityProvider`**
-----
# Operational Options

> Configure Entity Framework Core operational store options including database schema, pooling settings, and cleanup parameters for persisted grants.

## Duende.IdentityServer.EntityFramework.Options.OperationalStoreOptions [Section titled “Duende.IdentityServer.EntityFramework.Options.OperationalStoreOptions”](#duendeidentityserverentityframeworkoptionsoperationalstoreoptions) These options are configurable when using the Entity Framework Core for the [operational store](/identityserver/data/operational/): You set the options at startup time in your `AddOperationalStore` method: Program.cs ```csharp builder.Services.AddIdentityServer() .AddOperationalStore(options => { // configure options here.. }) ``` ### Pooling [Section titled “Pooling”](#pooling) Settings that affect the DbContext pooling feature of Entity Framework Core. * **`EnablePooling`** Gets or set if EF DbContext pooling is enabled. Defaults to `false`. * **`PoolSize`** Gets or set the pool size to use when DbContext pooling is enabled. If not set, the EF default is used. ### Schema [Section titled “Schema”](#schema) Settings that affect the database schema and table names. * **`DefaultSchema`** Gets or sets the default schema. Defaults to `null`. `TableConfiguration` settings for each individual table (schema and name) managed by this feature: * **`PersistedGrants`** * **`DeviceFlowCodes`** * **`Keys`** * **`ServerSideSessions`** ### Persisted Grants Cleanup [Section titled “Persisted Grants Cleanup”](#persisted-grants-cleanup) Settings that affect the background cleanup of expired entries (tokens) from the persisted grants table. * **`EnableTokenCleanup`** Gets or sets a value indicating whether stale entries will be automatically cleaned up from the database. This is implemented by periodically connecting to the database (according to the TokenCleanupInterval) from the hosting application. Defaults to `false`. * **`RemoveConsumedTokens`** Gets or sets a value indicating whether consumed tokens will be included in the automatic clean up. Defaults to `false`. * **`TokenCleanupInterval`** Gets or sets the token cleanup interval (in seconds). The default is `3600` (1 hour). * **`TokenCleanupBatchSize`** Gets or sets the number of records to remove per batch operation. The cleanup job will perform multiple batch operations as long as there are more records to remove than the configured `TokenCleanupBatchSize`. Defaults to `100`. * **`FuzzTokenCleanupStart`** The background token cleanup job runs at a configured interval. If multiple nodes run the cleanup job at the same time there will be updated conflicts in the store. To avoid that, the startup time can be fuzzed. The first run is scheduled at a random time between the host startup and the configured TokenCleanupInterval. Subsequent runs are run on the configured TokenCleanupInterval. Defaults to `true`
-----
# Authorize Endpoint

> Documentation for the authorize endpoint which handles browser-based token and authorization code requests, including authentication and consent flows.

The authorize endpoint can be used to request tokens or authorization codes via the browser. This process typically involves authentication of the end-user and optionally consent. IdentityServer supports a subset of the OpenID Connect and OAuth 2.0 authorize request parameters. For a full list, see [here](https://openid.net/specs/openid-connect-core-1_0.html#authrequest). ### Required Parameters [Section titled “Required Parameters”](#required-parameters) * **`client_id`** identifier of the client * **`scope`** one or more registered scopes * **`redirect_uri`** must exactly match one of the allowed redirect URIs for that client * **`response_type`** specifies the response type * **`id_token`** * **`token`** * **`id_token token`** * **`code`** * **`code id_token`** * **`code id_token token`** ### Optional Parameters [Section titled “Optional Parameters”](#optional-parameters) * **`response_mode`** specifies the response mode * **`query`** * **`fragment`** * **`form_post`** * **`state`** echos back the state value on the token response, this is for round tripping state between client and provider, correlating request and response and CSRF/replay protection. (recommended) * **`nonce`** echos back the nonce value in the identity token (for replay protection) Required when identity tokens is transmitted via the browser channel * **`prompt`** * **`none`** no UI will be shown during the request. If this is not possible (e.g. because the user has to sign in or consent) an error is returned * **`login`** the login UI will be shown, even if the user is already signed in and has a valid session * **`create`** the user registration UI will be shown, if the `UserInteraction.CreateAccountUrl` option is set (the option is null by default, which disables support for this prompt value) * **`code_challenge`** sends the code challenge for PKCE * **`code_challenge_method`** * **`plain`** indicates that the challenge is using plain text (not recommended) * **`S256`** indicates the challenge is hashed with SHA256 * **`login_hint`** can be used to pre-fill the username field on the login page * **`ui_locales`** gives a hint about the desired display language of the login UI * **`max_age`** if the user’s logon session exceeds the max age (in seconds), the login UI will be shown * **`acr_values`** allows passing in additional authentication related information - IdentityServer special cases the following proprietary acr\_values: * **`idp:name_of_idp`** bypasses the login/home realm screen and forwards the user directly to the selected identity provider (if allowed per client configuration) * **`tenant:name_of_tenant`** can be used to pass a tenant name to the login UI * **`request`** instead of providing all parameters as individual query string parameters, you can provide a subset or all them as a JWT * **`request_uri`** URL of a pre-packaged JWT containing request parameters ```text GET /connect/authorize? client_id=client1& scope=openid email api1& response_type=id_token token& redirect_uri=https://myapp/callback& state=abc& nonce=xyz ``` ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically create authorize request URLs from .NET code. ```csharp var ru = new RequestUrl("https://demo.duendesoftware.com/connect/authorize"); var url = ru.CreateAuthorizeUrl( clientId: "client", responseType: "code", redirectUri: "https://app.com/callback", scope: "openid"); ```
-----
# Backchannel Authentication Endpoint

> Documentation for the CIBA endpoint which allows clients to initiate backchannel authentication requests for users without browser interaction

The backchannel authentication endpoint is used by a client to initiate a [CIBA](/identityserver/ui/ciba/) request. Clients must be configured with the `"urn:openid:params:grant-type:ciba"` grant type to use this endpoint. You can use the `OidcConstants.GrantTypes.Ciba` constant rather than hard coding the value for the CIBA grant type. ### Required Parameters [Section titled “Required Parameters”](#required-parameters) * **`scope`** one or more registered scopes ### Exactly One Of These Values Is Required [Section titled “Exactly One Of These Values Is Required”](#exactly-one-of-these-values-is-required) * **`login_hint`** hint for the end user to be authenticated. the value used is implementation specific. * **`id_token_hint`** a previously issued id\_token for the end user to be authenticated * **`login_hint_token`** a token containing information for the end user to be authenticated. the details are implementation specific. ### Optional Parameters [Section titled “Optional Parameters”](#optional-parameters) * **`binding_message`** identifier or message intended to be displayed on both the consumption device and the authentication device * **`user_code`** a secret code, such as a password or pin, that is known only to the user but verifiable by the OP * **`requested_expiry`** a positive integer allowing the client to request the `expires_in` value for the `auth_req_id` the server will return. if not present, then the optional `CibaLifetime` property on the `Client` is used, and if that is not present, then the `DefaultLifetime` on the `CibaOptions` will be used. * **`acr_values`** allows passing in additional authentication related information - IdentityServer special cases the following proprietary acr\_values: * **`idp:name_of_idp`** bypasses the login/home realm screen and forwards the user directly to the selected identity provider (if allowed per client configuration) * **`tenant:name_of_tenant`** can be used to pass a tenant name to the login UI * **`resource`** resource indicator identifying the `ApiResource` for which the access token should be restricted to * **`request`** instead of providing all parameters as individual parameters, you can provide all them as a JWT And a successful response will look something like: ```http HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "auth_req_id": "1C266114A1BE42528AD104986C5B9AC1", "expires_in": 600, "interval": 5 } ``` ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically interact with the protocol endpoint from .NET code. ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var cibaResponse = await client.RequestBackchannelAuthenticationAsync(new BackchannelAuthenticationRequest { Address = "https://demo.duendesoftware.com/connect/ciba", ClientId = "client1", ClientSecret = "secret", Scope = "openid api1", LoginHint = "alice", }); ``` And with a successful response, it can be used to poll the token endpoint: ```csharp while (true) { var response = await client.RequestBackchannelAuthenticationTokenAsync(new BackchannelAuthenticationTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "client1", ClientSecret = "secret", AuthenticationRequestId = cibaResponse.AuthenticationRequestId }); if (response.IsError) { if (response.Error == OidcConstants.TokenErrors.AuthorizationPending || response.Error == OidcConstants.TokenErrors.SlowDown) { await Task.Delay(cibaResponse.Interval.Value * 1000); } else { throw new Exception(response.Error); } } else { // success! use response.IdentityToken, response.AccessToken, and response.RefreshToken (if requested) } } ```
-----
# Device Authorization Endpoint

> Documentation for the device authorization endpoint which handles device flow authentication requests and issues device and user codes for authorization.

The device authorization endpoint can be used to request device and user codes. This endpoint is used to start the device flow authorization process. * **`client_id`** client identifier (required) * **`client_secret`** client secret either in the post body, or as a basic authentication header. Optional. * **`scope`** one or more registered scopes. If not specified, a token for all explicitly allowed scopes will be issued ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically interact with the protocol endpoint from .NET code. ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var response = await client.RequestDeviceAuthorizationAsync(new DeviceAuthorizationRequest { Address = "https://demo.duendesoftware.com/connect/device_authorize", ClientId = "device" }); ```
-----
# Discovery Endpoint

> Learn about the discovery endpoint that provides metadata about your IdentityServer configuration, including issuer name, key material, and supported scopes.

The [discovery endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html) can be used to retrieve metadata about your IdentityServer - it returns information like the issuer name, key material, supported scopes etc. The discovery endpoint is available via `/.well-known/openid-configuration` relative to the base address, e.g.: ```text https://demo.duendesoftware.com/.well-known/openid-configuration ``` ## Issuer Name and Path Base [Section titled “Issuer Name and Path Base”](#issuer-name-and-path-base) When your IdentityServer is hosted in an application that uses [ASP.NET Core’s `PathBaseMiddleware`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.extensions.usepathbasemiddleware), the base path will be included in the issuer name and discovery document URLs. For example, if your application is configured with a path base of `/identity`, your configuration will look like this: Program.cs ```csharp var builder = WebApplication.CreateBuilder(args); // 👨‍💻 configure Application Host var app = builder.Build(); app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // 👋 Configuring the path base app.UsePathBase("/identity"); app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthorization(); app.MapRazorPages() .RequireAuthorization(); return app; ``` And the discovery document will look like this: .well-known/openid-configuration ```json { "issuer": "https://localhost:5001/identity", "jwks_uri": "https://localhost:5001/identity/.well-known/openid-configuration/jwks", "authorization_endpoint": "https://localhost:5001/identity/connect/authorize", "token_endpoint": "https://localhost:5001/identity/connect/token", "userinfo_endpoint": "https://localhost:5001/identity/connect/userinfo", "end_session_endpoint": "https://localhost:5001/identity/connect/endsession", "check_session_iframe": "https://localhost:5001/identity/connect/checksession", "revocation_endpoint": "https://localhost:5001/identity/connect/revocation", "introspection_endpoint": "https://localhost:5001/identity/connect/introspect", "device_authorization_endpoint": "https://localhost:5001/identity/connect/deviceauthorization", "backchannel_authentication_endpoint": "https://localhost:5001/identity/connect/ciba", "pushed_authorization_request_endpoint": "https://localhost:5001/identity/connect/par" } ``` This can be helpful when configuring IdentityServer in a multi-tenant scenario where the base path is used to identify the tenant. ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically interact with the protocol endpoint from .NET code. ```csharp var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://demo.duendesoftware.com"); ```
-----
# End Session Endpoint

> The end session endpoint enables single sign-out functionality in OpenID Connect, allowing users to terminate their sessions across multiple client applications.

The end session endpoint can be used to trigger single sign-out in the browser ( see [spec](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)). To use the end session endpoint a client application will redirect the user’s browser to the end session URL. All applications that the user has logged into via the browser during the user’s session can participate in the sign-out. The URL for the end session endpoint is available via discovery. * **`id_token_hint`** When the user is redirected to the endpoint, they will be prompted if they really want to sign-out. This prompt can be bypassed by a client sending the original `id_token` received from authentication. This is passed as a query string parameter called `id_token_hint`. * **`post_logout_redirect_uri`** If a valid `id_token_hint` is passed, then the client may also send a `post_logout_redirect_uri` parameter. This can be used to allow the user to redirect back to the client after sign-out. The value must match one of the client’s pre-configured `PostLogoutRedirectUris`. * **`state`** If a valid `post_logout_redirect_uri` is passed, then the client may also send a `state` parameter. This will be returned back to the client as a query string parameter after the user redirects back to the client. This is typically used by clients to roundtrip state across the redirect. ```text GET /connect/endsession?id_token_hint=...&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A7017%2Findex.html ``` ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically create end sessions request URLs from .NET code. ```csharp var ru = new RequestUrl("https://demo.duendesoftware.com/connect/end_session"); var url = ru.CreateEndSessionUrl( idTokenHint: "...", postLogoutRedirectUri: "..."); ```
-----
# Introspection Endpoint

> Documentation for the RFC 7662 compliant introspection endpoint used to validate reference tokens, JWTs, and refresh tokens.

The introspection endpoint is an implementation of [RFC 7662](https://tools.ietf.org/html/rfc7662). It can be used to validate reference tokens, JWTs (if the consumer does not have support for appropriate JWT or cryptographic libraries) and refresh tokens. Refresh tokens can only be introspected by the client that requested them. The introspection endpoint requires authentication. Since the request to the introspection endpoint is typically done by an API, which is not an OAuth client, the [`ApiResource`](/identityserver/fundamentals/resources/api-resources/) is used to configure credentials: ```csharp new ApiResource("resource1") { Scopes = { "scope1", "scope2" }, // Replace "scope1", "scope2" with the actual scopes required for your API ApiSecrets = { new Secret("secret".Sha256()) } } ``` Here the id used for authentication is the name of the `ApiResource`: “resource1” and the secret the configured secret. The introspection endpoint uses HTTP basic auth to communicate these credentials: ```text POST /connect/introspect Authorization: Basic xxxyyy token= ``` A successful response will return a status code of 200, the token claims, the token type, and a flag indicating the token is active: ```json { "iss": "https://localhost:5001", "nbf": 1729599599, "iat": 1729599599, "exp": 1729603199, "client_id": "client", "jti": "44FD2DE9E9F8E9F4DDD141CD7C244BE9", "scope": "api1", "token_type": "access_token", "active": true } ``` Unknown or expired tokens will be marked as inactive: ```json { "active": false } ``` An invalid request will return a 400, an unauthorized request 401. ## JWT Response from Introspection Endpoint v7.3 [Section titled “JWT Response from Introspection Endpoint ”v7.3](#jwt-response-from-introspection-endpoint) IdentityServer supports [RFC 9701](https://www.rfc-editor.org/rfc/rfc9701.html) to return a JWT response from the introspection endpoint. To return a JWT response, set the `Accept` header in the HTTP request to `application/token-introspection+jwt`: ```text POST /connect/introspect Accept: application/token-introspection+jwt Authorization: Basic xxxyyy token= ``` A successful response will return a status code of 200 and has a `Content-Type: application/token-introspection+jwt` header, indicating that the response body contains a raw JWT instead. The base64 decoded JWT will have a `typ` claim in the header with the value `token-introspection+jwt`. The token’s payload contains a `token_introspection` JSON object similar to the default response type: ```json { "alg": "RS256", "kid": "BE9D78519A8BBCB28A65FADEECF49CBC", "typ": "token-introspection+jwt" }.{ "iss": "https://localhost:5001", "iat": 1729599599, "aud": "api1", "token_introspection": { "iss": "https://localhost:5001", "nbf": 1729599599, "iat": 1729599599, "exp": 1729603199, "aud": [ "api1" ], "client_id": "client", "jti": "44FD2DE9E9F8E9F4DDD141CD7C244BE9", "active": true, "scope": "api1" } }.[Signature] ``` ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically interact with the protocol endpoint from .NET code. ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var response = await client.IntrospectTokenAsync(new TokenIntrospectionRequest { Address = "https://demo.duendesoftware.com/connect/introspect", ClientId = "resource1", ClientSecret = "secret", Token = "" // Replace with the actual token }); ```
-----
# OAuth Metadata Endpoint

> Learn about the OAuth metadata endpoint that provides information about your IdentityServer configuration, including issuer name, key material, and supported scopes.

The [OAuth Metadata Endpoint](https://www.rfc-editor.org/rfc/rfc8414.html) is a standardized way to retrieve metadata about your IdentityServer. The discovery endpoint is available via `/.well-known/oauth-authorization-server` relative to the base address, e.g.: ```text https://demo.duendesoftware.com/.well-known/oauth-authorization-server ``` ## Issuer Name and Path Base [Section titled “Issuer Name and Path Base”](#issuer-name-and-path-base) When hosting IdentityServer in an application that uses [ASP.NET Core’s `PathBaseMiddleware`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.extensions.usepathbasemiddleware), the base path will be included in the issuer name and discovery document URLs. Refer the [Discovery Endpoint](/identityserver/reference/v7/endpoints/discovery/#issuer-name-and-path-base) for more information.
-----
# Revocation Endpoint

> Learn about the revocation endpoint that allows invalidating access and refresh tokens according to RFC 7009 specification.

This endpoint allows revoking access tokens (reference tokens only) and refresh token. It implements the token revocation specification [(RFC 7009)](https://tools.ietf.org/html/rfc7009). * **`token`** the token to revoke (required) * **`token_type_hint`** either `access_token` or `refresh_token` (optional) ```text POST /connect/revocation HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW token=...&token_type_hint=refresh_token ``` ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically interact with the protocol endpoint from .NET code. ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var result = await client.RevokeTokenAsync(new TokenRevocationRequest { Address = "https://demo.duendesoftware.com/connect/revocation", ClientId = "client", ClientSecret = "secret", Token = token }); ```
-----
# Token Endpoint

> Documentation for the token endpoint that enables programmatic token requests using various grant types and parameters in Duende IdentityServer.

The token endpoint can be used to programmatically request tokens. Duende IdentityServer supports a subset of the OpenID Connect and OAuth 2.0 token request parameters. For a full list, see [here](https://openid.net/specs/openid-connect-core-1_0.html#tokenrequest). ### Required Parameters [Section titled “Required Parameters”](#required-parameters) * **`client_id`** client identifier; not necessary in body if it is present in the authorization header * **`grant_type`** * **`authorization_code`** * **`client_credentials`** * **`password`** * **`refresh_token`** * **`urn:ietf:params:oauth:grant-type:device_code`** * ***extension grant*** ### Optional Parameters [Section titled “Optional Parameters”](#optional-parameters) * **`client_secret`** client secret for confidential/credentials clients - either in the post body, or as a basic authentication header. * **`scope`** one or more registered scopes. If not specified, a token for all explicitly allowed scopes will be issued. * **`redirect_uri`** required for the `authorization_code` grant type * **`code`** the authorization code (required for `authorization_code` grant type) * **`code_verifier`** PKCE proof key * **`username`** resource owner username (required for `password` grant type) * **`password`** resource owner password (required for `password` grant type) * **`acr_values`** allows passing in additional authentication related information. Duende IdentityServer special cases the following proprietary acr\_values * **`tenant:name_of_tenant`** can be used to pass a tenant name to the token endpoint * **`refresh_token`** the refresh token (required for `refresh_token` grant type) * **`device_code`** the device code (required for `urn:ietf:params:oauth:grant-type:device_code` grant type) * **`auth_req_id`** the backchannel authentication request id (required for `urn:openid:params:grant-type:ciba` grant type) ```text POST /connect/token CONTENT-TYPE application/x-www-form-urlencoded client_id=client1& client_secret=secret& grant_type=authorization_code& code=hdh922& redirect_uri=https://myapp.com/callback ``` ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically interact with the protocol endpoint from .NET code. ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var response = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest { Address = TokenEndpoint, ClientId = "client", ClientSecret = "secret", Code = "...", CodeVerifier = "...", RedirectUri = "https://app.com/callback" }); ```
-----
# UserInfo Endpoint

> Reference documentation for the UserInfo endpoint, which allows retrieval of authenticated user claims using a valid access token.

The UserInfo endpoint can be used to retrieve claims about a user ( see [spec](https://openid.net/specs/openid-connect-core-1_0.html#userinfo)). The caller needs to send a valid access token. Depending on the granted scopes, the UserInfo endpoint will return the mapped claims (at least the `openid` scope is required). ```text GET /connect/userinfo Authorization: Bearer  ``` ```text HTTP/1.1 200 OK Content-Type: application/json { "sub": "248289761001", "name": "Bob Smith", "given_name": "Bob", "family_name": "Smith" } ``` ## .NET Client Library [Section titled “.NET Client Library”](#net-client-library) You can use the [Duende IdentityModel](/identitymodel/) client library to programmatically interact with the protocol endpoint from .NET code. ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); var token = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Code = "...", CodeVerifier = "...", RedirectUri = "https://app.com/callback" }); var userInfo = await client.GetUserInfoAsync(new UserInfoRequest { Address = disco.UserInfoEndpoint, Token = token.AccessToken }); ```
-----
# API Resource

> Reference documentation for the ApiResource class which models an API in Duende IdentityServer, including its properties and configuration options.

## Duende.IdentityServer.Models.ApiResource [Section titled “Duende.IdentityServer.Models.ApiResource”](#duendeidentityservermodelsapiresource) This class models an API. * **`Enabled`** Indicates if this resource is enabled and can be requested. Defaults to true. * **`Name`** The unique name of the API. This value is used for authentication with introspection and will be added to the audience of the outgoing access token. * **`DisplayName`** This value can be used e.g. on the consent screen. * **`Description`** This value can be used e.g. on the consent screen. * **`RequireResourceIndicator`** Indicates if this API resource requires the resource indicator to request it, and expects access tokens issued to it will only ever contain this API resource as the audience. * **`ApiSecrets`** The API secret is used for the introspection endpoint. The API can authenticate with introspection using the API name and secret. * **`AllowedAccessTokenSigningAlgorithms`** List of allowed signing algorithms for access token. If empty, will use the server default signing algorithm. * **`UserClaims`** List of associated user claim types that should be included in the access token. * **`Scopes`** List of API scope names. You need to create those using [ApiScope](/identityserver/reference/v7/models/api-scope/). ## Defining API resources In appsettings.json [Section titled “Defining API resources In appsettings.json”](#defining-api-resources-in-appsettingsjson) The `AddInMemoryApiResource` extensions method also supports adding API resources from the ASP.NET Core configuration file: ```plaintext "IdentityServer": { "IssuerUri": "urn:sso.company.com", "ApiResources": [ { "Name": "resource1", "DisplayName": "Resource #1", "Scopes": [ "resource1.scope1", "shared.scope" ] }, { "Name": "resource2", "DisplayName": "Resource #2", "UserClaims": [ "name", "email" ], "Scopes": [ "resource2.scope1", "shared.scope" ] } ] } ``` Then pass the configuration section to the `AddInMemoryApiResource` method: Program.cs ```csharp idsvrBuilder.AddInMemoryApiResources(configuration.GetSection("IdentityServer:ApiResources")) ```
-----
# API Scope

> Reference documentation for the ApiScope class which models an OAuth scope in Duende IdentityServer, including its properties and configuration options.

## Duende.IdentityServer.Models.ApiScope [Section titled “Duende.IdentityServer.Models.ApiScope”](#duendeidentityservermodelsapiscope) This class models an OAuth scope. * **`Enabled`** Indicates if this resource is enabled and can be requested. Defaults to true. * **`Name`** The unique name of the API. This value is used for authentication with introspection and will be added to the audience of the outgoing access token. * **`DisplayName`** This value can be used e.g. on the consent screen. * **`Description`** This value can be used e.g. on the consent screen. * **`UserClaims`** List of associated user claim types that should be included in the access token. ## Defining API Scope In appsettings.json [Section titled “Defining API Scope In appsettings.json”](#defining-api-scope-in-appsettingsjson) The `AddInMemoryApiResource` extension method also supports adding clients from the ASP.NET Core configuration file: ```json { "IdentityServer": { "IssuerUri": "urn:sso.company.com", "ApiScopes": [ { "Name": "IdentityServerApi" }, { "Name": "resource1.scope1" }, { "Name": "resource2.scope1" }, { "Name": "scope3" }, { "Name": "shared.scope" }, { "Name": "transaction", "DisplayName": "Transaction", "Description": "A transaction" } ] } } ``` Then pass the configuration section to the `AddInMemoryApiScopes` method: Program.cs ```csharp idsvrBuilder.AddInMemoryApiScopes(configuration.GetSection("IdentityServer:ApiScopes")) ```
-----
# Backchannel User Login Request

> Reference documentation for the BackchannelUserLoginRequest class which models the information needed to initiate a user login request for Client Initiated Backchannel Authentication (CIBA).

## Duende.IdentityServer.Models.BackchannelUserLoginRequest [Section titled “Duende.IdentityServer.Models.BackchannelUserLoginRequest”](#duendeidentityservermodelsbackchanneluserloginrequest) Models the information to initiate a user login request for [CIBA](/identityserver/ui/ciba/). * **`InternalId`** The identifier of the request in the store. * **`Subject`** The subject for whom the login request is intended. * **`BindingMessage`** The binding message used in the request. * **`AuthenticationContextReferenceClasses`** The `acr_values` used in the request. * **`Tenant`** The `tenant` value from the `acr_values` used in the request. * **`IdP`** The `idp` value from the `acr_values` used in the request. * **`RequestedResourceIndicators`** The resource indicator values used in the request. * **`Client`** The client that initiated the request. * **`ValidatedResources`** The validated resources (i.e. scopes) used in the request.
-----
# Client

> Reference documentation for the Client class which models an OpenID Connect or OAuth 2.0 client in Duende IdentityServer, including configuration for authentication, tokens, consent, refresh tokens, and advanced features.

## Duende.IdentityServer.Models.Client [Section titled “Duende.IdentityServer.Models.Client”](#duendeidentityservermodelsclient) The `Client` class models an OpenID Connect or OAuth 2.0 client - e.g. a native application, a web application or a JS-based application. ```csharp public static IEnumerable Get() { return new List { /////////////////////////////////////////// // machine to machine client ////////////////////////////////////////// new Client { ClientId = "machine", ClientSecrets = { Configuration["machine.secret"] }, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = machineScopes }, /////////////////////////////////////////// // web client ////////////////////////////////////////// new Client { ClientId = "web", ClientSecrets = { new Secret(Configuration["web.secret"]) }, AllowedGrantTypes = GrantTypes.Code, RedirectUris = { "https://myapp.com:/signin-oidc" }, PostLogoutRedirectUris = { "https://myapp.com/signout-callback-oidc" }, BackChannelLogoutUri = "https://myapp.com/backchannel-logout", AllowOfflineAccess = true, AllowedScopes = webScopes } } } ``` ## Basics [Section titled “Basics”](#basics) * **`Enabled`** Specifies if client is enabled. Defaults to `true`. * **`ClientId`** Unique ID of the client * **`ClientSecrets`** List of client secrets - credentials to access the token endpoint. * **`RequireClientSecret`** Specifies whether this client needs a secret to request tokens from the token endpoint (defaults to `true`) * **`RequireRequestObject`** Specifies whether this client needs to wrap the authorize request parameters in a JWT (defaults to `false`) * **`AllowedGrantTypes`** Specifies the grant types the client is allowed to use. Use the `GrantTypes` class for common combinations. * **`RequirePkce`** Specifies whether clients using an authorization code based grant type must send a proof key (defaults to `true`). * **`AllowPlainTextPkce`** Specifies whether clients using PKCE can use a plain text code challenge (not recommended - and defaults to `false`) * **`RedirectUris`** Specifies the allowed URIs to return tokens or authorization codes to * **`AllowedScopes`** By default, a client has no access to any resources - specify the allowed resources by adding the corresponding scopes names * **`AllowOfflineAccess`** Specifies whether this client can request refresh tokens (be requesting the `offline_access` scope) * **`AllowAccessTokensViaBrowser`** Specifies whether this client is allowed to receive access tokens via the browser. This is useful to harden flows that allow multiple response types (e.g. by disallowing a hybrid flow client that is supposed to use *code id\_token* to add the `token` response type and thus leaking the token to the browser). * **`Properties`** Dictionary to hold any custom client-specific values as needed. ## Authentication / Session Management [Section titled “Authentication / Session Management”](#authentication--session-management) * **`PostLogoutRedirectUris`** Specifies allowed URIs to redirect to after logout. * **`FrontChannelLogoutUri`** Specifies logout URI at client for HTTP based front-channel logout. * **`FrontChannelLogoutSessionRequired`** Specifies if the user’s session id should be sent to the FrontChannelLogoutUri. Defaults to true. * **`BackChannelLogoutUri`** Specifies logout URI at client for HTTP based back-channel logout. * **`BackChannelLogoutSessionRequired`** Specifies if the user’s session id should be sent in the request to the BackChannelLogoutUri. Defaults to true. * **`EnableLocalLogin`** Specifies if this client can use local accounts, or external IdPs only. Defaults to `true`. * **`IdentityProviderRestrictions`** Specifies which external IdPs can be used with this client (if list is empty all IdPs are allowed). Defaults to empty. * **`UserSsoLifetime`** The maximum duration (in seconds) since the last time the user authenticated. Defaults to `null`. You can adjust the lifetime of a session token to control when and how often a user is required to reenter credentials instead of being silently authenticated, when using a web application. * **`AllowedCorsOrigins`** If specified, will be used by the default CORS policy service implementations (In-Memory and EF) to build a CORS policy for JavaScript clients. * **`CoordinateLifetimeWithUserSession`** (added in v6.1) When enabled, the client’s token lifetimes (e.g. refresh tokens) will be tied to the user’s session lifetime. This means when the user logs out, any revokable tokens will be removed. If using server-side sessions, expired sessions will also remove any revokable tokens, and backchannel logout will be triggered. This client’s setting overrides the global `CoordinateClientLifetimesWithUserSession` configuration setting. ## Token [Section titled “Token”](#token) * **`IdentityTokenLifetime`** Lifetime to identity token in seconds (defaults to 300 seconds / 5 minutes) * **`AllowedIdentityTokenSigningAlgorithms`** List of allowed signing algorithms for identity token. If empty, will use the server default signing algorithm. * **`AccessTokenLifetime`** Lifetime of access token in seconds (defaults to 3600 seconds / 1 hour) * **`AuthorizationCodeLifetime`** Lifetime of authorization code in seconds (defaults to 300 seconds / 5 minutes) * **`AccessTokenType`** Specifies whether the access token is a reference token or a self-contained JWT token (defaults to `Jwt`). * **`IncludeJwtId`** Specifies whether JWT access tokens should have an embedded unique ID (via the `jti` claim). Defaults to `true`. * **`Claims`** Allows settings claims for the client (will be included in the access token). * **`AlwaysSendClientClaims`** If set, the client claims will be sent for every flow. If not, only for client credentials flow (default is `false`) * **`AlwaysIncludeUserClaimsInIdToken`** When requesting both an id token and access token, should the user claims always be added to the id token instead of requiring the client to use the userinfo endpoint. Default is `false`. * **`ClientClaimsPrefix`** If set, the prefix client claim types will be prefixed with. Defaults to `client_`. The intent is to make sure they don’t accidentally collide with user claims. * **`PairWiseSubjectSalt`** Salt value used in pair-wise subjectId generation for users of this client. Currently not implemented. ## Refresh Token [Section titled “Refresh Token”](#refresh-token) * **`AbsoluteRefreshTokenLifetime`** Maximum lifetime of a refresh token in seconds. Defaults to 2592000 seconds / 30 days. Setting this to 0 has the following effect: * When `RefreshTokenExpiration` is set to `Absolute`, the behavior is the same as when no refresh tokens are used. * When `RefreshTokenExpiration` is set to `Sliding`, refresh tokens only expire after the `SlidingRefreshTokenLifetime` has passed. * **`SlidingRefreshTokenLifetime`** Sliding lifetime of a refresh token in seconds. Defaults to 1296000 seconds / 15 days. * **`RefreshTokenUsage`** * **`ReUse`** the refresh token handle will stay the same when refreshing tokens. This is the default. * **`OneTimeOnly`** the refresh token handle will be updated when refreshing tokens. * **`RefreshTokenExpiration`** * **`Absolute`** the refresh token will expire on a fixed point in time (specified by the `AbsoluteRefreshTokenLifetime`). This is the default. * **`Sliding`** when refreshing the token, the lifetime of the refresh token will be renewed (by the amount specified in `SlidingRefreshTokenLifetime`). The lifetime will not exceed `AbsoluteRefreshTokenLifetime`. * **`UpdateAccessTokenClaimsOnRefresh`** Gets or sets a value indicating whether the access token (and its claims) should be updated on a refresh token request. ## Consent Screen [Section titled “Consent Screen”](#consent-screen) Consent screen specific settings. * **`RequireConsent`** Specifies whether a consent screen is required. Defaults to `false`. * **`AllowRememberConsent`** Specifies whether user can choose to store consent decisions. Defaults to `true`. * **`ConsentLifetime`** Lifetime of a user consent in seconds. Defaults to null (no expiration). * **`ClientName`** Client display name (used for logging and consent screen). * **`ClientUri`** URI to further information about client. * **`LogoUri`** URI to client logo. ## Cross Device Flows [Section titled “Cross Device Flows”](#cross-device-flows) Settings used in the CIBA and OAuth device flows. * **`PollingInterval`** Maximum polling interval for the client in cross device flows. If the client polls more frequently than the polling interval during those flows, it will receive a `slow_down` error response. Defaults to `null`, which means the throttling will use the global default appropriate for the flow (`IdentityServerOptions.Ciba.DefaultPollingInterval` or `IdentityServerOptions.DeviceFlow.Interval`). #### Device Flow [Section titled “Device Flow”](#device-flow) Device flow specific settings. * **`UserCodeType`** Specifies the type of user code to use for the client. Otherwise, falls back to default. * **`DeviceCodeLifetime`** Lifetime to device code in seconds (defaults to 300 seconds / 5 minutes) #### CIBA [Section titled “CIBA”](#ciba) Client initiated backchannel authentication specific settings. * **`CibaLifetime`** Specifies the backchannel authentication request lifetime in seconds. Defaults to `null`. ## DPoP [Section titled “DPoP”](#dpop) Added in 6.3.0. Settings specific to the Demonstration of Proof-of-Possession at the Application Layer ([DPoP](/identityserver/tokens/pop/)) feature. * **`RequireDPoP`** Specifies whether a DPoP (Demonstrating Proof-of-Possession) token is required to be used by this client. Defaults to `false`. * **`DPoPValidationMode`** Enum setting to control validation for the DPoP proof token expiration. This supports both the client generated ‘iat’ value and/or the server generated ‘nonce’ value. Defaults to `DPoPTokenExpirationValidationMode.Iat`, which only validates the ‘iat’ value. * **`DPoPClockSkew`** Clock skew used in validating the client’s DPoP proof token ‘iat’ claim value. Defaults to *5 minutes*. ## Third-Party Initiated Login [Section titled “Third-Party Initiated Login”](#third-party-initiated-login) Added in 6.3.0. * **`InitiateLoginUri`** An optional URI that can be used to [initiate login](https://openid.net/specs/openid-connect-core-1_0.html#thirdpartyinitiatedlogin) from the IdentityServer host or a third party. This is most commonly used to create a client application portal within the IdentityServer host. Defaults to null. ## Pushed Authorization Requests [Section titled “Pushed Authorization Requests”](#pushed-authorization-requests) Added in 7.0.0 * **`RequirePushedAuthorization`** Controls if this client requires PAR. PAR is required if either the global configuration is enabled or if the client’s flag is enabled (this can’t be used to opt out of the global configuration). This defaults to `false`, which means the global configuration will be used. * **`PushedAuthorizationLifetime`** Controls the lifetime of pushed authorization requests for a client. If this lifetime is set, it takes precedence over the global configuration. This defaults to `null`, which means the global configuration is used.
-----
# Grant Validation Result

> Reference documentation for the GrantValidationResult class which models the outcome of grant validation for extension grants and resource owner password grants in Duende IdentityServer.

## Duende.IdentityServer.Validation.GrantValidationResult [Section titled “Duende.IdentityServer.Validation.GrantValidationResult”](#duendeidentityservervalidationgrantvalidationresult) The `GrantValidationResult` class models the outcome of grant validation for [extensions grants](/identityserver/tokens/extension-grants/) and [resource owner password grants](/identityserver/tokens/password-grant/). It models either a successful validation result with claims (e.g. subject ID) or an invalid result with an error code and message, e.g.: ```csharp public class ExtensionGrantValidator : IExtensionGrantValidator { public Task ValidateAsync(ExtensionGrantValidationContext context) { // some validation steps if (success) { context.Result = new GrantValidationResult( subject: "818727", authenticationMethod: "custom", claims: extraClaims); } else { // custom error message context.Result = new GrantValidationResult( TokenRequestErrors.InvalidGrant, "invalid custom credential"); } return Task.CompletedTask; } } ``` It also allows passing additional custom values that will be included in the token response, e.g.: ```csharp context.Result = new GrantValidationResult( subject: "818727", authenticationMethod: "custom", customResponse: new Dictionary { { "some_data", "some_value" } }); ``` This will result in the following token response: ```json { "access_token": "...", "token_type": "Bearer", "expires_in": 360, "some_data": "some_value" } ```
-----
# Identity Resource

> Reference documentation for the IdentityResource class which models an identity resource in Duende IdentityServer, including standard and custom identity resources and their properties.

## Duende.IdentityServer.Models.IdentityResource [Section titled “Duende.IdentityServer.Models.IdentityResource”](#duendeidentityservermodelsidentityresource) This class models an identity resource. ```csharp public static readonly IEnumerable IdentityResources = new[] { // some standard scopes from the OIDC spec new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResources.Email(), // custom identity resource with some associated claims new IdentityResource("custom.profile", userClaims: new[] { JwtClaimTypes.Name, JwtClaimTypes.Email, "location", JwtClaimTypes.Address }) }; ``` * **`Enabled`** Indicates if this resource is enabled and can be requested. Defaults to true. * **`Name`** The unique name of the identity resource. This is the value a client will use for the scope parameter in the authorize request. * **`DisplayName`** This value will be used e.g. on the consent screen. * **`Description`** This value will be used e.g. on the consent screen. * **`Required`** Specifies whether the user can de-select the scope on the consent screen (if the consent screen wants to implement such a feature). Defaults to false. * **`Emphasize`** Specifies whether the consent screen will emphasize this scope (if the consent screen wants to implement such a feature). Use this setting for sensitive or important scopes. Defaults to false. * **`ShowInDiscoveryDocument`** Specifies whether this scope is shown in the discovery document. Defaults to `true`. * **`UserClaims`** List of associated user claim types that should be included in the identity token.
-----
# Identity Provider

> Reference documentation for identity provider models in Duende IdentityServer, including OidcProvider for external OpenID Connect providers, IdentityProviderName, and the base IdentityProvider class.

## Duende.IdentityServer.Models.OidcProvider [Section titled “Duende.IdentityServer.Models.OidcProvider”](#duendeidentityservermodelsoidcprovider) The `OidcProvider` models an external OpenID Connect provider for use in the [dynamic providers](/identityserver/ui/login/dynamicproviders/) feature. Its properties map to the Open ID Connect options class from ASP.NET Core, and those properties include: * **`Enabled`** Specifies if provider is enabled. Defaults to `true`. * **`Scheme`** Scheme name for the provider. * **`DisplayName`** Display name for the provider. * **`Type`** Protocol type of the provider. Defaults to `"oidc"` for the `OidcProvider`. * **`Authority`** The base address of the OIDC provider. * **`ResponseType`** The response type. Defaults to `"id_token"`. * **`ClientId`** The client id. * **`ClientSecret`** The client secret. By default, this is the plaintext client secret and great consideration should be taken if this value is to be stored as plaintext in the store. It is possible to store this in a protected way and then unprotect when loading from the store either by implementing a custom `IIdentityProviderStore` or registering a custom `IConfigureNamedOptions`. * **`Scope`** Space separated list of scope values. * **`GetClaimsFromUserInfoEndpoint`** Indicates if userinfo endpoint is to be contacted. Defaults to true. * **`UsePkce`** Indicates if PKCE should be used. Defaults to true. #### Duende.IdentityServer.Models.IdentityProviderName [Section titled “Duende.IdentityServer.Models.IdentityProviderName”](#duendeidentityservermodelsidentityprovidername) The `IdentityProviderName` models the display name of an identity provider. * **`Enabled`** Specifies if provider is enabled. Defaults to `true`. * **`Scheme`** Scheme name for the provider. * **`DisplayName`** Display name for the provider. #### Duende.IdentityServer.Models.IdentityProvider [Section titled “Duende.IdentityServer.Models.IdentityProvider”](#duendeidentityservermodelsidentityprovider) The `IdentityProvider` is a base class to model arbitrary identity providers, which `OidcProvider` derives from. This leaves open the possibility for extensions to the dynamic provider feature to support other protocol types (as distinguished by the `Type` property).
-----
# License Usage Summary

> Reference documentation for the LicenseUsageSummary class which provides detailed information about clients, issuers, and features used in Duende IdentityServer for self-auditing and license compliance.

## Duende.IdentityServer.Licensing.LicenseUsageSummary [Section titled “Duende.IdentityServer.Licensing.LicenseUsageSummary”](#duendeidentityserverlicensinglicenseusagesummary) Added in 7.1 The `LicenseUsageSummary` class allows developers to get a detailed summary of clients, issuers, and features used during the lifetime of an active .NET application for self-auditing purposes. * **`LicenseEdition`** Indicates the current IdentityServer instance’s license edition. * **`ClientsUsed`** A `string` collection of clients used with the current IdentityServer instance. * **`IssuersUsed`** A `string` collection of issuers used with the current IdentityServer instance. * **`FeaturesUsed`** A `string` collection of features has been used since the IdentityServer instance ran. ## Register LicenseUsageSummary Services [Section titled “Register LicenseUsageSummary Services”](#register-licenseusagesummary-services) To make the `LicenseUsageSummary` class available in your application, you’ll need to make sure it is registered in the service collection at startup. You can do this by calling the `AddLicenseSummary()` extension method when registering IdentityServer: Program.cs ```csharp builder.Services.AddIdentityServer() .AddLicenseSummary(); ``` ## Using LicenseUsageSummary with .NET Lifetime Events [Section titled “Using LicenseUsageSummary with .NET Lifetime Events”](#using-licenseusagesummary-with-net-lifetime-events) In .NET, an [`IHost`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostapplicationlifetime) implementation allows developers to subscribe to application lifetime events, including **Application Started**, **Application Stopped**, and **Application Stopping**. IdentityServer tracks usage metrics internally and that information may be accessed by developers at any time during the application’s lifetime from the application’s service collection using the following code snippet. ```csharp // from a valid services scope app.Services.GetRequiredService(); ``` For self-auditing purposes, we recommend using the `IHost` lifetime event `ApplicationStopping` as shown in the example below. Note, `LicenseUsageSummary` is *`read-only`*. ```csharp app.Lifetime.ApplicationStopping.Register(() => { var usage = app.Services.GetRequiredService(); // Todo: Substitue a different logging mechanism Console.Write(Summary(usage)); }); ``` Developers may also use common dependency injection techniques such as property or constructor injection. ```csharp // An ASP.NET Core MVC Controller public class MyController : Controller { public MyController(LicenseUsageSummary summary) { // use the summary information } } ``` Developers can use the license usage summary to determine if their organization is within their current licensing tier or if they need to make adjustments to stay within compliance of [Duende licensing terms](https://duendesoftware.com/products/identityserver).
-----
# Secrets

> Reference documentation for secret handling in Duende IdentityServer, including the ISecretParser interface for extracting secrets from HTTP requests, the ParsedSecret class, and the ISecretValidator interface.

## Duende.IdentityServer.Validation.ISecretParser [Section titled “Duende.IdentityServer.Validation.ISecretParser”](#duendeidentityservervalidationisecretparser) Parses a secret from the raw HTTP request. ```csharp public interface ISecretParser { ///  /// Tries to find a secret on the context that can be used for authentication ///  /// The HTTP context. /// A parsed secret Task ParseAsync(HttpContext context); ///  /// Returns the authentication method name that this parser implements ///  /// The authentication method. string AuthenticationMethod { get; } } ``` * **`AuthenticationMethod`** The name of the authentication method that this parser registers for. This value must be unique and will be displayed in the discovery document. * **`ParseAsync`** The job of this method is to extract the secret from the HTTP request and parse it into a `ParsedSecret` #### Duende.IdentityServer.Model.ParsedSecret [Section titled “Duende.IdentityServer.Model.ParsedSecret”](#duendeidentityservermodelparsedsecret) Represents a parsed secret. ```csharp ///  /// Represents a secret extracted from the HttpContext ///  public class ParsedSecret { ///  /// Gets or sets the identifier associated with this secret ///  ///  /// The identifier. ///  public string Id { get; set; } ///  /// Gets or sets the credential to verify the secret ///  ///  /// The credential. ///  public object Credential { get; set; } ///  /// Gets or sets the type of the secret ///  ///  /// The type. ///  public string Type { get; set; } ///  /// Gets or sets additional properties. ///  ///  /// The properties. ///  public Dictionary Properties { get; set; } = new Dictionary(); } ``` The parsed secret is forwarded to the registered secret validator. The validator will typically inspect the `Type` property to determine if this secret is something that can be validated by that validator instance. If yes, it will know how to cast the `Credential` object into a format that is understood. #### Duende.IdentityServer.Validation.ISecretParser [Section titled “Duende.IdentityServer.Validation.ISecretParser”](#duendeidentityservervalidationisecretparser-1) Validates a parsed secret. ```csharp public interface ISecretValidator { /// Validates a secret /// The stored secrets. /// The received secret. /// A validation result Task ValidateAsync( IEnumerable secrets, ParsedSecret parsedSecret); } ```
-----
# IdentityServer Options

> Documentation of all configuration options in Duende IdentityServer, including settings for key management, endpoints, authentication, events, logging, CORS, Content Security Policy, device flow, mutual TLS, dynamic providers, CIBA, server-side sessions, validation and other core features.

#### Duende.IdentityServer.Configuration.IdentityServerOptions [Section titled “Duende.IdentityServer.Configuration.IdentityServerOptions”](#duendeidentityserverconfigurationidentityserveroptions) The `IdentityServerOptions` is the central place to configure fundamental settings in Duende IdentityServer. You set the options when registering IdentityServer at startup time, using a lambda expression in the AddIdentityServer method: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { // configure options here.. }) ``` ## Main [Section titled “Main”](#main) Top-level settings. Available directly on the `IdentityServerOptions` object. * **`IssuerUri`** The name of the token server, used in the discovery document as the `issuer` claim and in JWT tokens and introspection responses as the `iss` claim. It is not recommended to set this option. If it is not set (the default), the issuer is inferred from the URL used by clients. This better conforms to the OpenID Connect specification, which requires that issuer values be “identical to the Issuer URL that was directly used to retrieve the configuration information”. It is also more convenient for clients to validate the issuer of tokens, because they will not need additional configuration or customization to know the expected issuer. If you need to access IdentityServer on a different address from the expected issuer value, for example internally in a Kubernetes cluster, setting the issuer is a good practice. Note that when doing so, you will need to set the OpenID Connect metadata address manually in your client application to prevent the address derived from the authority from being used. * **`LowerCaseIssuerUri`** Controls the casing of inferred `IssuerUri`s. When set to `false`, the original casing of the IssuerUri in requests is preserved. When set to `true`, the `IssuerUri` is converted to lowercase. Defaults to `true`. * **`AccessTokenJwtType`** The value used for the `typ` header in JWT access tokens. Defaults to `at+jwt`, as specified by the [RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068). If `AccessTokenJwtType` is set to `null` or the empty string, the `typ` header will not be emitted in JWT access tokens. * **`LogoutTokenJwtType`** The value for the `typ` header in back-channel logout tokens. Defaults to “logout+jwt”, as specified by [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html#logouttoken). * **`EmitScopesAsSpaceDelimitedStringInJwt`** Controls the format of scope claims in JWTs and introspection responses. Historically scopes values were emitted as an array in JWT access tokens. [RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068) now specifies a space delimited string instead. Defaults to `false` for backwards compatibility. * **`EmitStaticAudienceClaim`** Emits a static `aud` (audience) claim in all access tokens with the format `{issuer}/resources`. For example, if IdentityServer was running at `https://identity.example.com`, the static `aud` claim’s value would be `https://identity.example.com/resources`. Historically, older versions of IdentityServer produced tokens with a static audience claim in this format. This flag is intended for use when you need to produce backwards-compatible access tokens. Also note that multiple audience claims are possible. If you enable this flag and also configure `ApiResource`s you can have both the static audience and audiences from the API resources. Defaults to `false`. * **`EmitIssuerIdentificationResponseParameter`** Emits the `iss` response parameter on authorize responses, as specified by [RFC 9207](https://datatracker.ietf.org/doc/rfc9207/). Defaults to `true`. * **`EmitStateHash`** Emits the s\_hash claim in identity tokens. The s\_hash claim is a hash of the state parameter that is specified in the OpenID Connect [Financial-grade API Security Profile](https://openid.net/specs/openid-financial-api-part-2-1_0.html). Defaults to `false`. * **`StrictJarValidation`** Strictly validate JWT-secured authorization requests according to [RFC 9101](https://datatracker.ietf.org/doc/rfc9101/). When enabled, JWTs used to secure authorization requests must have the `typ` header value `oauth-authz-req+jwt` and JWT-secured authorization requests must have the HTTP `content-type` header value `application/oauth-authz-req+jwt`. This might break older OIDC conformant request objects. Defaults to `false`. * **`ValidateTenantOnAuthorization`** Specifies if a user’s `tenant` claim is compared to the tenant `acr_values` parameter value to determine if the login page is displayed. Defaults to `false`. ## Key management [Section titled “Key management”](#key-management) Automatic key management settings. Available on the `KeyManagement` property of the `IdentityServerOptions` object. * **`Enabled`** Enables automatic key management. Defaults to true. * **`SigningAlgorithms`** The signing algorithms for which automatic key management will manage keys. This option is configured with a list of objects containing a Name property, which is the name of a supported signing algorithm, and a UseX509Certificate property, which is a flag indicating if the signing key should be wrapped in an X.509 certificate. The first algorithm in the collection will be used as the default for clients that do not specify `AllowedIdentityTokenSigningAlgorithms`. The supported signing algorithm names are `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, and `ES512`. X.509 certificates are not supported for `ES256`, `ES384`, and `ES512` keys. Defaults to `RS256` without an X.509 certificate. - **`RsaKeySize`** Key size (in bits) of RSA keys. The signing algorithms that use RSA keys (`RS256`, `RS384`, `RS512`, `PS256`, `PS384`, and `PS512`) will generate an RSA key of this length. Defaults to 2048. - **`RotationInterval`** Age at which keys will no longer be used for signing, but will still be used in discovery for validation. Defaults to 90 days. - **`PropagationTime`** Time expected to propagate new keys to all servers, and time expected all clients to refresh discovery. Defaults to 14 days. - **`RetentionDuration`** Duration for keys to remain in discovery after rotation. Defaults to 14 days. - **`DeleteRetiredKeys`** Automatically delete retired keys. Defaults to true. - **`KeyPath`** Path for storing keys when using the default file system store. Defaults to the “keys” directory relative to the hosting application. - **`DataProtectKeys`** Automatically protect keys in the storage using data protection. Defaults to true. - **`KeyCacheDuration`** When in normal operation, duration to cache keys from store. Defaults to 24 hours. - **`InitializationDuration`** When no keys have been created yet, this is the window of time considered to be an initialization period to allow all servers to synchronize if the keys are being created for the first time. Defaults to 5 minutes. - **`InitializationSynchronizationDelay`** Delay used when re-loading from the store when the initialization period. It allows other servers more time to write new keys so other servers can include them. Defaults to 5 seconds. - **`InitializationKeyCacheDuration`** Cache duration when within the initialization period. Defaults to 1 minute. ## Endpoints [Section titled “Endpoints”](#endpoints) Endpoint settings, including flags to disable individual endpoints and support for the request\_uri JAR parameter. Available on the `Endpoints` property of the `IdentityServerOptions` object. * **`EnableAuthorizeEndpoint`** Enables the authorize endpoint. Defaults to true. * **`EnableTokenEndpoint`** Enables the token endpoint. Defaults to true. * **`EnableDiscoveryEndpoint`** Enables the discovery endpoint. Defaults to true. * **`EnableUserInfoEndpoint`** Enables the user info endpoint. Defaults to true. * **`EnableEndSessionEndpoint`** Enables the end session endpoint. Defaults to true. * **`EnableCheckSessionEndpoint`** Enables the check session endpoint. Defaults to true. * **`EnableTokenRevocationEndpoint`** Enables the token revocation endpoint. Defaults to true. * **`EnableIntrospectionEndpoint`** Enables the introspection endpoint. Defaults to true. * **`EnableDeviceAuthorizationEndpoint`** Enables the device authorization endpoint. Defaults to true. * **`EnableBackchannelAuthenticationEndpoint`** Enables the backchannel authentication endpoint. Defaults to true. * **`EnablePushedAuthorizationEndpoint`** Enables the pushed authorization endpoint. Defaults to true. * **`EnableJwtRequestUri`** Enables the `request_uri` parameter for JWT-Secured Authorization Requests. This allows the JWT to be passed by reference. Disabled by default, due to the security implications of enabling the request\_uri parameter (see [RFC 9101 section 10.4](https://datatracker.ietf.org/doc/rfc9101/)). ## Discovery [Section titled “Discovery”](#discovery) Discovery settings, including flags to toggle sections of the discovery document and settings to add custom entries to it. Available on the `Discovery` property of the `IdentityServerOptions` object. If you want to take full control over the rendering of the discovery and jwks documents, you can implement the `IDiscoveryResponseGenerator` interface (or derive from our default implementation). * **`ShowEndpoints`** Shows endpoints (authorization\_endpoint, token\_endpoint, etc.) in the discovery document. Defaults to true. * **`ShowKeySet`** Shows the jwks\_uri in the discovery document and enables the jwks endpoint. Defaults to true. * **`ShowIdentityScopes`** Includes IdentityResources in the supported\_scopes of the discovery document. Defaults to true. * **`ShowApiScopes`** Includes ApiScopes in the supported\_scopes of the discovery document. Defaults to true. * **`ShowClaims`** Shows claims\_supported in the discovery document. Defaults to true. * **`ShowResponseTypes`** Shows response\_types\_supported in the discovery document. Defaults to true. * **`ShowResponseModes`** Shows response\_modes\_supported in the discovery document. Defaults to true. * **`ShowGrantTypes`** Shows grant\_types\_supported in the discovery document. Defaults to true. * **`ShowExtensionGrantTypes`** Includes extension grant types in the grant\_types\_supported of the discovery document. Defaults to true. * **`ShowTokenEndpointAuthenticationMethods`** Shows token\_endpoint\_auth\_methods\_supported in the discovery document. Defaults to true. * **`CustomEntries`** Adds custom elements to the discovery document. For example: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.Discovery.CustomEntries.Add("my_setting", "foo"); options.Discovery.CustomEntries.Add("my_complex_setting", new { foo = "foo", bar = "bar" }); }); ``` * **`ExpandRelativePathsInCustomEntries`** Expands paths in custom entries that begin with ”\~/” into absolute paths below the IdentityServer base address. Defaults to true. In the following example, if IdentityServer’s base address is `https://localhost:5001`, then `my_custom_endpoint`’s value will be expanded to `https://localhost:5001/custom`. ```csharp options.Discovery.CustomEntries.Add("my_custom_endpoint", "~/custom"); ``` ## Authentication [Section titled “Authentication”](#authentication) Login/logout related settings. Available on the `Authentication` property of the `IdentityServerOptions` * **`CookieAuthenticationScheme`** Sets the cookie authentication scheme configured by the host used for interactive users. If not set, the scheme will be inferred from the host’s default authentication scheme. This setting is typically used when AddPolicyScheme is used in the host as the default scheme. * **`CookieLifetime`** The authentication cookie lifetime (only effective if the IdentityServer-provided cookie handler is used). Defaults to 10 hours. * **`CookieSlidingExpiration`** Specifies if the cookie should be sliding or not (only effective if the IdentityServer-provided cookie handler is used). Defaults to false. * **`CookieSameSiteMode`** Specifies the SameSite mode for the internal cookies. Defaults to None. * **`RequireAuthenticatedUserForSignOutMessage`** Indicates if user must be authenticated to accept parameters to end session endpoint. Defaults to false. * **`CheckSessionCookieName`** The name of the cookie used for the check session endpoint. Defaults to the constant `IdentityServerConstants.DefaultCheckSessionCookieName`, which has the value “idsrv.session”. * **`CheckSessionCookieDomain`** The domain of the cookie used for the check session endpoint. Defaults to `null`. * **`CheckSessionCookieSameSiteMode`** The SameSite mode of the cookie used for the check session endpoint. Defaults to None. * **`RequireCspFrameSrcForSignout`** Enables all content security policy headers on the end session endpoint. For historical reasons, this option’s name mentions `frame-src`, but the content security policy headers on the end session endpoint also include other fetch directives, including a *default-src ‘none’* directive, which prevents most resources from being loaded by the end session endpoint, and a `style-src` directive that specifies the hash of the expected style on the page. * **`CoordinateClientLifetimesWithUserSession`** (added in `v6.1`) When enabled, all clients’ token lifetimes (e.g. refresh tokens) will be tied to the user’s session lifetime. This means when the user logs out, any revokable tokens will be removed. If using server-side sessions, expired sessions will also remove any revokable tokens, and backchannel logout will be triggered. An individual client can override this setting with its own `CoordinateLifetimeWithUserSession` configuration setting. ## Events [Section titled “Events”](#events) Configures which [events](/identityserver/diagnostics/events/) should be raised at the registered event sink. * **`RaiseSuccessEvents`** Enables success events. Defaults to false. Success events include all the events whose names are postfixed with “SuccessEvent”. In general, they are raised when properly formed and valid requests are processed without errors. * **`RaiseFailureEvents`** Enables failure events. Defaults to false. Failure events include all the events whose names are postfixed with “FailureEvent”. In general, they are raised when an action has failed because of incorrect or badly formed parameters in a request. They indicate that the user or client calling IdentityServer has done something wrong and are analogous to a 400: bad request error. * **`RaiseErrorEvents`** Enables Error events. Defaults to false. Error events are raised when an error has occurred, either because of invalid configuration or an unhandled exception. They indicate that there is something wrong within the token server or its configuration and are analogous to a 500: internal server error. * **`RaiseInformationEvents`** Enables Information events. Defaults to false. Information events are emitted when an action has occurred that is of informational interest, but that is neither a success nor a failure. For example, when the end user grants, denies, or revokes consent, that is considered an information event, because these events capture a valid choice of the user rather than success or failure. ## Logging [Section titled “Logging”](#logging) Logging related settings, including filters that will remove sensitive values and unwanted exceptions from logs. Available on the `Logging` property of the `IdentityServerOptions` object. * **`AuthorizeRequestSensitiveValuesFilter`** Collection of parameter names passed to the authorize endpoint that are considered sensitive and will be redacted in logs. Note that authorization parameters pushed to the Pushed Authorization Request (PAR) endpoint are eventually handled by the authorize request pipeline. This filter should be configured to exclude sensitive values wether or not they are pushed, and usually should be set to the same value as `PushedAuthorizationSensitiveValuesFilter`. Defaults to `client_secret`, `client_assertion`, `id_token_hint`. The default value was changed in version 7.2.2 to include `client_secret` and `client_assertion`. * **`PushedAuthorizationSensitiveValuesFilter`** Collection of parameter names passed to the Pushed Authorization Request (PAR) endpoint that are considered sensitive and will be redacted in logs. Note that authorization parameters pushed to the PAR endpoint are eventually handled by the authorize request pipeline. This filter should be configured to exclude sensitive values that are pushed, and usually should be set to the same value as `AuthorizeRequestSensitiveValuesFilter`. Defaults to `client_secret`, `client_assertion`, `id_token_hint`. * **`TokenRequestSensitiveValuesFilter`** Collection of parameter names passed to the token endpoint that are considered sensitive and will be redacted in logs. In `v7.0` and earlier, defaults to `client_secret`, `password`, `client_assertion`, `refresh_token`, and `device_code`. In `v7.1`, `subject_token` is also excluded. * **`BackchannelAuthenticationRequestSensitiveValuesFilter`** Collection of parameter names passed to the backchannel authentication endpoint that are considered sensitive and will be redacted in logs. Defaults to `client_secret`, `client_assertion`, and `id_token_hint`. * **`UnhandledExceptionLoggingFilter`** (added in `v6.2`) A function that is called when the IdentityServer middleware detects an unhandled exception, and is used to determine if the exception is logged. The arguments to the function are the HttpContext and the Exception. It should return true to log the exception, and false to suppress. The default is to suppress logging of cancellation-related exceptions when the `CancellationToken` on the `HttpContext` has requested cancellation. Such exceptions are thrown when Http requests are canceled, which is an expected occurrence. Logging them creates unnecessary noise in the logs. In `v7.0` and earlier, only `TaskCanceledException`s were filtered. Beginning in `v7.1`, `OperationCanceledException`s are filtered as well. ## InputLengthRestrictions [Section titled “InputLengthRestrictions”](#inputlengthrestrictions) Settings that control the allowed length of various protocol parameters, such as client id, scope, redirect URI etc. Available on the `InputLengthRestrictions` property of the `IdentityServerOptions` object. * **`ClientId`** Max length for ClientId. Defaults to 100. * **`ClientSecret`** Max length for external client secrets. Defaults to 100. * **`Scope`** Max length for scope. Defaults to 300. * **`RedirectUri`** Max length for redirect\_uri. Defaults to 400. * **`Nonce`** Max length for nonce. Defaults to 300. * **`UiLocale`** Max length for ui\_locale. Defaults to 100. * **`LoginHint`** Max length for login\_hint. Defaults to 100. * **`AcrValues`** Max length for acr\_values. Defaults to 300. * **`GrantType`** Max length for grant\_type. Defaults to 100. * **`UserName`** Max length for username. Defaults to 100. * **`Password`** Max length for password. Defaults to 100. * **`CspReport`** Max length for CSP reports. Defaults to 2000. * **`IdentityProvider`** Max length for external identity provider name. Defaults to 100. * **`ExternalError`** Max length for external identity provider errors. Defaults to 100. * **`AuthorizationCode`** Max length for authorization codes. Defaults to 100. * **`DeviceCode`** Max length for device codes. Defaults to 100. * **`RefreshToken`** Max length for refresh tokens. Defaults to 100. * **`TokenHandle`** Max length for token handles. Defaults to 100. * **`Jwt`** Max length for JWTs. Defaults to 51200. * **`CodeChallengeMinLength`** Min length for the code challenge. Defaults to 43. * **`CodeChallengeMaxLength`** Max length for the code challenge. Defaults to 128. * **`CodeVerifierMinLength`** Min length for the code verifier. Defaults to 43. * **`CodeVerifierMaxLength`** Max length for the code verifier. Defaults to 128. * **`ResourceIndicatorMaxLength`** Max length for resource indicator parameter. Defaults to 512. * **`BindingMessage`** Max length for binding\_message. Defaults to 100. * **`UserCode`** Max length for user\_code. Defaults to 100. * **`IdTokenHint`** Max length for id\_token\_hint. Defaults to 4000. * **`LoginHintToken`** Max length for login\_hint\_token. Defaults to 4000. * **`AuthenticationRequestId`** Max length for auth\_req\_id. Defaults to 100. ## UserInteraction [Section titled “UserInteraction”](#userinteraction) User interaction settings, including urls for pages in the UI, names of parameters to those pages, and other settings related to interactive flows. Available on the `UserInteraction` property of the `IdentityServerOptions` object. * **`LoginUrl`**, **`LogoutUrl`**, **`ConsentUrl`**, **`ErrorUrl`**, **`DeviceVerificationUrl`** Sets the URLs for the login, logout, consent, error and device verification pages. * **`CreateAccountUrl`** (added in `v6.3`) Sets the URL for the create account page, which is used by OIDC requests that include the `prompt=create` parameter. When this option is set, including the `prompt=create` parameter will cause the user to be redirected to the specified url. `create` will also be added to the discovery document’s `prompt_values_supported` array to announce support for this feature. When this option is not set, the `prompt=create` parameter is ignored, and `create` is not added to discovery. Defaults to `null`. * **`LoginReturnUrlParameter`** Sets the name of the return URL parameter passed to the login page. Defaults to `returnUrl`. * **`LogoutIdParameter`** Sets the name of the logout message id parameter passed to the logout page. Defaults to `logoutId`. * **`ConsentReturnUrlParameter`** Sets the name of the return URL parameter passed to the consent page. Defaults to `returnUrl`. * **`ErrorIdParameter`** Sets the name of the error message id parameter passed to the error page. Defaults to `errorId`. * **`CustomRedirectReturnUrlParameter`** Sets the name of the return URL parameter passed to a custom redirect from the authorization endpoint. Defaults to `returnUrl`. * **`DeviceVerificationUserCodeParameter`** Sets the name of the user code parameter passed to the device verification page. Defaults to `userCode`. * **`CookieMessageThreshold`** Certain interactions between IdentityServer and some UI pages require a cookie to pass state and context (any of the pages above that have a configurable “message id” parameter). Since browsers have limits on the number of cookies and their size, this setting is used to prevent too many cookies being created. The value sets the maximum number of message cookies of any type that will be created. The oldest message cookies will be purged once the limit has been reached. This effectively indicates how many tabs can be opened by a user when using IdentityServer. Defaults to 2. * **`AllowOriginInReturnUrl`** Flag that allows return URL validation to accept full URL that includes the IdentityServer origin. Defaults to `false`. * **`PromptValuesSupported`** (added in `v7.0.7`) The collection of OIDC prompt modes supported and that will be published in discovery. By default, this includes all values in `Constants.SupportedPromptModes`. If the `CreateAccountUrl` option is set, then the “create” value is also included. If additional prompt values are added, a customized [`IAuthorizeInteractionResponseGenerator"`](/identityserver/ui/custom/) is also required to handle those values. ## Caching [Section titled “Caching”](#caching) Caching settings for the stores. Available on the `Caching` property of the `IdentityServerOptions` object. These settings only apply if the respective caching has been enabled in the services configuration in startup. * **`ClientStoreExpiration`** Cache duration of client configuration loaded from the client store. Defaults to 15 minutes. * **`ResourceStoreExpiration`** Cache duration of identity and API resource configuration loaded from the resource store. Defaults to 15 minutes. * **`CorsExpiration`** Cache duration of CORS configuration loaded from the CORS policy service. Defaults to 15 minutes. * **`IdentityProviderCacheDuration`** Cache duration of identity provider configuration loaded from the identity provider store. Defaults to 60 minutes. * **`CacheLockTimeout`** The timeout for concurrency locking in the default cache. Defaults to 60 seconds. ## CORS [Section titled “CORS”](#cors) CORS settings for IdentityServer’s endpoints. Available on the `Cors` property of the `IdentityServerOptions` object. The underlying CORS implementation is provided from ASP.NET Core, and as such it is automatically registered in the dependency injection system. * **`CorsPolicyName`** Name of the CORS policy that will be evaluated for CORS requests into IdentityServer. Defaults to `IdentityServer`. The policy provider that handles this is implemented in terms of the `ICorsPolicyService` registered in the dependency injection system. If you wish to customize the set of CORS origins allowed to connect, then it is recommended that you provide a custom implementation of `ICorsPolicyService`. * **`CorsPaths`** The endpoints within IdentityServer where CORS is supported. Defaults to the discovery, user info, token, and revocation endpoints. * **`PreflightCacheDuration`** Indicates the value to be used in the preflight `Access-Control-Max-Age` response header. Defaults to `null` indicating no caching header is set on the response. ## Content Security Policy [Section titled “Content Security Policy”](#content-security-policy) Settings for Content Security Policy (CSP) headers that IdentityServer emits. Available on the `Csp` property of the `IdentityServerOptions` object. * **`Level`** The level of CSP to use. CSP Level 2 is used by default, but this can be changed to `CspLevel.One` to accommodate older browsers. * **`AddDeprecatedHeader`** Indicates if the older `X-Content-Security-Policy` CSP header should also be emitted in addition to the standards-based header value. Defaults to `true`. ## Device Flow [Section titled “Device Flow”](#device-flow) OAuth device flow settings. Available on the `DeviceFlow` property of the `IdentityServerOptions` object. * **`DefaultUserCodeType`** The user code type to use, unless set at the client level. Defaults to `Numeric`, a 9-digit code. * **`Interval`** The maximum frequency in seconds that a client may poll the token endpoint in the device flow. Defaults to `5`. ## Mutual TLS [Section titled “Mutual TLS”](#mutual-tls) [Mutual TLS](/identityserver/tokens/client-authentication/) settings. Available on the `MutualTls` property of the `IdentityServerOptions` object. Program.cs ```csharp var builder = services.AddIdentityServer(options => { options.MutualTls.Enabled = true; // use mtls subdomain options.MutualTls.DomainName = "mtls"; options.MutualTls.AlwaysEmitConfirmationClaim = true; }) ``` * **`Enabled`** Specifies if MTLS support should be enabled. Defaults to `false`. * **`ClientCertificateAuthenticationScheme`** Specifies the name of the authentication handler for X.509 client certificates. Defaults to `Certificate`. * **`DomainName`** Specifies either the name of the subdomain or full domain for running the MTLS endpoints. MTLS will use path-based endpoints if not set (the default). Use a simple string (e.g. “mtls”) to set a subdomain, use a full domain name (e.g. “identityserver-mtls.io”) to set a full domain name. When a full domain name is used, you also need to set the `IssuerUri` to a fixed value. * **`AlwaysEmitConfirmationClaim`** Specifies whether a cnf claim gets emitted for access tokens if a client certificate was present. Normally the cnf claims only gets emitted if the client used the client certificate for authentication, setting this to true, will set the claim regardless of the authentication method. Defaults to false. ## PersistentGrants [Section titled “PersistentGrants”](#persistentgrants) Shared settings for persisted grants behavior. * **`DataProtectData`** Data protect the persisted grants “data” column. Defaults to `true`. If your database is already protecting data at rest, then you can consider disabling this. * **`DeleteOneTimeOnlyRefreshTokensOnUse`** (added in `v6.3`) When Refresh tokens that are configured with RefreshTokenUsage.OneTime are used, this option controls if they will be deleted immediately or retained and marked as consumed. The default is on - immediately delete. ## Dynamic Providers [Section titled “Dynamic Providers”](#dynamic-providers) Settings for [dynamic providers](/identityserver/ui/login/dynamicproviders/). Available on the `DynamicProviders` property of the `IdentityServerOptions` object. * **`PathPrefix`** Prefix in the pipeline for callbacks from external providers. Defaults to “/federation”. * **`SignInScheme`** Scheme used for signin. Defaults to the constant `IdentityServerConstants.ExternalCookieAuthenticationScheme`, which has the value “idsrv.external”. * **`SignOutScheme`** Scheme for signout. Defaults to the constant `IdentityServerConstants.DefaultCookieAuthenticationScheme`, which has the value “idsrv”. ## CIBA [Section titled “CIBA”](#ciba) [CIBA](/identityserver/ui/ciba/) settings. Available on the `Ciba` property of the `IdentityServerOptions` object. * **`DefaultLifetime`** The default lifetime of the pending authentication requests in seconds. Defaults to 300. * **`DefaultPollingInterval`** The maximum frequency in seconds that a client may poll the token endpoint in the CIBA flow. Defaults to 5. ## Server-Side Sessions [Section titled “Server-Side Sessions”](#server-side-sessions) Settings for [server-side sessions](/identityserver/ui/server-side-sessions/). Added in `v6.1`. Available on the `ServerSideSessions` property of the `IdentityServerOptions` object. * **`UserDisplayNameClaimType`** Claim type used for the user’s display name. Unset by default due to possible PII concerns. If used, this would commonly be `JwtClaimTypes.Name`, `JwtClaimType.Email` or a custom claim. * **`RemoveExpiredSessions`** Enables periodic cleanup of expired sessions. Defaults to true. * **`RemoveExpiredSessionsFrequency`** Frequency that expired sessions will be removed. Defaults to 10 minutes. * **`RemoveExpiredSessionsBatchSize`** Number of expired session records to be removed at a time. Defaults to 100. * **`ExpiredSessionsTriggerBackchannelLogout`** If enabled, when server-side sessions are removed due to expiration, back-channel logout notifications will be sent. This will, in effect, tie a user’s session lifetime at a client to their session lifetime at IdentityServer. Defaults to false. * **`FuzzExpiredSessionRemovalStart`** The background session cleanup job runs at a configured interval. If multiple nodes run the cleanup job at the same time update conflicts might occur in the store. To reduce the propability of that happening, the startup time can be fuzzed. The first run is scheduled at a random time between the host startup and the configured RemoveExpiredSessionsFrequency. Subsequent runs are run on the configured RemoveExpiredSessionsFrequency. Defaults to `true`. ## Validation [Section titled “Validation”](#validation) * **`InvalidRedirectUriPrefixes`** Collection of URI scheme prefixes that should never be used as custom URI schemes in the `redirect_uri` passed to tha authorize endpoint or the `post_logout_redirect_uri` passed to the end\_session endpoint. Defaults to *\[“javascript:”, “file:”, “data:”, “mailto:”, “ftp:”, “blob:”, “about:”, “ssh:”, “tel:”, “view-source:”, “ws:”, “wss:”]*. ## DPoP [Section titled “DPoP”](#dpop) Added in 6.3.0. Demonstration of Proof-of-Possession settings. Available on the `DPoP` property of the `IdentityServerOptions` object. * **`ProofTokenValidityDuration`** Duration that DPoP proof tokens are considered valid. Defaults to *1 minute*. * **`ServerClockSkew`** Clock skew used in validating DPoP proof token expiration using a server-generated nonce value. Defaults to `0`. ## Pushed Authorization Requests [Section titled “Pushed Authorization Requests”](#pushed-authorization-requests) [Pushed Authorization Requests (PAR)](/identityserver/tokens/par/) settings. Added in `v7.0`. Available on the `PushedAuthorization` property of the `IdentityServerOptions` object. * **`Required`** Causes PAR to be required globally. Defaults to `false`. * **`Lifetime`** Controls the lifetime of pushed authorization requests. The pushed authorization request’s lifetime begins when the request to the PAR endpoint is received, and is validated until the authorize endpoint returns a response to the client application. Note that user interaction, such as entering credentials or granting consent, may need to occur before the authorize endpoint can do so. Setting the lifetime too low will likely cause login failures for interactive users, if pushed authorization requests expire before those users complete authentication. Some security profiles, such as the FAPI 2.0 Security Profile recommend an expiration within 10 minutes to prevent attackers from pre-generating requests. To balance these constraints, this lifetime defaults to 10 minutes. ## Diagnostics [Section titled “Diagnostics”](#diagnostics) [Diagnostic data](/identityserver/diagnostics/data/) settings. Added in `v7.3`. Available on the `Diagnostics` property of the `IdentityServerOptions` object. * **`LogFrequency`** Frequency at which the diagnostic data is logged. Defaults to 1 hour. * **`ChunkSize`** Maximum size of diagnostic data log message chunks in kilobytes. Defaults to 8160 bytes. 8 KB is a conservative limit for the max size of a log message that is imposed by some logging tools. We take 32 bytes less than that to allow for additional formatting of the log message. ## Preview Features [Section titled “Preview Features”](#preview-features) Preview Features settings. Available on the `Preview` property of the `IdentityServerOptions` object. #### Discovery Document Cache [Section titled “Discovery Document Cache”](#discovery-document-cache) In large deployments of Duende IdentityServer, where a lot of concurrent users attempt to consume the [discovery endpoint](/identityserver/reference/v7/endpoints/discovery/) to retrieve metadata about your IdentityServer, you can increase throughput by enabling the discovery document cache preview using the *`EnableDiscoveryDocumentCache`* flag. This will cache discovery document information for the duration specified in the *`DiscoveryDocumentCacheDuration`* option. It’s best to keep the cache time low if you use the *`CustomEntries`* element on the discovery document or implement a custom *`IDiscoveryResponseGenerator`*. #### Strict Audience Validation [Section titled “Strict Audience Validation”](#strict-audience-validation) When using [*private key JWT*](/identityserver/tokens/client-authentication/#private-key-jwts), there is a theoretical vulnerability where a Relying Party trusting multiple OpenID Providers could be attacked if one of the OpenID Providers is malicious or compromised. The OpenID Foundation proposed a two-part fix: strictly validate the audience and set an explicit `typ` header in the authentication JWT. You can [enable strict audience validation in Duende IdentityServer](/identityserver/tokens/client-authentication/#strict-audience-validation) using the *`StrictClientAssertionAudienceValidation`* flag, which strictly validates that the audience is equal to the issuer and validates the token’s `typ` header.
-----
# Response Generators

> An overview of IdentityServer's response generation pattern and customization options for protocol endpoint responses.

IdentityServer’s endpoints follow a pattern of abstraction in which a response generator uses a validated input model to produce a response model. The response model is a type that represents the data that will be returned from the endpoint. The response model is then wrapped in a result model, which is a type that facilitates serialization by an implementation of `IHttpResponseWriter`. Customization of protocol endpoint responses is possible in both the response generators and response writers. Response generator customization is appropriate when you want to change the “business logic” of an endpoint and is typically accomplished by overriding virtual methods in the default response generator. Response writer customization is appropriate when you want to change the serialization, encoding, or headers of the HTTP response and is accomplished by registering a custom implementation of the `IHttpResponseWriter`.
-----
# Authorize Interaction Response Generator

> Documentation for the IAuthorizeInteractionResponseGenerator interface which determines if a user must log in or consent when making requests to the authorization endpoint.

#### Duende.IdentityServer.ResponseHandling.IAuthorizeInteractionResponseGenerator [Section titled “Duende.IdentityServer.ResponseHandling.IAuthorizeInteractionResponseGenerator”](#duendeidentityserverresponsehandlingiauthorizeinteractionresponsegenerator) The `IAuthorizeInteractionResponseGenerator` interface models the logic for determining if user must log in or consent when making requests to the authorization endpoint. ## IAuthorizeInteractionResponseGenerator APIs [Section titled “IAuthorizeInteractionResponseGenerator APIs”](#iauthorizeinteractionresponsegenerator-apis) * **`ProcessInteractionAsync`** Returns the `InteractionResponse` based on the `ValidatedAuthorizeRequest` an and optional `ConsentResponse` if the user was shown a consent page. ## InteractionResponse [Section titled “InteractionResponse”](#interactionresponse) * **`IsLogin`** Specifies if the user must log in. * **`IsConsent`** Specifies if the user must consent. * **`IsCreateAccount`** Added in `v6.3`. Specifies if the user must create an account. * **`IsError`** Specifies if the user must be shown an error page. * **`Error`** The error to display on the error page. * **`ErrorDescription`** The description of the error to display on the error page. * **`IsRedirect`** Specifies if the user must be redirected to a custom page for custom processing. * **`RedirectUrl`** The URL for the redirect to the page for custom processing.
-----
# IHttpResponseWriter

> Documentation for the IHttpResponseWriter interface, a low-level abstraction for customizing serialization, encoding, and HTTP headers in protocol endpoint responses.

The `IHttpResponseWriter` interface is the contract for services that can produce HTTP responses for `IEndpointResult`s. This is a low-level abstraction that is intended to be used if you need to customize the serialization, encoding, or HTTP headers in a response from a protocol endpoint. #### Duende.IdentityServer.Hosting.IHttpResponseWriter [Section titled “Duende.IdentityServer.Hosting.IHttpResponseWriter”](#duendeidentityserverhostingihttpresponsewriter) ```csharp ///  /// Contract for a service that writes appropriate http responses for  objects. ///  public interface IHttpResponseWriter where T : IEndpointResult { ///  /// Writes the endpoint result to the HTTP response. ///  Task WriteHttpResponse(T result, HttpContext context); } ``` #### Duende.IdentityServer.Hosting.IEndpointResult [Section titled “Duende.IdentityServer.Hosting.IEndpointResult”](#duendeidentityserverhostingiendpointresult) ```csharp ///  /// An  is the object model that describes the /// results that will returned by one of the protocol endpoints provided by /// IdentityServer, and can be executed to produce an HTTP response. ///  public interface IEndpointResult { ///  /// Executes the result to write an http response. ///  /// The HTTP context. Task ExecuteAsync(HttpContext context); } ```
-----
# Token Response Generator

> Documentation for the ITokenResponseGenerator interface and its implementation, which generates responses to valid token endpoint requests with customization options for different token flows.

## Duende.IdentityServer.ResponseHandling.ITokenResponseGenerator [Section titled “Duende.IdentityServer.ResponseHandling.ITokenResponseGenerator”](#duendeidentityserverresponsehandlingitokenresponsegenerator) The `ITokenResponseGenerator` interface is the contract for the service that generates responses to valid requests to the token endpoint. A response in this context refers to an object model that describes the content that will be serialized and transmitted in the HTTP response. The default implementation is the `TokenResponseGenerator` class. You can customize the behavior of the token endpoint by providing your own implementation of the `ITokenResponseGenerator` to the ASP.NET Core service provider. To create a customized implementation of `ITokenResponseGenerator`, we recommend that you create a class that derives from the default implementation. Your custom implementation should override the appropriate virtual methods of the default implementation and add your custom behavior to those overrides, possibly calling the base methods first and then manipulating their results. ## ITokenResponseGenerator [Section titled “ITokenResponseGenerator”](#itokenresponsegenerator) The `ITokenResponseGenerator` contains a single method to process validated token requests and return token responses. * **`ProcessInteractionAsync`** Returns the `TokenResponse` based on the `ValidatedTokenRequest`. ## TokenResponseGenerator [Section titled “TokenResponseGenerator”](#tokenresponsegenerator) The default implementation of the `ITokenResponseGenerator` contains virtual methods that can be overridden to customize particular behavior for particular token requests. * **`ProcessAsync`** Returns the `TokenResponse` for any `TokenRequestValidationResult`. * **`ProcessClientCredentialsRequestAsync`** Returns the `TokenResponse` for a `TokenRequestValidationResult` from the client credentials flow. * **`ProcessPasswordRequestAsync`** Returns the `TokenResponse` for a `TokenRequestValidationResult` from the resource owner password flow. * **`ProcessAuthorizationCodeRequestAsync`** Returns the `TokenResponse` for a `TokenRequestValidationResult` from the authorization code flow. * **`ProcessRefreshTokenRequestAsync`** Returns the `TokenResponse` for a `TokenRequestValidationResult` from the refresh token flow. * **`ProcessDeviceCodeRequestAsync`** Returns the `TokenResponse` for a `TokenRequestValidationResult` from the device code flow. * **`ProcessCibaRequestAsync`** Returns the `TokenResponse` for a `TokenRequestValidationResult` from the CIBA flow. * **`ProcessExtensionGrantRequestAsync`** Returns the `TokenResponse` for a `TokenRequestValidationResult` from an extension grant. * **`CreateAccessTokenAsync`** Creates an access token and optionally a refresh token. * **`CreateIdTokenFromRefreshTokenRequestAsync`** Creates an ID token in a refresh token request. ## TokenResponse [Section titled “TokenResponse”](#tokenresponse) The `TokenResponse` class represents the data that will be included in the body of the response returned from the token endpoint. It contains properties for the various tokens that can be returned, the scope and expiration of the access token, and a mechanism for adding custom properties to the result. Omitting property values will cause the entire property to be absent from the response. * **`IdentityToken`** The identity token. * **`AccessToken`** The access token. * **`RefreshToken`** The refresh token. * **`AccessTokenLifetime`** The access token lifetime in seconds. * **`Scope`** The scope. * **`Custom`** A dictionary of strings to objects that will be serialized to json and added to the token response.
-----
# Backchannel Authentication Interaction Service

> Documentation for the IBackchannelAuthenticationInteractionService interface which provides services for accessing and completing CIBA login requests.

#### Duende.IdentityServer.Services.IBackchannelAuthenticationInteractionService [Section titled “Duende.IdentityServer.Services.IBackchannelAuthenticationInteractionService”](#duendeidentityserverservicesibackchannelauthenticationinteractionservice) The `IBackchannelAuthenticationInteractionService` interface provides services for a user to access or complete a login requests for [CIBA](/identityserver/ui/ciba/). It is available from the dependency injection system and would normally be injected as a constructor parameter into your MVC controllers for the user interface of IdentityServer. ## IBackchannelAuthenticationInteractionService APIs [Section titled “IBackchannelAuthenticationInteractionService APIs”](#ibackchannelauthenticationinteractionservice-apis) * **`GetPendingLoginRequestsForCurrentUserAsync`** Returns a collection of [BackchannelUserLoginRequest](/identityserver/reference/v7/models/ciba-login-request/) objects which represent pending login requests for the current user. * **`GetLoginRequestByInternalIdAsync`** Returns the [BackchannelUserLoginRequest](/identityserver/reference/v7/models/ciba-login-request/) object for the id. * **`CompleteLoginRequestAsync`** Completes the login request with the provided `CompleteBackchannelLoginRequest` response for the current user or the subject passed. ### CompleteBackchannelLoginRequest [Section titled “CompleteBackchannelLoginRequest”](#completebackchannelloginrequest) Models the data needed for a user to complete a backchannel authentication request. * **`InternalId`** The internal store id for the request. * **`ScopesValuesConsented`** Gets or sets the scope values consented to. Setting any scopes grants the login request. Leaving the scopes null or empty denies the request. * **`Description`** Gets or sets the optional description to associate with the consent. * **`Subject`** The subject for which the completion is being made. This allows more claims to be associated with the request that was identified on the backchannel authentication request. If not provided, then the `IUserSession` service will be consulting to obtain the current subject. * **`SessionId`** The session id to associate with the completion request if the Subject is provided. If the Subject is not provided, then this property is ignored in favor of the session id provided by the `IUserSession` service.
-----
# Backchannel Authentication User Notification Service

> Documentation for the IBackchannelAuthenticationUserNotificationService interface which is used to notify users when a CIBA login request has been made.

#### Duende.IdentityServer.Services.IBackchannelAuthenticationUserNotificationService [Section titled “Duende.IdentityServer.Services.IBackchannelAuthenticationUserNotificationService”](#duendeidentityserverservicesibackchannelauthenticationusernotificationservice) The `IBackchannelAuthenticationUserNotificationService` interface is used to contact users when a [CIBA](/identityserver/ui/ciba/) login request has been made. To use CIBA, you are expected to implement this interface and register it in the ASP.NET Core service provider. ## IBackchannelAuthenticationUserNotificationService APIs [Section titled “IBackchannelAuthenticationUserNotificationService APIs”](#ibackchannelauthenticationusernotificationservice-apis) * **`SendLoginRequestAsync`** Sends a notification for the user to login via the [BackchannelUserLoginRequest](/identityserver/reference/v7/models/ciba-login-request/) parameter.
-----
# Device Flow Interaction Service

> Documentation for the IDeviceFlowInteractionService interface which provides services for user interfaces to communicate with IdentityServer during device flow authorization.

#### Duende.IdentityServer.Services.IDeviceFlowInteractionService [Section titled “Duende.IdentityServer.Services.IDeviceFlowInteractionService”](#duendeidentityserverservicesideviceflowinteractionservice) The `IDeviceFlowInteractionService` interface is intended to provide services to be used by the user interface to communicate with Duende IdentityServer during device flow authorization. It is available from the dependency injection system and would normally be injected as a constructor parameter into your MVC controllers for the user interface of IdentityServer. ## IDeviceFlowInteractionService APIs [Section titled “IDeviceFlowInteractionService APIs”](#ideviceflowinteractionservice-apis) * **`GetAuthorizationContextAsync`** Returns the `DeviceFlowAuthorizationRequest` based on the `userCode` passed to the login or consent pages. * **`DeviceFlowInteractionResult`** Completes device authorization for the given `userCode`. ## DeviceFlowAuthorizationRequest [Section titled “DeviceFlowAuthorizationRequest”](#deviceflowauthorizationrequest) * **`ClientId`** The client identifier that initiated the request. * **`ScopesRequested`** The scopes requested from the authorization request. ## DeviceFlowInteractionResult [Section titled “DeviceFlowInteractionResult”](#deviceflowinteractionresult) * **`IsError`** Specifies if the authorization request errored. * **`ErrorDescription`** Error description upon failure.
-----
# IdentityServer Interaction Service

> Documentation for the IIdentityServerInteractionService interface which provides services for user interfaces to communicate with IdentityServer for authorization, consent, logout, and other user interactions.

#### Duende.IdentityServer.Services.IIdentityServerInteractionService [Section titled “Duende.IdentityServer.Services.IIdentityServerInteractionService”](#duendeidentityserverservicesiidentityserverinteractionservice) The `IIdentityServerInteractionService` interface is intended to provide services to be used by the user interface to communicate with IdentityServer, mainly pertaining to user interaction. It is available from the dependency injection system and would normally be injected as a constructor parameter into your MVC controllers for the user interface of IdentityServer. ## IIdentityServerInteractionService APIs [Section titled “IIdentityServerInteractionService APIs”](#iidentityserverinteractionservice-apis) * **`GetAuthorizationContextAsync`** Returns the `AuthorizationRequest` based on the `returnUrl` passed to the login or consent pages. * **`IsValidReturnUrl`** Indicates if the `returnUrl` is a valid URL for redirect after login or consent. * **`GetErrorContextAsync`** Returns the `ErrorMessage` based on the `errorId` passed to the error page. * **`GetLogoutContextAsync`** Returns the `LogoutRequest` based on the `logoutId` passed to the logout page. * **`CreateLogoutContextAsync`** Used to create a `logoutId` if there is not one presently. This creates a cookie capturing all the current state needed for signout and the `logoutId` identifies that cookie. This is typically used when there is no current `logoutId` and the logout page must capture the current user’s state needed for sign-out prior to redirecting to an external identity provider for signout. The newly created `logoutId` would need to be roundtripped to the external identity provider at signout time, and then used on the signout callback page in the same way it would be on the normal logout page. * **`GrantConsentAsync`** Accepts a `ConsentResponse` to inform IdentityServer of the user’s consent to a particular `AuthorizationRequest`. * **`DenyAuthorizationAsync`** Accepts a `AuthorizationError` to inform IdentityServer of the error to return to the client for a particular `AuthorizationRequest`. * **`GetAllUserGrantsAsync`** Returns a collection of `Grant` for the user. These represent a user’s consent or a clients access to a user’s resource. * **`RevokeUserConsentAsync`** Revokes all of a user’s consents and grants for a client. * **`RevokeTokensForCurrentSessionAsync`** Revokes all of a user’s consents and grants for clients the user has signed in to during their current session. ## Returned models [Section titled “Returned models”](#returned-models) The above methods return various models. ### AuthorizationRequest [Section titled “AuthorizationRequest”](#authorizationrequest) * **`Client`** The client that initiated the request. * **`RedirectUri`** The URI to redirect the user to after successful authorization. * **`DisplayMode`** The display mode passed from the authorization request. * **`UiLocales`** The UI locales passed from the authorization request. * **`IdP`** The external identity provider requested. This is used to bypass home realm discovery (HRD). This is provided via the “idp:” prefix to the `acr_values` parameter on the authorize request. * **`Tenant`** The tenant requested. This is provided via the “tenant:” prefix to the `acr_values` parameter on the authorize request. * **`LoginHint`** The expected username the user will use to login. This is requested from the client via the `login_hint` parameter on the authorize request. * **`PromptMode`** The prompt mode requested from the authorization request. * **`AcrValues`** The acr values passed from the authorization request. * **`ValidatedResources`** The `ResourceValidationResult` which represents the validated resources from the authorization request. * **`Parameters`** The entire parameter collection passed to the authorization request. * **`RequestObjectValues`** The validated contents of the request object (if present). ### ResourceValidationResult [Section titled “ResourceValidationResult”](#resourcevalidationresult) * **`Resources`** The resources of the result. * **`ParsedScopes`** The parsed scopes represented by the result. * **`RawScopeValues`** The original (raw) scope values represented by the validated result. ### ErrorMessage [Section titled “ErrorMessage”](#errormessage) * **`Error`** The error code. * **`ErrorDescription`** The error description. * **`DisplayMode`** The display mode passed from the authorization request. * **`UiLocales`** The UI locales passed from the authorization request. * **`RequestId`** The per-request identifier. This can be used to display to the end user and can be used in diagnostics. * **`ClientId`** The client id making the request (if available). * **`RedirectUri`** The redirect URI back to the client (if available). ### LogoutRequest [Section titled “LogoutRequest”](#logoutrequest) * **`ClientId`** The client identifier that initiated the request. * **`PostLogoutRedirectUri`** The URL to redirect the user to after they have logged out. * **`SessionId`** The user’s current session id. * **`SignOutIFrameUrl`** The URL to render in an `