This is the full developer documentation for Duende Software Docs ----- # Duende Software
Docs > Get started building your .NET applications with IdentityServer, 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/) 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(); } } ``` Note You need to explicitly set the `Audience` to the authorization server’s issuer URL (usually the URL of identity server). Don’t set the audience to the `TokenUrl`. Setting the `Audience` value to the token endpoint leaves you vulnerable to these vulnerabilities: (CVE-2025-27370/CVE-2025-27371). ----- # 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 ```cs 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 ```cs services.AddOpenIdConnectAccessTokenManagement(options => { options.DPoPJsonWebKey = jwk; }); ``` Similarly, for an application using `AddClientCredentialsTokenManagement`, it would look like this: Program.cs ```cs 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(); var accessTokenHandler = 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()); ``` When to use ITokenRequestCustomizer vs ITokenRetriever * Use `ITokenRequestCustomizer` when you need to modify token request parameters (scopes, resources, audiences) based on request context * Use `ITokenRetriever` when you need to replace the entire token acquisition logic with a custom flow ### 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 ----- # 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 ```cs 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/). Sample code Take a look at the [`Worker` project in the samples folder](https://github.com/DuendeSoftware/foss/tree/main/access-token-management/samples/) for example code. 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 } } } ``` Default resiliency handler in BFF v4 When you use `AddClientCredentialsHttpClient`, the configured HTTP client will include a resiliency message handler which automatically retries the HTTP request in case of a `401 Unauthorized` response. This retry helps in two scenarios: * The access token has expired. A new token is requested to retry the original HTTP request. * You’re using [DPoP](/accesstokenmanagement/advanced/dpop/). The DPoP request may need to be retried with a nonce value present in the HTTP response. The retry only happens once: if it still results in `401 Unauthorized`, the response is returned to the caller. This functionality is **not** included when you use `AddClientCredentialsTokenHandler` when registering your own HTTP clients. You can however add the resiliency message handler manually: ```csharp services.AddHttpClient(client => { client.BaseAddress = new Uri("https://apis.company.com/catalog/"); }) .AddDefaultAccessTokenResiliency() .AddClientCredentialsTokenHandler("catalog.client"); ``` ## 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 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) Currently, the most recent version is 4 (preview 2). 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/) ## 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 Duende BFF framework [Section titled “The Duende BFF framework”](#the-duende-bff-framework) 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) ## 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. ![Backend For Frontend application architecture diagram](/_astro/bff_application_architecture.IlwGmaoW_z1i7q.svg) 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. 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. 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: 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. ----- # 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, tied to a single browser based application. It performs the following functions: * Authenticate the user using OpenID Connect * Manages the user’s session using Secure Cookies and optional Server-side Session Management. * Optionally, provides access to the UI assets. * Server-side Token Management * Blazor support with unified authentication state management across rendering modes. ## Authentication Flow [Section titled “Authentication Flow”](#authentication-flow) The following diagram shows how the BFF protects browser-based applications: ![BFF Security Framework Architecture Overview](/_astro/bff_application_architecture.IlwGmaoW_z1i7q.svg) * **Authentication flows**: The server handles the authentication flows. There are specific endpoints for login / logout. While the browser is involved with these authentication flows, because the user is redirected to and from the identity provider, the browser-based application will never see the authentication tokens. These are exchanged for a code on the server only. * **Cookies**: After successful authentication, a cookie is added. This cookie protects all subsequent calls to the APIs. When using this type of authentication, **CSRF protection** is very important. * **Access to APIs**: The BFF can expose embedded APIs (which are hosted by the BFF itself) or proxy calls to remote APIs (which is more common in a microservice environment). While proxying, it will exchange the authentication cookie for an access token. * **Session Management**: The BFF can manage the users session. This can either be cookie-based session management or storage-based session management. ## Internals [Section titled “Internals”](#internals) Duende.BFF builds on widely used tools and frameworks, including ASP.NET Core’s OpenID Connect and cookie authentication handlers, YARP, and [Duende.AccessTokenManagement](/accesstokenmanagement/). Duende.BFF combines these tools and adds additional security and application features that are useful with a BFF architecture so that you can focus on providing application logic instead of security logic: ![Duende BFF Security Framework - components](/_astro/bff_blocs.CgltHOg__Z8hsnk.svg) ### ASP.NET OpenID Connect Handler [Section titled “ASP.NET OpenID Connect Handler”](#aspnet-openid-connect-handler) Duende.BFF uses ASP.NET’s OpenID Connect handler for OIDC and OAuth protocol processing. As long-term users of and contributors to this library, we think it is a well implemented and flexible implementation of the protocols. ### ASP.NET Cookie Handler [Section titled “ASP.NET Cookie Handler”](#aspnet-cookie-handler) Duende.BFF uses ASP.NET’s Cookie handler for session management. The cookie handler provides a claims-based identity to the application persisted in a digitally signed and encrypted cookie that is protected with modern cookie security features, including the Secure, HttpOnly and SameSite attributes. The handler also provides absolute and sliding session support, and has a flexible extensibility model, which Duende.BFF uses to implement [server-side session management](/bff/fundamentals/session/server-side-sessions/) and [back-channel logout support](/bff/fundamentals/session/management/back-channel-logout/). ### Duende.AccessTokenManagement [Section titled “Duende.AccessTokenManagement”](#duendeaccesstokenmanagement) Duende.BFF uses the Duende.AccessTokenManagement library for access token management and storage. This includes storage and retrieval of tokens, refreshing tokens as needed, and revoking tokens on logout. The library provides integration with the ASP.NET HTTP client to automatically attach tokens to outgoing HTTP requests, and its underlying management actions can also be programmatically invoked through an imperative API. ### API Endpoints [Section titled “API Endpoints”](#api-endpoints) In the BFF architecture, the frontend makes API calls to backend services via the BFF host exclusively. Typically, the BFF acts as a reverse proxy to [remote APIs](/bff/fundamentals/apis/remote/), providing session and token management. Implementing local APIs within the BFF host is also [possible](/bff/fundamentals/apis/local/). Regardless, requests to APIs are authenticated with the session cookie and need to be secured with an anti-forgery protection header. ### YARP [Section titled “YARP”](#yarp) Duende.BFF proxies requests to remote APIs using Microsoft’s YARP (Yet Another Reverse Proxy). You can set up YARP using a simplified developer-centric configuration API provided by Duende.BFF, or if you have more complex requirements, you can use the full YARP configuration system directly. If you are using YARP directly, Duende.BFF provides [YARP integration](/bff/fundamentals/apis/yarp/) to add BFF security and identity features. ### UI Assets [Section titled “UI Assets”](#ui-assets) The BFF host typically serves at least some of the UI assets of the frontend, which can be HTML/JS/CSS, WASM, and/or server-rendered content. Serving the UI assets, or at least the index page of the UI from the same origin as the backend simplifies requests from the frontend to the backend. Doing so makes the two components same-origin, so that browsers will allow requests with no need to use CORS and automatically include cookies (including the crucial authentication cookie). This also avoids issues where [third-party cookie blocking](/bff/architecture/third-party-cookies/) or the SameSite cookie attribute prevents the frontend from sending the authentication cookie to the backend. It is also possible to separate the BFF and UI and host them separately. See [here](/bff/architecture/ui-hosting/) for more discussion of UI hosting architecture. ### Blazor Support [Section titled “Blazor Support”](#blazor-support) Blazor based applications have unique challenges when it comes to authentication state. It’s possible to mix various rendering models in a single application. Auto mode even starts off server rendered, then transitions to WASM when the code has loaded. BFF Security Framework has built support for Blazor, where it helps to unify access to authentication state and to secure access to backend services. ----- # 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 Origins 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 Origin 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. Note The Duende BFF V4 library doesn’t ship with an abstraction to store or read frontends from a database. It’s possible to implement this by creating your own store (based on your requirements), then modify the `FrontendCollection` at run-time. ## 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: ![BFF Multi-Frontend Pipeline](/_astro/bff_multi_frontend_pipeline.CgQTXVlD_21LGAV.svg) 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 origin 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: ![Hosting BFF UI from the UI](/_astro/bff_ui_hosting_loc.Cidk5i8u_ZBS8tg.svg) 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: ![Hosting BFF UI on CDN](/_astro/bff_ui_hosting_cdn.BhY4WhaY_5PT3z.svg) 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: ![BFF Proxies the Index html from CDN](/_astro/bff_ui_hosting_proxy_index.C6Kqvm-L_Z1xNdvN.svg) 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. Note BFF V4 has built-in support for proxying the index.html from a CDN. ----- # 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, similar to [how Duende IdentityServer uses log levels](/identityserver/diagnostics/logging/). Logs are typically written under the `Duende.Bff` category, with more concrete categories for specific components. Multiple frontends When using [multiple frontends and the `FrontendSelectionMiddleware`](/bff/architecture/multi-frontend/), log messages are written in a log scope that contains a `frontend` property with the name of the frontend for which the log message was emitted. ## 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 ```cs 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 are typically used to show graphs on a dashboard, to inspect overall application health, or to set up monitoring rules. The BFF host emits metrics from several sources, and collects these through the `Duende.Bff` meter: * `session.started` - a counter that communicates the number of sessions started * `session.ended` - a counter that communicates the number of sessions ended ----- # BFF Extensibility > Overview of the extensibility points available in Duende.BFF for customizing session management, HTTP forwarding, and data storage Duende.BFF can be extended in the following areas * custom logic at the session management endpoints * custom logic and configuration for HTTP forwarding * custom data storage for server-side sessions and access/refresh tokens ----- # 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, e.g.: ```cs public class MyInvokerFactory : IForwarderHttpClientFactory { public HttpMessageInvoker CreateClient(ForwarderHttpClientContext context) { return Clients.GetOrAdd(localPath, (key) => { 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: ```cs 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. ```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(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: ```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 an 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() { // 100 ms timeout, which is not too short that the normal process might fail, // but not too long that the test will take forever 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); } ``` You can customize the behavior of the endpoints either by implementing the appropriate interface. The [default implementations](https://github.com/DuendeSoftware/products/tree/releases/bff/4.0.0/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`. ## Request Processing [Section titled “Request Processing”](#request-processing) You can add custom logic to the endpoint by implementing the `IBackChannelLogoutEndpoint` . `ProcessRequestAsync` is the top-level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. ```csharp public class CustomizedBackChannelLogoutService : IBackChannelLogoutEndpoint { public override Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here } } ``` ## 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`. ## Request Processing [Section titled “Request Processing”](#request-processing) `ProcessRequestAsync` is the top-level function called in the endpoint service 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 Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here } ``` ----- # 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. ## Request Processing [Section titled “Request Processing”](#request-processing) `ProcessRequestAsync` is the top-level function called in the endpoint service 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 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. ## Request Processing [Section titled “Request Processing”](#request-processing) `ProcessRequestAsync` is the top-level function called in the endpoint service 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 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`. ## Request Processing [Section titled “Request Processing”](#request-processing) `ProcessRequestAsync` is the top-level function called in the endpoint service 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 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`. ## Request Processing [Section titled “Request Processing”](#request-processing) `ProcessRequestAsync` is the top-level function called in the endpoint service 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 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`. ## Request Processing [Section titled “Request Processing”](#request-processing) `ProcessRequestAsync` is the top-level function called in the endpoint service 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 Task ProcessRequestAsync(HttpContext context, CancellationToken ct) { // Custom logic here } ``` ### 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. The most robust way would be to implement a custom `IClaimsTransformation`. ```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. ----- # 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 will need to have 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, then you can implement the *IUserSessionStore* interface: ```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 => { // ... }); ``` Note You can also supply client assertions to the token management library. See this [sample](/bff/samples) for JWT-based client authentication. ### 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 GetAccessToken(AccessTokenRetrievalContext context, CancellationToken token); } ``` 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 GetAccessToken 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 *GetAccessToken* 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 [Section titled “Embedded (Local) APIs”](#embedded-local-apis) These APIs 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/) ----- # 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. MyApiController.cs ```csharp [Route("myApi")] public class MyApiController : ControllerBase { private readonly IHttpClientFactory _httpClientFactory; public MyApiController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task Get(string id) { // create HTTP client var client = _httpClientFactory.CreateClient(); // get current user access token and set it on HttpClient var token = await HttpContext.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 new JsonResult(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 { /* ... */ } ``` Note It’s also possible to disable anti-forgery protection using *BffOptions.DisableAntiForgeryCheck()* ----- # Proxying Remote APIs > Learn how to configure and secure remote API access through BFF using HTTP forwarding and token management. Note You will need to have the [`Duende.Bff.Yarp`](https://www.nuget.org/packages/Duende.BFF.Yarp) NuGet package installed to use these features. 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. Note The BFF multi-frontend feature has built-in support for direct forwarding. #### 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); ``` Note This example opens up the complete */users* API namespace to the frontend, and thus, to the outside world. While it is convenient to register API paths this way, consider if you need to be more specific when designing the forwarding paths to prevent accidentally exposing unintended endpoints. 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. Note In Duende.BFF version 3, use the `MapRemoteBffApiEndpoint` method with the `RequireAuthorization` extension method to attach authorization policies. #### 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 three 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. Note These settings only specify the logic that is applied before the API call gets proxied. The remote APIs you are calling should always specify their own authorization and token requirements. ----- # 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 *User*, *Client*, *UserOrClient*. 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(TokenType.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 also attach user access tokens optionally by adding metadata named “Duende.Bff.Yarp.OptionalUserToken” to a YARP route. ```json { "ReverseProxy": { "Routes": { "todos": { "ClusterId": "cluster1", "Match": { "Path": "/todos/{**catch-all}" }, "Metadata": { "Duende.Bff.Yarp.OptionalUserToken": "true" } } } } } ``` This metadata 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. It is an error to set both *Duende.Bff.Yarp.TokenType* and *Duende.Bff.Yarp.OptionalUserToken*, since they have conflicting semantics (*TokenType* requires the token, *OptionalUserToken* makes it optional). If you are using the code config method, call the *WithOptionalUserAccessToken* extension method to achieve the same thing: ```csharp yarpBuilder.LoadFromMemory( new[] { new RouteConfig() { RouteId = "todos", ClusterId = "cluster1", Match = new RouteMatch { Path = "/todos/{**catch-all}" } }.WithOptionalUserAccessToken() }, // 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: ```cs 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: ```cs yarpBuilder.LoadFromMemory( new[] { new RouteConfig() { RouteId = "todos", ClusterId = "cluster1", Match = new RouteMatch { Path = "/todos/{**catch-all}" } }.WithAntiforgeryCheck() }, // rest omitted ); ``` Note You can combine the token management feature with the anti-forgery check. To enforce the presence of the anti-forgery headers, you need to add a middleware to the YARP pipeline: Program.cs ```cs app.MapReverseProxy(proxyApp => { proxyApp.UseAntiforgeryCheck(); }); ``` ----- # BFF Security Framework Blazor Support > Learn how to integrate and use the BFF Security Framework with Microsoft Blazor applications for secure authentication and authorization. Microsoft’s Blazor framework aids developers in creating rich, interactive web applications using C# and .NET. Taking inspiration from the popular JavaScript library React, Blazor helps deliver experiences through a component-based model, with multiple rendering modes, as you will see below. While Blazor is a suitable framework for building rich, interactive web applications, it also has some challenges when it comes to secure authentication and authorization. With the Duende BFF Security Framework, we aim to address these challenges or at the very least give guidance on how to deal with them given your Blazor’s solution choices. You will notice that the BFF security pattern is not applicable to all Blazor implementations but rather to specific rendering modes. The goal of the BFF is to keep tokens out of the client and only use them in the secure context of the server. ## Architecture [Section titled “Architecture”](#architecture) Blazor has many architectural options, and it is essential to understand how they work to implement security in your Blazor applications. Like most web applications, the model has three elements, the backend, the frontend, and the client. The chosen model determines the execution context’s location. The BFF’s role is to manage the security context between all elements within the chosen execution context when appropriate. From a high level, let’s define what the hosting elements are: * **Backend**: The server-side application with logic for handling operations. i.e. APIs. * **Frontend**: The client-side Blazor application. * **Client**: The browser that is used to interact with the frontend. For Blazor applications, we recommend the BFF be the host for the frontend and the backend of a solution. As you will see in later sections, this allows for a more straightforward integration and provides a unified approach to managing authentication and authorization. Here’s a diagram of what a typical Blazor solution might look like when implemented with the BFF pattern: ![blazor-architecture](/_astro/bff_blazor.Dszjy4VW_ZMi69y.svg) Note that both the frontend and backend are within a single project within the BFF host, similar to the simplified diagram we previously showed. While it’s possible to separate the frontend and backend into separate projects, this comes with additional complexity and is not recommended. Let’s get into Blazor rending modes and whether the modes are suitable with the BFF pattern. ## Blazor Rendering Modes [Section titled “Blazor Rendering Modes”](#blazor-rendering-modes) Blazor supports [several rendering](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-9.0#render-modes) modes: * **Static Server** - Static server-side rendering (static SSR) * **Interactive Server** - Interactive server-side rendering (interactive SSR) using Blazor Server and WebSockets. * **Interactive WebAssembly** - Client-side rendering (CSR) using Blazor WebAssembly. * **Interactive Auto** - Interactive SSR using Blazor Server initially and then CSR on subsequent visits after the Blazor bundle is downloaded. For developers considering BFF security with these Blazor modes, here is a table with our recommendation of whether to use the BFF pattern or not: | Name | Description | Render Location | Interactive | BFF? | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------- | ----------- | ---- | | Static Server | Static server-side rendering (static SSR) | Server | ❌ | ❌ | | Interactive Server | Interactive server-side rendering (interactive SSR) using Blazor Server. | Server | ✅ | ❌ | | Interactive WebAssembly | Client-side rendering (CSR) using Blazor WebAssembly | Client | ✅ | ✅ | | Interactive Auto | Interactive SSR using Blazor Server initially and then CSR on subsequent visits after the Blazor bundle is downloaded. | Server, then client | ✅ | ✅ | See the following sections for a more detailed explanation of each mode and how it works with the BFF, if at all. ### Static Server [Section titled “Static Server”](#static-server) Caution We advise not using the BFF pattern with this rendering mode as interactivity is limited, though you may want to consider BFF if you have other interactive JavaScript elements. The Static server mode allows developers to render pages built with Blazor components, but that doesn’t require any interactivity beyond basic HTML elements. These applications are typically used for static content, such as marketing pages, landing pages, and so on. If your application is static, then you don’t need to use the BFF pattern, as you can utilize the same security patterns that you would use in a typical ASP.NET Core application. You may still need to use the `AuthenticationStateProvider` to manage authentication state, see the section below for more information. While you could certainly use the BFF pattern with a static server implementation for future extensibility plans, it would not add value to an application that is static with no client-side interactivity. ### Interactive Server [Section titled “Interactive Server”](#interactive-server) Caution We advise not using the BFF pattern with this rendering mode is managed on the server. Though you may want to consider BFF if you have other interactive JavaScript elements, but it is typically unlikely. The Interactive Server mode allows developers to render pages built with Blazor components, and that also allows for interactivity. This mode is ideal for applications that require a rich user experience, such as a web application that allows users to create, edit, and delete data. The interactivity for this mode is handled by the Blazor Server framework powered by WebSockets and more specifically [SignalR](https://dotnet.microsoft.com/en-us/apps/aspnet/signalr). The BFF pattern is not typically applicable to this mode, as most interactivity is handled on the server by the Blazor Server framework with state changes being pushed to the client via WebSockets. You may still want to explore `AuthenticationStateProvider` for managing authentication state, see the section below for more information. You may also want to explore the [Session Management](/bff/fundamentals/session/) section for more information on how to configure the BFF to use sessions. ### Interactive WebAssembly [Section titled “Interactive WebAssembly”](#interactive-webassembly) Note **We recommend using the BFF pattern with this rendering mode, as your frontend will be operating with the context of the client, and not the server.** The Interactive WebAssembly mode allows developers to render pages built with Blazor components, and that also allows for interactivity. This mode is ideal for applications that require a rich user experience, such as a web application that allows users to create, edit, and delete data. **The interactivity for this mode is handled by the Blazor WebAssembly framework and operates within the context of the client.** In a typical Blazor WebAssembly application, you will have three projects: `Client`, `Server`, and `Shared`. The `Client` project is the Blazor application that is rendered by the browser. The `Server` project is the ASP.NET Core web application that hosts the Blazor application. The `Shared` project is a project that contains C# classes that are shared between the `Client` and `Server` projects. Let’s take a look at how to install and configure the BFF pattern given the above project structure. In the `Server` project, you will need to add the following NuGet packages, assuming you will want to use the OpenID Connect handler: ```bash dotnet add package Duende.Bff dotnet add package Duende.Bff.Blazor dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect ``` You will also need to modify the `Program.cs` file in the `Server` project to configure the BFF in the services collection: Server/Program.cs ```csharp builder.Services.AddBff() // Add in-memory implementation .AddServerSideSessions() .AddBlazorServer(); ``` The `AddBlazorServer` method will configure the BFF to use services on the host that allow the client to interact with the server securely. You will also need to modify the ASP.NET Core pipeline to use the BFF: Server/Program.cs ```csharp var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseWebAssemblyDebugging(); } else { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); // 👋 Add BFF Middleware app.UseBff(); app.UseAuthorization(); app.UseAntiforgery(); app.MapRazorPages(); app.MapControllers() .RequireAuthorization() .AsBffApiEndpoint(); app.MapFallbackToFile("index.html"); app.Run(); ``` Now, on the client, you will need to add the following NuGet packages: ```bash dotnet add package Duende.BFF.Blazor.Client ``` You will also need to modify the `Program.cs` file in the `Client` project to configure the BFF in the services collection: Client/Program.cs ```csharp using BlazorWasm.Client; using Duende.Bff.Blazor.Client; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); builder.Services // 👋 Provides auth state provider that polls the /bff/user endpoint .AddBffBlazorClient() .AddCascadingAuthenticationState(); await builder.Build().RunAsync(); ``` See our [Session Management section](/bff/fundamentals/session/) for more information on how to configure the BFF to use sessions. ### Interactive Auto [Section titled “Interactive Auto”](#interactive-auto) Note **We recommend using the BFF pattern with this rendering mode, as your frontend may be executing code within the client context, or the server.** Blazor Interactive Auto is a combination of Interactive Server and Interactive WebAssembly, where rendering is initially done on the server, but then the client is updated with the latest WebAssembly version of the application on subsequent visits. As you may have guessed, this creates a security state that is unpredictable and can add complexity to your application. Since your Blazor application may be running within the context of the client, you will need to use a BFF and the Duende library to manage authentication state between these two modalities. ## Authentication State [Section titled “Authentication State”](#authentication-state) The `AuthenticationState` contains information about the currently logged-in user. This is partly populated from information from the user, but is also enriched with several management claims, such as the Logout URL. Blazor uses AuthenticationStateProviders to make authentication state available to components. On the server, the authentication state is already mostly managed by the authentication framework. However, the BFF will add the Logout url to the claims using the **AddServerManagementClaimsTransform**. On the client, there are some other claims that might be useful. The **BffClientAuthenticationStateProvider** will poll the server to update the client on the latest authentication state, such as the user’s claims. This also notifies the front-end if the session is terminated on the server. ## Server Side Token Store [Section titled “Server Side Token Store”](#server-side-token-store) 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 can’t 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 The **ServerSideTokenStore**, together with the Blazor Server functionality in Duende.AccessTokenManagement is automatically registered when you register Blazor Server. For more information on this, see [Blazor Server](/accesstokenmanagement/blazor-server/) ## Data Access Techniques [Section titled “Data Access Techniques”](#data-access-techniques) Depending on the type of Blazor application you are building, you may need to use different techniques to access data from within your components and pages. The following sections will cover some of the common scenarios. If your BFF application can directly access data (for example, a database or an unsecured HTTP API), then you have to decide where this information is rendered. For server side rendering, you’ll typically abstract your data access logic into a separate class (such as a repository or a query object) and inject this into your component for rendering. For web assembly rendering, you’ll need to make the data available via a web service on the server. Then on the client, you’ll need a configured HTTP client that accesses this information securely. When using auto-rendering mode, you’ll need to make sure that the component gets a different ‘data access’ component for server rendering vs client rendering. Consider the following diagram: ![Embedded APIs](/_astro/bff_blazor_local_api.Dm1benno_Z1c9fwk.svg) In this diagram, you’ll see the example `IDataAccessor` that has two implementations. One that accesses the data via an HTTP client (for use in WASM) and one that directly accesses the data. ### Embedded APIs [Section titled “Embedded APIs”](#embedded-apis) Embedded APIs are a way to access data from within a Blazor application without the need to authenticate outside the current security boundary of the client or the backend. Below is an example of registering an `IDataAccessor` abstraction. First let’s create the `IDataAccessor` interface: Shared/IDataAccessor.cs ```csharp public interface IDataAccessor { Task GetData(); } public record Data(string Value); ``` We can implement a Server implementation of the `IDataAccessor` interface. Server/ServerWeatherClient.cs ```csharp // Create a class that would actually get the data from the database internal class ServerWeatherClient() : IDataAccessor { public Task GetData() { // get the actual data from the database } } ``` and register it in the `Program.cs` file: Server/Program.cs ```csharp // Register the server implementation for accessing some data builder.Services.AddSingleton(); ``` Then we can use the `IDataAccessor` in our endpoints: Server/Program.cs ```plaintext // Register an api that will access the data app.MapGet("/some_data", async (IDataAccessor dataAccessor) => await dataAccessor.GetData()) .RequireAuthorization() .AsBffApiEndpoint(); ``` We can also register a `HttpClientDataAccessor` that will be used by the Blazor client to access the data. Client/Program.cs ```csharp // Setup on the client // Register an HTTP client that can access the data via an Embedded API. builder.Services.AddLocalApiHttpClient(); // Register an adapter that would abstract between the data accessor and the http client. builder.Services.AddSingleton(sp => sp.GetRequiredService()); internal class HttpClientDataAccessor(HttpClient client) : IDataAccessor { public async Task GetSomeData() => await client.GetFromJsonAsync("/some_data") ?? throw new JsonException("Failed to deserialize"); } ``` Note that data access is contained within the security boundary of the host, so we never need to pass a token to any client to access data. This is what we mean by ‘embedded’ APIs. ### Secured Remote APIs [Section titled “Secured Remote APIs”](#secured-remote-apis) If your BFF needs to secure access to remote APIs, then your components can both directly use a (typed) `HttpClient`. How this `HttpClient` is configured is quite different on the client vs the server though. * On the **Client**, the HTTP client needs to be secured with the authentication cookie and CORS protection headers. This then calls the http endpoint on the server. * On the **Server**, you’d need to expose the proxied http endpoint. This then uses a http client that’s configured to send access tokens. These may or may not contain a user token. This diagram shows this in more detail: ![remote APIs](/_astro/bff_blazor_remote_api.SQ4NGe1a_Z1a13oi.svg) Server/Program.cs ```csharp app.MapRemoteBffApiEndpoint("/remote-apis/user-token", new Uri("https://localhost:5010")) builder.Services.AddUserAccessTokenHttpClient("backend", configureClient: client => client.BaseAddress = new Uri("https://localhost:5010/")); ``` Then in the client application, we can use the `HttpClient` to access the remote API. ```csharp // Copyright (c) Duende Software. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using BlazorWasm.Client; using Duende.Bff.Blazor.Client; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); builder.Services .AddBffBlazorClient() // Provides auth state provider that polls the /bff/user endpoint .AddCascadingAuthenticationState(); builder.Services.AddRemoteApiHttpClient("backend"); builder.Services.AddTransient(sp => sp.GetRequiredService().CreateClient("backend")); await builder.Build().RunAsync(); ``` ## Other Resources [Section titled “Other Resources”](#other-resources) Here are some other resources that may be useful as you implement security in your Blazor applications: * [Access Token Management](/accesstokenmanagement/) * [Blazor Server](/accesstokenmanagement/blazor-server/) * [IdentityServer Quickstarts](/identityserver/quickstarts/0-overview/) * [Big Picture](/identityserver/overview/big-picture/) ----- # 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 origin 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 origin 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 origin and path:** If there is a frontend that matches both the origin AND has the most specific match to a path, it’s selected. 2. **Selection by origin only:** Then, if there is a frontend with only origins 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. Note When using path based routing, then the frontend’s path is added to the [`PathBase`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httprequest.pathbase) and removed from the [`Path`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httprequest.path). This means that any routing that happens in the application is relative to the path of the frontend. This also includes the OpenID callback paths. ### 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() .WithFrontends(new BffFrontend(BffFrontendName.Parse("frontend1"))); ``` You can call `WithFrontends` 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")) .WithCdnIndexHtml(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. ### 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); ``` Note This function is evaluated immediately when calling the method `.WithBffStaticAssets()`. If you call this method during startup, the condition is only evaluated at startup time. It’s not evaluated at runtime for every request. ----- # 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 and BFF Configuration. ```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 for properties). * `defaultCookieSettings` Cookie settings applied globally to all frontends unless overridden.\ Type: CookieConfiguration object (properties depend on your implementation). * `frontends` Dictionary of frontend configurations.\ Each key is a frontend name, and the value is a BffFrontendConfiguration object (see below). *** ### 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 origin to match for this frontend.\ Example: `"https://localhost:5005"` * `oidc` OIDC settings specific to this frontend.\ Type: OidcConfiguration object (see below). * `cookies` Cookie settings specific to this frontend.\ Type: CookieConfiguration object (see below) * `remoteApis` Remote APIs for this frontend. (see below) ### 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: `"User"`, `"Client"`, `"None"`, `"OptionalUserOrClient"`, `"OptionalUserOrNone"`\ 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). * `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 strictg.\ 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"] } } } } ``` ----- # 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... }) ``` ## 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 business edition or higher license key is required for production deployments. The same license key is used in IdentityServer and the BFF. Just as in the [IdentityServer host](/general/licensing/), you can either set the license key using this option in code or include *Duende\_License.key* in the same directory as your BFF host. * ***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 applying BFF V4 multiple frontends, a lot of middlewares get automatically added to the pipeline. For example, the frontend selection middleware, the authentication handlers, etc. If you don’t want this automatic behavior, then you can turn it off and register these middlewares manually. * ***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. # 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. Note Be careful processing `X-Forwarded-*` headers from untrusted sources. Accepting these headers without validating the proxy IP address or network origin may leave you vulnerable to IP Spoofing attacks. See [Microsoft Security Advisory CVE-2018-0787](https://github.com/aspnet/Announcements/issues/295) for information on an elevation-of-privileges vulnerability that affects systems where the proxy doesn’t validate or restrict `Host` headers to known good values. ----- # Authentication & Session Management > Learn how to set up authentication and session management components in ASP.NET Core BFF applications, including OpenID Connect, cookie handling, and back-channel logout support. This section deals with setting up the following components * the ASP.NET Core authentication system * the OpenID Connect handler * the cookie handler * the BFF session management endpoints * server-side sessions * back-channel logout support ----- # 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`. Note Chrome will make an exception for cookies set without a `SameSite` attribute less than 2 minutes ago. Such cookies will also be sent with non-idempotent (e.g. POST) top-level cross-site requests despite normal `SameSite=Lax` cookies requiring top-level cross-site requests to have a safe (e.g. GET) HTTP method. Support for this intervention (“Lax + POST”) will be removed in the future. (source: [chromestatus](https://chromestatus.com/feature/5088147346030592)) ----- # 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. Note In V3 and below, only the method `MapBffManagementEndpoints` exists. ----- # 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/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. ----- # BFF Diagnostics Endpoint > Learn about the BFF diagnostics endpoint that provides access to user and client access tokens for development testing purposes. Note This endpoint is only enabled in *Development* mode. 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. ----- # 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"; ``` ----- # 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. ----- # BFF Silent Login Endpoint > Endpoint for non-interactive authentication using an existing session at the remote identity provider Note Deprecated. See [OIDC Prompt support](/bff/fundamentals/session/oidc-prompts/) instead. **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(); } }); ``` ----- # 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/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("/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) Note The cookie sliding prevention feature requires either usage of server-side sessions or .NET 6 or higher (or both). 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("/user?slide=false", { headers: new Headers({ "X-CSRF": "1", }), }); ``` ----- # 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. Note Be aware that the exact behavior of the `prompt` parameter is not defined by the OpenID Connect specification and may vary between identity providers. Consult the documentation for your identity provider for more information. ## 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 found at the user endpoint of `/silent-login`. You should discontinue use of the silent login feature and instead use the `prompt=none` parameter to achieve the same result. ----- # 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 and register it 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(); ``` Note In V4, we changed how you enable session cleanup. We no longer automatically register the session cleanup hosted service. This has to be done manually. In a load-balanced environment, you can choose to run the cleanup job on all instances the BFF. However, you can also decide to spin up a separate host that’s responsible for background jobs such as this cleanup job. * 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 leverage the *HttpClientFactory* to fabricate 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 [Route("myApi")] public class MyApiController : ControllerBase { private readonly IHttpClientFactory _httpClientFactory; public MyController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task Get(string id) { // 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 a controller’s action method: ```csharp public async Task CallApiAsUserTyped( [FromServices] 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. ----- # Getting started > A collection of getting started guides to start with the BFF Currently, the most recent version is 4 (preview 2). 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 sourcecode for this quickstart is available on [GitHub](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/Quickstarts/BlazorBffApp) ## Creating the project structure [Section titled “Creating the project structure”](#creating-the-project-structure) The first step is to create a Blazor app. You can do so using the command line: ```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. ## Configuring the BffApp server project [Section titled “Configuring the BffApp server project”](#configuring-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 --version 3.0.0 ``` Then you need to configure the application to use the BFF Blazor application. 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 loggin 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 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 loggin 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. Replace the app.UseAntiforgery() with the code below: ```csharp app.UseRouting(); app.UseAuthentication(); // Add the BFF middleware which performs anti forgery protection app.UseBff(); app.UseAuthorization(); app.UseAntiforgery(); // Add the BFF management endpoints, such as login, logout, etc. app.MapBffManagementEndpoints(); ``` ## Configuring the BffApp.Client project [Section titled “Configuring the BffApp.Client project”](#configuring-the-bffappclient-project) To add the BFF to the client project, add the following: ```shell cd .. cd BlazorBffApp.Client dotnet add package Duende.BFF.Blazor.Client --version 3.0.0 ``` 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’s Component folder: ### LoginDisplay.razor [Section titled “LoginDisplay.razor”](#logindisplayrazor) The following code shows a login / logout button depending on your state. Note, you’ll need to use the logout link from the LogoutUrl claim, because this contains both the correct route and the session id. Add it to the BffBlazorApp.Client/Components folder ```csharp @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}"; } } ``` ### RedirectToLogin.razor [Section titled “RedirectToLogin.razor”](#redirecttologinrazor) The following code will redirect users to Identity Server for authentication. Once authentication is complete, the users will be redirected back to where they came from. Add it to the BffBlazorApp.Client/Components folder ```csharp @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); } } ``` ### Modifications to Routes.razor [Section titled “Modifications to Routes.razor”](#modifications-to-routesrazor) Replace the contents of routes.razor so it matches below: ```csharp @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 makes sure that, if you’re accessing a page that requires authorization, that you are automatically redirected to Identity Server for authentication. ### Modifications to MainLayout.razor [Section titled “Modifications to MainLayout.razor”](#modifications-to-mainlayoutrazor) Modify your MainLayout so it matches below: ```csharp @inherits LayoutComponentBase @using BlazorBffApp.Client.Components
@Body
An unhandled error has occurred. Reload 🗙
``` This adds the LoginDisplay to the header. Now your application supports logging in / 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 a HttpClient. > By default, the system will perform both pre-rendering on the server AND WASM based rendering on the client. For this reason, you’ll need to register both a server and client version of a component that retrieves data. See the [Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-9.0#client-side-services-fail-to-resolve-during-prerendering) for more information on this. ### Configuring the Client app [Section titled “Configuring the Client app”](#configuring-the-client-app) Add a class called WeatherClient to the BffBlazorApp.Client project: ```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 will form an abstraction between 'server' logic and client logic. public interface IWeatherClient { Task GetWeatherForecasts(); } ``` Then register this as a component in program.cs. ```csharp builder.Services .AddBffBlazorClient()// Provides auth state provider that polls the /bff/user endpoint // Register a HTTP Client that's configured to fetch data from the server. .AddLocalApiHttpClient() ; // Register the concrete implementation with the abstraction builder.Services.AddSingleton(); ``` ### Configuring the server [Section titled “Configuring the server”](#configuring-the-server) Add a class called ServerWeatherClient to your BlazorBffApp server project: ```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: ```csharp app.MapGet("/WeatherForecast", (IWeatherClient weatherClient) => weatherClient.GetWeatherForecasts()); ``` Also register the client abstraction: ```csharp builder.Services.AddSingleton(); ``` ### Displaying Weather Information From The API [Section titled “Displaying Weather Information From The API”](#displaying-weather-information-from-the-api) By default, the blazor template ships with a weather page. Change the content of the **Weather.razor** like this: ```csharp @page "/weather" @using BlazorBffApp.Client.Components @using Microsoft.AspNetCore.Authorization @rendermode InteractiveWebAssembly @attribute [Authorize] Weather ``` Now add a component called WeatherComponent ```csharp @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(); } } ``` ----- # 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. Note Multi-frontend support is available in Duende.BFF v4 and later. The v3-style of wiring up BFF is not supported for this scenario. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * .NET 8.0 or later * Multiple frontend applications (e.g., React, Angular, Vue, or plain JavaScript) ## 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 [Section titled “1. Create A New ASP.NET Core Project”](#1-create-a-new-aspnet-core-project) Terminal ```bash dotnet new web -n MyMultiBffApp cd MyMultiBffApp ``` ### 2. Add The Duende.BFF NuGet Package [Section titled “2. Add The Duende.BFF NuGet Package”](#2-add-the-duendebff-nuget-package) Terminal ```bash dotnet add package Duende.BFF ``` ### 3. OpenID Connect Configuration [Section titled “3. OpenID Connect Configuration”](#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` [Section titled “4. Configure BFF In Program.cs”](#4-configure-bff-in-programcs) * 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": [ { "matchingPath": "/api/client-token", "targetUri": "https://localhost:5010", "tokenRequirement": "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 [Section titled “5. Remote API Proxying”](#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/#5-adding-remote-apis). * **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")) .WithCdnIndexUrl(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 [Section titled “6. Server Side Sessions”](#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/#6-adding-server-side-sessions) for details. ----- # 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. Note Duende.BFF V4 introduced a new way of configuring the BFF, which automatically configures the BFF using recommended practices. If you’re upgrading from V3, please refer to the [upgrade guide](/bff/upgrading/bff-v3-to-v4/). When in single frontend mode, an implicit default frontend is automatically registered. This ensures all the management routes and OpenID Connect-handling routes are available for your frontend. When you call `.AddFrontend()` to add a new frontend, the system switches to multi-frontend mode. If you wish to have a default frontend in multi-frontend mode, you’ll need to explicitly add it. See [multi-frontend support](/bff/fundamentals/multi-frontend/) for more information on this topic. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * .NET 8.0 or later * A frontend application (e.g., React, Angular, Vue, or plain JavaScript) ## 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 [Section titled “1. Create A New ASP.NET Core Project”](#1-create-a-new-aspnet-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 [Section titled “2. Add The Duende.BFF NuGet Package”](#2-add-the-duendebff-nuget-package) Install the Duende.BFF package: ```sh dotnet add package Duende.BFF ``` ### 3. Configure BFF In `Program.cs` [Section titled “3. Configure BFF In Program.cs”](#3-configure-bff-in-programcs) 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 [Section titled “4. Adding Local APIs”](#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 [Section titled “5. Adding Remote APIs”](#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 [Section titled “6. Adding Server-Side Sessions”](#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. ----- # 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 shows how to use the BFF framework with a JavaScript-based frontend (e.g. SPA). [JavaScript Frontend Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/JsBffSample)GitHub Repository for the BFF framework with JavaScript-based frontend ## ReactJs Frontend [Section titled “ReactJs Frontend”](#reactjs-frontend) This sample shows how to use the BFF framework with the .NET 6 React template. [ReactJS Frontend Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/React)GitHub Repository for the BFF framework with React ## Angular Frontend [Section titled “Angular Frontend”](#angular-frontend) This sample shows how to use the BFF framework with the .NET 8 Angular template. [Angular Frontend Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/Angular)GitHub Repository for the BFF framework with Angular ## Vue Frontend Community [Section titled “Vue Frontend ”Community](#vue-frontend) This sample shows how to use the BFF framework with the .NET 8 Vue template. (contributed by [@Marco Cabrera](https://github.com/mck231)) [Vue Frontend Sample (Community) ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/Vue)GitHub Repository for the BFF framework with Vue ## Blazor WASM [Section titled “Blazor WASM”](#blazor-wasm) This sample shows how to use the BFF framework with Blazor WASM. [Blazor WASM Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/BlazorWasm)GitHub Repository for the BFF framework with Blazor WASM ## Blazor Auto Rendering [Section titled “Blazor Auto Rendering”](#blazor-auto-rendering) This sample shows how to use Authentication in combination with Blazor’s AutoRendering feature. [Blazor Auto Rendering Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/BlazorAutoRendering)GitHub Repository for the BFF framework using Blazor Auto Rendering ## YARP Integration [Section titled “YARP Integration”](#yarp-integration) This sample shows how to use the BFF extensions for Microsoft YARP. [YARP Integration Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/JsBffYarpSample)GitHub Repository for the BFF framework integration with YARP ## OpenAPI and SwaggerUI [Section titled “OpenAPI and SwaggerUI”](#openapi-and-swaggerui) This sample shows how to use OpenAPI and SwaggerUI in combination with BFF. A walkthrough is available [on the Duende blog](https://duendesoftware.com/blog/20250430-managing-openapi-specifications-with-backend-for-frontend-and-swagger-ui). [OpenAPI and SwaggerUI Sample ](https://github.com/DuendeSoftware/samples/tree/main/BFF/v3/OpenApi)GitHub Repository for the BFF framework integration with OpenAPI and SwaggerUI ## Separate Host for UI [Section titled “Separate Host for UI”](#separate-host-for-ui) This sample shows how to have separate projects from the frontend and backend, using CORS to allow cross-site requests from the frontend to the backend. [Separate Host for UI Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/SplitHosts)GitHub Repository for separate projects using CORS ## Docker Hosting Community [Section titled “Docker Hosting ”Community](#docker-hosting) This sample shows how to host the BFF framework and IdentityServer with Docker. (contributed by [@Marco Cabrera](https://github.com/mck231)) [Docker Sample (Community) ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/docker)GitHub Repository for hosting BFF and IdentityServer in with Docker containers. ## DPoP [Section titled “DPoP”](#dpop) This sample shows how to configure the BFF to use [DPoP](/identityserver/tokens/pop/) to obtain sender-constrained tokens. [DPoP Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/DPoP)GitHub Repository for configuring DPoP with BFF ## Token Exchange using the IAccessTokenRetriever [Section titled “Token Exchange using the IAccessTokenRetriever”](#token-exchange-using-the-iaccesstokenretriever) This sample shows how to extend the BFF with an *IAccessTokenRetriever*. This example of an IAccessTokenRetriever performs token exchange for impersonation. If you are logged in as alice you will get a token for bob, and vice versa. [Token Exchange Sample ](https://github.com/DuendeSoftware/Samples/tree/main/BFF/v3/TokenExchange)GitHub Repository for token exchange using the IAccessTokenRetriever ## 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 shows how to extend the BFF in a scenario where a new user needs to onboard to the application and the additional information does not need to be provided/stored with the identity provider. Instead, the onboarding flow stores additional data in the application database. [New User Onboarding Sample (Community) ](https://github.com/hugh-maaskant/BlazorBffOnboarding)GitHub Repository for New User Onboarding with Blazor Auto Rendering ## 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 ----- # 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. 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) #### 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); ``` Note This function is evaluated immediately when calling the`.WithBffStaticAssets()` extension method. When you call this method during startup, the condition is only evaluated at startup time. It’s not evaluated at runtime for every request. ### 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"); ``` Note This is a breaking database schema change. If you have multiple BFF V3 applications that share the same database table, you either need to update all BFF applications to V4 at the same time or use a new database for the upgraded BFF V4 application. ----- # 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 ## Automatic Key Management [Section titled “Automatic Key Management”](#automatic-key-management) **License: Business** 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** 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) 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** 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** 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** 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** 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/20201230-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** 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** 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 ## 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 Deployment [Section titled “Multiple Deployment”](#multiple-deployment) 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). ## 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 Developer Support [Section titled “Priority Developer Support”](#priority-developer-support) 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. Free for development IdentityServer is [free](#trial-mode) for development, testing and personal projects, but production use requires a [license](https://duendesoftware.com/products/identityserver). ### Editions [Section titled “Editions”](#editions) There are three license editions which include different [features](https://duendesoftware.com/products/features). #### Starter Edition [Section titled “Starter Edition”](#starter-edition) The Starter 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-v7/) and licensed. The Starter 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. #### Business Edition [Section titled “Business Edition”](#business-edition) The Business 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 support for server side sessions and automatic signing key management. #### Enterprise Edition [Section titled “Enterprise Edition”](#enterprise-edition) Finally, the Enterprise edition includes everything in the Business edition and adds support for features that are typically used by enterprises with particularly complex architectures or that handle particularly sensitive data. Highlights include resource isolation, the OpenId Connect CIBA flow, and dynamic federation. This is the best option when you have a specific threat model or architectural need for these features. ### 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) The license is validated at startup and during runtime. All license validation is self-contained and does not leave the host. There are no outbound network calls related to license validation. #### Startup Validation [Section titled “Startup Validation”](#startup-validation) At startup, IdentityServer first checks for a license. If there is no license configured, IdentityServer logs a warning indicating that a license is required in a production deployment and enters [Trial Mode](#trial-mode). Next, assuming a license is configured, IdentityServer compares its configuration to the license. If there are discrepancies between the license and the configuration, IdentityServer will write log messages indicating the nature of the problem. #### Runtime Validation [Section titled “Runtime Validation”](#runtime-validation) Most common licensing issues, such as expiration of the license or configuring more clients than are included in the license do not prevent IdentityServer from functioning. We trust our customers, and we don’t want a simple oversight to cause an outage. However, some features will be disabled at runtime if your license does not include them, including: * [Server Side Sessions](/identityserver/ui/server-side-sessions/) * [Demonstrating Proof-of-Possession (DPoP)](/identityserver/tokens/pop/) * [Resource Isolation](/identityserver/fundamentals/resources/isolation/) * [Pushed Authorization Requests (PAR)](/identityserver/tokens/par/) * [Dynamic Identity Providers](/identityserver/ui/login/dynamicproviders/) * [Client Initiated Backchannel Authentication (CIBA)](/identityserver/ui/ciba/) Again, the absence of a license is permitted for development and testing, and therefore does not disable any of these features. Similarly, using an expired license that includes those features does not cause those features to be disabled. Tip When rolling over to a renewed license, you can configure the new license before the old license expires. While the expiration timestamp of a license is used to validate a license is active, the start date is an administrative data point IdentityServer does not take into account for license validation. In other words, you can safely configure the new license before the old one lapses. #### Trial Mode [Section titled “Trial Mode”](#trial-mode) Using IdentityServer without a license is considered Trial Mode. In Trial Mode, all enterprise features are enabled. Trial Mode is limited to 500 protocol requests. This includes all HTTP requests that IdentityServer itself handles, such as requests for the discovery, authorize, and token endpoints. UI requests, such as the login page, are not included in this limit. Beginning in IdentityServer 7.1, IdentityServer will log a warning when the trial mode threshold is exceeded: ```text You are using IdentityServer in trial mode and have exceeded the trial threshold of 500 requests handled by IdentityServer. In a future version, you will need to restart the server or configure a license key to continue testing. ``` In a future version, IdentityServer will shut down at that time instead. Note When operating non-production environments, such as development, test, or QA, without a valid license key, you may run into this trial mode limitation. To prevent your non-production IdentityServer from shutting down in the future, you can use your production license key. IdentityServer is [free](#trial-mode) for development, testing and personal projects, and we support using your production license in these environments when trial mode is not sufficient. If you have feedback on trial mode, or specific use cases where you’d prefer other options, please [open a community discussion](https://github.com/DuendeSoftware/community/discussions). #### Redistribution [Section titled “Redistribution”](#redistribution-1) We understand that when IdentityServer is redistributed, log messages from the licensing system are not likely to be very useful to your redistribution customers. For that reason, in a redistribution the severity of log messages from the license system is turned all the way down to the trace level. We also appreciate that it might be cumbersome to deploy updated licenses in this scenario, especially if the deployment of your software does not coincide with the duration of the IdentityServer license. In that situation, we ask that you update the license key at the next deployment of your software to your redistribution customers. Of course, you are always responsible for ensuring that your license is renewed. #### Log Severity [Section titled “Log Severity”](#log-severity) The severity of the log messages described above depend on the nature of the message and the type of license. | Type of Message | Standard License | Redistribution License (development\*) | Redistribution License (production\*) | | ----------------------------- | ---------------- | -------------------------------------- | ------------------------------------- | | Startup, missing license | Warning | Warning | Warning | | Startup, license details | Debug | Debug | Trace | | Startup, valid license notice | Informational | Informational | Trace | | Startup, violations | Error | Error | Trace | | Runtime, violations | Error | Error | Trace | \* as determined by `IHostEnvironment.IsDevelopment()` ## 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. Trial mode Duende BFF has a [limited trial mode](#bff-trial-mode) for development and testing. For small organizations or personal projects, consider the [community edition](https://duendesoftware.com/products/communityedition/). For production use, a [license](https://duendesoftware.com/products/bff) is required. ### 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. Whenrunning 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. Note When operating non-production environments, such as development, test, or QA, without a valid license key, you may run into this trial mode limitation. If you require a larger number of sessions, we support using your production license in these environments when trial mode is not enough. ## License Key [Section titled “License Key”](#license-key) The license key can be configured in one of two ways: * Via a well-known file on the file system * Programmatically in your startup code You can also use other configuration sources such as Azure Key Vault, by using the programmatic approach. Redistributable license If you use our [redistributable license](https://duendesoftware.com/products/identityserverredist), we recommend loading the license at startup from an embedded resource. 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. ### 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. ----- # 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) The IdentityServer [issue tracker](https://github.com/DuendeSoftware/products/issues) and [pull requests](https://github.com/DuendeSoftware/products/pulls) allow you to follow the current work. Join our [developer community forum](https://github.com/DuendeSoftware/community/discussions) to ask questions and discuss potential bugs. [Milestones ](https://github.com/DuendeSoftware/IdentityServer/milestones)See what's coming up in the next release [Release Notes ](https://github.com/DuendeSoftware/products/releases)See what's new in the latest release ## Support [Section titled “Support”](#support) ### Standard Support [Section titled “Standard Support”](#standard-support) Standard support and feature requests are handled via our public [developer community forum](https://github.com/DuendeSoftware/community/discussions). Please start a discussion on our community forums if you need help. [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: Enterprise** [Priority Support](https://duendesoftware.com/license/PrioritySupportLicense.pdf), included with our Enterprise Edition, provides access to a dedicated email channel with guaranteed response within **two business days**. [Priority Support PDF ](https://duendesoftware.com/license/PrioritySupportLicense.pdf)Learn more about Priority Support ## Supported Versions [Section titled “Supported Versions”](#supported-versions) The following tables track release and end of support dates for Duende products. ### Duende IdentityServer [Section titled “Duende IdentityServer”](#duende-identityserver) Duende IdentityServer v7 | Version | Release Date | Supported .NET Platforms | Support End-of-Life | | ------- | ---------------- | ------------------------ | ------------------- | | 7.4 | December 2, 2025 | .NET 10 | November 14, 2028 | | | | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | | 7.3 | August 14, 2025 | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | | 7.2 | March 18, 2025 | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | | 7.1 | January 15, 2025 | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | | 7.0 | January 24, 2024 | .NET 8 | November 10, 2026 | Duende IdentityServer v6 Caution Duende IdentityServer v6 is no longer supported. | Version | Release Date | Supported .NET Platforms | Support End-of-Life | | ------- | ----------------- | ------------------------ | ------------------- | | 6.3 | May 16, 2023 | .NET 7 | May 14, 2024 | | | | .NET 6 | November 12, 2024 | | 6.2 | November 22, 2022 | .NET 7 | May 14, 2024 | | | | .NET 6 | November 12, 2024 | | 6.1 | May 20, 2022 | .NET 6 | November 12, 2024 | | 6.0 | January 13, 2022 | .NET 6 | November 12, 2024 | Duende IdentityServer v5 Caution Duende IdentityServer v5 is no longer supported. | Version | Release Date | Supported .NET Platforms | Support End-of-Life | | ------- | ---------------- | ------------------------ | ------------------- | | 5.2 | May 19, 2021 | .NET 5 | May 10, 2022 | | | | .NET Core 3.1 | December 13, 2022 | | 5.1 | March 25, 2021 | .NET 5 | May 10, 2022 | | | | .NET Core 3.1 | December 13, 2022 | | 5.0 | January 14, 2021 | .NET 5 | May 10, 2022 | | | | .NET Core 3.1 | December 13, 2022 | ### Duende Backend For Frontend (BFF) [Section titled “Duende Backend For Frontend (BFF)”](#duende-backend-for-frontend-bff) Duende BFF v4 | Version | Release Date | Supported .NET Platforms | Support End-of-Life | | ------- | ---------------- | ------------------------ | ------------------- | | 4.0 | December 2, 2025 | .NET 10 | November 14, 2028 | | | | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | Duende BFF v3 | Version | Release Date | Supported .NET Platforms | Support End-of-Life | | ------- | ---------------- | ------------------------ | ------------------- | | 3.1 | December 2, 2025 | .NET 10 | November 14, 2028 | | | | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | | 3.0 | March 17, 2025 | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | Duende BFF v2 | Version | Release Date | Supported .NET Platforms | Support End-of-Life | | ------- | ----------------- | ------------------------ | ------------------- | | 2.3 | December 20, 2024 | .NET 9 | November 10, 2026 | | | | .NET 8 | November 10, 2026 | | 2.2 | April 1, 2024 | .NET 8 | November 10, 2026 | | | | .NET 6 | November 12, 2024 | | 2.1 | June 27, 2022 | .NET 6 | November 12, 2024 | | 2.0 | November 11, 2022 | .NET 6 | November 12, 2024 | Duende BFF v1 Caution Duende BFF v1 is no longer supported. | Version | Release Date | Supported .NET Platforms | Support End-of-Life | | ------- | ----------------- | ------------------------ | ------------------- | | 1.2 | April 1, 2022 | .NET 6 | November 12, 2024 | | | | .NET 5 | May 10, 2022 | | | | .NET Core 3.1 | December 13, 2022 | | 1.1 | December 16, 2021 | .NET 6 | November 12, 2024 | | | | .NET 5 | May 10, 2022 | | | | .NET Core 3.1 | December 13, 2022 | | 1.0 | October 24, 2021 | .NET 5 | May 10, 2022 | | | | .NET Core 3.1 | December 13, 2022 | ## 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, in addition to extension methods to invoke requests constants defined in the specifications, such as standard scope, claim, and parameter names, 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 Tip **`Duende.IdentityModel.OidcClient` is a [certified](https://openid.net/certification/) OpenID Connect relying party implementation.** 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). Note The client application is responsible for creating the DPoP key, rotating it, and managing its lifetime. For as long as there are access tokens (and possibly refresh tokens) bound to a DPoP key, that key needs to remain available to the client application. 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; } } ``` Browser is platform-specific The `IBrowser` implementation is specific to the platform and environment and must be provided by the host application. For example, a Windows-specific implementation will not work within a macOS, iOS, Android, or Linux environment. 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. ```cs 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. ```cs 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. ```cs 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(); ``` You can use any logging provider to store your logs however you like, by setting the `LoggerFactory` property on `OidcClientOptions`. For example, you could configure [Serilog](https://github.com/serilog/serilog-extensions-hosting) like this: ```csharp var serilog = new LoggerConfiguration() .MinimumLevel.Verbose() .Enrich.FromLogContext() .WriteTo.LiterateConsole(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}") .CreateLogger(); options.LoggerFactory.AddSerilog(serilog); ``` ## 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. ```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) ----- # 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 } }); ``` When the URIs in the discovery document are on a different base address than the issuer URI, you may encounter the error *Endpoint is on a different host than authority*. For such scenario, additional endpoint base addresses can be configured: ```csharp var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest { Address = "https://demo.duendesoftware.com", Policy = { AdditionalEndpointBaseAddresses = [ "https://auth.domain.tld" ] } }); ``` 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" } }); ``` Note The `DynamicClientRegistrationDocument` class has strongly typed properties for all standard registration parameters as defines by the specification. If you want to add custom parameters, it is recommended to derive from this class and add your own properties. 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: ```cs 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: ```cs 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: ```cs 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. Note Some client libraries also include a stateful client object (e.g. `TokenClient` and `IntrospectionClient`). See the corresponding section to find out more. ## Client Credential Style [Section titled “Client Credential Style”](#client-credential-style) Note We recommend only changing the Client Credential Style if you’re experiencing HTTP Basic authentication encoding issues. 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`. ```cs 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. Note All values will be URL encoded. ## 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"); ``` Note The *extra* parameter can either be a string dictionary or an arbitrary other type with properties. In both cases the values will be serialized as keys/values. ## 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) { ... } ``` Note The *extra* parameter can either be a string dictionary or an arbitrary other type with properties. In both cases the values will be serialized as keys/values. ----- # Time-Constant String Comparison > Learn about implementing secure string comparison to prevent timing attacks in security-sensitive contexts using TimeConstantComparer Note Starting with .NET Core 2.1 this functionality is built in via [CryptographicOperations.FixedTimeEquals](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=netcore-2.1) 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 and OAuth 2.x protocols in 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. Note You could achieve the same by using either Microsoft’s `JwtBearer` handler. But this requires more configuration and creates dependencies on external libraries that might lead to conflicts in future updates. Start by registering your API as an `ApiScope`, (or resource) e.g.: ```cs var scopes = new List { // local API new ApiScope(IdentityServerConstants.LocalApi.ScopeName), }; ``` …and give your clients access to this API, e.g.: ```cs new Client { // rest omitted AllowedScopes = { IdentityServerConstants.LocalApi.ScopeName }, } ``` Note The value of `IdentityServerConstants.LocalApi.ScopeName` is `IdentityServerApi`. To enable token validation for local APIs, add the following to your IdentityServer startup: Program.cs ```cs builder.Services.AddLocalApiAuthentication(); ``` To protect an API controller, decorate it with an `Authorize` attribute using the `LocalApi.PolicyName` policy: ```cs [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 ```cs builder.Services.AddIdentityServer(options => { options.Discovery.CustomEntries.Add("local_api", "~/localapi"); }) ``` ## Advanced [Section titled “Advanced”](#advanced) Under the covers, 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 ```cs 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 ```cs 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. ----- # 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#: ```cs 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: ```cs builder.Services.AddAuthorization(options => { options.AddPolicy("read_access", policy => policy.RequireClaim("scope", "read")); }); ``` …and then enforce it, e.g. using the routing table: ```cs app.MapControllers().RequireAuthorization("read_access"); ``` …or imperatively inside the controller: ```cs public class DataController : ControllerBase { IAuthorizationService _authz; public DataController(IAuthorizationService authz) { _authz = authz; } public async Task Get() { var allowed = _authz.CheckAccess(User, "read_access"); // rest omitted } } ``` … or declaratively: ```cs public class DataController : ControllerBase { [Authorize("read_access")] public async Task Get() { var allowed = authz.CheckAccess(User, "read_access"); // rest omitted } } ``` #### 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/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: ```cs 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: ```cs // 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: ```cs 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: ```cs // 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: ```cs // 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/v7/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: ```cs 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: ```cs 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 this library, ensure that you have the NuGet package for the ASP.NET Identity integration. It is called `Duende.IdentityServer.AspNetIdentity`. You can install it with: 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) Alternatively, 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. ----- # Configuration API > Documentation for the Configuration API endpoints that enable management and configuration of IdentityServer implementations Tip Added in Duende IdentityServer 6.3 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 is part of the [Duende IdentityServer](https://duendesoftware.com/products/identityserver) Business Edition or higher. The same [license](https://duendesoftware.com/products/identityserver#pricing) and [special offers](https://duendesoftware.com/specialoffers) apply. 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 = ""; ); ``` Note The Configuration API feature is included in the Duende IdentityServer Business edition license and higher. Use the same license key for IdentityServer and the Configuration API. 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/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 = ""; ); ``` Note The Configuration API feature is included in the Duende IdentityServer Business edition license and higher. Use the same license key for IdentityServer and the Configuration API. 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/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. ## 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/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/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/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/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 rDCR esponse generation](/identityserver/reference/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. Note Given that data stores abstract the details of the data stored, strictly speaking, IdentityServer does not know or understand where the data is actually being stored. As such, there is no built-in administrative tool to populate or manage this data. There are third-party options (both commercial and FOSS) that provide an administrative UI for managing the data when using the EntityFramework Core 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/stores/client-store/) for `Client` data. * [CORS policy service](/identityserver/reference/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/stores/resource-store/) for `IdentityResource`, `ApiResource`, and `ApiScope` data. * [Identity Provider store](/identityserver/reference/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/di/#configuration-stores) for registering these. For example: Program.cs ```cs 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. Duende IdentityServer provides [convenience methods](/identityserver/reference/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 ```cs 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/options/#caching). For example: Program.cs ```cs 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/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`. Note The token cleanup feature does `not` remove persisted grants that are `consumed` (see [persisted grants](/identityserver/reference/stores/persisted-grant-store/)). It only removes persisted grants that are beyond their `Expiration`. ## 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/stores/persisted-grant-store/) is a common store for most grants. * The [device flow store](/identityserver/reference/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 ```cs 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/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/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/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 ```cs 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/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/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) Tip Added in Duende IdentityServer 6.1 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/stores/server-side-sessions/) abstracts storing the server-side session data. [ServerSideSession](/identityserver/reference/stores/server-side-sessions/#serversidesession) objects act as the storage entity, and provide several properties uses as metadata for the session. The `Ticket` property contains the actual serialized data used by the ASP.NET Cookie Authentication handler. The methods on the [IServerSideSessionStore](/identityserver/reference/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 ```cs 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 ```cs 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 ```cs 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. Note Our experience has been that these topics are very important. Some of our most common support requests are related to [Data Protection](#data-protection-keys) and [Load Balancing](#proxy-servers-and-load-balancers), so we strongly encourage you to review those pages, along with the rest of this chapter before deploying IdentityServer to production. ## 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. 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 IdentityServer 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 [here](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 IdentityServer implementation should include data protection configuration code, like this: Program.cs ```cs 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("IdentityServer"); ``` Ensure data protection keys are persisted Always make sure data protection is configured to persist data protection keys to storage, using `.PersistKeys...()` for your storage mechanism. In addition, make sure 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, when your Redis instance reboots, you will lose all data protection keys. If you lose your data protection keys, all data protected with those keys to no longer be readable. ### Data Protection Keys and IdentityServer’s Signing Keys [Section titled “Data Protection Keys and IdentityServer’s Signing Keys”](#data-protection-keys-and-identityservers-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. #### Data Protection Keys [Section titled “Data Protection Keys”](#data-protection-keys) Data protection is a cryptographic library that is part of the ASP.NET framework. 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. #### IdentityServer Signing Key [Section titled “IdentityServer Signing Key”](#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/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. ### 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 IdentityServer logs. For example, when automatic key management fails to read its signing keys due to a data protection failure, IdentityServer 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 IdentityServer and ASP.NET use 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 IdentityServer 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 creates keys isolated by application name. If you don’t specify a name, the content root path of the application will be used. But, beginning in .NET 6.0 Microsoft changed how they handle the path, which can cause data protection keys to break. Their docs on the problem are [here](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview#setapplicationname), including a work-around where you de-normalize the path. Then, in .NET 7.0, this change was reverted. The solution is always to specify an explicit application name, and if you have old keys that were generated without an explicit application name, you need to set your application name to match the default behavior that produced the keys you want to be able to read. 4. If your IdentityServer is hosted by IIS, special configuration is needed for data protection. In most default deployments, IIS lacks the permissions required to persist data protection keys, and falls back to using an ephemeral key generated every time the site starts up. Microsoft’s docs on this issue are [here](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/advanced?view=aspnetcore-7.0#data-protection). ### Identity Server’s Usage of Data Protection [Section titled “Identity Server’s Usage of Data Protection”](#identity-servers-usage-of-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) ## 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. ## 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/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/) ----- # 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/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/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/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/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/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 `UseSerilog()` 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.Host.UseSerilog((context, services, configuration) => { configuration .ReadFrom.Configuration(context.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. Example diagnostics data JSON ```json { "AssemblyInfo":{ "DotnetVersion":".NET 9.0.6", "IdentityServerVersion":"7.3", "Assemblies":[ { "Name":"Duende.IdentityModel", "Version":"7.0.0.0" }, { "Name":"Duende.IdentityServer", "Version":"7.0.0.0" }, { "Name":"Duende.IdentityServer.Storage", "Version":"7.0.0.0" }, { "Name":"Microsoft.AspNetCore", "Version":"9.0.0.0" }, { "Name":"Microsoft.AspNetCore.Authentication.Abstractions", "Version":"9.0.0.0" }, { "Name":"Microsoft.AspNetCore.Authentication.Cookies", "Version":"9.0.0.0" }, { "Name":"Microsoft.AspNetCore.Authentication.Core", "Version":"9.0.0.0" }, { "Name":"Microsoft.AspNetCore.Authentication.Google", "Version":"9.0.3.0" }, { "Name":"Microsoft.AspNetCore.Authentication.OAuth", "Version":"9.0.0.0" }, { "Name":"Microsoft.AspNetCore.Authentication.OpenIdConnect", "Version":"9.0.3.0" }, { "Name":"Microsoft.IdentityModel.Abstractions", "Version":"8.0.1.0" }, { "Name":"Microsoft.IdentityModel.JsonWebTokens", "Version":"8.0.1.0" }, { "Name":"Microsoft.IdentityModel.Logging", "Version":"8.0.1.0" }, { "Name":"Microsoft.IdentityModel.Protocols", "Version":"8.0.1.0" }, { "Name":"Microsoft.IdentityModel.Protocols.OpenIdConnect", "Version":"8.0.1.0" }, { "Name":"Microsoft.IdentityModel.Tokens", "Version":"8.0.1.0" }, { "Name":"System.IdentityModel.Tokens.Jwt", "Version":"8.0.1.0" } ] }, "AuthSchemeInfo":{ "Schemes":[ { "idsrv":"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler" }, { "idsrv.external":"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler" }, { "Google":"Google.Apis.Auth.AspNetCore3.GoogleOpenIdConnectHandler" } ] }, "RegisteredImplementations":{ "Root":[ ], "Hosting":[ ], "Infrastructure":[ ], "ResponseHandling":[ ], "Services":[ { "ICorsPolicyService":[ { "TypeName":"Duende.IdentityServer.Services.InMemoryCorsPolicyService", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IProfileService":[ { "TypeName":"Duende.IdentityServer.Test.TestUserProfileService", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IRefreshTokenService":[ { "TypeName":"Duende.IdentityServer.Services.ServerSideSessionRefreshTokenService", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "ISessionManagementService":[ { "TypeName":"Duende.IdentityServer.Services.DefaultSessionManagementService", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] } ], "Stores":[ { "IClientStore":[ { "TypeName":"Duende.IdentityServer.Stores.ValidatingClientStore\u00601[[Duende.IdentityServer.Stores.InMemoryClientStore, Duende.IdentityServer, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null]]", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IDeviceFlowStore":[ { "TypeName":"Duende.IdentityServer.Stores.InMemoryDeviceFlowStore", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IIdentityProviderStore":[ { "TypeName":"Duende.IdentityServer.Hosting.DynamicProviders.NonCachingIdentityProviderStore\u00601[[Duende.IdentityServer.Hosting.DynamicProviders.ValidatingIdentityProviderStore\u00601[[Duende.IdentityServer.Hosting.DynamicProviders.InMemoryIdentityProviderStore, Duende.IdentityServer, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null]], Duende.IdentityServer, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null]]", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IResourceStore":[ { "TypeName":"Duende.IdentityServer.Stores.InMemoryResourcesStore", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" }, { "TypeName":"Duende.IdentityServer.Stores.InMemoryResourcesStore", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" }, { "TypeName":"Duende.IdentityServer.Stores.InMemoryResourcesStore", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IServerSideSessionsMarker":[ { "TypeName":"Duende.IdentityServer.Stores.NopIServerSideSessionsMarker", "Assembly":"Duende.IdentityServer.Storage", "AssemblyVersion":"7.0.0.0" } ] }, { "IServerSideSessionStore":[ { "TypeName":"Duende.IdentityServer.Stores.InMemoryServerSideSessionStore", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IServerSideTicketStore":[ { "TypeName":"Duende.IdentityServer.Stores.ServerSideTicketStore", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] } ], "Validation":[ { "IBackchannelAuthenticationUserValidator":[ { "TypeName":"Microsoft.Extensions.DependencyInjection.TestBackchannelLoginUserValidator", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "IResourceOwnerPasswordValidator":[ { "TypeName":"Duende.IdentityServer.Test.TestUserResourceOwnerPasswordValidator", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "ISecretParser":[ { "TypeName":"Duende.IdentityServer.Validation.JwtBearerClientAssertionSecretParser", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] }, { "ISecretValidator":[ { "TypeName":"Duende.IdentityServer.Validation.PrivateKeyJwtSecretValidator", "Assembly":"Duende.IdentityServer", "AssemblyVersion":"7.0.0.0" } ] } ] }, "IdentityServerOptions":{ "IssuerUri":null, "LowerCaseIssuerUri":true, "AccessTokenJwtType":"at\u002Bjwt", "LogoutTokenJwtType":"logout\u002Bjwt", "EmitStaticAudienceClaim":true, "EmitScopesAsSpaceDelimitedStringInJwt":false, "EmitIssuerIdentificationResponseParameter":true, "EmitStateHash":false, "StrictJarValidation":false, "ValidateTenantOnAuthorization":false, "Endpoints":{ "EnableAuthorizeEndpoint":true, "EnableJwtRequestUri":false, "EnableTokenEndpoint":true, "EnableUserInfoEndpoint":true, "EnableDiscoveryEndpoint":true, "EnableEndSessionEndpoint":true, "EnableCheckSessionEndpoint":true, "EnableTokenRevocationEndpoint":true, "EnableIntrospectionEndpoint":true, "EnableDeviceAuthorizationEndpoint":true, "EnableBackchannelAuthenticationEndpoint":true, "EnablePushedAuthorizationEndpoint":true }, "Discovery":{ "ShowEndpoints":true, "ShowKeySet":true, "ShowIdentityScopes":true, "ShowApiScopes":true, "ShowClaims":true, "ShowResponseTypes":true, "ShowResponseModes":true, "ShowGrantTypes":true, "ShowExtensionGrantTypes":true, "ShowTokenEndpointAuthenticationMethods":true, "ExpandRelativePathsInCustomEntries":true, "ResponseCacheInterval":null, "CustomEntries":{ } }, "Authentication":{ "CookieAuthenticationScheme":null, "CookieLifetime":"10:00:00", "CookieSlidingExpiration":false, "CookieSameSiteMode":0, "RequireAuthenticatedUserForSignOutMessage":false, "CheckSessionCookieName":"idsrv.session", "CheckSessionCookieDomain":null, "CheckSessionCookieSameSiteMode":0, "RequireCspFrameSrcForSignout":true, "CoordinateClientLifetimesWithUserSession":false }, "Events":{ "RaiseSuccessEvents":true, "RaiseFailureEvents":true, "RaiseInformationEvents":true, "RaiseErrorEvents":true }, "InputLengthRestrictions":{ "ClientId":100, "ClientSecret":100, "Scope":300, "RedirectUri":400, "Nonce":300, "UiLocale":100, "LoginHint":100, "AcrValues":300, "GrantType":100, "UserName":100, "Password":100, "CspReport":2000, "IdentityProvider":100, "ExternalError":100, "AuthorizationCode":100, "DeviceCode":100, "RefreshToken":100, "TokenHandle":100, "Jwt":51200, "CodeChallengeMinLength":43, "CodeChallengeMaxLength":128, "CodeVerifierMinLength":43, "CodeVerifierMaxLength":128, "ResourceIndicatorMaxLength":512, "BindingMessage":100, "UserCode":100, "IdTokenHint":4000, "LoginHintToken":4000, "AuthenticationRequestId":100, "DPoPKeyThumbprint":100, "DPoPProofToken":4000 }, "UserInteraction":{ "LoginUrl":"/Account/Login", "LoginReturnUrlParameter":"ReturnUrl", "LogoutUrl":"/Account/Logout", "LogoutIdParameter":"logoutId", "ConsentUrl":"/consent", "ConsentReturnUrlParameter":"returnUrl", "CreateAccountUrl":null, "CreateAccountReturnUrlParameter":"returnUrl", "ErrorUrl":"/home/error", "ErrorIdParameter":"errorId", "CustomRedirectReturnUrlParameter":"returnUrl", "CookieMessageThreshold":2, "DeviceVerificationUrl":"/device", "DeviceVerificationUserCodeParameter":"userCode", "AllowOriginInReturnUrl":false, "PromptValuesSupported":[ "none", "login", "consent", "select_account" ] }, "Caching":{ "ClientStoreExpiration":"00:15:00", "ResourceStoreExpiration":"00:15:00", "CorsExpiration":"00:15:00", "IdentityProviderCacheDuration":"01:00:00", "CacheLockTimeout":"00:01:00" }, "Cors":{ "CorsPolicyName":"Duende.IdentityServer", "PreflightCacheDuration":null, "CorsPaths":[ { "Value":"/.well-known/openid-configuration", "HasValue":true }, { "Value":"/.well-known/openid-configuration/jwks", "HasValue":true }, { "Value":"/connect/token", "HasValue":true }, { "Value":"/connect/userinfo", "HasValue":true }, { "Value":"/connect/revocation", "HasValue":true } ] }, "Csp":{ "Level":1, "AddDeprecatedHeader":true }, "Validation":{ "InvalidRedirectUriPrefixes":[ "javascript:", "file:", "data:", "mailto:", "ftp:", "blob:", "about:", "ssh:", "tel:", "view-source:", "ws:", "wss:" ] }, "DeviceFlow":{ "DefaultUserCodeType":"Numeric", "Interval":5 }, "Ciba":{ "DefaultLifetime":300, "DefaultPollingInterval":5 }, "Logging":{ "BackchannelAuthenticationRequestSensitiveValuesFilter":[ "client_secret", "client_assertion", "id_token_hint", "request" ], "TokenRequestSensitiveValuesFilter":[ "client_secret", "password", "client_assertion", "refresh_token", "device_code", "code", "subject_token" ], "AuthorizeRequestSensitiveValuesFilter":[ "id_token_hint", "request" ], "PushedAuthorizationSensitiveValuesFilter":[ "client_secret", "client_assertion", "request" ] }, "MutualTls":{ "Enabled":false, "ClientCertificateAuthenticationScheme":"Certificate", "DomainName":null, "AlwaysEmitConfirmationClaim":false }, "KeyManagement":{ "Enabled":true, "RsaKeySize":2048, "SigningAlgorithms":[ { "Name":"RS256", "UseX509Certificate":false } ], "InitializationDuration":"00:05:00", "InitializationSynchronizationDelay":"00:00:05", "InitializationKeyCacheDuration":"00:01:00", "KeyCacheDuration":"1.00:00:00", "PropagationTime":"14.00:00:00", "RotationInterval":"90.00:00:00", "RetentionDuration":"14.00:00:00", "DeleteRetiredKeys":true, "DataProtectKeys":true, "KeyPath":"/Users/maartenba/Projects/AcmeCorp/AcmeCorp.IdentityServer/keys" }, "PersistentGrants":{ "DataProtectData":true, "DeleteOneTimeOnlyRefreshTokensOnUse":true }, "DPoP":{ "ProofTokenValidityDuration":"00:01:00", "ServerClockSkew":"00:00:00", "SupportedDPoPSigningAlgorithms":[ "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512" ] }, "DynamicProviders":{ "PathPrefix":{ "Value":"/federation", "HasValue":true }, "SignInScheme":"idsrv.external", "SignOutScheme":"idsrv", "SignOutSchemeSetExplicitly":false }, "ServerSideSessions":{ "UserDisplayNameClaimType":"name", "RemoveExpiredSessions":true, "ExpiredSessionsTriggerBackchannelLogout":true, "RemoveExpiredSessionsFrequency":"00:10:00", "FuzzExpiredSessionRemovalStart":true, "RemoveExpiredSessionsBatchSize":100 }, "PushedAuthorization":{ "Required":false, "Lifetime":600, "AllowUnregisteredPushedRedirectUris":true }, "JwtValidationClockSkew":"00:05:00", "SupportedRequestObjectSigningAlgorithms":[ "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512", "HS256", "HS384", "HS512" ], "SupportedClientAssertionSigningAlgorithms":[ "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512", "HS256", "HS384", "HS512" ], "Preview":{ "EnableDiscoveryDocumentCache":false, "StrictClientAssertionAudienceValidation":false, "DiscoveryDocumentCacheDuration":"00:01:00" }, "Diagnostics":{ "LogFrequency":"00:10:00", "ChunkSize":8160 } }, "DataProtectionConfiguration":{ "ApplicationDiscriminator":"/Users/maartenba/Projects/AcmeCorp/AcmeCorp.IdentityServer/", "XmlEncryptor":"Not Configured", "XmlRepository":"Not Configured" }, "TokenIssueCounts":{ "Jwt":1, "Reference":0, "JwtDPoP":0, "ReferenceDPoP":0, "JwtMTLS":0, "ReferenceMTLS":0, "Refresh":0, "Id":1, "implicit":0, "hybrid":0, "authorization_code":1, "client_credentials":0, "password":0, "urn:ietf:params:oauth:grant-type:device_code":0, "Other":0 }, "LicenseUsageSummary":{ "ClientsUsedCount":1, "IssuersUsed":[ "https://localhost:5443" ], "FeaturesUsed":[ "KeyManagement", "PAR", "ServerSideSessions" ], "LicenseEdition":"None" }, "BasicServerInfo":{ "HostName":"M4-MAARTEN" }, "EndpointUsage":{ "/connect/authorize/callback":2, "/connect/authorize":1, "/connect/ciba":0, "/connect/checksession":0, "/connect/deviceauthorization":0, "/.well-known/openid-configuration/jwks":3, "/.well-known/openid-configuration":3, "/connect/endsession/callback":1, "/connect/endsession":1, "/connect/introspect":0, "/connect/par":1, "/connect/revocation":0, "/connect/token":1, "/connect/userinfo":1, "other":0 }, "Clients":[ { "ClientId":"interactive", "SecretTypes":[ "SharedSecret" ], "RequireConsent":true, "AllowedGrantTypes":[ "authorization_code" ], "RedirectUris":[ "https://localhost:5444/signin-oidc" ], "PostLogoutRedirectUris":[ "https://localhost:5444/" ], "FrontChannelLogoutUri":"https://localhost:5444/signout-oidc", "AllowOfflineAccess":true, "AllowedScopes":[ "openid", "profile", "email", "weatherapi.read" ], "CoordinateLifetimeWithUserSession":true, "InitiateLoginUri":"https://localhost:5444/signin-idp" } ], "Resources":{ "IdentityResource":[ "email", "openid", "profile" ], "ApiResource":[ { "Name":"weatherapi", "ResourceIndicatorRequired":false, "SecretTypes":[ "SharedSecret" ] } ], "ApiScope":[ "weatherapi.read" ] } } ``` Diagnostics data format The structure and format of the diagnostics data output should not be considered stable, and may change in future IdentityServer versions. ----- # 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 ```cs 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.: ```cs 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: ```cs 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. ### 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:: ```cs 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. The Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) has a good intro and a description of the built-in logging providers. 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… Note In production, logging might produce too much data. It is recommended you either turn it off, or default to the `Warning` level. Have a look at [events](/identityserver/diagnostics/events/) for more high-level production instrumentation. ### Setup for Microsoft.Extensions.Logging [Section titled “Setup for Microsoft.Extensions.Logging”](#setup-for-microsoftextensionslogging) .NET provides a logging abstraction interface found in the [`Microsoft.Extensions.Logging`](https://www.nuget.org/packages/Microsoft.Extensions.Logging) package and is the default logging provider for ASP.NET Core. If you prefer to use Microsoft’s logging option, you can remove references to Serilog and fall back to the default logging implementation. Duende IdentityServer already uses the `ILogger` interface, and will use any implementation registered with the services collection. Below you will find a modified version of the in-memory Duende IdentityServer sample. You can use it as a guide to adapt your own instance of Duende IdentityServer to use Microsoft’s logging implementation.. ```csharp using System.Globalization; using System.Text; using Duende.IdentityServer.Licensing; // App1 contains WebApplicationBuilder extension methods // update according to your application's namespace using App1; var builder = WebApplication.CreateBuilder(args); var app = builder // WebApplicationBuilder extension methods .ConfigureServices() .ConfigurePipeline(); try { app.Logger.LogInformation("Starting up"); if (app.Environment.IsDevelopment()) { app.Lifetime.ApplicationStopping.Register(() => { var usage = app.Services.GetRequiredService(); app.Logger.LogInformation(Summary(usage)); }); } app.Run(); } catch (Exception ex) when (ex is not HostAbortedException) { app.Logger.LogCritical(ex, "Host terminated unexpectedly"); } finally { app.Logger.LogInformation("Shut down complete"); } static string Summary(LicenseUsageSummary usage) { var sb = new StringBuilder(); sb.AppendLine("IdentityServer Usage Summary:"); sb.AppendLine(CultureInfo.InvariantCulture, $" License: {usage.LicenseEdition}"); var features = usage.FeaturesUsed.Count > 0 ? string.Join(", ", usage.FeaturesUsed) : "None"; sb.AppendLine(CultureInfo.InvariantCulture, $" Business and Enterprise Edition Features Used: {features}"); sb.AppendLine(CultureInfo.InvariantCulture, $" {usage.ClientsUsed.Count} Client Id(s) Used"); sb.AppendLine(CultureInfo.InvariantCulture, $" {usage.IssuersUsed.Count} Issuer(s) Used"); return sb.ToString(); } ``` You will also need to modify the `appSettings.json` file to include the `Logging` section: ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Duende.IdentityServer": "Information" } }, "AllowedHosts": "*" } ``` Learn more about configuring logging in .NET applications by reading the [Microsoft documentation on logging fundamentals](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-9.0#configure-logging). As you’ll see in the Microsoft documentation, configuring logging can be very involved and target different log levels, which can be useful for troubleshooting. ### Setup For Serilog [Section titled “Setup For Serilog”](#setup-for-serilog) [Serilog](https://serilog.net) is a trusted and popular logging library for .NET applications. It is highly configurable, and at Duende, we think it is a **great alternative** to the default logging implementation, especially for .NET developers looking for more control over their logging configuration. Additionally, ASP.NET Core developers can use the [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) for better integration with ASP.NET Core applications. Program.cs ```csharp Activity.DefaultIdFormat = ActivityIdFormat.W3C; Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) .MinimumLevel.Override("System", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) .CreateLogger(); builder.Logging.AddSeriLog(); ``` You can also use ASP.NET Core’s configuration pattern to configure Serilog using `appsettings.json` and other configuration sources. To do so, you first need to tell Serilog to read its configuration from the `IConfiguration` root: Program.cs ```csharp var builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog((ctx, lc) => lc .WriteTo.Console( outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", formatProvider: CultureInfo.InvariantCulture) .Enrich.FromLogContext() .ReadFrom.Configuration(ctx.Configuration)); ``` Then, in your `appsettings.json` file, you can set the default minimum log level and log level overrides like so: appsettings.json ```json { "Serilog": { "MinimumLevel": { "Default": "Debug", "Override": { "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.AspNetCore.Authentication": "Debug", "System": "Warning", "Duende": "Verbose" // As an example, we've enabled more verbose logging for the Duende.* namespace } } } } ``` ## 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 > Documentation for OpenTelemetry integration in IdentityServer, covering metrics, traces and logs collection for monitoring and diagnostics Tip Added in Duende IdentityServer v6.1 and expanded in v7.0 [OpenTelemetry](https://opentelemetry.io) 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. .NET 8 comes with first class support for OpenTelemetry. IdentityServer emits traces, metrics, and logs. ### Metrics [Section titled “Metrics”](#metrics) Metrics are high level statistic counters. They provide an aggregated overview and can be used to set monitoring rules. ### Logs [Section titled “Logs”](#logs) OpenTelemetry in .NET 8 exports the logs written to the standard ILogger system. The logs are augmented with trace ids to be able to correlate log entries with traces. ### 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_1QTL4e.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) 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. Add the OpenTelemetry configuration to your service setup. Program.cs ```cs 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 ```cs // 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) Tip Added in Duende IdentityServer v7.0 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. Note In IdentityServer versions <7.3, these metrics are created by the meter named “Duende.IdentityServer.Experimental”, starting with IdentityServer 7.3, they are created by the meter named “Duende.IdentityServer”. #### 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 | ### 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) Tip Added in .NET 10 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) Tip Added in .NET 10 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) Tip Added in Duende IdentityServer v6.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_Z2pEf51.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_7usFM.webp) …and then contacting the userinfo endpoint: ![Honeycomb UI showing traces for the userinfo endpoint](/_astro/otel_flow_2.DcVRg6r2_1N1aeR.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 ----- # 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/endpoints/userinfo/). The central extensibility point to implement to emit claims is called the [profile service](/identityserver/reference/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/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: ```cs 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: ```cs 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/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: ```cs 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/models/client/#token). Note By default, client claims are only sent in the client credentials flow. If you want to enable them for other flows, you need to set the `AlwaysSendClientClaims` property on the client definition. ### 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) 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): ```cs 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): ```cs 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 ```cs 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. Note While technically you could share the ASP.NET Core host between Duende IdentityServer, clients or APIs. We recommend putting your IdentityServer into a separate application. ## 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 ```cs var idsvrBuilder = builder.Services.AddIdentityServer(options => { // ... }); ``` Many of the fundamental configuration settings can be set on the options. See the [`IdentityServerOptions`](/identityserver/reference/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/di/) section, but very commonly you start by adding the configuration stores for clients and resources, e.g.: Program.cs ```cs 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. ## Request Pipeline [Section titled “Request Pipeline”](#request-pipeline) Note `UseIdentityServer` includes a call to `UseAuthentication`, so it’s not necessary to have both. You need to add the Duende IdentityServer middleware to the pipeline by calling `UseIdentityServer`. Since ordering is important in the pipeline, you typically want to put the IdentityServer middleware after the static files, but before the UI framework like MVC. This would be a very typical minimal pipeline: Program.cs ```cs app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthorization(); app.MapDefaultControllerRoute(); ``` ----- # 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 Automatic Key Management is included in [IdentityServer](https://duendesoftware.com/products/identityserver) Business Edition or higher. ### Configuration [Section titled “Configuration”](#configuration) Automatic Key Management is configured by the options in the `KeyManagement` property on the [`IdentityServerOptions`](/identityserver/reference/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. All of these options are configurable in the `KeyManagement` options. For example: Program.cs ```cs 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 ```cs 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. Note *X.509 certificates* have an expiration date, but IdentityServer does not use this data to validate the certificate and throw an exception. If a certificate has expired then you must decide whether to continue using it or replace it with a new certificate. ```cs 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) }; ``` Note When you register multiple signing algorithms, the first in the list 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. ## 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 (e.g. the Starter Edition), 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/options/#key-management): Program.cs ```cs 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 KeyVault](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/di/#signing-keys) configuration method: Program.cs ```cs 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 ```cs 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 ```cs 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. ```cs 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. ```cs 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. ```cs 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. ```cs 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: ```cs 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: ```cs 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. ```cs 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/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: ```cs var invoiceApi = new ApiResource("invoice", "Invoice API") { Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" }, AllowedAccessTokenSigningAlgorithms = { SecurityAlgorithms.RsaSsaPssSha256 } } ``` Note Make sure that you have configured your IdentityServer for the required signing algorithm. See [here](/identityserver/fundamentals/key-management/) for more details. ----- # 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: ```cs 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.: ```cs 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" } ``` Note The format of the `scope` parameter can be controlled by the `EmitScopesAsSpaceDelimitedStringInJwt` setting on the options. Historically IdentityServer emitted scopes as an array, but you can switch to a space delimited string instead. 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: ```cs 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. Note When using the scope-only model, no aud (audience) claim will be added to the token since this concept does not apply. If you need an aud claim, you can enable the `EmitStaticAudienceClaim` setting on the options. This will emit an aud claim in the `issuer_name/resources` format. If you need more control of the aud claim, use API resources. ### 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.: ```cs 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: ```cs 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: ```cs 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: ```cs public static IEnumerable GetIdentityResources() { return new List { new IdentityResources.OpenId() }; } ``` Note See the [reference](/identityserver/reference/models/identity-resource/) section for more information on `IdentityResource`. The following example shows a custom identity resource called `profile` that represents the display name, email address and website claim: ```cs 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): ```cs var client = new Client { ClientId = "client", AllowedScopes = { "openid", "profile" } }; ``` Note See the [reference](/identityserver/reference/models/client/) section for more information on the `Client` class. 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/services/profile-service/). ----- # Resource Isolation > Learn about isolating OAuth resources and using the resource parameter to control access token scope and audience Note This is an Enterprise Edition feature. 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: * 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). ## 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. ----- # 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). Note While you can use any custom user database or identity management library for your users, we provide [integration support](/identityserver/aspnet-identity/) for ASP.NET Identity. ## 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/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. Note A user’s authentication session is managed using Microsoft’s ASP.NET [cookie authentication framework](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie). It is very important that you understand how it works when building the login pages in IdentityServer. Recall the diagram showing the relationship of your custom UI pages and the IdentityServer middleware in your IdentityServer host application: ![middleware diagram](/_astro/middleware.BXUZZu-U_1X8NBc.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.CUDALGQL_28qPuC.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.DsTexwvr_Z441mn.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.DF-2hIpy_Z441mn.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 relatinship in the ASP.NET Core pipeline](/_astro/middleware.BXUZZu-U_1X8NBc.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. Note You may have a previous version of Duende templates (`Duende.IdentityServer.Templates`) installed on your machine. Please uninstall the template package and install the latest version. 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. Note All templates currently target .NET 8.0, but you can alter the target framework after creating the project to target higher framework versions. 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 Edition: OAuth 2.0 Dynamic Client Registration Protocol ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)) * Business Edition: OAuth 2.0 Pushed Authorization Requests ([RFC 9126](https://www.rfc-editor.org/rfc/rfc9126)) * Enterprise Edition: Resource Indicators for OAuth 2.0 ([RFC 8707](https://tools.ietf.org/html/rfc8707)) * Enterprise 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.DQQWb4Nf_ZYcqjI.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/). Note A client application can potentially have many instances - e.g. your web application might be physically deployed on multiple servers for load-balancing purposes, or your mobile application might be deployed to thousands of different phones. Logically these instances are still a single client. ## 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/v7/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. Note You may have a previous version of Duende templates (`Duende.IdentityServer.Templates`) installed on your machine. Please uninstall the template package and install the latest version. [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/v7/Quickstarts) repository, and a reference implementation of this quickstart is available [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/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 ``` Note You may have a previous version of Duende templates (`Duende.IdentityServer.Templates`) installed on your machine. Please uninstall the template package and install the latest version. ## 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 Note The `src/IdentityServer/Properties/launchSettings.json` file created by the `isempty` template sets the `applicationUrl` to `https://localhost:5001`. You can change the port that your IdentityServer host listens on by changing the port in this url. This url also sets the protocol (http or https) that the IdentityServer host will use. In production scenarios you should always use `https`. 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/v7/Quickstarts/1_ClientCredentials/src/IdentityServer/Config.cs). Note In production, it is important to give your API a useful name and display name. Use these names to describe your API in simple terms to both developers and users. Developers will use the name to connect to your API, and end users will see the display name on consent screens, etc. ### 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/v7/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/v7/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/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_7d55s.webp) Note On first startup, IdentityServer will use its automatic key management feature to create a signing key and store it in the `src/IdentityServer/keys` directory. To avoid accidentally disclosing cryptographic secrets, the entire `keys` directory should be excluded from source control. It will be recreated if it is not present. ## 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(); ``` Note Audience validation is disabled here because access to the api is modeled with `ApiScopes` only. By default, no audience will be emitted unless the api is modeled with `ApiResources` instead. See [here](/identityserver/apis/aspnetcore/jwt/#adding-audience-validation) for a more in-depth discussion. ### 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/v7/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; } ``` Note If you get an error connecting, it may be that the development certificate for `localhost` is not trusted. You can run *dotnet dev-certs https —trust* in order to trust the development certificate. This only needs to be done once. ### 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); ``` Note Copy and paste the access token from the console to [jwt.ms](https://jwt.ms) to inspect the raw token. ### 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/v7/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_Z1KI4gF.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. Note By default, an access token will contain claims about the scope, lifetime (nbf and exp), the client ID (client\_id) and the issuer name (iss). #### 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`: ```cs 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. Note We recommend you do the quickstarts in order. If you’d like to start here, begin from a copy of the [reference implementation of Quickstart 1](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/1_ClientCredentials). Throughout this quickstart, paths are written relative to the base `quickstart` directory created in part 1, which is the root directory of the reference implementation. You will also need to [install the IdentityServer templates](/identityserver/quickstarts/0-overview/#preparation). ## 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`. Note There is also a template called `duende-is-inmem` which combines the basic IdentityServer from the `duende-is-empty` template with the quickstart UI from the `duende-is-ui` template. 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); ``` Note All standard scopes and their corresponding claims can be found in the OpenID Connect [specification](https://openid.net/specs/openid-connect-core-1_0.html#scopeclaims). ### 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 ``` Note This version of the quickstarts uses [Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-8.0\&tabs=visual-studio) for the web client. If you prefer MVC, the conversion is straightforward. See the [quickstart for IdentityServer](/identityserver/quickstarts/2-interactive/) that uses it. ### 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; }); ``` Note If you are unfamiliar with the fundamentals of how the ASP.NET Core authentication system works, then we recommend this recording of an [Introduction to ASP.NET Core Authentication and Authorization](https://www.youtube.com/watch?v=02Yh3sxzAYI). `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). Note This uses the *authorization code* flow with PKCE to connect to the OpenID Connect provider. See [here](/identityserver/fundamentals/clients/) for more information on protocol flows. ### 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(); ``` Note See the ASP.NET Core documentation on [Razor Pages authorization conventions](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/razor-pages-authorization?view=aspnetcore-8.0) for more options that allow you to specify authorization on a per page or directory basis. ### 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_AJ3a6.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_ZRI7FL.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.ms](https://jwt.ms) to inspect their content. ## 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_Z2x1vAC.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 ```cs 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. Note The quickstart UI auto-provisions external users. When an external user logs in for the first time, a new local user is created with a copy of all the external user’s claims. This auto-provisioning process occurs in the `OnGet` method of `src/IdentityServer/Pages/ExternalLogin/Callback.cshtml.cs`, and is completely customizable. For example, you could modify `Callback` so that it will require registration before provisioning the external user. #### Add Google Support [Section titled “Add Google Support”](#add-google-support) `Microsoft.AspnetCore.Authentication.Google` no longer maintained Before .NET 10, the `Microsoft.AspnetCore.Authentication.Google` package was provided by Microsoft. Starting with .NET 10, Microsoft [stopped shipping new versions of the `Microsoft.AspnetCore.Authentication.Google` package](https://github.com/dotnet/aspnetcore/issues/61817). To add Google authentication, we recommend using the [`Google.Apis.Auth.AspNetCore3`](https://www.nuget.org/packages/Google.Apis.Auth.AspNetCore3/) package that is shipped by Google. 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 ```cs 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"]; }); ``` Note Note that the `authenticationScheme` and `displayName` parameters are optional. They are added here to make the login button display a short and concise “Google” instad of the default “Google OpenIdConnect”. 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_1ioikG.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. Note The login page renders the Google button automatically when there are external providers registered as authentication schemes. See the `BuildModelAsync` method in `src/IdentityServer/Pages/Account/Login/Index.cshtml.cs` and the corresponding Razor template for more details. ----- # 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 Note We recommend you do the quickstarts in order. If you’d like to start here, begin from a copy of the [reference implementation of Quickstart 2](https://github.com/DuendeSoftware/samples/tree/main/IdentityServer/v7/Quickstarts/2_InteractiveAspNetCore). Throughout this quickstart, paths are written relative to the base `_quickstart` directory created in part 1, which is the root directory of the reference implementation. You will also need to [install the IdentityServer templates](/identityserver/quickstarts/0-overview/#preparation). ## 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) Note We recommend you do the quickstarts in order. If you’d like to start here, begin from a copy of the [reference implementation of Quickstart 3](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/3_AspNetCoreAndApis). Throughout this quickstart, paths are written relative to the base `quickstart` directory created in part 1, which is the root directory of the reference implementation. You will also need to [install the IdentityServer templates](/identityserver/quickstarts/0-overview/#preparation). 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. Note This quickstart shows how to add Entity Framework support to IdentityServer manually. There is also a template that will create a new IdentityServer project with the EntityFramework integration already added: `dotnet new duende-is-ef`. ## 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); //... } ``` Note You will use Entity Framework migrations later on in this quickstart to manage the database schema. The call to `MigrationsAssembly(...)` tells Entity Framework that the host project will contain the migrations. This is necessary since the host project is in a different assembly than the one that contains the `DbContext` classes. ## 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. Note The approach used in this quickstart is used to make it easy to get IdentityServer up and running. You should devise your own database creation and maintenance strategy that is appropriate for your architecture. 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_ZQhnCA.webp) Note The `InitializeDatabase` method is convenient way to seed the database, but this approach is not ideal to leave in to execute each time the application runs. Once your database is populated, consider removing the call to the API. ## 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. Note We recommend you do the quickstarts in order. If you’d like to start here, begin from a copy of the [reference implementation of Quickstart 4](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/4_EntityFramework). Throughout this quickstart, paths are written relative to the base `quickstart` directory created in part 1, which is the root directory of the reference implementation. You will also need to [install the IdentityServer templates](/identityserver/quickstarts/0-overview/#preparation). 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. Note This quickstart assumes you are familiar with how ASP.NET Core Identity works. If you are not, it is recommended that you first [learn about it](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-8.0). 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$”. Note The template uses Sqlite as the database for the users, and EF migrations are pre-created in the template. If you wish to use a different database provider, you will need to change the provider used in the code and re-create the EF migrations. ## 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_Z1dPLbb.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_Z2mEuoa.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(); ``` Note Caution: this will destroy your test users when you make changes to them. While that is convenient for this quickstart, it is not recommended in production! 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/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. Similar to JavaScript SPAs, you can build Blazor WASM applications with and without a backend. Not having a backend has all the security disadvantages we discussed already in the JavaScript quickstart. If you are building Blazor WASM apps that do not deal with sensitive data and you want to use the no-backend approach, have a look at the standard Microsoft templates, which are using this style. In this quickstart we will focus on how to build a Blazor WASM application using our Duende.BFF security framework. You can find the full source code [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/7_Blazor) Note To keep things simple, we will utilize our demo IdentityServer instance hosted at . We will provide more details on how to configure a Blazor client in your own IdentityServer at then end. ## Setting Up The Project [Section titled “Setting Up The Project”](#setting-up-the-project) The .NET 6 CLI includes a Blazor WASM with backend template. Create the directory where you want to work in, and run the following command: ```plaintext dotnet new blazorwasm --hosted ``` This will create three projects - server, client and shared. ## Configuring The Backend [Section titled “Configuring The Backend”](#configuring-the-backend) First add the following package references to the server project: ```xml ``` Next, we will add OpenID Connect and OAuth support to the backend. For this we are adding the Microsoft OpenID Connect authentication handler for the protocol interactions with the token service, 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` above the call to `builder.Build();` * Duende BFF v4 Program.cs ```cs 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.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; } .ConfigureCookies(options => { options.Cookie.Name = "__Host-blazor"; options.Cookie.SameSite = SameSiteMode.Strict; }); ``` * Duende BFF v3 Program.cs ```cs 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.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; }); ``` The last step is to add the required middleware for authentication, authorization and BFF session management. Add the following snippet after the call to `UseRouting`: Program.cs ```cs app.UseAuthentication(); app.UseBff(); app.UseAuthorization(); app.MapBffManagementEndpoints(); ``` Finally you can run the server project. This will start the host, which will in turn deploy the Blazor application to your browser. 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 a Blazor application. *`a)`* Add the authentication/authorization related package to the client project file: ```xml ``` *`b)`* Add a using statement to `_Imports.razor` to bring the above package in scope: ```cs @using Microsoft.AspNetCore.Components.Authorization ``` *`c)`* To propagate the current authentication state to all pages in your Blazor client, you add a special component called `CascadingAuthenticationState` to your application. This is done by wrapping the Blazor router with that component in `App.razor`: ```xml Not found

Sorry, there's nothing at this address.

``` *`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`: ```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. This component needs to be implemented, 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 has a server-side component that allows querying 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. Add a file with the following content: ```cs using System.Net; using System.Net.Http.Json; using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; namespace Blazor6.Client.BFF; public class BffAuthenticationStateProvider : AuthenticationStateProvider { private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60); private readonly HttpClient _client; private readonly ILogger _logger; private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0); private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity()); public BffAuthenticationStateProvider( HttpClient client, ILogger logger) { _client = client; _logger = logger; } public override async Task GetAuthenticationStateAsync() { return new AuthenticationState(await GetUser()); } private async ValueTask GetUser(bool useCache = true) { var now = DateTimeOffset.Now; if (useCache && now < _userLastCheck + UserCacheRefreshInterval) { _logger.LogDebug("Taking user from cache"); return _cachedUser; } _logger.LogDebug("Fetching user"); _cachedUser = await FetchUser(); _userLastCheck = now; return _cachedUser; } record ClaimRecord(string Type, object Value); private async Task FetchUser() { try { _logger.LogInformation("Fetching user information."); var response = await _client.GetAsync("bff/user?slide=false"); if (response.StatusCode == HttpStatusCode.OK) { var claims = await response.Content.ReadFromJsonAsync>(); var identity = new ClaimsIdentity( nameof(BffAuthenticationStateProvider), "name", "role"); foreach (var claim in claims) { identity.AddClaim(new Claim(claim.Type, claim.Value.ToString())); } return new ClaimsPrincipal(identity); } } catch (Exception ex) { _logger.LogWarning(ex, "Fetching user failed."); } return new ClaimsPrincipal(new ClaimsIdentity()); } } ``` …and register it in the client’s `Program.cs`: Program.cs ```cs builder.Services.AddAuthorizationCore(); builder.Services.AddScoped(); ``` If you run the server app now again, you will see a different error: Terminal ```bash fail: Duende.Bff.Endpoints.BffMiddleware[1] Anti-forgery validation failed. local path: '/bff/user' ``` This is due to the antiforgery protection that is applied automatically to the management endpoints in the BFF host. To properly secure the call, you need to add a static `X-CSRF` header to the call. See [here](/bff/fundamentals/apis/local/) for more background information. This can be easily accomplished by a delegating handler that can be plugged into the default HTTP client used by the Blazor frontend. Let’s first add the handler: ```cs public class AntiforgeryHandler : DelegatingHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { request.Headers.Add("X-CSRF", "1"); return base.SendAsync(request, cancellationToken); } } ``` …and register it in the client’s `Program.cs` (overriding the standard HTTP client configuration; requires package Microsoft.Extensions.Http): Program.cs ```cs // HTTP client configuration builder.Services.AddTransient(); builder.Services.AddHttpClient("backend", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler(); builder.Services.AddTransient(sp => sp.GetRequiredService().CreateClient("backend")); ``` This requires an additional reference in the client project: ```plaintext ``` 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 adding this code to `Index.razor`: ```plaintext @page "/" Home

Hello, Blazor BFF!

@foreach (var claim in @context.User.Claims) {
@claim.Type
@claim.Value
}
``` ## Securing The Local API [Section titled “Securing The Local API”](#securing-the-local-api) The standard Blazor template contains an API endpoint (`WeatherForecastController.cs`). Try invoking the weather page from the UI. It works both in logged in and anonymous state. We want to change the code to make sure, that only authenticated users can call the API. The standard way in ASP.NET Core would be to add an authorization requirement to the endpoint, either on the controller/action or via the endpoint routing, e.g.: ```cs app.MapControllers() .RequireAuthorization(); ``` When you now try to invoke the API anonymously, you will see the following error in the browser console: ```plaintext Access to fetch at 'https://demo.duendesoftware.com/connect/authorize?client_id=...[shortened]... (redirected from 'https://localhost:5002/WeatherForecast') from origin 'https://localhost:5002' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. ``` This happens because the ASP.NET Core authentication plumbing is triggering a redirect to the OpenID Connect provider for authentication. What we really want in that case is an API friendly status code - 401 in this scenario. This is one of the features of the BFF middleware, but you need to mark the endpoint as a BFF API endpoint for that to take effect: ```cs app.MapControllers() .RequireAuthorization() .AsBffApiEndpoint(); ``` After making this change, you should see a much better error message: ```plaintext Response status code does not indicate success: 401 (Unauthorized). ``` The client code can properly respond to this, e.g. triggering a login redirect. When you logon now and call the API, you can put a breakpoint server-side and inspect that the API controller has access to the claims of the authenticated user via the `.User` property. ## 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: ```cs 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" } }; ``` ## Further Experiments [Section titled “Further Experiments”](#further-experiments) Our Blazor BFF [sample](/bff/samples/#blazor-wasm) is based on this Quickstart. In addition it shows concepts like * better organization with components * reacting to logout * using the authorize attribute to trigger automatic redirects to the login page ----- # 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. **Consequently, we don’t offer a quickstart for this style**. 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 Note We recommend you do the quickstarts in order. If you’d like to start here, begin from a copy of the [reference implementation of Quickstart 3](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/3_AspNetCoreAndApis). Throughout this quickstart, paths are written relative to the base `quickstart` directory created in part 1, which is the root directory of the reference implementation. You will also need to [install the IdentityServer templates](/identityserver/quickstarts/0-overview/#preparation). 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() .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 ```cs 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:

```cs
// 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_Z1qWCrv.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_28Nl1j.webp)

Finally, the logout button should successfully get the user logged out.

![showing the logout view on IdentityServer](/_astro/jsbff_signed_out.C6LecfKJ_gW5qI.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:

```cs
[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 });
}
```

Note

Local APIs often make requests to remote APIs that are authorized with the user’s access token. To get the access token, call the `GetUserAccessTokenAsync` extension method on the `HttpContext`. For example: *var token = await HttpContext.GetUserAccessTokenAsync();*

### 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`.

```cs
  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(Duende.Bff.AccessTokenManagement.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.

Note

See the [client credentials quickstart](/identityserver/quickstarts/1-client-credentials/) for information on how to create the remote API used in the code above.

## 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_Z14K8q2.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_M6UnA.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.

Note

We recommend you do the quickstarts in order. If you’d like to start here, begin from a copy of the [reference implementation of Quickstart 3](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/3_AspNetCoreAndApis). Throughout this quickstart, paths are written relative to the base `quickstart` directory created in part 1, which is the root directory of the reference implementation. You will also need to [install the IdentityServer templates](/identityserver/quickstarts/0-overview/#preparation).

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 adds complexity (and thus security concerns) to your application, so consider if the [“BFF” pattern](/identityserver/quickstarts/javascript-clients/js-with-backend/) might be a better choice.

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:

```cs
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();
}
```

Note

See the [client credentials quickstart](/identityserver/quickstarts/1-client-credentials/) for information on how to create the remote API used in the code above.

*`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:

```cs
// 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

```cs
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

```cs
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__1QKUe1.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_Z20l0a9.webp)

And click the “API” button to invoke the web API:

![Showing the API results from a JavaScript fetch](/_astro/jsclient_api_results.rd6LEavw_ZFK1c9.webp)

And finally click “Logout” to sign the user out.

![Showing the IdentityServer Logged Out view](/_astro/jsclient_signed_out.DI52Y-Hj_ZNcPqM.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.

Note

Some browsers limit cross-site interactions (especially in iframes). In Safari, Firefox, or Brave you will notice that some important features will not work such as silent token renewal and check session monitoring.
-----
# 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 that 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 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 be included in the Logout Token to identify the RP session with the OP when the backchannel\_logout\_uri is used.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| BackChannelLogoutUri { get; set; }                  | RP URL that will cause the RP to log itself out when sent a Logout Token by 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. 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. 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 be included to identify the RP session with the OP when the frontchannel\_logout\_uri is used.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| 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 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](#dynamicclientregistrationoptions) { get; set; } | 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/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/dcr/models/#dynamicclientregistrationcontext) and returns a task that returns an [`IStepResult`](/identityserver/reference/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 communication 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/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/dcr/models/#dynamicclientregistrationcontext) and returns a task that returns an [`IStepResult`](/identityserver/reference/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 jwks 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

```cs
var idsvrBuilder = builder.Services.AddIdentityServer();
```

Note

Many of the fundamental configuration settings can be set on the options. See the `[IdentityServerOptions](/identityserver/reference/options)` reference for more details.

## 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).

Note

We recommend that you use automatic key management. This section covers the configuration methods needed for manual configuration of signing keys, which are usually only needed if your license does not include automatic key management or if you are [migrating](/identityserver/fundamentals/key-management/#migrating-from-static-keys-to-automatic-key-management) from manually managed keys to automatic 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

```cs
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

```cs
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.

```cs
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

Note

The client id and a client credential is required to authenticate to the endpoint using any valid form of authentication that has been configured for it (much like the token endpoint).

### 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.

Note

To validate these implementation specific values and use them to identity the user that is to be authenticated, you are required to implement the `IBackchannelAuthenticationUserValidator` interface.

### 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

```http
POST /connect/ciba


    client_id=client1&
    client_secret=secret&
    scope=openid api1&
    login_hint=alice
```

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.

```cs
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:

```cs
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

```text
POST /connect/deviceauthorization


    client_id=client1&
    client_secret=secret&
    scope=openid api1
```

## .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.

```cs
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.

```cs
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.

```cs
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.

```cs
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/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.

```cs
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.

```cs
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.

```cs
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/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

```cs
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

```cs
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`**

  Ihe 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 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.

```cs
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.:

```cs
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.:

```cs
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.

```cs
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.

```cs
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.

```cs
/// 
/// 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.

```cs
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

```cs
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.

Note

*X.509 certificates* have an expiration date, but IdentityServer does not use this data to validate the certificate and throw an exception. If a certificate has expired then you must decide whether to continue using it or replace it with a new 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

```cs
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`.

```cs
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

```cs
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 `IssuerName` 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 true.

* **`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.

Note

Duende IdentityServer may ship preview features, which can be configured using preview options. Note that preview features can be removed and may break in future releases.

#### 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/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.

Note

If a custom implementation of `IAuthorizeInteractionResponseGenerator` is desired, then it’s [recommended](/identityserver/ui/custom/#built-in-authorizeinteractionresponsegenerator) to derive from the built-in `AuthorizeInteractionResponseGenerator` to inherit all the default logic pertaining to log in and consent semantics.

## 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)

```cs
/// 
/// 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)

```cs
/// 
/// 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/models/ciba-login-request/) objects which represent pending login requests for the current user.

* **`GetLoginRequestByInternalIdAsync`**

  Returns the [BackchannelUserLoginRequest](/identityserver/reference/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/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 `