IdentityServer: Documentation for Duende IdentityServer ----- # Duende IdentityServer > Overview of Duende IdentityServer framework for OpenID Connect and OAuth 2.x protocols, covering extensibility, security scenarios, licensing, and support. Duende IdentityServer is a highly extensible, standards-compliant framework for implementing the OpenID Connect, OAuth 2.x and SAML protocols in .NET and ASP.NET Core. It offers deep flexibility for handling authentication, authorization, and token issuance and can be adapted to fit complex custom security scenarios. [GitHub Repository ](https://github.com/DuendeSoftware/products/tree/main/identity-server/)View the source code for this library on GitHub. [NuGet Package ](https://www.nuget.org/packages/Duende.IdentityServer/)View the package on NuGet.org. ## Extensibility Points [Section titled “Extensibility Points”](#extensibility-points) * **Customizable User Experience**: Go beyond simple branding to fully customizable user interfaces. * **Core Engine Customization**: The engine itself is modular and built from services that can be extended or overridden. ## Advanced Security Scenarios [Section titled “Advanced Security Scenarios”](#advanced-security-scenarios) Duende IdentityServer supports a wide range of security scenarios for modern applications: * **Federation**: Easily integrate with external identity providers or other authentication services using [federation](/identityserver/ui/federation/). * **Token Exchange**: Enable secure token exchange between clients and services with [Token Exchange](/identityserver/tokens/extension-grants/#token-exchange). * **Audience Constrained Tokens**: Restrict tokens to specific audiences, increasing security in multi-service architectures. Learn more about [audience-constrained tokens](/identityserver/fundamentals/resources/isolation/). * **Sender Constrained Tokens**: Implement Proof of Possession (PoP) tokens with [DPoP or mTLS](/identityserver/tokens/pop/), which bind tokens to the client, adding another layer of protection. * **Pushed Authorization Requests (PAR)**: Support [Pushed Authorization Requests](/identityserver/tokens/par/) to enhance the security of the authorization flow. * **FAPI 2.0**: protect APIs in high-value scenarios with the [FAPI 2.0 Security profile](/identityserver/tokens/fapi-2-0-specification/). ## Licensing [Section titled “Licensing”](#licensing) Duende IdentityServer is source-available, but **requires a paid [license](https://duendesoftware.com/products/identityserver) for production use.** * **Development and Testing**: You are free to use and explore the code for development, testing, or personal projects without a license. * **Production**: A license is required for production environments. * **Free Community Edition**: A free Community Edition license is available for qualifying companies and non-profit organizations. Learn more [here](https://duendesoftware.com/products/communityedition). ## Reporting Issues and Getting Support [Section titled “Reporting Issues and Getting Support”](#reporting-issues-and-getting-support) * For bug reports or feature requests, [use our developer community forum](https://github.com/DuendeSoftware/community). * For security-related concerns, please contact us privately at: ****. ----- # Protecting APIs > Learn how to secure and protect your APIs using Duende IdentityServer's token-based authentication and authorization Duende IdentityServer issues tokens for accessing resources. These resources are very often HTTP-based APIs, but could be also other “invocable” functionality like messaging endpoints, gRPC services or even good old XML Web Services. See the [issuing tokens](/identityserver/tokens/) section on more information on access tokens and how to request them. ## Adding API Endpoints to IdentityServer [Section titled “Adding API Endpoints to IdentityServer”](#adding-api-endpoints-to-identityserver) It’s a common scenario to add additional API endpoints to the application hosting IdentityServer. These endpoints are typically protected by IdentityServer itself. For simple scenarios, we give you some helpers. See the advanced section to understand more of the internal plumbing. Start by registering your API as an `ApiScope`, (or resource) e.g.: ```csharp var scopes = new List { // local API new ApiScope(IdentityServerConstants.LocalApi.ScopeName), }; ``` …and give your clients access to this API, e.g.: ```csharp new Client { // rest omitted AllowedScopes = { IdentityServerConstants.LocalApi.ScopeName }, } ``` To enable token validation for local APIs, add the following to your IdentityServer startup: Program.cs ```csharp builder.Services.AddLocalApiAuthentication(); ``` To protect an API endpoint, call `RequireAuthorization` with the `LocalApi.PolicyName` policy: ```csharp app.MapGet("/localApi", () => { // omitted }).RequireAuthorization(LocalApi.PolicyName); ``` To protect an API controller, decorate it with an `Authorize` attribute using the `LocalApi.PolicyName` policy: ```csharp [Route("localApi")] [Authorize(LocalApi.PolicyName)] public class LocalApiController : ControllerBase { public IActionResult Get() { // omitted } } ``` Authorized clients can then request a token for the `IdentityServerApi` scope and use it to call the API. ## Discovery [Section titled “Discovery”](#discovery) You can also add your endpoints to the discovery document if you want, e.g.like this:: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Discovery.CustomEntries.Add("local_api", "~/localapi"); }) ``` ## Advanced [Section titled “Advanced”](#advanced) Under the hood, the `AddLocalApiAuthentication` helper does a couple of things: * adds an authentication handler that validates incoming tokens using IdentityServer’s built-in token validation engine (the name of this handler is `IdentityServerAccessToken` or `IdentityServerConstants.LocalApi.AuthenticationScheme` * configures the authentication handler to require a scope claim inside the access token of value `IdentityServerApi` * sets up an authorization policy that checks for a scope claim of value `IdentityServerApi` This covers the most common scenarios. You can customize this behavior in the following ways: * Add the authentication handler yourself by calling `services.AddAuthentication().AddLocalApi(...)`. This way you can specify the required scope name yourself, or (by specifying no scope at all) accept any token from the current IdentityServer instance * Do your own scope validation/authorization in your controllers using custom policies or code, e.g.: Program.cs ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy => { policy.AddAuthenticationSchemes(IdentityServerConstants.LocalApi.AuthenticationScheme); policy.RequireAuthenticatedUser(); // custom requirements }); }); ``` ## Claims Transformation [Section titled “Claims Transformation”](#claims-transformation) You can provide a callback to transform the claims of the incoming token after validation. Either use the helper method, e.g.: Program.cs ```csharp builder.Services.AddLocalApiAuthentication(principal => { principal.Identities.First().AddClaim(new Claim("additional_claim", "additional_value")); return Task.FromResult(principal); }); ``` …or implement the event on the options if you add the authentication handler manually. ## DPoP Support v8.0 [Section titled “DPoP Support ”v8.0](#dpop-support) The local API authentication handler supports [DPoP](/identityserver/tokens/pop/) proof validation. When a client sends a DPoP-bound access token to a local API, the handler validates the accompanying proof token automatically. Requests must include exactly one `DPoP` header. If multiple `DPoP` headers are present, the handler rejects the request with an `invalid_dpop_proof` error. ----- # Authorization based on Scopes and Claims > Guide for implementing authorization using scope claims and ASP.NET Core authorization policies with IdentityServer access tokens The access token will include additional claims that can be used for authorization, e.g. the `scope` claim will reflect the scope the client requested (and was granted) during the token request. In ASP.NET core, the contents of the JWT payload get transformed into claims and packaged up in a `ClaimsPrincipal`. So you can always write custom validation or authorization logic in C#: ```csharp public IActionResult Get() { var isAllowed = User.HasClaim("scope", "read"); // rest omitted } ``` For better encapsulation and re-use, consider using the ASP.NET Core [authorization policy](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) feature. With this approach, you would first turn the claim requirement(s) into a named policy: ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy("read_access", policy => policy.RequireClaim("scope", "read")); }); ``` …and then enforce it, e.g. using the routing table: ```csharp app.MapControllers().RequireAuthorization("read_access"); ``` …or imperatively inside the endpoint handler: ```csharp app.MapGet("/", async (IAuthorizationService authz, ClaimsPrincipal user) => { var allowed = await authz.AuthorizeAsync(user, "read_access"); if (!allowed.Succeeded) { return Results.Forbid(); } // rest omitted }); ``` … or declaratively: ```csharp app.MapGet("/", () => { // rest omitted }).RequireAuthorization("read_access"); ``` #### Scope Claim Format [Section titled “Scope Claim Format”](#scope-claim-format) Historically, Duende IdentityServer emitted the `scope` claims as an array in the JWT. This works very well with the .NET deserialization logic, which turns every array item into a separate claim of type `scope`. The newer *JWT Profile for OAuth* [spec](/identityserver/overview/specs/) mandates that the scope claim is a single space delimited string. You can switch the format by setting the `EmitScopesAsSpaceDelimitedStringInJwt` on the [options](/identityserver/reference/v8/options/). But this means that the code consuming access tokens might need to be adjusted. The following code can do a conversion to the *multiple claims* format that .NET prefers: ```csharp namespace IdentityModel.AspNetCore.AccessTokenValidation; /// /// Logic for normalizing scope claims to separate claim types /// public static class ScopeConverter { /// /// Logic for normalizing scope claims to separate claim types /// /// /// public static ClaimsPrincipal NormalizeScopeClaims(this ClaimsPrincipal principal) { var identities = new List(); foreach (var id in principal.Identities) { var identity = new ClaimsIdentity(id.AuthenticationType, id.NameClaimType, id.RoleClaimType); foreach (var claim in id.Claims) { if (claim.Type == "scope") { if (claim.Value.Contains(' ')) { var scopes = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var scope in scopes) { identity.AddClaim(new Claim("scope", scope, claim.ValueType, claim.Issuer)); } } else { identity.AddClaim(claim); } } else { identity.AddClaim(claim); } } identities.Add(identity); } return new ClaimsPrincipal(identities); } } ``` The above code could then be called as an extension method or as part of [claims transformation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.iclaimstransformation). ----- # Validating Proof-of-Possession > Guide for validating Proof-of-Possession (PoP) access tokens in ASP.NET Core using mTLS or DPoP mechanisms IdentityServer can [bind tokens to clients](/identityserver/tokens/pop/#proof-of-possession-styles) using either mTLS or DPoP, creating a `Proof-of-Possession` (PoP) access token. When one of these mechanisms is used, APIs that use those access tokens for authorization need to validate the binding between the client and token. This document describes how to perform such validation, depending on which mechanism was used to produce a PoP token. ### Validating mTLS [Section titled “Validating mTLS”](#validating-mtls) If you are using a [mutual TLS connection](/identityserver/tokens/pop/#mutual-tls) to establish proof-of-possession, the resulting access token will contain a `cnf` claim containing the client’s certificate thumbprint. APIs validate such tokens by comparing this thumbprint to the thumbprint of the client certificate in the mTLS connection. This validation should be performed early in the pipeline, ideally immediately after the standard validation of the access token. You can do so with custom middleware like this: ```csharp // normal token validation happens here app.UseAuthentication(); // This adds custom middleware to validate cnf claim app.UseConfirmationValidation(); app.UseAuthorization(); ``` Here, `UseConfirmationValidation` is an extension method that registers the middleware that performs the necessary validation: ```csharp public static class ConfirmationValidationExtensions { public static IApplicationBuilder UseConfirmationValidation(this IApplicationBuilder app, ConfirmationValidationMiddlewareOptions options = default) { return app.UseMiddleware(options ?? new ConfirmationValidationMiddlewareOptions()); } } ``` And this is the actual middleware that validates the `cnf` claim: ```csharp // this middleware validates the cnf claim (if present) against the thumbprint of the X.509 client certificate for the current client public class ConfirmationValidationMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly ConfirmationValidationMiddlewareOptions _options; public ConfirmationValidationMiddleware( RequestDelegate next, ILogger logger, ConfirmationValidationMiddlewareOptions options = null) { _next = next; _logger = logger; _options ??= new ConfirmationValidationMiddlewareOptions(); } public async Task Invoke(HttpContext ctx) { if (ctx.User.Identity.IsAuthenticated) { // read the cnf claim from the validated token var cnfJson = ctx.User.FindFirst("cnf")?.Value; if (!String.IsNullOrWhiteSpace(cnfJson)) { // if present, make sure a valid certificate was presented as well var certResult = await ctx.AuthenticateAsync(_options.CertificateSchemeName); if (!certResult.Succeeded) { await ctx.ChallengeAsync(_options.CertificateSchemeName); return; } // get access to certificate from transport var certificate = await ctx.Connection.GetClientCertificateAsync(); var thumbprint = Base64UrlTextEncoder.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256)); // retrieve value of the thumbprint from cnf claim var cnf = JObject.Parse(cnfJson); var sha256 = cnf.Value("x5t#S256"); // compare thumbprint claim with thumbprint of current TLS client certificate if (String.IsNullOrWhiteSpace(sha256) || !thumbprint.Equals(sha256, StringComparison.OrdinalIgnoreCase)) { _logger.LogError("certificate thumbprint does not match cnf claim."); await ctx.ChallengeAsync(_options.JwtBearerSchemeName); return; } _logger.LogDebug("certificate thumbprint matches cnf claim."); } } await _next(ctx); } } public class ConfirmationValidationMiddlewareOptions { public string CertificateSchemeName { get; set; } = CertificateAuthenticationDefaults.AuthenticationScheme; public string JwtBearerSchemeName { get; set; } = JwtBearerDefaults.AuthenticationScheme; } ``` ### Validating DPoP [Section titled “Validating DPoP”](#validating-dpop) When using [DPoP](/identityserver/tokens/pop/#enabling-dpop-in-identityserver) for proof-of-possession, validating the `cnf` claim requires several steps: 1. Validating the access token as normal 2. Validating the DPoP proof token from the `DPoP` HTTP request header 3. Ensuring the authorization header uses the DPoP scheme 4. Validating the JWT format of the proof token 5. Verifying the `cnf` claim matches between tokens 6. Validating the HTTP method and URL match the request 7. Detecting replay attacks using storage 8. Managing nonce generation and validation 9. Handling clock skew between systems 10. Returning appropriate error response headers when validation fails This comprehensive validation process requires careful implementation to ensure security. Luckily for developers, we’ve implemented these steps into an easy-to-use library. You can use the `Duende.AspNetCore.Authentication.JwtBearer` NuGet package to implement this validation. ```bash dotnet add package Duende.AspnetCore.Authentication.JwtBearer ``` With this package, the configuration necessary in your startup can be as simple as this: ```csharp // adds the normal JWT bearer validation builder.Services.AddAuthentication("token") .AddJwtBearer("token", options => { options.Authority = Constants.Authority; options.TokenValidationParameters.ValidateAudience = false; options.MapInboundClaims = false; options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; }); // extends the "token" scheme above with DPoP processing and validation builder.Services.ConfigureDPoPTokensForScheme("token"); ``` You will also typically need a distributed cache, used to perform replay detection of DPoP proofs. `Duende.AspNetCore.Authentication.JwtBearer` relies on `IDistributedCache` for this, so you can supply the cache implementation of your choice. See the [Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-8.0) for more details on setting up distributed caches, along with many examples, including Redis, CosmosDB, and Sql Server. A full sample [using the default in memory caching](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/DPoP) is available on GitHub. ----- # Using JSON Web Tokens (JWTs) > Guide for validating JWT bearer tokens in ASP.NET Core applications using the JWT authentication handler On ASP.NET Core, you typically use the [JWT authentication handler](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer) for validating JWT bearer tokens. ## Validating A JWT [Section titled “Validating A JWT”](#validating-a-jwt) First you need to add a reference to the authentication handler in your API project: ```xml ``` If all you care about is making sure that an access token comes from your trusted IdentityServer, the following snippet shows the typical JWT validation configuration for ASP.NET Core: ```csharp builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // base-address of your identityserver options.Authority = "https://demo.duendesoftware.com"; // audience is optional, make sure you read the following paragraphs // to understand your options options.TokenValidationParameters.ValidateAudience = false; // it's recommended to check the type header to avoid "JWT confusion" attacks options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; }); ``` ## Adding Audience Validation [Section titled “Adding Audience Validation”](#adding-audience-validation) Simply making sure that the token is coming from a trusted issuer is not good enough for most cases. In more complex systems, you will have multiple resources and multiple clients. Not every client might be authorized to access every resource. In OAuth there are two complementary mechanisms to embed more information about the “functionality” that the token is for - `audience` and `scope` (see [defining resources](/identityserver/fundamentals/resources/api-resources/) for more information). If you designed your APIs around the concept of [API resources](/identityserver/fundamentals/resources/api-resources/), your IdentityServer will emit the `aud` claim by default (`api1` in this example): ```text { "typ": "at+jwt", "kid": "123" }. { "aud": "api1", "client_id": "mobile_app", "sub": "123", "scope": "read write delete" } ``` If you want to express in your API, that only access tokens for the `api1` audience (aka API resource name) are accepted, change the above code snippet to: ```csharp builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://demo.duendesoftware.com"; options.Audience = "api1"; options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; }); ``` ----- # Reference Tokens > Guide for implementing reference token validation in ASP.NET Core APIs using OAuth 2.0 token introspection If you are using [reference tokens](/identityserver/tokens/reference/), you need an authentication handler that implements the back-channel validation via the [OAuth 2.0 token introspection](https://tools.ietf.org/html/rfc7662) protocol, e.g. [Duende.AspNetCore.Authentication.OAuth2Introspection](/introspection/): Program.cs ```csharp builder.Services.AddAuthentication("token") .AddOAuth2Introspection("token", options => { options.Authority = Constants.Authority; // this maps to the API resource name and secret options.ClientId = "resource1"; options.ClientSecret = "secret"; }); ``` ## Supporting Both JWTs And Reference Tokens [Section titled “Supporting Both JWTs And Reference Tokens”](#supporting-both-jwts-and-reference-tokens) It is not uncommon to use the same API with both JWTs and reference tokens. In this case you set up two authentication handlers, make one the default handler and provide some forwarding logic, e.g.: Program.cs ```csharp builder.Services.AddAuthentication("token") // JWT tokens .AddJwtBearer("token", options => { options.Authority = Constants.Authority; options.Audience = "resource1"; options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; // if token does not contain a dot, it is a reference token options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection"); }) // reference tokens .AddOAuth2Introspection("introspection", options => { options.Authority = Constants.Authority; options.ClientId = "resource1"; options.ClientSecret = "secret"; }); ``` The logic of the forward selector looks like this: IntrospectionUtilities.cs ```csharp /// /// Provides a forwarding func for JWT vs reference tokens (based on existence of dot in token) /// /// Scheme name of the introspection handler /// public static Func ForwardReferenceToken(string introspectionScheme = "introspection") { string Select(HttpContext context) { var (scheme, credential) = GetSchemeAndCredential(context); if (scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase) && !credential.Contains(".")) { return introspectionScheme; } return null; } return Select; } /// /// Extracts scheme and credential from Authorization header (if present) /// /// /// public static (string, string) GetSchemeAndCredential(HttpContext context) { var header = context.Request.Headers["Authorization"].FirstOrDefault(); if (string.IsNullOrEmpty(header)) { return ("", ""); } var parts = header.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) { return ("", ""); } return (parts[0], parts[1]); } ``` ----- # ASP.NET Identity Integration > Guide to integrating ASP.NET Identity with IdentityServer for user management, including setup instructions and configuration options An ASP.NET Identity-based implementation is provided for managing the identity database for users of IdentityServer. This implementation implements the extensibility points in IdentityServer needed to load identity data for your users to emit claims into tokens. To use the ASP.NET Identity-based implementation, ensure that you have the NuGet package for the ASP.NET Identity integration. It is called `Duende.IdentityServer.AspNetIdentity`: Terminal ```bash dotnet add package Duende.IdentityServer.AspNetIdentity ``` Next, configure ASP.NET Identity normally in your IdentityServer host with the standard calls to `AddIdentity` and any other related configuration. Then in your `Program.cs`, use the `AddAspNetIdentity` extension method after the call to `AddIdentityServer`: Program.cs ```csharp builder.Services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); builder.Services.AddIdentityServer() .AddAspNetIdentity(); ``` `AddAspNetIdentity` requires as a generic parameter the class that models your user for ASP.NET Identity (and the same one passed to `AddIdentity` to configure ASP.NET Identity). This configures IdentityServer to use the ASP.NET Identity implementations of [IUserClaimsPrincipalFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.iuserclaimsprincipalfactory-1) to convert the user data into claims, `IResourceOwnerPasswordValidator` to support the [password grant type](/identityserver/tokens/password-grant/), and `IProfileService`, which uses the `IUserClaimsPrincipalFactory` to add [claims](/identityserver/fundamentals/claims/) to tokens. It also configures some of ASP.NET Identity’s options for use with IdentityServer (such as claim types to use and authentication cookie settings). If you need to use your own implementation of `IUserClaimsPrincipalFactory`, then that is supported. Our implementation of the `IUserClaimsPrincipalFactory` will use the decorator pattern to encapsulate yours. For this to work correctly, ensure that your implementation is registered in the ASP.NET Core service provider before calling the IdentityServer `AddAspNetIdentity` extension method. The `IUserProfileService` interface has two methods that IdentityServer uses to interact with the user store. The profile service added for ASP.NET Identity implements `GetProfileDataAsync` by invoking the `IUserClaimsPrincipalFactory` implementation registered in the dependency injection container. The other method on `IProfileService` is `IsActiveAsync`, which is used in various places in IdentityServer to validate that the user is ( still) active. There is no built-in concept in ASP.NET Identity to inactive users, so our implementation is hard-coded to return `true`. If you extend the ASP.NET Identity user with enabled/disabled functionality, you should derive from our `ProfileService` and override `IsUserActiveAsync(TUser user)` to check your custom enabled/disabled flags. ## Template [Section titled “Template”](#template) You can use the `duende-is-aspid` [template](/identityserver/overview/packaging/#templates) to create a starter IdentityServer host project configured to use ASP.NET Identity. See the [Quickstart Documentation](/identityserver/quickstarts/5-aspnetid/) for a detailed walkthrough. ## User Management Pages [Section titled “User Management Pages”](#user-management-pages) The IdentityServer templates only include pages necessary for the authentication flow (login, logout, consent, error). User management pages, such as forgot password, password reset, or two-factor authentication setup, are not part of the IdentityServer templates because they are specific to your user store implementation. Since ASP.NET Core Identity provides built-in support for these features, you can add them to your IdentityServer host by [scaffolding Identity into your project](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity). This gives you ready-made pages for password reset, email confirmation, two-factor authentication, and more — all integrated with the ASP.NET Core Identity user store you’ve already configured. ----- # Authentication Schemes and Cookies > Understanding the authentication schemes and cookies used by Duende IdentityServer, especially when integrated with ASP.NET Identity. Authentication in ASP.NET Core is organized into [authentication schemes](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/#authentication-scheme). A scheme is a name that corresponds to an authentication handler and its configuration options. IdentityServer relies on several specific schemes for different purposes, and understanding them is crucial, especially when integrating with ASP.NET Identity. ## Cookie Schemes [Section titled “Cookie Schemes”](#cookie-schemes) When a user logs in, their identity is established and persisted across requests using a cookie. IdentityServer uses a primary authentication cookie to track the user’s session. ### Standalone IdentityServer [Section titled “Standalone IdentityServer”](#standalone-identityserver) When using IdentityServer without ASP.NET Identity, the default cookie scheme is named `"idsrv"`, though we recommend using the constant `IdentityServerConstants.DefaultCookieAuthenticationScheme` in your code if you ever need it. The default cookie scheme is configured by default in `AddIdentityServer()`, which sets up the cookie authentication handler with this scheme name. This cookie is essential for: * maintaining the user’s authenticated session * supporting single sign-on (SSO) * managing sign-out ### With ASP.NET Identity [Section titled “With ASP.NET Identity”](#with-aspnet-identity) When you integrate ASP.NET Identity, for example using `AddAspNetIdentity()`, the configuration changes to align with ASP.NET Identity’s defaults. In this scenario, the main authentication cookie scheme is not `"idsrv"`. Instead, it uses the ASP.NET Identity default scheme name: `"Identity.Application"` (or the `IdentityConstants.ApplicationScheme` constant). This is a common point of confusion. ASP.NET Identity registers its own cookie handlers, and `AddAspNetIdentity` configures IdentityServer to use them. This means: 1. **Login UI:** When you call `HttpContext.SignInAsync`, you must use the correct scheme. If you use the `SignInManager` provided by ASP.NET Identity, it automatically uses `"Identity.Application"`. 2. **Configuration:** If you need to configure cookie options (like expiration or sliding expiration), you must configure the options for `"Identity.Application"`, not `"idsrv"`. Program.cs ```csharp services.ConfigureApplicationCookie(options => { // The default ("Identity.Application") options.Cookie.Name = IdentityConstants.ApplicationScheme; // Configure other options here... options.ExpireTimeSpan = TimeSpan.FromHours(1); options.SlidingExpiration = true; }); ``` ## Other Important Schemes [Section titled “Other Important Schemes”](#other-important-schemes) Besides the main application cookie, IdentityServer uses other schemes for specific features. ### External Authentication (e.g., Google, OIDC) [Section titled “External Authentication (e.g., Google, OIDC)”](#external-authentication-eg-google-oidc) When a user signs in with an external provider (like Google or another OIDC provider), the result of that remote authentication is temporarily stored in an “external” cookie. This allows your login logic to read the claims from the external provider before fully signing the user into your main local session. IdentityServer always uses the `"idsrv.external"` scheme here, available in the `IdentityServerConstants.ExternalCookieAuthenticationScheme` constant. ### Check Session Cookie [Section titled “Check Session Cookie”](#check-session-cookie) IdentityServer session management requires a separate cookie to monitor the session state without sending the large authentication cookie. The [User Session Service](/identityserver/reference/v8/services/user-session-service/) manages this cookie. * **Default Name:** `"idsrv.session"` (Constant: `IdentityServerConstants.DefaultCheckSessionCookieName`). Note this cookie is not marked as `HttpOnly`, so it can be accessed in client-side code. The JavaScript code that is required to check user sessions in the background also requires access to this cookie, and needs it to be `HttpOnly`. ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) * **Mixing Schemes:** Attempting to `SignOutAsync("idsrv")` when ASP.NET Identity is in use will have no effect on the actual `"Identity.Application"` cookie, leaving the user logged in. Always use the constants or the helper services (like `SignInManager`) that match your configuration. * **Cookie Configuration:** Setting options on the default authentication scheme (which might differ from the effective cookie scheme) or configuring the wrong named options instance will result in settings (like `Cookie.SameSite` or `ExpireTimeSpan`) being ignored. ----- # Configuration API > Documentation for the Configuration API endpoints that enable management and configuration of IdentityServer implementations The Configuration API is a collection of endpoints that allow for management and configuration of an IdentityServer implementation. The Configuration API can be hosted either separately or within the IdentityServer implementation, and is distributed through the separate [Duende.IdentityServer.Configuration NuGet package](https://www.nuget.org/packages/Duende.IdentityServer.Configuration). Currently, the Configuration API supports the [Dynamic Client Registration](/identityserver/configuration/dcr/) protocol. The Configuration API source code is available [on GitHub](https://github.com/DuendeSoftware/products/tree/main/identity-server/src/Configuration). Samples of the Configuration API are available [here](/identityserver/samples/configuration/). ----- # Dynamic Client Registration (DCR) > Learn how to configure and use Dynamic Client Registration (DCR) to automatically register OAuth clients with IdentityServer Dynamic Client Registration (DCR) is the process of registering OAuth clients dynamically. It allows OAuth client applications to programmatically register themselves with an authorization server at runtime, rather than requiring manual configuration. The client provides information about itself and specifies its desired configuration in an HTTP request to the configuration endpoint. If the request is authorized and valid, the endpoint will then create the necessary client configuration and return an HTTP response describing the new client. DCR eliminates the need for a manual registration process, making it more efficient and less time-consuming to register new clients. It can help automate the onboarding of new applications in large-scale OAuth ecosystems, such as microservices, mobile apps, and partner APIs. ## Installation And Hosting [Section titled “Installation And Hosting”](#installation-and-hosting) DCR in Duende IdentityServer is provided as a separate NuGet package, [`Duende.IdentityServer.Configuration`](https://www.nuget.org/packages/Duende.IdentityServer.Configuration), which contains the Configuration API and endpoints required to support DCR. The Configuration API can be installed in a separate host from IdentityServer, or in the same host. In many cases, it is desirable to host the configuration API and IdentityServer separately. This facilitates the ability to restrict access to the configuration API at the network level separately from IdentityServer and keeps IdentityServer’s access to the configuration data read-only. In other cases, you may find that hosting the two systems together better fits your needs. ### Separate Host For Configuration API [Section titled “Separate Host For Configuration API”](#separate-host-for-configuration-api) To host the Configuration API separately from IdentityServer, you will need to create a new ASP.NET Core Web application which will host the Configuration API. 1. **Create a new project of type “Empty Web Application”** Terminal ```bash dotnet new web -n Configuration ``` 2. **Add the `Duende.IdentityServer.Configuration` package** Terminal ```bash cd Configuration dotnet add package Duende.IdentityServer.Configuration ``` 3. **Configure services to include the Configuration API** Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = ""; ); ``` 4. **Add and configure the client configuration store** The Configuration API uses the `IClientConfigurationStore` abstraction to persist new clients to the configuration store. Your Configuration API host needs an implementation of this interface. You can either use the Entity Framework Core-based implementation, or implement the interface yourself. See [the IClientConfigurationStore reference](/identityserver/reference/v8/stores/) for more details. If you wish to use the built-in implementation, install its NuGet package and add it to the ASP.NET Core service provider. Terminal ```bash dotnet add package Duende.IdentityServer.Configuration.EntityFramework ``` The `AddClientConfigurationStore()` extension method registers the built-in implementation of the `IClientConfigurationStore` interface with the service provider. Make sure to also configure the connection string to the [configuration store](/identityserver/data/ef/#configuration-store-support): Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = "" ).AddClientConfigurationStore(); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddConfigurationDbContext(options => { options.ConfigureDbContext = builder => builder.UseSqlite(connectionString); }); ``` 5. **Map the Configuration API endpoints** Program.cs ```csharp app.MapDynamicClientRegistration() .RequireAuthorization("DCR"); ``` The `MapDynamicClientRegistration` extension method registers the DCR endpoints and returns an `IEndpointConventionBuilder` which you can use to define authorization requirements for your DCR endpoint. See [Authorization](#authorization) for more details about implementing authorization for the DCR endpoint. ### Shared Host For Configuration API and IdentityServer [Section titled “Shared Host For Configuration API and IdentityServer”](#shared-host-for-configuration-api-and-identityserver) The Configuration API can be hosted by your Duende IdentityServer host. You’ll need to add the Configuration API’s services to the service collection, and configure the store implementation. 1. **Add the `Duende.IdentityServer.Configuration` package** Terminal ```bash cd Configuration dotnet add package Duende.IdentityServer.Configuration ``` 2. **Configure services to include the Configuration API** Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = ""; ); ``` 3. **Add and configure the client configuration store** The Configuration API uses the `IClientConfigurationStore` abstraction to persist new clients to the configuration store. Your Configuration API host needs an implementation of this interface. You can either use the Entity Framework Core-based implementation, or implement the interface yourself. See [the IClientConfigurationStore reference](/identityserver/reference/v8/stores/) for more details. If you wish to use the built-in implementation, install its NuGet package and add it to the ASP.NET Core service provider. Terminal ```bash dotnet add package Duende.IdentityServer.Configuration.EntityFramework ``` The `AddClientConfigurationStore()` extension method registers the built-in implementation of the `IClientConfigurationStore` interface with the service provider. Make sure to also configure the connection string to the [configuration store](/identityserver/data/ef/#configuration-store-support) if you haven’t already as part of your IdentityServer host: Program.cs ```csharp builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = "" ).AddClientConfigurationStore(); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddConfigurationDbContext(options => { options.ConfigureDbContext = builder => builder.UseSqlite(connectionString); }); ``` 4. **Map the Configuration API endpoints** Program.cs ```csharp app.MapDynamicClientRegistration() .RequireAuthorization("DCR"); ``` The `MapDynamicClientRegistration` extension method registers the DCR endpoints and returns an `IEndpointConventionBuilder` which you can use to define authorization requirements for your DCR endpoint. See [Authorization](#authorization) for more details about implementing authorization for the DCR endpoint. ### Adding the Registration Endpoint to the Discovery Document [Section titled “Adding the Registration Endpoint to the Discovery Document”](#adding-the-registration-endpoint-to-the-discovery-document) By default, the Dynamic Client Registration (DCR) endpoint is not included in the [discovery document](/identityserver/reference/v8/endpoints/discovery/) of Duende IdentityServer. To include it, change the Discovery Document options when registering IdentityServer in the service collection: Program.cs ```csharp builder.Services.AddIdentityServer(options => { // Either use a static URL for the registration endpoint, when hosted outside of IdentityServer: options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Static; options.Discovery.DynamicClientRegistration.StaticRegistrationEndpoint = new Uri("https://my-configuration-api/connect/dcr"); // Or use inferred when the registration endpoint is hosted within IdentityServer: options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Inferred; }); ``` Cross-host Registration Endpoint When using `RegistrationEndpointMode.Static` with a registration endpoint on a different host than the authority, clients that consume the discovery document may reject the cross-host endpoint by default due to endpoint validation in their discovery policy. This applies to [Duende IdentityModel](/identitymodel/endpoints/discovery/#cross-host-endpoints), and may also apply to other client libraries that validate discovery document endpoints. You will need to configure the discovery policy on any downstream service that fetches the discovery document. ## Authorization [Section titled “Authorization”](#authorization) When implementing Dynamic Client Registration (DCR), it is important to consider authentication and authorization for the Configuration API endpoint. While not strictly required, it is recommended that you implement some form of authentication and authorization for the DCR endpoint. You don’t want anyone with access to the DCR endpoint to be able to register clients! The specifications that define DCR allow both open registration, where authentication and authorization are absent and all client software can register with the authorization server, and protected registration, where an initial access token is required to register. The Configuration API creates standard ASP.NET endpoints that can be protected through traditional ASP.NET authorization. Alternatively, the Dynamic Client Registration `software_statement` parameter can be used to authenticate requests. ### Traditional ASP.NET Authorization [Section titled “Traditional ASP.NET Authorization”](#traditional-aspnet-authorization) You can authorize access to the Configuration API Endpoints using [authorization policies](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies), just like any other endpoint created in an ASP.NET Web application. That authorization policy can use any criteria that an authorization policy might enforce, such as checking for particular claims or scopes. One possibility is to authenticate the provisioning system, that is, the system making the DCR call, using OAuth. The resulting access token could include a scope that grants access to the Configuration API. For example, you might protect the Configuration APIs with a JWT-bearer authentication scheme and an authorization policy that requires a particular scope to be present in the JWTs. You could choose any name for the scope that gives access to the Configuration APIs. Let’s use the name `IdentityServer.Configuration` for this example. You would then define the `IdentityServer.Configuration` scope as an [ApiScope](/identityserver/reference/v8/models/api-scope/) in your IdentityServer and allow the appropriate clients to access it. An automated process running in a CI pipeline could be configured as an OAuth client that uses the client credentials flow and is allowed to request the `IdentityServer.Configuration` scope. It could obtain a token using its client id and secret and then present that token when it calls the Configuration API. You might also have an interactive web application with a user interface that makes calls to the Configuration API. Again, you would define the application as an OAuth client allowed to request the appropriate scope, but this time, you’d use the authorization code flow. ### Software Statement [Section titled “Software Statement”](#software-statement) The metadata within requests to the Configuration API can be bundled together into a JWT and sent in the [`software_statement` parameter](https://datatracker.ietf.org/doc/html/rfc7591#section-2.3). If you can establish a trust relationship between the Configuration API and the issuer of the software statement, then that can be used to decide if you want to accept registration requests. To use a software statement in this way, you would need to design the specific semantics of your software statements. How you will issue them, how you will create the necessary trust relationship between the issuer and your Configuration API, and how the Configuration API will validate the software statements are all aspects to consider. The configuration API doesn’t make any assumptions about the software statement design. By default, it does nothing with the `software_statement` parameter. To make use of software statements, customize the `DynamicClientRegistrationValidator.ValidateSoftwareStatementAsync` extension point and add your validation logic. ## Calling The Registration Endpoint [Section titled “Calling The Registration Endpoint”](#calling-the-registration-endpoint) The registration endpoint is invoked by making an HTTP POST request to the `/connect/dcr` endpoint with a JSON payload containing metadata describing the desired client as described in [RFC 7591](https://datatracker.ietf.org/doc/rfc7591/) and [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html). The supported metadata properties are listed in the reference section on the [`DynamicClientRegistrationRequest` model](/identityserver/reference/v8/dcr/models/#dynamicclientregistrationrequest). A mixture of standardized and IdentityServer-specific properties are supported. Most standardized properties that are applicable to the client credentials or code flow grants are supported. Where IdentityServer’s configuration model includes important properties that are not standardized, we have included those properties as extensions. For example, there are no standardized properties describing token lifetimes, so the dynamic client registration endpoint adds `absolute_refresh_token_lifetime`, `access_token_lifetime`, `identity_token_lifetime`, etc. ## Customization [Section titled “Customization”](#customization) The behavior of the Configuration API can be customized through the use of several extension points that control the steps that occur when a dynamic client registration request arrives. First, the incoming request is validated to ensure that it is syntactically valid and semantically correct. The result of the validation process is a model which will either contain error details or a validated `Client` model. When validation succeeds, the validated request is passed on to the request processor. The request processor is responsible for generating properties of the `Client` that are not specified in the request. For example, the `client_id` is not normally specified in the request and is instead generated by the processor. When the processor is finished generating values, it passes the final client object to the store and returns an `IDynamicClientRegistrationResponse` indicating success or failure. This response object is finally used by the response generator to generate an HTTP response. Each of the validation and processing steps might also encounter an error. When that occurs, errors are conveyed using the `DynamicClientRegistrationError` class. ### Validation [Section titled “Validation”](#validation) To customize the validation process, you can implement the `IDynamicClientRegistrationValidator` interface, or extend the default implementation, `DynamicClientRegistrationValidator`. The default implementation includes many virtual methods, allowing you to use most of the base functionality and add your customization in a targeted manner. Each virtual method is responsible for validating a small number of parameters in the request and setting corresponding values on the client. A context object is passed to each virtual method. It contains the client object that is being built up, the original request, the claims principal that made the request, and a dictionary of additional items that can be used to pass state between customized steps. Each step should update the client in the context and return an `IStepResult` to indicate success or failure. For more details, see the [reference section on DCR validation](/identityserver/reference/v8/dcr/validation/). ### Processing [Section titled “Processing”](#processing) The request processor can be customized by implementing the `IDynamicClientRegistrationRequestProcessor` interface, or by extending the default `DynamicClientRegistrationRequestProcessor`. The default request processor contains virtual methods that allow you to override (part of) its functionality. For more details, see the [reference section on DCR request processing](/identityserver/reference/v8/dcr/processing/). ### Response Generation [Section titled “Response Generation”](#response-generation) To customize the HTTP responses of the Configuration API, you can implement the `IDynamicClientRegistrationResponseGenerator` interface, or extend the default `DynamicClientRegistrationResponseGenerator`. For more details, see the [reference section on DCR response generation](/identityserver/reference/v8/dcr/response/). ----- # Data Stores and Persistence > Overview of IdentityServer data stores types, including configuration and operational data, and their implementation options Duende IdentityServer is backed by two kinds of data: * [Configuration Data](/identityserver/data/configuration/) * [Operational Data](/identityserver/data/operational/) Data access is abstracted by store interfaces that are registered in the ASP.NET Core service provider. These store interfaces allow IdentityServer to access the data it needs at runtime when processing requests. You can implement these interfaces yourself and thus can use any database you wish. If you prefer a relational database for this data, then we provide [EntityFramework Core](/identityserver/data/ef/) implementations. ----- # Configuration Data > Documentation about configuration data models and stores in Duende IdentityServer, including client, resource, and identity provider stores Configuration data models the information for [Clients](/identityserver/fundamentals/clients/) and [Resources](/identityserver/fundamentals/resources). ## Stores [Section titled “Stores”](#stores) Store interfaces are designed to abstract accessing the configuration data. The stores used in Duende IdentityServer are: * [Client store](/identityserver/reference/v8/stores/client-store/) for `Client` data. * [CORS policy service](/identityserver/reference/v8/stores/cors-policy-service/) for [CORS support](/identityserver/tokens/cors/). Given that this is so closely tied to the `Client` configuration data, the CORS policy service is considered one of the configuration stores. * [Resource store](/identityserver/reference/v8/stores/resource-store/) for `IdentityResource`, `ApiResource`, and `ApiScope` data. * [Identity Provider store](/identityserver/reference/v8/stores/idp-store/) for `IdentityProvider` data. ## Registering Custom Stores [Section titled “Registering Custom Stores”](#registering-custom-stores) Custom implementations of the stores must be registered in the ASP.NET Core service provider. There are [convenience methods](/identityserver/reference/v8/di/#configuration-stores) for registering these. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddIdentityProviderStore(); ``` ## Caching Configuration Data [Section titled “Caching Configuration Data”](#caching-configuration-data) Configuration data is used frequently during request processing. If this data is loaded from a database or other external store, then it might be expensive to frequently re-load the same data. * v8.0+ Duende IdentityServer provides [convenience methods](/identityserver/reference/v8/di#caching-configuration-data) to enable caching data from the various stores. The caching implementation is built on Microsoft’s [`HybridCache`](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid) from the `Microsoft.Extensions.Caching.Hybrid` package, registered as a [keyed service](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#keyed-services) under `ServiceProviderKeys.ConfigurationStoreCache`. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache() .AddIdentityProviderStoreCache(); ``` For Entity Framework users, there is a convenience method `AddConfigurationStoreCache()` that enables caching for all configuration stores at once: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConfigurationStore(...) .AddConfigurationStoreCache(); ``` The duration of the data in the default cache is configurable on the [`IdentityServerOptions`](/identityserver/reference/v8/options#caching). For example: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(5); options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(5); }) .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache(); ``` Further customization of the cache is possible: * The caching stores use a keyed `HybridCache` instance registered under `ServiceProviderKeys.ConfigurationStoreCache`. You can customize the `HybridCache` behavior by configuring the keyed service registration (e.g., adding a distributed cache backend via `IDistributedCache`). * By default, only the L1 (in-memory) cache tier is used. To enable L2 (distributed) caching, register an `IDistributedCache` implementation (e.g., Redis via `AddStackExchangeRedisCache`). `HybridCache` will automatically use it as the L2 tier. * v7.0 Duende IdentityServer provides [convenience methods](/identityserver/reference/v8/di/#caching-configuration-data) to enable caching data from the various stores. The caching implementation relies upon an `ICache` service and must also be added to the ASP.NET Core service provider. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache() .AddIdentityProviderStoreCache(); ``` The duration of the data in the default cache is configurable on the [`IdentityServerOptions`](/identityserver/reference/v7/options#caching). For example: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(5); options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(5); }) .AddClientStore() .AddCorsPolicyService() .AddResourceStore() .AddInMemoryCaching() .AddClientStoreCache() .AddCorsPolicyCache() .AddResourceStoreCache(); ``` Further customization of the cache is possible: * If you wish to customize the caching behavior for the specific configuration objects, you can replace the `ICache` service implementation in the dependency injection system. * The default implementation of the `ICache` itself relies upon the `IMemoryCache` interface (and `MemoryCache` implementation) provided by .NET. If you wish to customize the in-memory caching behavior, you can replace the `IMemoryCache` implementation in the dependency injection system. ## In-Memory Stores [Section titled “In-Memory Stores”](#in-memory-stores) The various [in-memory configuration APIs](/identityserver/reference/v8/di/#configuration-stores) allow for configuring IdentityServer from an in-memory list of the various configuration objects. These in-memory collections can be hard-coded in the hosting application, or could be loaded dynamically from a configuration file or a database. By design, though, these collections are only created when the hosting application is starting up. Use of these configuration APIs are designed for use when prototyping, developing, and/or testing where it is not necessary to dynamically consult database at runtime for the configuration data. This style of configuration might also be appropriate for production scenarios if the configuration rarely changes, or it is not inconvenient to require restarting the application if the value must be changed. ----- # Entity Framework Core Integration > Documentation for using Entity Framework with IdentityServer to store configuration and operational data in any EF-supported database An EntityFramework-based implementation is provided for the configuration and operational data extensibility points in IdentityServer. The use of EntityFramework allows any EF-supported database to be used with this library. The features provided by this library are broken down into two main areas: configuration store and operational store support. These two different areas can be used independently or together, based upon the needs of the hosting application. To use this library, ensure that you have the NuGet package for the EntityFramework integration. It is called `Duende.IdentityServer.EntityFramework`. You can install it with: ```plaintext dotnet add package Duende.IdentityServer.EntityFramework ``` ## Configuration Store Support [Section titled “Configuration Store Support”](#configuration-store-support) For storing [configuration data](/identityserver/configuration/), the configuration store can be used. This support provides implementations of the `IClientStore`, `IResourceStore`, `IIdentityProviderStore`, and the `ICorsPolicyService` extensibility points. These implementations use a `DbContext`-derived class called `ConfigurationDbContext` to model the tables in the database. To use the configuration store support, in Program.cs use the `AddConfigurationStore` extension method after the call to `AddIdentityServer`: Program.cs ```csharp const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; builder.Services.AddIdentityServer() // this adds the config data from DB (clients, resources, CORS) .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }); ``` To configure the configuration store, use the `ConfigurationStoreOptions` options object passed to the configuration callback. ### ConfigurationStoreOptions [Section titled “ConfigurationStoreOptions”](#configurationstoreoptions) This options class contains properties to control the configuration store and `ConfigurationDbContext`. `ConfigureDbContext` Delegate of type `Action` used as a callback to configure the underlying `ConfigurationDbContext`. The delegate can configure the `ConfigurationDbContext` in the same way if EF were being used directly with `AddDbContext`, which allows any EF-supported database to be used. `DefaultSchema` Allows setting the default database schema name for all the tables in the `ConfigurationDbContext` ```csharp options.DefaultSchema = "myConfigurationSchema"; ``` If you need to change the schema for the Migration History Table, you can chain another action to the `UseSqlServer`: ```csharp options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly) .MigrationsHistoryTable("MyConfigurationMigrationTable", "myConfigurationSchema")); ``` ### Enabling Caching For Configuration Store [Section titled “Enabling Caching For Configuration Store”](#enabling-caching-for-configuration-store) To enable caching for the EF configuration store implementation, use the `AddConfigurationStoreCache` extension method: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConfigurationStore(options => { // ... }) // this is something you will want in production to reduce load on and requests to the DB .AddConfigurationStoreCache(); ``` ## Operational Store [Section titled “Operational Store”](#operational-store) For storing [operational data](/identityserver/data/operational/) then the operational store can be used. This support provides implementations of the `IPersistedGrantStore`, `IDeviceFlowStore`, `IServerSideSessionStore`, and `ISigningKeyStore` extensibility points. The implementation uses a `DbContext`-derived class called `PersistedGrantDbContext` to model the table in the database. To use the operational store support, in Program.cs use the `AddOperationalStore` extension method after the call to `AddIdentityServer`: Program.cs ```csharp const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; builder.Services.AddIdentityServer() // this adds the operational data from DB (codes, tokens, consents) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); // this enables automatic token cleanup. this is optional. options.EnableTokenCleanup = true; options.TokenCleanupInterval = 3600; // interval in seconds (default is 3600) }); ``` To configure the operational store, use the `OperationalStoreOptions` options object passed to the configuration callback. ### OperationalStoreOptions [Section titled “OperationalStoreOptions”](#operationalstoreoptions) This options class contains properties to control the operational store and `PersistedGrantDbContext`. `ConfigureDbContext` Delegate of type `Action` used as a callback to configure the underlying `PersistedGrantDbContext`. The delegate can configure the `PersistedGrantDbContext` in the same way if EF were being used directly with `AddDbContext`, which allows any EF-supported database to be used. `DefaultSchema` Allows setting the default database schema name for all the tables in the `PersistedGrantDbContext`. `EnableTokenCleanup` Indicates whether expired grants and pushed authorization requests will be automatically cleaned up from the database. The default is `false`. `RemoveConsumedTokens` added >=5.1 Indicates whether consumed grants will be automatically cleaned up from the database. The default is `false`. `TokenCleanupInterval` The token cleanup interval (in seconds). The default is 3600 (1 hour). `ConsumedTokenCleanupDelay` added >=6.3 The consumed token cleanup delay (in seconds). The default is 0. This delay is the amount of time that must elapse before tokens marked as consumed can be deleted. Note that only refresh tokens with OneTime usage can be marked as consumed. `FuzzTokenCleanupStart` added >=7.0 The background token cleanup job runs at a configured interval. If multiple nodes run the cleanup job at the same time, update conflicts might occur in the store. To reduce the probability of that happening, the startup time can be fuzzed. When enabled, the first run is scheduled at a random time between the host startup and the configured TokenCleanupInterval. Subsequent runs are run on the configured TokenCleanupInterval. Defaults to `true`. ## Database Creation And Schema Changes Across Different IdentityServer Versions [Section titled “Database Creation And Schema Changes Across Different IdentityServer Versions”](#database-creation-and-schema-changes-across-different-identityserver-versions) It is very likely that across different versions of IdentityServer (and the EF support) that the database schema will change to accommodate new and changing features. We do not provide any support for creating your database or migrating your data from one version to another. You are expected to manage the database creation, schema changes, and data migration in any way your organization sees fit. Using EF migrations is one possible approach to this. If you do wish to use migrations, then see the [EF quickstart](/identityserver/quickstarts/4-entity-framework/) for samples on how to get started, or consult the Microsoft [documentation on EF migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/index). We publish a [sample app](https://github.com/DuendeSoftware/products/tree/main/identity-server/migrations/IdentityServerDb) that we use internally for creating databases to test the latest database schema (this is SQL Server specific). ----- # Operational Data > Documentation for managing dynamic operational data in IdentityServer including grants, keys, and server-side sessions For certain operations, IdentityServer needs a persistence store to keep dynamically created state. This data is collectively called *operational data*, and includes: * [Grants](#grants) for authorization and device codes, reference and refresh tokens, and remembered user consent * [Keys](#keys) managing dynamically created signing keys * [Server Side Sessions](#server-side-sessions) for storing authentication session data for interactive users server-side ## Grants [Section titled “Grants”](#grants) Many protocol flows produce state that represents a grant of one type or another. These include authorization and device codes, reference and refresh tokens, and remembered user consent. ### Stores [Section titled “Stores”](#stores) The persistence for grants is abstracted behind two interfaces: * The [persisted grant store](/identityserver/reference/v8/stores/persisted-grant-store/) is a common store for most grants. * The [device flow store](/identityserver/reference/v8/stores/device-flow-store/) is a specialized store for device grants. ### Registering Custom Stores [Section titled “Registering Custom Stores”](#registering-custom-stores) Custom implementations of `IPersistedGrantStore`, and/or `IDeviceFlowStore` must be registered in the ASP.NET Core service provider. For example: Program.cs ```csharp builder.Services.AddIdentityServer(); builder.Services.AddTransient(); builder.Services.AddTransient(); ``` ### Grant Expiration and Consumption [Section titled “Grant Expiration and Consumption”](#grant-expiration-and-consumption) The presence of the record in the store without a `ConsumedTime` and while still within the `Expiration` represents the validity of the grant. Setting either of these two values, or removing the record from the store effectively revokes the grant. Some grant types are one-time use only (either by definition or configuration). Once they are “used”, rather than deleting the record, the `ConsumedTime` value is set in the database marking them as having been used. This “soft delete” allows for custom implementations to either have flexibility in allowing a grant to be re-used (typically within a short window of time), or to be used in risk assessment and threat mitigation scenarios (where suspicious activity is detected) to revoke access. For refresh tokens, this sort of custom logic would be performed in the [IRefreshTokenService](/identityserver/reference/v8/services/refresh-token-service/). ### Grant Data [Section titled “Grant Data”](#grant-data) The `Data` property of the model contains the authoritative copy of the values in the store. This data is protected at rest using the ASP.NET Data Protection API. Except for `ConsumedTime`, the other properties of the model should be treated as read-only. ### Persisted Grant Service [Section titled “Persisted Grant Service”](#persisted-grant-service) Working with the grants store directly might be too low level. As such, a higher level service called the [IPersistedGrantService](/identityserver/reference/v8/services/persisted-grant-service/) is provided. It abstracts and aggregates the different grant types into one concept, and allows querying and revoking the persisted grants for a user. ## Keys [Section titled “Keys”](#keys) The [automatic key management](/identityserver/fundamentals/key-management/#automatic-key-management) feature in Duende IdentityServer requires a store to persist keys that are dynamically created. ### Signing Key Store [Section titled “Signing Key Store”](#signing-key-store) By default, the file system is used, but the storage of these keys is abstracted behind an extensible store interface. The [ISigningKeyStore](/identityserver/reference/v8/stores/signing-key-store/) is that storage interface. ### Registering a custom signing key store [Section titled “Registering a custom signing key store”](#registering-a-custom-signing-key-store) To register a custom signing key store in the ASP.NET Core service provider, there is a `AddSigningKeyStore` helper on the `IIdentityServerBuilder`. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddSigningKeyStore(); ``` ### Key Lifecycle [Section titled “Key Lifecycle”](#key-lifecycle) When keys are required, `LoadKeysAsync` will be called to load them all from the store. They are then cached automatically for some amount of time based on [configuration](/identityserver/reference/v8/options/#key-management). Periodically a new key will be created, and `StoreKeyAsync` will be used to persist the new key. Once a key is past its retirement, `DeleteKeyAsync` will be used to purge the key from the store. ### Serialized Key [Section titled “Serialized Key”](#serialized-key) The [SerializedKey](/identityserver/reference/v8/stores/signing-key-store/#serializedkey) is the model that contains the key data to persist. It is expected that the `Id` is the unique identifier for the key in the store. The `Data` property is the main payload of the key and contains a copy of all the other values. Some of the properties affect how the `Data` is processed (e.g. `DataProtected`), and the other properties are considered read-only and thus can’t be changed to affect the behavior (e.g. changing the `Created` value will not affect the key lifetime, nor will changing `Algorithm` change which signing algorithm the key is used for). ## Server Side Sessions [Section titled “Server Side Sessions”](#server-side-sessions) The [server-side sessions](/identityserver/ui/server-side-sessions/) feature in Duende IdentityServer requires a store to persist a user’s session data. ### Server-Side Session Store [Section titled “Server-Side Session Store”](#server-side-session-store) The [IServerSideSessionStore](/identityserver/reference/v8/stores/server-side-sessions/) abstracts storing the server-side session data. [ServerSideSession](/identityserver/reference/v8/stores/server-side-sessions/#serversidesession) objects act as the storage entity, and provide several properties used as metadata for the session. The `Ticket` property contains the actual serialized data used by the ASP.NET Cookie Authentication handler. By default, this serialized data is stored in an encrypted state using ASP.NET Core Data Protection. The methods on the [IServerSideSessionStore](/identityserver/reference/v8/stores/server-side-sessions/) are used to orchestrate the various management functions needed by the [server-side sessions](/identityserver/ui/server-side-sessions/#session-management) feature. ### Registering a custom store [Section titled “Registering a custom store”](#registering-a-custom-store) To register a custom server-side session store in the ASP.NET Core service provider, there is a `AddServerSideSessionStore` helper on the `IIdentityServerBuilder`. It is still necessary to call `AddServerSideSessions` to enable the server-side session feature. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddServerSideSessions() .AddServerSideSessionStore(); ``` There is also an overloaded version of a `AddServerSideSessions` that will perform both registration steps in one call. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddServerSideSessions(); ``` ### EntityFramework Store Implementation [Section titled “EntityFramework Store Implementation”](#entityframework-store-implementation) An EntityFramework Core implementation of the server-side session store is included in the [Entity Framework Integration](/identityserver/data/ef/#operational-store) operational store. When using the EntityFramework Core operational store, it will be necessary to indicate that server-side sessions need to be used with the call to the `AddServerSideSessions` fluent API. For example: Program.cs ```csharp builder.Services.AddIdentityServer() .AddServerSideSessions() .AddOperationalStore(options => { // ... }); ``` ----- # IdentityServer Deployment > Comprehensive guide covering key aspects of deploying IdentityServer including proxy configuration, data protection, data stores, caching, and health monitoring. Because IdentityServer is made up of middleware and services that you use within an ASP.NET Core application, it can be hosted and deployed with the same diversity of technology as any other ASP.NET Core application. You have the choice about * where to host your IdentityServer (on-prem or in the cloud, and if in the cloud, which one?) * which web server to use (IIS, Kestrel, Nginx, Apache, etc.) * how you’ll scale and load-balance the deployment * what kind of deployment artifacts you’ll publish (files in a folder, containers, etc.) * how you’ll manage the environment (a managed app service in the cloud, a Kubernetes cluster, etc.) While this is a lot of decisions to make, this also means that your IdentityServer implementation can be built, deployed, hosted, and managed with the same technology that you’re using for any other ASP.NET applications that you have. Microsoft publishes extensive [advice and documentation](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) about deploying ASP.NET Core applications, and it is applicable to IdentityServer implementations. We’re not attempting to replace that documentation - or the documentation for other tools that you might be using in your environment. Rather, this section of our documentation focuses on IdentityServer-specific deployment and hosting considerations. ## Proxy Servers and Load Balancers [Section titled “Proxy Servers and Load Balancers”](#proxy-servers-and-load-balancers) In typical deployments, your IdentityServer will be hosted behind a load balancer or reverse proxy. These and other network appliances often obscure information about the request before it reaches the host. Some of the behavior of IdentityServer and the ASP.NET authentication handlers depend on that information, most notably the scheme (HTTP vs HTTPS) of the request and the originating client IP address. Requests to your IdentityServer that come through a proxy will appear to come from that proxy instead of its true source on the Internet or corporate network. If the proxy performs TLS termination (that is, HTTPS requests are proxied over HTTP), the original HTTPS scheme will also no longer be present in the proxied request. Then, when the IdentityServer middleware and the ASP.NET authentication middleware process these requests, they will have incorrect values for the scheme and originating IP address. Common symptoms of this problem are * HTTPS requests get downgraded to HTTP * HTTP issuer is being published instead of HTTPS in `.well-known/openid-configuration` * Host names are incorrect in the discovery document or on redirect * Cookies are not sent with the secure attribute, which can especially cause problems with the samesite cookie attribute. In almost all cases, these problems can be solved by adding the ASP.NET `ForwardedHeaders` middleware to your pipeline. Most network infrastructure that proxies requests will set the [`X-Forwarded-For`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) and [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) HTTP headers to describe the original request’s IP address and scheme. The `ForwardedHeaders` middleware reads the information in these headers on incoming requests and makes it available to the rest of the ASP.NET pipeline by updating the [`HttpContext.HttpRequest`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/use-http-context?view=aspnetcore-7.0#httprequest). This transformation should be done early in the pipeline, certainly before the IdentityServer middleware and ASP.NET authentication middleware process requests, so that the presence of a proxy is abstracted away first. The appropriate configuration for the forwarded headers middleware depends on your environment. In general, you need to configure which headers it should respect, the IP address or IP address range of your proxy, and the number of proxies you expect (when there are multiple proxies, each one is captured in the `X-Forwarded-*` headers). There are two ways to configure this middleware: 1. Enable the environment variable `ASPNETCORE_FORWARDEDHEADERS_ENABLED`. This is the simplest option, but doesn’t give you as much control. It automatically adds the forwarded headers middleware to the pipeline, and configures it to accept forwarded headers from any single proxy, respecting the `X-Forwarded-For` and `X-Forwarded-Proto` headers. This is often the right choice for cloud hosted environments and Kubernetes clusters. 2. Configure the `ForwardedHeadersOptions` in DI, and use the `ForwardedHeaders` middleware explicitly in your pipeline. The advantage of configuring the middleware explicitly is that you can configure it in a way that is appropriate for your environment, if the defaults used by `ASPNETCORE_FORWARDEDHEADERS_ENABLED` are not what you need. Most notably, you can use the `KnownNetworks` or `KnownProxies` options to only accept headers sent by a known proxy, and you can set the `ForwardLimit` to allow for multiple proxies in front of your IdentityServer. This is often the right choice when you have more complex proxying going on, or if your proxy has a stable IP address. By default, `KnownNetworks` and `KnownProxies` support localhost with values of `127.0.0.1/8` and `::1` respectively. This is useful (and secure!) for local development environments and for solutions where the reverse proxy and the .NET web host runs on the same machine. In production environments when operating behind a proxy, you’ll need to configure the `ForwardedHeadersOptions`. Be sure to correctly set values for `KnownNetworks` and `KnownProxies` for your environments, as otherwise requests may be blocked. ```csharp builder.Services.Configure(options => { // you may need to change these ForwardedHeaders // values based on your network architecture options.ForwardedHeaders = ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto; // exact Addresses of known proxies to accept forwarded headers from. options.KnownProxies.Add(IPAddress.Parse("203.0.113.42")); // <-- change this value to the IP Address of the proxy // if the proxies could use any address from a block, that can be configured too: // var network = new IPNetwork(IPAddress.Parse("198.51.100.0"), 24); // options.KnownNetworks.Add(network); // default is 1 options.ForwardLimit = 1; }); ``` Please consult the [Microsoft documentation on configuring ASP.NET Core to work with proxy servers and load balancers](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer) for more details. ## ASP.NET Core Data Protection [Section titled “ASP.NET Core Data Protection”](#aspnet-core-data-protection) Duende IdentityServer makes extensive use of ASP.NET’s [data protection](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/) feature. It is crucial that you configure data protection correctly before you start using your IdentityServer in production. The recommended practices for setting up and using ASP.NET Core Data Protection for Duende IdentityServer are the same as for other server-side products, like BFF. See the [general ASP.NET Core Data Protection page](/general/data-protection). ### ASP.NET Data Protection Keys and IdentityServer Signing Keys [Section titled “ASP.NET Data Protection Keys and IdentityServer Signing Keys”](#aspnet-data-protection-keys-and-identityserver-signing-keys) ASP.NET’s data protection keys are sometimes confused with IdentityServer’s signing keys, but the two are completely separate keys with different purposes. IdentityServer implementations need both to function correctly. #### ASP.NET Data Protection Keys [Section titled “ASP.NET Data Protection Keys”](#aspnet-data-protection-keys) Data protection is a cryptographic library that is part of ASP.NET Core. Data protection uses private key cryptography to encrypt and sign sensitive data to ensure that it is only written and read by the application. The framework uses data protection to secure data that is commonly used by IdentityServer implementations, such as authentication cookies and anti-forgery tokens. In addition, IdentityServer itself uses data protection to protect sensitive data at rest, such as persisted grants, and sensitive data passed through the browser, such as the context objects passed to pages in the UI. The data protection keys are critical secrets for an IdentityServer implementation because they encrypt a great deal of sensitive data at rest and prevent sensitive data that is round-tripped through the browser from being tampered with. #### The IdentityServer Signing Key [Section titled “The IdentityServer Signing Key”](#the-identityserver-signing-key) Separately, IdentityServer needs cryptographic keys, called [signing keys](/identityserver/fundamentals/key-management/), to sign tokens such as JWT access tokens and id tokens. The signing keys use public key cryptography to allow client applications and APIs to validate token signatures using the public keys, which are published by IdentityServer through [discovery](/identityserver/reference/v8/endpoints/discovery/). The private key component of the signing keys are also critical secrets for IdentityServer because a valid signature provides integrity and non-repudiation guarantees that allow client applications and APIs to trust those tokens. ### IdentityServer Data Stores [Section titled “IdentityServer Data Stores”](#identityserver-data-stores) IdentityServer itself is stateless and does not require server affinity - but there is data that needs to be shared between in multi-instance deployments. ### Configuration Data [Section titled “Configuration Data”](#configuration-data) This typically includes: * resources * clients * startup configuration, e.g. key material, external provider settings etc… The way you store that data depends on your environment. In situations where configuration data rarely changes we recommend using the in-memory stores and code or configuration files. In highly dynamic environments (e.g. Saas) we recommend using a database or configuration service to load configuration dynamically. ### Operational Data [Section titled “Operational Data”](#operational-data) For certain operations, IdentityServer needs a persistence store to keep state, this includes: * issuing authorization codes * issuing reference and refresh tokens * storing consent * automatic management for signing keys You can either use a traditional database for storing operational data, or use a cache with persistence features like Redis. Duende IdentityServer includes storage implementations for above data using EntityFramework, and you can build your own. See the [data stores](/identityserver/data) section for more information. ### IdentityServer Features Using Data Protection [Section titled “IdentityServer Features Using Data Protection”](#identityserver-features-using-data-protection) Duende IdentityServer’s features that rely on data protection include: * protecting signing keys at rest (if [automatic key management](/identityserver/fundamentals/key-management/#automatic-key-management) is used and enabled) * protecting [persisted grants](/identityserver/data/operational/#persisted-grant-service) at rest (if enabled) * protecting [server-side session](/identityserver/ui/server-side-sessions/) data at rest (if enabled) * protecting [the state parameter](/identityserver/ui/login/external/#state-url-length-and-isecuredataformat) for external OIDC providers (if enabled) * protecting message payloads sent between pages in the UI (e.g. [logout context](/identityserver/ui/logout/logout-context/) and [error context](/identityserver/ui/error/)). * session management (because the ASP.NET Core cookie authentication handler requires it) ## Distributed Caching [Section titled “Distributed Caching”](#distributed-caching) Some optional features rely on ASP.NET Core distributed caching: * [State data formatter for OpenID Connect](/identityserver/ui/login/external/#state-url-length-and-isecuredataformat) * Replay cache (e.g. for [JWT client credentials](/identityserver/tokens/client-authentication/#setting-up-a-private-key-jwt-secret)) * [Device flow](/identityserver/reference/v8/stores/device-flow-store/) throttling service * Authorization parameter store In order to work in a multi-server environment, this needs to be set up correctly. Please consult the Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more details. ## Health Checks [Section titled “Health Checks”](#health-checks) You can use ASP.NET’s [health checks](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks) to monitor the health of your IdentityServer deployment. Health checks can contain arbitrary logic to test various conditions of a system. One common strategy for checking the health of IdentityServer is to make discovery requests. Successful discovery responses indicate not just that the IdentityServer host is running and able to receive requests and generate responses, but also that it was able to communicate with the configuration store. The following example code creates a health check that makes requests to the discovery endpoint. It finds the discovery endpoint’s handler by name, which requires IdentityServer `v6.3`. ```csharp public class DiscoveryHealthCheck : IHealthCheck { private readonly IEnumerable _endpoints; private readonly IHttpContextAccessor _httpContextAccessor; public DiscoveryHealthCheck(IEnumerable endpoints, IHttpContextAccessor httpContextAccessor) { _endpoints = endpoints; _httpContextAccessor = httpContextAccessor; } public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { try { var endpoint = _endpoints.FirstOrDefault(x => x.Name == IdentityServerConstants.EndpointNames.Discovery); if (endpoint != null) { var handler = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService(endpoint.Handler) as IEndpointHandler; if (handler != null) { var result = await handler.ProcessAsync(_httpContextAccessor.HttpContext); if (result is DiscoveryDocumentResult) { return HealthCheckResult.Healthy(); } } } } catch { } return new HealthCheckResult(context.Registration.FailureStatus); } } ``` Another health check that you can perform is to request the public keys that IdentityServer uses to sign tokens - the JWKS (JSON Web Key Set). Doing so demonstrates that IdentityServer is able to communicate with the signing key store, a critical dependency. The following example code creates such a health check. Just as with the previous health check, it finds the endpoint’s handler by name, which requires IdentityServer `v6.3`. ```csharp public class DiscoveryKeysHealthCheck : IHealthCheck { private readonly IEnumerable _endpoints; private readonly IHttpContextAccessor _httpContextAccessor; public DiscoveryKeysHealthCheck(IEnumerable endpoints, IHttpContextAccessor httpContextAccessor) { _endpoints = endpoints; _httpContextAccessor = httpContextAccessor; } public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { try { var endpoint = _endpoints.FirstOrDefault(x => x.Name == IdentityServerConstants.EndpointNames.Jwks); if (endpoint != null) { var handler = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService(endpoint.Handler) as IEndpointHandler; if (handler != null) { var result = await handler.ProcessAsync(_httpContextAccessor.HttpContext); if (result is JsonWebKeysResult) { return HealthCheckResult.Healthy(); } } } } catch { } return new HealthCheckResult(context.Registration.FailureStatus); } } ``` ----- # Federal Information Processing Standard (FIPS) compliance > Explains Duende IdentityServer Federal Information Processing Standard (FIPS) compliance. The Federal Information Processing Standard (FIPS) Publication 140-2 is a U.S. government standard that defines minimum security requirements for cryptographic modules in information technology products. IdentityServer does not provide built-in FIPS enforcement or a configuration option to enable FIPS compliance. There is no toggle switch or configuration profile that will automatically make your solution FIPS-compliant. You are solely responsible for ensuring FIPS compliance in your application and infrastructure. This includes: * Configuring your operating system for FIPS mode * Selecting and using only FIPS-validated cryptographic algorithms * Properly managing and storing cryptographic key material * Validating that your complete solution meets FIPS requirements Duende IdentityServer does not contain its own cryptographic algorithm implementations. Instead, it relies on cryptographic primitives provided by: * The underlying .NET runtime * The operating system When IdentityServer signs tokens or protects cookies, it uses the cryptographic modules provided by these underlying platforms. However, IdentityServer does not restrict or enforce which algorithms or key sizes you use. This is your responsibility to configure correctly. To build a FIPS-compliant solution with Duende IdentityServer, here is some guidance: 1. **Configure your operating system and .NET Core codebase** for FIPS mode following the guidance in the [Microsoft documentation on .NET Core FIPS compliance](https://learn.microsoft.com/en-us/dotnet/standard/security/fips-compliance) 2. **Select only FIPS-validated algorithms** in your IdentityServer configuration: * **Do not use:** `RS256`, `RS384`, or `RS512` * **Use instead:** `PS*` or `ES*` token signing algorithms 3. **Use secure key storage** for private key material, such as: * Azure Key Vault Hardware Security Module (HSM) * Other FIPS 140-2 validated hardware security modules 4. **Configure ASP.NET Core Data Protection** appropriately: * Use FIPS-compliant algorithms for generating data protection keys * Store data protection keys securely in a FIPS-validated module Remember, it is your responsibility to validate that your complete solution meets FIPS compliance requirements for your specific use case and regulatory environment. ----- # Diagnostics > Overview of IdentityServer's diagnostic capabilities including logging, OpenTelemetry integration, and event system for monitoring and troubleshooting ## Logging [Section titled “Logging”](#logging) IdentityServer offers multiple diagnostics possibilities. The logs contains detailed information and are your best friend when troubleshooting. For security reasons the error messages returned to the UI/client are very brief - the logs always have all the details of what went wrong. [Read More](/identityserver/diagnostics/logging/) ## OpenTelemetry [Section titled “OpenTelemetry”](#opentelemetry) OpenTelemetry is a standard way of emitting diagnostics information from a process and IdentityServer supports Traces (.NET Activities), Metrics and Logs. [Read More](/identityserver/diagnostics/otel/) ## Events [Section titled “Events”](#events) The eventing system was created as an extension point to integrate with application monitoring systems (APM). They used to have their own different APIs so IdentityServer only provided events that could be used to call the APM’s APIs. Thanks to OpenTelemetry there is now a standardized way to emit diagnostic information from a process. The events may eventually be deprecated and removed. [Read More](/identityserver/diagnostics/events/) ## Conformance Report [Section titled “Conformance Report”](#conformance-report) IdentityServer can generate a conformance report that assesses your configuration against OAuth 2.1 and FAPI 2.0 specifications. [Read More](/identityserver/diagnostics/conformance-report/) ----- # Financial-Grade Security and Conformance Report > How to install, configure, and use the IdentityServer Financial-Grade Security and Conformance report to assess OAuth 2.1 and FAPI 2.0 compliance. Added in 8.0 Part of Financial-Grade Security and Conformance, the conformance report assesses your IdentityServer deployment against [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) and [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile.html) specifications, generating an HTML report accessible via a protected endpoint. ## Installation [Section titled “Installation”](#installation) Install the NuGet package: Terminal ```bash dotnet add package Duende.IdentityServer.ConformanceReport ``` ## Setup [Section titled “Setup”](#setup) ### 1. Register the Financial-Grade Security and Conformance Report [Section titled “1. Register the Financial-Grade Security and Conformance Report”](#1-register-the-financial-grade-security-and-conformance-report) Call `AddConformanceReport()` on the IdentityServer builder: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConformanceReport(options => { options.Enabled = true; }); ``` ### 2. Map the Endpoint [Section titled “2. Map the Endpoint”](#2-map-the-endpoint) Add the Financial-Grade Security and Conformance report endpoint to your middleware pipeline: Program.cs ```csharp app.MapConformanceReport(); ``` ### 3. Access the Report [Section titled “3. Access the Report”](#3-access-the-report) Navigate to: `https://your-server/_duende/conformance-report` The endpoint requires an authenticated user by default (see [Authorization](#authorization) below). ## Configuration Options [Section titled “Configuration Options”](#configuration-options) `ConformanceReportOptions` controls the Financial-Grade Security and Conformance report feature: * **`Enabled`** Enable or disable the conformance report endpoint. Defaults to `false`. * **`EnableOAuth21Assessment`** Include OAuth 2.1 profile assessment in the report. Defaults to `true`. * **`EnableFapi2SecurityAssessment`** Include FAPI 2.0 Security Profile assessment in the report. Defaults to `true`. * **`PathPrefix`** URL path prefix for the conformance endpoint (no leading slash). Defaults to `"_duende"`. * **`ConfigureAuthorization`** Authorization policy for the HTML report endpoint. Defaults to require an authenticated user. * **`AuthorizationPolicyName`** ASP.NET Core authorization policy name used internally. Defaults to `"ConformanceReport"`. * **`HostCompanyName`** Optional company name shown in the report header. Defaults to `null`. * **`HostCompanyLogoUrl`** Optional company logo URL shown in the report header. Defaults to `null`. ## Authorization [Section titled “Authorization”](#authorization) By default, the report endpoint requires an authenticated user. Customize the policy using `ConfigureAuthorization`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddConformanceReport(options => { options.Enabled = true; // Require a specific role options.ConfigureAuthorization = policy => policy.RequireRole("Admin"); // Or require multiple conditions // options.ConfigureAuthorization = policy => policy // .RequireRole("Admin") // .RequireClaim("department", "IT"); // Or allow anonymous (development/testing only) // options.ConfigureAuthorization = policy => // policy.RequireAssertion(_ => builder.Environment.IsDevelopment()); }); ``` Caution If you set `ConfigureAuthorization = null`, you must manually register an ASP.NET Core authorization policy with the name specified in `AuthorizationPolicyName` (default: `"ConformanceReport"`). Otherwise, the endpoint will fail at runtime with a “policy not found” error. ## Understanding the Report [Section titled “Understanding the Report”](#understanding-the-report) The HTML report displays: * **Server Configuration** — a matrix of server-level conformance rules and their status * **Client Configurations** — a matrix of per-client conformance rules and their status * **Rule Legend** — explanation of each rule identifier * **Notes** — detailed messages for warnings and failures ### Status Indicators [Section titled “Status Indicators”](#status-indicators) | Symbol | Meaning | | ------- | -------------------------------------------------------- | | Pass | Requirement is met | | Fail | Requirement is not met (configuration is non-conformant) | | Warning | Recommended practice is not followed | | N/A | Rule is not applicable to this configuration | ## Requirements [Section titled “Requirements”](#requirements) The conformance report uses `IClientStore.GetAllClientsAsync` to enumerate all clients for assessment. Custom `IClientStore` implementations must implement this method (added in v8.0). See the [upgrade guide](/identityserver/upgrades/v7_4-to-v8_0/#iclientstoregetallclientsasync-now-required) for details. ## Full Example [Section titled “Full Example”](#full-example) Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) .AddConformanceReport(options => { options.Enabled = true; options.EnableOAuth21Assessment = true; options.EnableFapi2SecurityAssessment = true; options.HostCompanyName = "Acme Corp"; options.ConfigureAuthorization = policy => policy.RequireRole("ComplianceTeam"); }); // ... app.MapConformanceReport(); app.UseIdentityServer(); ``` ----- # Diagnostics Data Added in 7.3 To make troubleshooting easier, newer versions of IdentityServer can collect important configuration and operational diagnostics data from your IdentityServer host. Diagnostics data is [written to logs periodically](/identityserver/reference/v8/options/#diagnostics), and can be used by your operations team to help analyze your IdentityServer configuration. Diagnostics information is never automatically shared with Duende. In support scenarios, you can choose to manually share this diagnostics data with [Duende priority support](/general/support-and-issues/#priority-support) to provide additional context. If needed, you can redact/remove entries before doing so. ## Diagnostics Data Contents [Section titled “Diagnostics Data Contents”](#diagnostics-data-contents) Diagnostics data contains information that is relevant to the configuration and behavior of your IdentityServer instance. The diagnostics data contains the following information: * Assembly information for [IdentityServer-related assemblies](https://github.com/DuendeSoftware/products/blob/main/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/AssemblyInfoDiagnosticEntry.cs#L17) * .NET runtime version * IdentityServer version * Assembly name and version * Registered authentication schemes (does not include [dynamic providers](/identityserver/ui/login/dynamicproviders/)) * Name of the scheme and authentication handler type * Registered non-default implementations of Duende IdentityServer extension points * Extension point type, implementation type, assembly name and version * [`IdentityServerOptions`](/identityserver/reference/v8/options/) configuration * [Data Protection](/identityserver/deployment/#aspnet-core-data-protection) configuration * `ApplicationDiscriminator`, `XmlEncryptor` and `XmlRepository` * Basic server information * Host name * [License Usage Summary](/identityserver/reference/v8/models/license-usage-summary/) data * Token issue counts (for various token types) * Endpoint usage (only for IdentityServer endpoints) * Clients configuration (limited to first 100 clients, excluding sensitive information/secrets) * Resources configuration (limited to the first 100 resources) * Identity resources * API resources * API scopes Diagnostics data [is formatted as JSON](#diagnostics-data-format). ## Capturing Diagnostics Data [Section titled “Capturing Diagnostics Data”](#capturing-diagnostics-data) The IdentityServer diagnostics data is [written to logs periodically](/identityserver/reference/v8/options/#diagnostics). By default, you will see log entries similar to the following in your IdentityServer logs ```log info: Duende.IdentityServer.Diagnostics.Summary[7000] Diagnostic data (1 of 2): { ... info: Duende.IdentityServer.Diagnostics.Summary[7000] Diagnostic data (2 of 2): ... } ``` Diagnostics data [may be chunked](/identityserver/reference/v8/options/#diagnostics), and you will need to concatenate chunks to collect the full diagnostics JSON data. To capture diagnostics data from your IdentityServer instance, you can log entries written to the `Duende.IdentityServer.Diagnostics.Summary` log category. You may want to set up your IdentityServer logging to filter diagnostics data and emit these to a separate log provider/sink. Let’s look at some examples of how you can filter diagnostics data and write it to a separate log file. Note that to read the contents of this log file, you will need access to your IdentityServer host storage (or use another provider/sink to extract log data). ### .NET Core Default Logger [Section titled “.NET Core Default Logger”](#net-core-default-logger) To write log entries to a file using the default [.NET Core `ILogger` API](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging), you will need a log provider that supports doing this. In the example below, we are using the [`NReco.Logging.File`](https://www.nuget.org/packages/NReco.Logging.File) package. * In code Use the `AddFile()` extension method to add a file logger to your `ILoggingBuilder` instance. The `FilterLogEntry` property on the file logger can be used to filter log entries based on the log category, which is what we are using to filter the `Duende.IdentityServer.Diagnostics.Summary` log category. Program.cs ```csharp // ... builder.Services.AddLogging(configure => { configure.AddFile("diagnostics.log", options => { options.Append = true; options.FilterLogEntry = entry => entry.LogName == "Duende.IdentityServer.Diagnostics.Summary"; }); }); ``` * With configuration pattern The file logger will need to be registered in your application. Use the `AddFile()` extension method to add a file logger to your `ILoggingBuilder` instance. Note the `NReco.Logging.File` requires a file name to be specified. Program.cs ```csharp // ... builder.Services.AddLogging(configure => { configure.AddFile("diagnostics.log", append: true); }); ``` In your `appsettings.json`, you can configure the file logger to filter log entries based on the log category. appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Duende.IdentityServer.Diagnostics.Summary": "None" }, "File": { "LogLevel": { "Default": "None", "Duende.IdentityServer.Diagnostics.Summary": "Information" } } } } ``` ### Serilog [Section titled “Serilog”](#serilog) When using [Serilog](https://serilog.net/), you can configure a separate file logger sink to write `Duende.IdentityServer.Diagnostics.Summary` log entries to. * In code In the `AddSerilog()` extension method’s configuration builder, you can add a file logger that filters log messages and only emits those from the `Duende.IdentityServer.Diagnostics.Summary` category. The console logger (or another default logger you are using) can be configured to exclude this category. Program.cs ```csharp // ... builder.Services.AddSerilog((services, configuration) => { configuration .ReadFrom.Configuration(builder.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) .MinimumLevel.Override("System", LogEventLevel.Warning) .WriteTo.Logger(fileLogger => { fileLogger .WriteTo.File("./diagnostics/diagnostic.log", rollingInterval: RollingInterval.Day, fileSizeLimitBytes: 1024 * 1024 * 10, // 10 MB rollOnFileSizeLimit: true, outputTemplate: "[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") .Filter.ByIncludingOnly(Matching.FromSource("Duende.IdentityServer.Diagnostics.Summary")); }) .WriteTo.Logger(consoleLogger => { consoleLogger .WriteTo.Console( outputTemplate: "[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") .Filter.ByExcluding(Matching.FromSource("Duende.IdentityServer.Diagnostics.Summary")); }); ``` * With configuration pattern When using the configuration pattern, you can configure the file logger to filter log entries based on the log category. Note that you will need the [`Serilog.Expressions`](https://github.com/serilog/serilog-expressions) package installed and configured in your IdentityServer host In your `appsettings.json`, you can configure Serilog to filter log entries based on the log category. appsettings.json ```json { "Serilog":{ "Using":[ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "Enrich":[ "FromLogContext" ], "MinimumLevel":{ "Default":"Debug", "Override":{ "Microsoft":"Warning", "Microsoft.Hosting.Lifetime":"Information", "Microsoft.AspNetCore.Authentication":"Debug", "System":"Warning" } }, "WriteTo":[ { "Name":"Logger", "Args":{ "configureLogger":{ "WriteTo":[ { "Name":"File", "Args":{ "path":"diagnostics/identity-server-diagnostics.log", "outputTemplate":"[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", "rollingInterval":"Day", "fileSizeLimitBytes":10000000, "rollOnFileSizeLimit":true } } ], "Filter":[ { "Name":"ByIncludingOnly", "Args":{ "expression":"StartsWith(SourceContext, 'Duende.IdentityServer.Diagnostics.Summary')" } } ] } } }, { "Name":"Logger", "Args":{ "configureLogger":{ "WriteTo":[ { "Name":"Console", "Args":{ "outputTemplate":"[{Timestamp:HH:mm:ss} {Level} {EventId}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}" } } ], "Filter":[ { "Name":"ByExcluding", "Args":{ "expression":"SourceContext = 'Duende.IdentityServer.Diagnostics.Summary'" } } ] } } } ] } } ``` ### log4net [Section titled “log4net”](#log4net) When using [log4net](https://logging.apache.org/log4net/index.html), you can use the `log4net.config` configuration file to configure a file appender that writes `Duende.IdentityServer.Diagnostics.Summary` log entries to a separate file. log4net.config ```xml ``` ### NLog [Section titled “NLog”](#nlog) When using [NLog](https://nlog-project.org/) and the [`NLog.Extensions.Logging`](https://www.nuget.org/packages/NLog.Extensions.Logging) package, you can use the configuration pattern to configure a file logger that writes `Duende.IdentityServer.Diagnostics.Summary` log entries to a separate file. appsettings.json ```json { "NLog": { "ThrowConfigExceptions": true, "Targets": { "file": { "type": "File", "fileName": "${basedir}/diagnostics/${shortdate}.log", "layout": "${longdate} ${level:uppercase=true} ${logger} ${message} ${exception:format=ToString}" }, "console": { "type": "ColoredConsole", "layout": "${longdate} ${level:uppercase=true} ${logger} ${message} ${exception:format=ToString}" } }, "Rules": [ { "logger": "Duende.IdentityServer.Diagnostics.Summary", "writeTo": "file", "final": true, "maxLevel": "Info" }, { "logger": "*", "minLevel": "Info", "writeTo": "console" } ] } } ``` ## Diagnostics Data Format [Section titled “Diagnostics Data Format”](#diagnostics-data-format) Diagnostics data is written to logs in one or more chunks containing data formatted as JSON. ----- # Events > Documentation about IdentityServer's event system for structured logging and monitoring of important operations While logging is more low level “printf” style - events represent higher level information about certain operations in IdentityServer. Events are structured data and include event IDs, success/failure information, categories and details. This makes it easy to query and analyze them and extract useful information that can be used for further processing. Events work great with structured logging stores like [ELK](https://www.elastic.co/webinars/introduction-elk-stack), [Seq](https://getseq.net) or [Splunk](https://www.splunk.com/). ### Emitting events [Section titled “Emitting events”](#emitting-events) Events are not turned on by default - but can be globally configured when `AddIdentityServer` is called, e.g.: Program.cs ```csharp builder.Services.AddIdentityServer(options => { options.Events.RaiseSuccessEvents = true; options.Events.RaiseFailureEvents = true; options.Events.RaiseErrorEvents = true; }); ``` To emit an event use the `IEventService` from the ASP.NET Core service provider and call the `RaiseAsync` method, e.g.: ```csharp public async Task Login(LoginInputModel model) { if (_users.ValidateCredentials(model.Username, model.Password)) { // issue authentication cookie with subject ID and username var user = _users.FindByUsername(model.Username); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); } else { await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); } } ``` ### Custom sinks [Section titled “Custom sinks”](#custom-sinks) Our default event sink will serialize the event class to JSON and forward it to the ASP.NET Core logging system. If you want to connect to a custom event store, implement the `IEventSink` interface and register it with the ASP.NET Core service provider. The following example uses [Seq](https://getseq.net) to emit events: ```csharp public class SeqEventSink : IEventSink { private readonly Logger _log; public SeqEventSink() { _log = new LoggerConfiguration() .WriteTo.Seq("http://localhost:5341") .CreateLogger(); } public Task PersistAsync(Event evt) { if (evt.EventType == EventTypes.Success || evt.EventType == EventTypes.Information) { _log.Information("{Name} ({Id}), Details: {@details}", evt.Name, evt.Id, evt); } else { _log.Error("{Name} ({Id}), Details: {@details}", evt.Name, evt.Id, evt); } return Task.CompletedTask; } } ``` Add the `Serilog.Sinks.Seq` package to your host to make the above code work. ## Built-in events [Section titled “Built-in events”](#built-in-events) The following events are defined in IdentityServer: * **`ApiAuthenticationFailureEvent`** & **`ApiAuthenticationSuccessEvent`** Gets raised for successful/failed API authentication at the introspection endpoint. * **`ClientAuthenticationSuccessEvent`** & **`ClientAuthenticationFailureEvent`** Gets raised for successful/failed client authentication at the token endpoint. * **`TokenIssuedSuccessEvent`** & **`TokenIssuedFailureEvent`** Gets raised for successful/failed attempts to request identity tokens, access tokens, refresh tokens and authorization codes. * **`TokenIntrospectionSuccessEvent`** & **`TokenIntrospectionFailureEvent`** Gets raised for successful token introspection requests. * **`TokenRevokedSuccessEvent`** Gets raised for successful token revocation requests. * **`UserLoginSuccessEvent`** & **`UserLoginFailureEvent`** Gets raised by the quickstart UI for successful/failed user logins. * **`UserLogoutSuccessEvent`** Gets raised for successful logout requests. * **`ConsentGrantedEvent`** & **`ConsentDeniedEvent`** Gets raised in the consent UI. * **`UnhandledExceptionEvent`** Gets raised for unhandled exceptions. * **`DeviceAuthorizationFailureEvent`** & **`DeviceAuthorizationSuccessEvent`** Gets raised for successful/failed device authorization requests. ### SAML Events v8.0 [Section titled “SAML Events ”v8.0](#saml-events) The following events are raised by SAML components: * **`SamlSsoSuccessEvent`** Raised when a SAML single sign-on request completes successfully. * **`SamlSsoFailureEvent`** Raised when a SAML single sign-on request fails. * **`SamlSloSuccessEvent`** Raised when a SAML single logout request completes successfully. * **`SamlSloFailureEvent`** Raised when a SAML single logout request fails. * **`SamlAuthnRequestValidationFailureEvent`** Raised when validation of an incoming SAML authentication request fails. * **`SamlLogoutRequestValidationFailureEvent`** Raised when validation of an incoming SAML logout request fails. ### Custom events [Section titled “Custom events”](#custom-events) You can create your own events and emit them via our infrastructure. You need to derive from our base `Event` class which injects contextual information like activity ID, timestamp, etc. Your derived class can then add arbitrary data fields specific to the event context:: ```csharp public class UserLoginFailureEvent : Event { public UserLoginFailureEvent(string username, string error) : base(EventCategories.Authentication, "User Login Failure", EventTypes.Failure, EventIds.UserLoginFailure, error) { Username = username; } public string Username { get; set; } } ``` ----- # Logging > Documentation for logging configuration and usage in Duende IdentityServer, including log levels and Serilog setup Duende IdentityServer uses the standard logging facilities provided by ASP.NET Core. You don’t need to do any extra configuration to benefit from rich logging functionality. For general information on how to configure logging, setting up Serilog, and understanding log levels in Duende products, see our [Logging Fundamentals](/general/logging/) guide. ## Configuration [Section titled “Configuration”](#configuration) Logs are typically written under the `Duende.IdentityServer` category. We are roughly following the Microsoft guidelines for usage of log levels: * **`Trace`** For information that is valuable only to a developer troubleshooting an issue. These messages may contain sensitive application data like tokens and should not be enabled in a production environment. * **`Debug`** For following the internal flow and understanding why certain decisions are made. Has short-term usefulness during development and debugging. * **`Information`** For tracking the general flow of the application. These logs typically have some long-term value. * **`Warning`** For abnormal or unexpected events in the application flow. These may include errors or other conditions that do not cause the application to stop, but which may need to be investigated. * **`Error`** For errors and exceptions that cannot be handled. Examples: failed validation of a protocol request. * **`Critical`** For failures that require immediate attention. Examples: missing store implementation, invalid key material… To get detailed logs from IdentityServer, you can configure your `appsettings.json` to enable `Debug` or `Information` level logs for the `Duende.IdentityServer` namespace: appsettings.json ```json { "Logging": { "LogLevel": { "Default": "Information", "Duende.IdentityServer": "Information" } } } ``` ### Filtering Exceptions [Section titled “Filtering Exceptions”](#filtering-exceptions) The `LoggingOptions` class allows developers to filter out any exceptions that could potentially lead to log bloat. For example, in a web application, developers should expect to see `OperationCanceledException` as clients end HTTP requests abruptly for many reasons. It’s such a common occurrence to see this exception that the default filter included with IdentityServer excludes it by default. ```csharp /// /// Called when the IdentityServer middleware detects an unhandled exception, and is used to determine if the exception is logged. /// Returns true to emit the log, false to suppress. /// public Func UnhandledExceptionLoggingFilter = (context, exception) => { var result = !(context.RequestAborted.IsCancellationRequested && exception is OperationCanceledException); return result; }; ``` To apply custom filtering, you can set the `UnhandledExceptionLoggingFilter` property on the `LoggingOptions` for your `IdentityServerOptions`. ```csharp var isBuilder = builder.Services.AddIdentityServer(options => { options.Logging.UnhandledExceptionLoggingFilter = (ctx, ex) => { if (ctx.User is { Identity.Name: "Jeff" }) { // Oh Jeff... return false; } if (ex.Message.Contains("Oops")) { // ignore this exception return false; } // this is a real exception return true; }; }) .AddTestUsers(TestUsers.Users) .AddLicenseSummary(); ``` Returning `true` means the exception will be logged, while returning `false` indicates the exception should not be logged. ## OpenTelemetry [Section titled “OpenTelemetry”](#opentelemetry) Logs written to the standard `ILogger` system in .NET 8+ can be exported to OpenTelemetry traces at runtime. This helps visualize when the log statement occurred in relation to the entire request. The logs are augmented with trace ids and correlated with traces. Have a look at [logs in OpenTelemetry](/identityserver/diagnostics/otel/#logs) for setup details. ----- # OpenTelemetry > Documentation for OpenTelemetry integration in IdentityServer, covering metrics, traces and logs collection for monitoring and diagnostics [OpenTelemetry](https://opentelemetry.io) (OTel) is a collection of tools, APIs, and SDKs for generating and collecting telemetry data (metrics, logs, and traces). This is very useful for analyzing software performance and behavior, especially in highly distributed systems. ## OpenTelemetry Signals [Section titled “OpenTelemetry Signals”](#opentelemetry-signals) OpenTelemetry signals are the information collected and processed to describe the internal activity of the system. The most common signals are traces, metrics, and logs. .NET 8+ comes with first class support for OpenTelemetry. IdentityServer emits traces, metrics, and logs you can collect. ### Metrics [Section titled “Metrics”](#metrics) Metrics are high level statistic counters. They provide an aggregated overview and can be used to set monitoring rules. ### Traces [Section titled “Traces”](#traces) Traces shows individual requests and dependencies. The output is very useful for visualizing the control flow and finding performance bottlenecks. This is an example of distributed traces from a web application calling an API (displayed using our [Aspire sample](/identityserver/samples/diagnostics/)). The web application uses a refresh token to call IdentityServer to get a new access token and then calls the API. The API reads the discovery endpoint, finds the jwks url and then gets the keys from jwks endpoint. ![.NET Aspire dashboard showing Duende IdentityServer traces](/_astro/aspire_traces.C5IYKs1g_8l8HE.webp) ### Logs [Section titled “Logs”](#logs) OpenTelemetry in .NET 8+ can export logs written to the standard `ILogger` system. The logs are augmented with trace ids and correlated with traces. This is an example of a structured log message from a web application calling an API (also displayed using our [Aspire sample](/identityserver/samples/diagnostics/)). ![.NET Aspire dashboard showing Duende IdentityServer Structured Logs](/_astro/aspire_structured_logs.C4_GEVBr_Z2uoiap.webp) Here is an example of that same log message appearing in the trace. Aspire displays the log entry details as dots on the trace timeline. ![.NET Aspire dashboard showing Duende IdentityServer a trace with a log entry](/_astro/aspire_structured_logs_in_trace.DDvSbnq__27frWP.webp) ## Setup [Section titled “Setup”](#setup) To start emitting OpenTelemetry tracing and metrics information you need to: * add the OpenTelemetry libraries to your IdentityServer and client applications * start collecting traces and metrics from the various IdentityServer sources (and other sources e.g. ASP.NET Core) * add the OpenTelemetry configuration to your service setup For development a simple option is to export the tracing information to the console and use the Prometheus exporter to create a human-readable `/metrics` endpoint for the metrics. ```bash dotnet add package OpenTelemetry dotnet add package OpenTelemetry.Extensions.Hosting dotnet add package OpenTelemetry.Instrumentation.AspNetCore dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol ``` Program.cs ```csharp using OpenTelemetry.Resources; // Add OpenTelemetry logging infrastructure // to correlate logs with traces builder.Logging.AddOpenTelemetry(); // Enable OpenTelemetry var openTelemetry = builder.Services.AddOpenTelemetry(); openTelemetry.ConfigureResource(r => r .AddService(builder.Environment.ApplicationName)); openTelemetry.WithMetrics(m => m .AddMeter(Telemetry.ServiceName) .AddMeter(Pages.Telemetry.ServiceName) .AddPrometheusExporter()); openTelemetry.WithTracing(t => t .AddSource(IdentityServerConstants.Tracing.Basic) .AddSource(IdentityServerConstants.Tracing.Cache) .AddSource(IdentityServerConstants.Tracing.Services) .AddSource(IdentityServerConstants.Tracing.Stores) .AddSource(IdentityServerConstants.Tracing.Validation) .AddAspNetCoreInstrumentation() .AddConsoleExporter()); ``` Add the Prometheus exporter to the pipeline Program.cs ```csharp // Map /metrics that displays OpenTelemetry data in human-readable form. app.UseOpenTelemetryPrometheusScrapingEndpoint(); ``` This setup will write the tracing information to the console and provide metrics on the /metrics endpoint. ## Metrics [Section titled “Metrics”](#metrics-1) OpenTelemetry metrics are run-time measurements that are intended to provide an indication of overall health and are typically used to show graphs on a dashboard or to set up monitoring rules. When that monitoring reveals issues, traces and logs are used to investigate further. OpenTelemetry monitoring tools often provide features to find the traces and logs corresponding to certain metrics. IdentityServer emits metrics from the IdentityServer middleware and services. Our quick start for the UI also [contains metrics](#metrics-in-the-ui) that can be used as a starting point for monitoring UI events. The metric counters that IdentityServer emits are designed to not contain any sensitive information. They are often tagged to indicate the source of the events. ### High level Metrics [Section titled “High level Metrics”](#high-level-metrics) These metrics are instrumented by the IdentityServer middleware and services and are intended to describe the overall usage and health of the system. They could provide the starting point for building a metrics dashboard. The high level metrics are created by the meter named “Duende.IdentityServer”, which is the value of the `Duende.IdentityServer.Telemetry.ServiceName` constant. #### Telemetry.Metrics.Counters.Operation [Section titled “Telemetry.Metrics.Counters.Operation”](#telemetrymetricscountersoperation) Counter name: `tokenservice.operation` Aggregated counter of failed and successful operations. The result tag indicates if an operation succeeded, failed, or caused an internal error. It is expected to have some failures during normal operations. In contrast, operations tagged with a result of internal\_error are abnormal and indicate an unhandled exception. The error/success ratio can be used as a very high level health metric. | Tag | Description | | ------ | ---------------------------------------------------- | | error | Error label on errors | | result | Success, error or internal\_error | | client | Id of client requesting the operation. May be empty. | #### Telemetry.Metrics.Counters.ActiveRequests [Section titled “Telemetry.Metrics.Counters.ActiveRequests”](#telemetrymetricscountersactiverequests) Counter name: `active_requests` Gauge/up-down counter that shows current active requests that are processed by any IdentityServer endpoint. Note that the pages in the user interface are not IdentityServer endpoints and are not included in this count. | Tag | Description | | -------- | ---------------------------------------- | | endpoint | The type name for the endpoint processor | | path | The path of the request | ### Detailed Metrics [Section titled “Detailed Metrics”](#detailed-metrics) These detailed metrics are instrumented by the IdentityServer middleware and services and track usage of specific flows and features. #### Telemetry.Metrics.Counters.ApiSecretValidation [Section titled “Telemetry.Metrics.Counters.ApiSecretValidation”](#telemetrymetricscountersapisecretvalidation) Counter name: `tokenservice.api.secret_validation` Number of successful/failed validations of API Secrets. | Tag | Description | | ------------ | -------------------------- | | api | The Api Id | | auth\_method | Authentication method used | | error | Error label on errors | #### Telemetry.Metrics.Counters.BackchannelAuthentication [Section titled “Telemetry.Metrics.Counters.BackchannelAuthentication”](#telemetrymetricscountersbackchannelauthentication) Counter name: `tokenservice.backchannel_authentication` Number of successful/failed back channel authentications (CIBA). | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.ClientConfigValidation [Section titled “Telemetry.Metrics.Counters.ClientConfigValidation”](#telemetrymetricscountersclientconfigvalidation) Counter name: `tokenservice.client.config_validation` Number of successful/failed client validations. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.ClientSecretValidation [Section titled “Telemetry.Metrics.Counters.ClientSecretValidation”](#telemetrymetricscountersclientsecretvalidation) Counter name: `tokenservice.client.secret_validation` Number of successful/failed client secret validations. | Tag | Description | | ------------ | ------------------------------------ | | client | The client Id | | auth\_method | The authentication method on success | | error | Error label on errors | #### Telemetry.Metrics.Counters.DeviceAuthentication [Section titled “Telemetry.Metrics.Counters.DeviceAuthentication”](#telemetrymetricscountersdeviceauthentication) Counter name: `tokenservice.device_authentication` Number of successful/failed device authentications. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.DynamicIdentityProviderValidation [Section titled “Telemetry.Metrics.Counters.DynamicIdentityProviderValidation”](#telemetrymetricscountersdynamicidentityprovidervalidation) Counter name: `tokenservice.dynamic_identityprovider.validation` Number of successful/failed validations of dynamic identity providers. | Tag | Description | | ------ | ------------------------------- | | scheme | The scheme name of the provider | | error | Error label on errors | #### Telemetry.Metrics.Counters.Introspection [Section titled “Telemetry.Metrics.Counters.Introspection”](#telemetrymetricscountersintrospection) Counter name: `tokenservice.introspection` Number of successful/failed token introspections. | Tag | Description | | ------ | -------------------------------------------------- | | caller | The caller of the endpoint, a client id or api id. | | active | Was the token active? Only sent on success | | error | Error label on errors | #### Telemetry.Metrics.Counters.PushedAuthorizationRequest [Section titled “Telemetry.Metrics.Counters.PushedAuthorizationRequest”](#telemetrymetricscounterspushedauthorizationrequest) Counter name: `tokenservice.pushed_authorization_request` Number of successful/failed pushed authorization requests. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.ResourceOwnerAuthentication [Section titled “Telemetry.Metrics.Counters.ResourceOwnerAuthentication”](#telemetrymetricscountersresourceownerauthentication) Counter name: `tokenservice.resourceowner_authentication` Number of successful/failed resource owner authentications. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.Revocation [Section titled “Telemetry.Metrics.Counters.Revocation”](#telemetrymetricscountersrevocation) Counter name: `tokenservice.revocation` Number of successful/failed token revocations. | Tag | Description | | ------ | --------------------- | | client | The client Id | | error | Error label on errors | #### Telemetry.Metrics.Counters.TokenIssued [Section titled “Telemetry.Metrics.Counters.TokenIssued”](#telemetrymetricscounterstokenissued) Counter name: `tokenservice.token_issued` Number of successful/failed token issuance attempts. Note that a token issuance might include multiple actual tokens (id\_token, access token, refresh token). | Tag | Description | | ------------------------ | ---------------------------------------------------------------- | | client | The client Id | | grant\_type | The grant type used | | authorize\_request\_type | The authorize request type, if information about it is available | | error | Error label on errors | #### Telemetry.Metrics.Counters.SamlSso v8.0 [Section titled “Telemetry.Metrics.Counters.SamlSso ”v8.0](#telemetrymetricscounterssamlsso) Counter name: `tokenservice.saml.sso` Number of SAML SSO attempts, both successful and failed. On success, the counter is tagged with the service provider entity ID and the SAML binding used. On failure, the binding tag is replaced with an error code so you can quickly see what went wrong without flooding your metrics system with high-cardinality data. On success: | Tag | Description | | -------------- | ------------------------------------------------------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | | binding | The SAML binding used (for example, `urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST`) | On failure: | Tag | Description | | -------------- | ------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | | error | Bounded error code (see below) | The `error` tag uses a bounded set of values to prevent cardinality explosion in your metrics backend: `invalid`, `unknown`, `sp_not_found`, `sp_disabled`, `invalid_acs_url`, `access_denied`, `interaction_error` #### Telemetry.Metrics.Counters.SamlSlo [Section titled “Telemetry.Metrics.Counters.SamlSlo”](#telemetrymetricscounterssamlslo) Counter name: `tokenservice.saml.slo` Number of SAML Single Logout (SLO) attempts, both successful and failed. Like the SSO counter, error codes are kept to a bounded set to keep your metrics cardinality under control. On success: | Tag | Description | | -------------- | ------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | On failure: | Tag | Description | | -------------- | ------------------------------------- | | sp\_entity\_id | The entity ID of the service provider | | error | Bounded error code (see below) | The `error` tag uses a bounded set of values to prevent cardinality explosion in your metrics backend: `invalid`, `unknown`, `sp_not_found`, `sp_disabled`, `invalid_acs_url`, `access_denied`, `partial_logout`, `interaction_error` ### Metrics In The UI [Section titled “Metrics In The UI”](#metrics-in-the-ui) The [UI in your IdentityServer host](/identityserver/ui/) can instrument these events to measure activities that occur during interactive flows, such as user login and logout. These events are not instrumented by the IdentityServer middleware or services because they are the responsibility of the UI. Our templated UI does instrument these events, and you can alter and add metrics as needed to the UI in your context. #### Telemetry.Metrics.Counters.Consent [Section titled “Telemetry.Metrics.Counters.Consent”](#telemetrymetricscountersconsent) Counter name: `tokenservice.consent` Consent requests granted or denied. The counters are per scope, so if a user consents to multiple scopes, the counter is increased multiple times, one for each scope. This allows the scope name to be included as a tag without causing an explosion of combination of tags. | Tag | Description | | ------- | ----------------- | | client | The client Id | | scope | The scope names | | consent | granted or denied | #### Telemetry.Metrics.Counters.GrantsRevoked [Section titled “Telemetry.Metrics.Counters.GrantsRevoked”](#telemetrymetricscountersgrantsrevoked) Counter name: `tokenservice.grants_revoked` Revocation of grants. | Tag | Description | | ------ | --------------------------------------------------------------------------------------------------------- | | client | The client Id, if grants are revoked only for one client. If not set, the revocation was for all clients. | #### Telemetry.Metrics.Counters.UserLogin [Section titled “Telemetry.Metrics.Counters.UserLogin”](#telemetrymetricscountersuserlogin) Counter names: `tokenservice.user_login` Successful and failed user logins. | Tag | Description | | ------ | ----------------------------------------------------------------- | | client | The client Id, if the login was caused by a request from a client | | idp | The idp (ASP.NET Core Scheme name) used to log in | | error | Error label on errors | #### Telemetry.Metrics.Counters.UserLogout [Section titled “Telemetry.Metrics.Counters.UserLogout”](#telemetrymetricscountersuserlogout) Counter name: `user_logout` User logout. Note that this is only raised on explicit user logout, not if the session times out. The number of logouts will typically be lower than the number of logins. | Tag | Description | | --- | ---------------------------------------------- | | idp | The idp (ASP.NET scheme name) logging out from | ### .NET Authentication And Authorization Metrics [Section titled “.NET Authentication And Authorization Metrics”](#net-authentication-and-authorization-metrics) Starting with .NET 10, metrics are available for certain authentication and authorization events in ASP.NET Core. You can get metrics for the following events: * [Authentication](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/built-in?view=aspnetcore-10.0#microsoftaspnetcoreauthentication) * Authenticated request duration (`aspnetcore.authentication.authenticate.duration`) * Challenge count (`aspnetcore.authentication.challenges`) * Forbid count (`aspnetcore.authentication.forbids`) * Sign in count (`aspnetcore.authentication.sign_ins`) * Sign out count (`aspnetcore.authentication.sign_outs`) * [Authorization](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/built-in?view=aspnetcore-10.0#microsoftaspnetcoreauthorization) * Count of requests requiring authorization (`aspnetcore.authorization.attempts`) Refer to the [ASP.NET Core documentation](https://learn.microsoft.com/en-us/aspnet/core/log-mon/metrics/built-in?view=aspnetcore-10.0) for more information about ASP.NET Core built-in metrics. ### ASP.NET Core Identity metrics [Section titled “ASP.NET Core Identity metrics”](#aspnet-core-identity-metrics) When using ASP.NET Identity, metrics are available for key user and sign-in operation metrics. These let you monitor user management activities like creating users, changing passwords, etc. It’s also possible to track login attempts, sign-ins, sign-outs, and two-factor authentication usage. The `Microsoft.AspNetCore.Identity` meter provides the following metrics: * User management metrics * Duration of user creation operations (`aspnetcore.identity.user.create.duration`) * Duration of user update operations (`aspnetcore.identity.user.update.duration`) * Duration of user deletion operations (`aspnetcore.identity.user.delete.duration`) * Number of password verification attempts (`aspnetcore.identity.user.check_password_attempts`) * Number of tokens generated for users, such as password reset tokens (`aspnetcore.identity.user.generated_tokens`) * Number of token verification attempts (`aspnetcore.identity.user.verify_token_attempts`) * Authentication metrics * Duration of authentication operations (`aspnetcore.identity.sign_in.authenticate.duration`) * Number of password check attempts at sign-in (`aspnetcore.identity.sign_in.check_password_attempts`) * Number of successful sign-ins (`aspnetcore.identity.sign_in.sign_ins`) * Number of sign-outs (`aspnetcore.identity.sign_in.sign_outs`) * Number of remembered two-factor authentication (2FA) clients (`aspnetcore.identity.sign_in.two_factor_clients_remembered`) * Number of forgotten two-factor authentication (2FA) clients (`aspnetcore.identity.sign_in.two_factor_clients_forgotten`) ## Traces [Section titled “Traces”](#traces-1) Here’s e.g. the output for a request to the discovery endpoint: ![Honeycomb UI showing traces for discovery document endpoint](/_astro/otel_disco.BBgm8ly2_RcRU3.webp) When multiple applications send their traces to the same OpenTelemetry server, this becomes super useful for following e.g. authentication flows over service boundaries. The following screenshot shows the ASP.NET Core OpenID Connect authentication handler redeeming the authorization code: ![HoneyComb UI showing traces for the OpenID Connect authentication handler](/_astro/otel_flow_1.BBYe6Iu9_1HRa9h.webp) …and then contacting the userinfo endpoint: ![Honeycomb UI showing traces for the userinfo endpoint](/_astro/otel_flow_2.DcVRg6r2_Z1FNh6z.webp) *The above screenshots are from .* ### Tracing Sources [Section titled “Tracing Sources”](#tracing-sources) IdentityServer can emit very fine-grained traces which is useful for performance troubleshooting and general exploration of the control flow. This might be too detailed in production. You can select which information you are interested in by selectively listening to various traces: * *`IdentityServerConstants.Tracing.Basic`* High level request processing like request validators and response generators * *`IdentityServerConstants.Tracing.Cache`* Caching related tracing * *`IdentityServerConstants.Tracing.Services`* Services related tracing * *`IdentityServerConstants.Tracing.Stores`* Store related tracing * *`IdentityServerConstants.Tracing.Validation`* More detailed tracing related to validation ## OpenTelemetry From 3rd Party Logging Frameworks [Section titled “OpenTelemetry From 3rd Party Logging Frameworks”](#opentelemetry-from-3rd-party-logging-frameworks) If you’re unable to use the `ILogger` system in .NET, your choice of logging framework may be able to push log messages to traces. You can view their documentation to set that up. ### OpenTelemetry with Serilog [Section titled “OpenTelemetry with Serilog”](#opentelemetry-with-serilog) If you are logging with Serilog and want to use that framework’s native API to push log messages to traces, you need to: * Add the Serilog OpenTelemetry sink library * Instruct the Serilog logger object to write to the OpenTelemetry sink Note: See the Serilog [OpenTelemetry sink](https://github.com/serilog/serilog-sinks-opentelemetry) documentation for the most up to date information. ```bash dotnet add package Serilog.Sinks.OpenTelemetry ``` ```csharp Log.Logger = new LoggerConfiguration() .WriteTo.OpenTelemetry() .CreateLogger(); ``` ----- # Claims > Learn about how IdentityServer emits and manages claims for users and clients, including claim emission strategies and serialization IdentityServer emits claims about users and clients into tokens. You are in full control of which claims you want to emit, in which situations you want to emit those claims, and where to retrieve those claims from. ## User Claims [Section titled “User Claims”](#user-claims) User claims can be emitted in both identity and access tokens and in the [userinfo endpoint](/identityserver/reference/v8/endpoints/userinfo/). The central extensibility point to implement to emit claims is called the [profile service](/identityserver/reference/v8/services/profile-service/). The profile service is responsible for both gathering claim data and deciding which claims should be emitted. Whenever IdentityServer needs the claims for a user, it invokes the registered profile service with a [context](/identityserver/reference/v8/services/profile-service/#duendeidentityservermodelsprofiledatarequestcontext) that presents detailed information about the current request, including * the client that is making the request * the identity of the user * the type of the request (access token, id token, or userinfo) * the requested claim types, which are the claims types associated with requested scopes and resources ### Strategies For Emitting Claims [Section titled “Strategies For Emitting Claims”](#strategies-for-emitting-claims) You can use different strategies to determine which claims to emit based on the information in the profile context. * emit claims based on the requested claim types * emit claims based on user or client identity * always emit certain claims #### Emit Claims Based On The Client’s Request [Section titled “Emit Claims Based On The Client’s Request”](#emit-claims-based-on-the-clients-request) You can filter the claims you emit to only include the claim types requested by the client. If your client requires consent, this will also give end users the opportunity to approve or deny sharing those claims with the client. Clients can request claims in several ways: * Requesting an [IdentityResource](/identityserver/fundamentals/resources/identity/) by including the scope parameter for the `IdentityResource` requests the claims associated with the `IdentityResource` in its `UserClaims` collection. * Requesting an [ApiScope](/identityserver/fundamentals/resources/api-scopes/) by including the scope parameter for the `ApiScope` requests the claims associated with the `ApiScope` in its `UserClaims` collection. * Requesting an [ApiResource](/identityserver/fundamentals/resources/api-resources/) by including the resource indicator parameter for the `ApiResource` requests the claims associated with the `ApiResource` in its `UserClaims` collection. The `RequestedClaimTypes` property of the `ProfileDataRequestContext` contains the collection of claims requested by the client. If your profile service extends the `DefaultProfileService`, you can use its `AddRequestedClaims` method to add only requested and approved claims. The intent is that your profile service can retrieve claim data and then filter that claim data based on what was requested by the client. For example: ```csharp public class SampleProfileService : DefaultProfileService { public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context) { var claims = await GetClaimsAsync(context); context.AddRequestedClaims(claims); } private async Task> GetClaimsAsync(ProfileDataRequestContext context) { // Your implementation that retrieves claims goes here } } ``` #### Always Emit Claims [Section titled “Always Emit Claims”](#always-emit-claims) We generally recommend emitting claims based on the requested claim types, as that respects the scopes and resources requested by the client and gives the end user an opportunity to consent to this sharing of information. However, if you have claims that don’t need to follow such rules, such as claims that are an integral part of the user’s identity and that are needed in most scenarios, they can be added by directly updating the `context.IssuedClaims` collection. For example: ```csharp public class SampleProfileService : DefaultProfileService { public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context) { var claims = await GetClaimsAsync(context); context.IssuedClaims.AddRange(claims); } private async Task GetClaimsAsync(ProfileDataRequestContext context) { // Your implementation that retrieves claims goes here } } ``` #### Emit Claims Based On The User Or Client Identity [Section titled “Emit Claims Based On The User Or Client Identity”](#emit-claims-based-on-the-user-or-client-identity) Finally, you might have claims that are only appropriate for certain users or clients. Your `ProfileService` can add whatever filtering or logic that you like. ### The Subject Of The ProfileDataRequestContext [Section titled “The Subject Of The ProfileDataRequestContext”](#the-subject-of-the-profiledatarequestcontext) When the profile service is invoked to add claims to tokens, the `Subject` property on the `ProfileDataRequestContext` contains the principal that was issued during user sign-in. Typically, the profile service will source some claims from the `Subject` and others from databases or other data sources. When the profile service is called for requests to the [userinfo endpoint](/identityserver/reference/v8/endpoints/userinfo/), the `Subject` property will not contain the principal issued during user sign-in, since userinfo calls don’t happen as part of a session. Instead, the `Subject` property will contain a claims principal populated with the claims in the access token used to authorize the userinfo call. You can check the caller of the profile service by querying the `Caller` property on the context. ## Client Claims [Section titled “Client Claims”](#client-claims) Client claims are a set of pre-defined claims that are emitted in access tokens. They are defined on a per-client basis, meaning that each client can have its own unique set of client claims. The following shows an example of a client that is associated with a certain customer in your system: ```csharp var client = new Client { ClientId = "client", // rest omitted Claims = { new ClientClaim("customer_id", "123") } }; ``` To avoid accidental collision with user claims, client claims are prefixed with `client_`. For example, the above `ClientClaim` would be emitted as the `client_customer_id` claim type in access tokens. You can change or remove this prefix by setting the `ClientClaimsPrefix` on the [client definition](/identityserver/reference/v8/models/client/#token). ### Setting Client Claims Dynamically [Section titled “Setting Client Claims Dynamically”](#setting-client-claims-dynamically) If you want to set client claims dynamically, you could either do that at client load time (via a client [store](/identityserver/data) implementation), or using a [custom token request validator](/identityserver/tokens/dynamic-validation/). ## Claim Serialization [Section titled “Claim Serialization”](#claim-serialization) Claim values are serialized based on the `ClaimValueType` of the claim. Claims that don’t specify a `ClaimValueType` are serialized as strings. Claims that specify a `ClaimValueType` of `System.Security.Claims.ClaimValueTypes.Integer`, `System.Security.Claims.ClaimValueTypes.Integer32`, `System.Security.Claims.ClaimValueTypes.Integer64`, `System.Security.Claims.ClaimValueTypes.Double`, or `System.Security.Claims.ClaimValueTypes.Boolean` are parsed as the corresponding type, while those that specify `IdentityServerConstants.ClaimValueTypes.Json` are serialized to JSON using `System.Text.Json`. ----- # Clients > Learn about configuring and managing client applications that can request tokens from IdentityServer [Clients](/identityserver/overview/terminology/#client), or [connected applications](/general/glossary/#connected-application), represent applications that can request tokens from your IdentityServer. The details vary, but you typically define the following common settings for a client: * a unique client ID * a secret if needed * the allowed interactions with the token service (called a grant type) * a network location where identity and/or access token gets sent to (called a redirect URI) * a list of scopes (aka resources) the client is allowed to access ## Defining A Client For Server To Server Communication [Section titled “Defining A Client For Server To Server Communication”](#defining-a-client-for-server-to-server-communication) In this scenario no interactive user is present - a service (i.e. the client) wants to communicate with an API (i.e. the resource that supports the scope): ```csharp public class Clients { public static IEnumerable Get() { return new List { new Client { ClientId = "service.client", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = { "api1", "api2.read_only" } } }; } } ``` ## Defining An Interactive Application: Authentication And Delegated API Access [Section titled “Defining An Interactive Application: Authentication And Delegated API Access”](#defining-an-interactive-application-authentication-and-delegated-api-access) Interactive applications (e.g. web applications or native desktop/mobile applications) use the authorization code flow. This flow gives you the best security because the access tokens are transmitted via back-channel calls only (and gives you access to refresh tokens): ```csharp var interactiveClient = new Client { ClientId = "interactive", AllowedGrantTypes = GrantTypes.Code, AllowOfflineAccess = true, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:21402/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:21402/" }, FrontChannelLogoutUri = "http://localhost:21402/signout-oidc", AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, "api1", "api2.read_only" }, }; ``` ## Defining Clients In `appsettings.json` [Section titled “Defining Clients In appsettings.json”](#defining-clients-in-appsettingsjson) The `AddInMemoryClients` extensions method also supports adding clients from the ASP.NET Core configuration file. This allows you to define static clients directly from the appsettings.json file: appsettings.json ```json { "IdentityServer": { "Clients": [ { "Enabled": true, "ClientId": "local-dev", "ClientName": "Local Development", "ClientSecrets": [ { "Value": "" } ], "AllowedGrantTypes": [ "client_credentials" ], "AllowedScopes": [ "api1" ] } ] } } ``` Then pass the configuration section to the `AddInMemoryClients` method: Program.cs ```csharp AddInMemoryClients(configuration.GetSection("IdentityServer:Clients")) ``` ----- # Hosting > Learn how to host and configure Duende IdentityServer in ASP.NET Core applications by adding services and middleware to the pipeline You add the Duende IdentityServer engine to any ASP.NET Core application by adding the relevant services to the dependency injection (DI) system and adding the middleware to the processing pipeline. ## Dependency Injection System [Section titled “Dependency Injection System”](#dependency-injection-system) You add the necessary services to the ASP.NET Core service provider by calling `AddIdentityServer` at application startup: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { // ... }); ``` Many of the fundamental configuration settings can be set on the options. See the [`IdentityServerOptions`](/identityserver/reference/v8/options/) reference for more details. The builder object has a number of extension methods to add additional services to the ASP.NET Core service provider. You can see the full list in the [reference](/identityserver/reference/v8/di/) section, but very commonly you start by adding the configuration stores for clients and resources, e.g.: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) ``` The above is using the in-memory stores, but we also support EntityFramework-based implementations and custom stores. See [here](/identityserver/data) for more information. ----- # Key Management > Learn how to manage cryptographic keys for token signing in IdentityServer using automatic or static key management Duende IdentityServer issues several types of tokens that are cryptographically signed, including identity tokens, JWT access tokens, and logout tokens. To create those signatures, IdentityServer needs key material. That key material can be configured automatically, by using the Automatic Key Management feature, or manually, by loading the keys from a secured location with static configuration. IdentityServer supports [signing](https://tools.ietf.org/html/rfc7515) tokens using the `RS`, `PS` and `ES` family of cryptographic signing algorithms. ## Automatic Key Management [Section titled “Automatic Key Management”](#automatic-key-management) Duende IdentityServer can manage signing keys for you using the Automatic Key Management feature. Automatic Key Management follows best practices for handling signing key material, including * automatic rotation of keys * secure storage of keys at rest using data protection * announcement of upcoming new keys * maintenance of retired keys ### Configuration [Section titled “Configuration”](#configuration) Automatic Key Management is configured by the options in the `KeyManagement` property on the [`IdentityServerOptions`](/identityserver/reference/v8/options/#key-management). ### Managed Key Lifecycle [Section titled “Managed Key Lifecycle”](#managed-key-lifecycle) Keys created by Automatic Key Management move through several phases. First, new keys are announced, that is, they are added to the list of keys in discovery, but not yet used for signing. After a configurable amount of `PropagationTime`, keys are promoted to be signing credentials, and will be used by IdentityServer to sign tokens. Eventually, enough time will pass that the key is older than the configurable `RotationTime`, at which point the key is retired, but kept in discovery for a configurable `RetentionDuration`. After the `RetentionDuration` has passed, keys are removed from discovery, and optionally deleted. The default is to rotate keys every 90 days, announce new keys with 14 days of propagation time, retain old keys for a duration of 14 days, and to delete keys when they are retired. ``` --- config: theme: default gantt: useWidth: 800 useMaxWidth: false --- gantt title 90 Day Key Rotation Schedule per Signing Algorithm todayMarker off section RS256 Signing :active, rsa_s, 2025-01-01, 76d Retire :rsa_r, after rsa_s, 14d Delete :crit, rsa_d, after rsa_r, 1d Announce :rsa_na, 2025-03-03, 14d Signing :active, rsa_ns, after rsa_na, 62d Retire :rsa_nr, after rsa_ns, 14d Delete :crit, rsa_nd, after rsa_nr, 1d section ES256 Signing :active, es_s, 2025-01-01, 76d Retire :es_r, after es_s, 14d Delete :crit, :es_d, after es_r, 1d Announce :es_na, 2025-03-03, 14d Signing :active, es_ns, after es_na, 62d Retire :es_nr, after es_ns, 14d Delete :crit, es_nd, after es_nr, 1d ``` All of these options are configurable in the `KeyManagement` options. For example: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { // new key every 30 days options.KeyManagement.RotationInterval = TimeSpan.FromDays(30); // announce new key 2 days in advance in discovery options.KeyManagement.PropagationTime = TimeSpan.FromDays(2); // keep old key for 7 days in discovery for validation of tokens options.KeyManagement.RetentionDuration = TimeSpan.FromDays(7); // don't delete keys after their retention period is over options.KeyManagement.DeleteRetiredKeys = false; }); ``` ### Key Storage [Section titled “Key Storage”](#key-storage) Automatic Key Management stores keys through the abstraction of the [`ISigningKeyStore`](/identityserver/data/operational/#keys). You can implement this extensibility point to customize the storage of your keys (perhaps using a key vault of some kind), or use one of the two implementations of the `ISigningKeyStore` that we provide: * the default `FileSystemKeyStore`, which writes keys to the file system. * the [EntityFramework operational store](/identityserver/data/ef/#operational-store) which writes keys to a database using EntityFramework. The default `FileSystemKeyStore` writes keys to the `KeyPath` directory configured in your IdentityServer host, which defaults to the directory `~/keys`. This directory should be excluded from source control. If you are deploying in a load balanced environment and wish to use the `FileSystemKeyStore`, all instances of IdentityServer will need read/write access to the `KeyPath`. Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { // set path to store keys options.KeyManagement.KeyPath = "/home/shared/keys"; }); ``` ### Encryption Of Keys at Rest [Section titled “Encryption Of Keys at Rest”](#encryption-of-keys-at-rest) The keys created by Automatic Key Management are sensitive cryptographic secrets that should be encrypted at rest. By default, keys managed by Automatic Key Management are protected at rest using ASP.NET Core Data Protection. This is controlled with the `DataProtectKeys` flag, which is on by default. We recommend leaving this flag on unless you are using a custom `ISigningKeyStore` to store your keys in a secure location that will ensure keys are encrypted at rest. For example, if you implement the `ISigningKeyStore` to store your keys in Azure Key Vault, you could safely disabled `DataProtectKeys`, relying on Azure Key Vault to encrypt your signing keys at rest. See the [deployment](/identityserver/deployment/) section for more information about setting up data protection. ### Manage Multiple Keys [Section titled “Manage Multiple Keys”](#manage-multiple-keys) By default, Automatic Key Management will maintain a signing credential and validation keys for a single cryptographic algorithm (`RS256`). You can specify multiple keys, algorithms, and if those keys should additionally get wrapped in an X.509 certificate. Automatic key management will create and rotate keys for each signing algorithm you specify. ```csharp options.KeyManagement.SigningAlgorithms = new[] { // RS256 for older clients (with additional X.509 wrapping) new SigningAlgorithmOptions(SecurityAlgorithms.RsaSha256) { UseX509Certificate = true }, // PS256 new SigningAlgorithmOptions(SecurityAlgorithms.RsaSsaPssSha256), // ES256 new SigningAlgorithmOptions(SecurityAlgorithms.EcdsaSha256) }; ``` ## Static Key Management [Section titled “Static Key Management”](#static-key-management) Instead of using [Automatic Key Management](#automatic-key-management), IdentityServer’s signing keys can be set manually. Automatic Key Management is generally recommended, but if you want to explicitly control your keys statically, or you have a license that does not include the feature, you will need to manually manage your keys. With static configuration you are responsible for secure storage, loading and rotation of keys. ## Disabling Key Management [Section titled “Disabling Key Management”](#disabling-key-management) The automatic key management feature can be disabled by setting the `Enabled` flag to `false` on the `KeyManagement` property of [`IdentityServerOptions`](/identityserver/reference/v8/options/#key-management): Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); ``` ## Key Creation [Section titled “Key Creation”](#key-creation) Without automatic key management, you are responsible for creating your own cryptographic keys. Such keys can be created with many tools. Some options include: * Use the PowerShell commandlet [New-SelfSignedCertificate](https://learn.microsoft.com/en-us/powershell/module/pki/new-selfsignedcertificate?view=windowsserver2022-ps) to self-sign your own certificate * Create certificates using [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/certificates/certificate-scenarios) * Create certificates using your Public Key Infrastructure. * Create certificates using C# (see below) ```csharp var name = "MySelfSignedCertificate"; // Generate a new key pair using var rsa = RSA.Create(keySizeInBits: 2048); // Create a certificate request var request = new CertificateRequest( subjectName: $"CN={name}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1 ); // Self-sign the certificate var certificate = request.CreateSelfSigned( DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1) ); // Export the certificate to a PFX file var pfxBytes = certificate.Export( // TODO: pick a format X509ContentType.Pfx, // TODO: change the password password: "password" ); File.WriteAllBytes($"{name}.pfx", pfxBytes); Console.Write(certificate); Console.WriteLine("Self-signed certificate created successfully."); Console.WriteLine($"Certificate saved to {name}.pfx"); ``` ## Adding Keys [Section titled “Adding Keys”](#adding-keys) Signing keys are added with the [`AddSigningCredential`](/identityserver/reference/v8/di/#signing-keys) configuration method: Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(); var key = LoadKeyFromVault(); // (Your code here) idsvrBuilder.AddSigningCredential(key, SecurityAlgorithms.RsaSha256); ``` You can call `AddSigningCredential` multiple times if you want to register more than one signing key. When you register multiple signing algorithms, the first one added will be the default used for signing tokens. Client and API resource definitions both have an `AllowedTokenSigningAlgorithm` property to override the default on a per resource and client basis. Another configuration method called `AddValidationKey` can be called to register public keys that should be accepted for token validation. ## Key Storage [Section titled “Key Storage”](#key-storage-1) With automatic key management disabled, secure storage of the key material is left to you. This key material should be treated as highly sensitive. Key material should be encrypted at rest, and access to it should be restricted. Loading a key from disk into memory can be done using the `X509CertificateLoader` found in .NET assuming your hosting environment has proper security practices in place. ```csharp // load certificate from disk var bytes = File.ReadAllBytes("mycertificate.pfx"); var importedCertificate = X509CertificateLoader.LoadPkcs12(bytes, "password"); ``` You may also choose to load a certificate from the current environment’s key store using the `X509Store` class. ```csharp // Pick the appropriate StoreName and StoreLocation var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); var certificate = store .Certificates .First(c => c.Thumbprint == ""); ``` If you’re generating self-signed certificates using C#, you can use the `X509Store` to store the certificate into the current hosting environment as well. ```csharp // Pick the appropriate StoreName and StoreLocation var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); // push certificate into store var certificate = CreateCertificate(); store.Add(certificate); ``` ## Manual Key Rotation [Section titled “Manual Key Rotation”](#manual-key-rotation) With automatic key management disabled, you will need to rotate your keys manually. The rotation process must be done carefully for two reasons: 1. Client applications and APIs cache key material. If you begin using a new key too quickly, new tokens will be signed with a key that is not yet in their caches. This will cause clients to not be able to validate the signatures of new id tokens which will prevent users from logging in, and APIs will not be able to validate signatures of access tokens, which will prevent authorization of calls to those APIs. 2. Tokens signed with the old key material probably exist. If you tell APIs to stop using the old key too quickly, APIs will reject the signatures of old tokens, again causing authorization failures at your APIs. There are two solutions to these problems. Which one is right for you depends on the level of control you have over client applications, the amount of downtime that is acceptable, and the degree to which invalidating old tokens matters to you. ### Solution 1: Invalidate All Caches When Keys Are Rotated [Section titled “Solution 1: Invalidate All Caches When Keys Are Rotated”](#solution-1-invalidate-all-caches-when-keys-are-rotated) One solution to these problems is to invalidate the caches in all the client applications and APIs immediately after the key is rotated. In ASP.NET, the simplest way to do so is to restart the hosting process, which clears the cached signing keys of the authentication middleware. This is only appropriate if all the following are true: * You have control over the deployment of all the client applications. * You can tolerate a maintenance window in which your services are all restarted. * You don’t mind that users will need to log in again after the key is rotated. ### Solution 2: Phased Rotation [Section titled “Solution 2: Phased Rotation”](#solution-2-phased-rotation) A more robust solution is to gradually transition from the old to the new key. This requires three phases. #### Phase 1: Announce The New Key [Section titled “Phase 1: Announce The New Key”](#phase-1-announce-the-new-key) First, announce a new key that will be used for signing in the future. During this phase, continue to sign tokens with the old key. The idea is to allow for all the applications and APIs to update their caches without any interruption in service. Configure IdentityServer for phase 1 by registering the new key as a validation key. Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var oldKey = LoadOldKeyFromVault(); var newKey = LoadNewKeyFromVault(); idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); idsvrBuilder.AddValidationKey(newKey, SecurityAlgorithms.RsaSha256) ``` Once IdentityServer is updated with the new key as a validation key, wait to proceed to phase 2 until all the applications and services have updated their signing key caches. The default cache duration in .NET is 24 hours, but this is customizable. You may also need to support clients or APIs built with other platforms or that were customized to use a different value. Ultimately you have to decide how long to wait to proceed to phase 2 in order to ensure that all clients and APIs have updated their caches. #### Phase 2: Start Signing With The New Key [Section titled “Phase 2: Start Signing With The New Key”](#phase-2-start-signing-with-the-new-key) Next, start signing tokens with the new key, but continue to publish the public key of the old key so that tokens that were signed with that key can continue to be validated. The IdentityServer configuration change needed is to swap the signing credential and validation key. Program.cs ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var oldKey = LoadOldKeyFromVault(); var newKey = LoadNewKeyFromVault(); idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256) ``` Again, you need to wait to proceed to phase 3. The delay here is typically shorter, because the reason for the delay is to ensure that tokens signed with the old key remain valid until they expire. IdentityServer’s token lifetime defaults to 1 hour, though it is configurable. #### Phase 3: Remove The Old Key [Section titled “Phase 3: Remove The Old Key”](#phase-3-remove-the-old-key) Once enough time has passed that there are no unexpired tokens signed with the old key, it is safe to completely remove the old key. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var newKey = LoadNewKeyFromVault(); idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); ``` ## Migrating From Static Keys To Automatic Key Management [Section titled “Migrating From Static Keys To Automatic Key Management”](#migrating-from-static-keys-to-automatic-key-management) To migrate from static to automatic key management, you can set keys manually and enable automatic key management at the same time. This allows the automatic key management feature to begin creating keys and announce them in discovery, while you continue to use the old statically configured key. Eventually you can transition from the statically configured key to the automatically managed keys. A signing key registered with `AddSigningCredential` will take precedence over any keys created by the automatic key management feature. IdentityServer will sign tokens with the credential specified in `AddSigningCredential`, but also automatically create and manage validation keys. Validation keys registered manually with `AddValidationKey` are added to the collection of validation keys along with the keys produced by automatic key management. When automatic key management is enabled and there are keys statically specified with `AddValidationkey`, the set of validation keys will include: * new keys created by automatic key management that are not yet used for signing * old keys created by automatic key management that are retired * the keys added explicitly with calls to `AddValidationKey`. The migration path from manual to automatic keys is a three-phase process, similar to the phased approach to [manual key rotation](#manual-key-rotation). The difference here is that you are phasing out the old key and allowing the automatically generated keys to phase in. ### Phase 1: Announce New (Automatic) Key [Section titled “Phase 1: Announce New (Automatic) Key”](#phase-1-announce-new-automatic-key) First, enable automatic key management while continuing to register your old key as the signing credential. In this phase, the new automatically managed key will be announced so that as client apps and APIs update their caches, they get the new key. IdentityServer will continue to sign keys with your old static key. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); var oldKey = LoadOldKeyFromVault(); idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); ``` Wait until all APIs and applications have updated their signing key caches, and then proceed to phase 2. ### Phase 2: Start Signing With The New (Automatic) Key [Section titled “Phase 2: Start Signing With The New (Automatic) Key”](#phase-2-start-signing-with-the-new-automatic-key) Next, switch to using the new automatically managed keys for signing, but still keep the old key for validation purposes. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); var oldKey = LoadOldKeyFromVault(); idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256); ``` Keep the old key as a validation key until all tokens signed with that key are expired, and then proceed to phase 3. ### Phase 3: Drop the old key [Section titled “Phase 3: Drop the old key”](#phase-3-drop-the-old-key) Now the static key configuration can be removed entirely. ```csharp var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); ``` ----- # ASP.NET Core OpenID Connect Handler Events > ASP.NET Core's OpenID Connect handler events, what they are, and why you might want to use them. The ASP.NET Core [OpenID Connect handler](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnecthandler?view=aspnetcore-9.0) exposes events that a client can subscribe to intercept the OpenID Connect protocol flow. Understanding these events is important to understanding how to customize the OpenID Connect protocol flow from the client. We’ll cover each of the events, what they are, and why you might want to subscribe to them. To use the `OpenIdConnectHandler` in your client applications, you will first need to install the `Microsoft.AspNetCore.Authentication.OpenIdConnect` NuGet package. ```bash dotnet package add Microsoft.AspNetCore.Authentication.OpenIdConnect ``` Followed by adding the `OpenIdConnectHandler` to your application. Program.cs ```csharp builder.Services.AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("cookie", options => { options.Cookie.Name = "__Host-bff"; options.Cookie.SameSite = SameSiteMode.Strict; }) .AddOpenIdConnect("oidc", options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.ResponseMode = "query"; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.MapInboundClaims = false; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("api"); options.Scope.Add("offline_access"); options.TokenValidationParameters.NameClaimType = "name"; options.TokenValidationParameters.RoleClaimType = "role"; }); ``` From here you can use the `options.Events` property to subscribe to the events you want to use. Let’s look at each of the events in more detail. ## OpenID Connect Events [Section titled “OpenID Connect Events”](#openid-connect-events) All events either occur before a request is sent to the identity provider, or after a response is received from the identity provider. Understanding the direction of these events can help you determine when to subscribe to them. Let’s call events coming from the identity provider **incoming** and events going to the identity provider **outgoing** for an easier understanding. | **Event Name** | **Usage** | | ---------------------------------------- | ------------ | | `OnAuthenticationFailed` | **Incoming** | | `OnAuthorizationCodeReceived` | **Incoming** | | `OnMessageReceived` | **Incoming** | | `OnRedirectToIdentityProvider` | **Outgoing** | | `OnRedirectToIdentityProviderForSignOut` | **Outgoing** | | `OnSignedOutCallbackRedirect` | **Outgoing** | | `OnRemoteSignOut` | **Incoming** | | `OnTokenResponseReceived` | **Incoming** | | `OnTokenValidated` | **Incoming** | | `OnUserInformationReceived` | **Incoming** | | `OnTicketReceived` | **Incoming** | | `OnPushAuthorization` (**.NET 9+ only**) | **Outgoing** | ## Commonly Subscribed Events [Section titled “Commonly Subscribed Events”](#commonly-subscribed-events) While there are many events available in the `OpenIdConnectEvents` class, only a few are commonly subscribed. We suggest you start with the most commonly subscribed events and then subscribe to the remaining events as needed. For ASP.NET Core developers, the most commonly subscribed events are: 1. **`OnRedirectToIdentityProvider`**: Useful for customizing login requests (e.g., appending extra parameters). 2. **`OnRedirectToIdentityProviderForSignOut`**: Often required to customize the behavior of sign-out requests. 3. **`OnTokenValidated`**: Frequently used to customize the claims processing or validate custom claims included in the ID token. 4. **`OnUserInformationReceived`**: Sometimes used to process additional user data retrieved from the UserInfo endpoint (if enabled). ## Descriptions [Section titled “Descriptions”](#descriptions) ### OnAuthenticationFailed [Section titled “OnAuthenticationFailed”](#onauthenticationfailed) * **When called**: Triggered whenever an exception occurs during the authentication process. This event provides an opportunity to handle or log errors. * **How often**: Only called when an authentication error happens. * **Example use case**: Use this event to log detailed error messages or display a custom error page to the user instead of the default behavior. * **Commonly subscribed**: No, unless you need specific error-handling logic. ### OnAuthorizationCodeReceived [Section titled “OnAuthorizationCodeReceived”](#onauthorizationcodereceived) * **When called**: Invoked after an authorization code is received and before it is redeemed for tokens. * **How often**: Called once per successful authorization code flow request. * **Example use case**: Validate the authorization code or add extra functionality (e.g., logging or monitoring) when the code is received. * **Commonly subscribed**: Rarely, unless custom logic is required before token redemption. ### OnMessageReceived [Section titled “OnMessageReceived”](#onmessagereceived) * **When called**: Triggered when a protocol message (e.g., an authorization response, logout request) is first received. * **How often**: Called once per incoming protocol message. * **Example use case**: Inspect or modify protocol messages for debugging or to handle additional query parameters passed by the identity provider. * **Commonly subscribed**: No, unless advanced customization is needed. ### OnRedirectToIdentityProvider [Section titled “OnRedirectToIdentityProvider”](#onredirecttoidentityprovider) * **When called**: Invoked when redirecting the user to the identity provider for authentication. You can modify the outgoing authentication request. * **How often**: Called once per user authentication attempt (e.g., a “login”). * **Example use case**: Add custom query parameters to the request or modify the state parameter. * **Commonly subscribed**: Yes—often used to customize the authentication request. ### OnRedirectToIdentityProviderForSignOut [Section titled “OnRedirectToIdentityProviderForSignOut”](#onredirecttoidentityproviderforsignout) * **When called**: Triggered before redirecting the user to the identity provider to start the sign-out process. * **How often**: Called once per user sign-out request. * **Example use case**: Modify the logout request, such as appending additional parameters. * **Commonly subscribed**: Yes, if signing out requires customization. ### OnSignedOutCallbackRedirect [Section titled “OnSignedOutCallbackRedirect”](#onsignedoutcallbackredirect) * **When called**: Invoked after a remote sign-out is completed and before redirecting the user to the `SignedOutRedirectUri`. * **How often**: Called once per remote sign-out. * **Example use case**: Log or perform business logic after the remote sign-out. * **Commonly subscribed**: Rarely, unless additional behavior is needed. ### OnRemoteSignOut [Section titled “OnRemoteSignOut”](#onremotesignout) * **When called**: Called when a remote sign-out request is received on the `RemoteSignOutPath` endpoint. * **How often**: Called once per incoming remote sign-out request. * **Example use case**: Perform cleanup tasks such as clearing local session data upon receiving a sign-out request from the identity provider. * **Commonly subscribed**: Rarely, but important in distributed or multi-tenant systems. ### OnTokenResponseReceived [Section titled “OnTokenResponseReceived”](#ontokenresponsereceived) * **When called**: Triggered after an authorization code exchange is completed and the token endpoint returns tokens. * **How often**: Called once per token request. * **Example use case**: Log or debug the token response, or inspect additional data included in the token response. * **Commonly subscribed**: No, unless debugging or inspection of tokens is required. ### OnTokenValidated [Section titled “OnTokenValidated”](#ontokenvalidated) * **When called**: Invoked after the ID token has been validated and an `AuthenticationTicket` has been created. * **How often**: Called once per token validation process. * **Example use case**: Add or modify claims in the `ClaimsPrincipal` or validate custom claims included in the token. * **Commonly subscribed**: Yes—this is one of the most commonly used events for customizing claims. ### OnUserInformationReceived [Section titled “OnUserInformationReceived”](#onuserinformationreceived) * **When called**: Triggered when retrieving user information from the UserInfo endpoint (if `GetClaimsFromUserInfoEndpoint = true`). * **How often**: Called once per user information fetch (e.g., per login). * **Example use case**: Extend or modify user claims based on the additional information retrieved from the UserInfo endpoint. * **Commonly subscribed**: Sometimes, if extra claims processing is required. ### OnTicketReceived [Section titled “OnTicketReceived”](#onticketreceived) * **When called**: Invoked after the OpenID Connect authentication flow is complete and before the authentication ticket is returned. * **How often**: Called once per successful authentication flow completion. * **Example use case**: Modify the final authentication ticket, perform additional validation, or execute custom logic before completing the authentication process. * **Commonly subscribed**: Sometimes, when final authentication customization is needed before completing the flow or for diagnostics and troubleshooting purposes. ### OnPushAuthorization [Section titled “OnPushAuthorization”](#onpushauthorization) * **When called**: Invoked before sending authorization parameters using the Pushed Authorization Request (PAR) mechanism. * **How often**: Called once per outgoing PAR-based authorization request. * **Example use case**: Modify or log pushed authorization parameters. * **Commonly subscribed**: Rarely, as this is used mainly in advanced scenarios. ----- # Resources > Overview of resource types in Duende IdentityServer including API resources, identity resources, API scopes, and resource isolation concepts The ultimate job of Duende IdentityServer is to control access to resources. ## API Resources [Section titled “API Resources”](#api-resources) In Duende IdentityServer, the *ApiResource* class allows for some additional organization and grouping and isolation of scopes and providing some common settings. [Read More](/identityserver/fundamentals/resources/api-resources/) ## Identity Resources [Section titled “Identity Resources”](#identity-resources) An identity resource is a named group of claims about a user that can be requested using the *scope* parameter. The OpenID Connect specification [suggests](https://openid.net/specs/openid-connect-core-1_0.html#scopeclaims) a couple of standard scope name to claim type mappings that might be useful to you for inspiration, but you can freely design them yourself. [Read More](/identityserver/fundamentals/resources/identity/) ## API Scopes [Section titled “API Scopes”](#api-scopes) Designing your API surface can be a complicated task. Duende IdentityServer provides a couple of primitives to help you with that. The original OAuth 2.0 specification has the concept of scopes, which is just defined as *the scope of access* that the client requests. Technically speaking, the *scope* parameter is a list of space delimited values - you need to provide the structure and semantics of it. In more complex systems, often the notion of a *resource* is introduced. This might be e.g. a physical or logical API. In turn each API can potentially have scopes as well. Some scopes might be exclusive to that resource, and some scopes might be shared. [Read More](/identityserver/fundamentals/resources/api-scopes/) ## Resources Isolation [Section titled “Resources Isolation”](#resources-isolation) OAuth itself only knows about scopes - the (API) resource concept does not exist from a pure protocol point of view. This means that all the requested scope and audience combination get merged into a single access token. This has a couple of downsides, e.g. * tokens can become very powerful (and big) * if such a token leaks, it allows access to multiple resources * resources within that single token might have conflicting settings, e.g. * user claims of all resources share the same token * resource specific processing like signing or encryption algorithms conflict * without sender-constraints, a resource could potentially re-use (or abuse) a token to call another contained resource directly To solve this problem [RFC 8707](https://tools.ietf.org/html/rfc8707) adds another request parameter for the authorize and token endpoint called *resource*. This allows requesting a token for a specific resource (in other words - making sure the audience claim has a single value only, and all scopes belong to that single resource). [Read More](/identityserver/fundamentals/resources/isolation/) ----- # API Resources > Learn how API Resources in Duende IdentityServer help organize and group scopes, manage token claims, and control access token properties When the API/resource surface gets larger, a flat list of scopes might become hard to manage. In Duende IdentityServer, the `ApiResource` class allows for some additional organization and grouping and isolation of scopes and providing some common settings. Let’s use the following scope definition as an example: ```csharp public static IEnumerable GetApiScopes() { return new List { // invoice API specific scopes new ApiScope(name: "invoice.read", displayName: "Reads your invoices."), new ApiScope(name: "invoice.pay", displayName: "Pays your invoices."), // customer API specific scopes new ApiScope(name: "customer.read", displayName: "Reads you customers information."), new ApiScope(name: "customer.contact", displayName: "Allows contacting one of your customers."), // shared scopes new ApiScope(name: "manage", displayName: "Provides administrative access."), new ApiScope(name: "enumerate", displayName: "Allows enumerating data.") }; } ``` With `ApiResource` you can now create two logical APIs and their corresponding scopes: ```csharp public static readonly IEnumerable GetApiResources() { return new List { new ApiResource("invoice", "Invoice API") { Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" } }, new ApiResource("customer", "Customer API") { Scopes = { "customer.read", "customer.contact", "manage", "enumerate" } } }; } ``` Using the API resource grouping gives you the following additional features * support for the JWT `aud` claim. The value(s) of the audience claim will be the name of the API resource(s) * support for adding common user claims across all contained scopes * support for introspection by assigning an API secret to the resource * support for configuring the access token signing algorithm for the resource Let’s have a look at some example access tokens for the above resource configuration. Client requests: *`invoice.read`* and *`invoice.pay`*: ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": "invoice", "scope": "invoice.read invoice.pay" } ``` Client requests: *`invoice.read`* and *`customer.read`*: ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": [ "invoice", "customer" ], "scope": "invoice.read customer.read" } ``` Client requests: *`manage`*: ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": [ "invoice", "customer" ], "scope": "manage" } ``` ### Adding User Claims [Section titled “Adding User Claims”](#adding-user-claims) You can specify that an access token for an API resource (regardless of which scope is requested) should contain additional user claims. ```csharp var customerResource = new ApiResource("customer", "Customer API") { Scopes = { "customer.read", "customer.contact", "manage", "enumerate" }, // additional claims to put into access token UserClaims = { "department_id", "sales_region" } } ``` If a client now requested a scope belonging to the `customer` resource, the access token would contain the additional claims (if provided by your [profile service](/identityserver/reference/v8/services/profile-service/)). ```json { "typ": "at+jwt" }. { "client_id": "client", "sub": "123", "aud": [ "invoice", "customer" ], "scope": "invoice.read customer.read", "department_id": 5, "sales_region": "south" } ``` ### Setting A Signing Algorithm [Section titled “Setting A Signing Algorithm”](#setting-a-signing-algorithm) Your APIs might have certain requirements for the cryptographic algorithm used to sign the access tokens for that resource. An example could be regulatory requirements, or that you are starting to migrate your system to higher security algorithms. The following sample sets `PS256` as the required signing algorithm for the `invoices` API: ```csharp var invoiceApi = new ApiResource("invoice", "Invoice API") { Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" }, AllowedAccessTokenSigningAlgorithms = { SecurityAlgorithms.RsaSsaPssSha256 } } ``` ### Resource Isolation [Section titled “Resource Isolation”](#resource-isolation) See [Resource Isolation](/identityserver/fundamentals/resources/isolation/) for more details on how to use the `resource` parameter to request a token with scopes for a specific resource. ----- # API Scopes > Learn about API scopes in IdentityServer, how to define and use them for access control, and how they work with OAuth 2.0 Designing your API surface can be a complicated task. Duende IdentityServer provides a couple of primitives to help you with that. The original OAuth 2.0 specification has the concept of scopes, which is just defined as *the scope of access* that the client requests. Technically speaking, the `scope` parameter is a list of space delimited values - you need to provide the structure and semantics of it. In more complex systems, often the notion of a `resource` is introduced. This might be e.g. a physical or logical API. In turn each API can potentially have scopes as well. Some scopes might be exclusive to that resource, and some scopes might be shared. Let’s start with simple scopes first, and then we’ll have a look how resources can help structure scopes. ### Scopes [Section titled “Scopes”](#scopes) Let’s model something very simple - a system that has three logical operations `read`, `write`, and `delete`. You can define them using the `ApiScope` class: ```csharp public static IEnumerable GetApiScopes() { return new List { new ApiScope(name: "read", displayName: "Read your data."), new ApiScope(name: "write", displayName: "Write your data."), new ApiScope(name: "delete", displayName: "Delete your data.") }; } ``` You can then assign the scopes to various clients, e.g.: ```csharp var webViewer = new Client { ClientId = "web_viewer", AllowedScopes = { "openid", "profile", "read" } }; var mobileApp = new Client { ClientId = "mobile_app", AllowedScopes = { "openid", "profile", "read", "write", "delete" } } ``` ### Authorization Based On Scopes [Section titled “Authorization Based On Scopes”](#authorization-based-on-scopes) When a client asks for a scope (and that scope is allowed via configuration and not denied via consent), the value of that scope will be included in the resulting access token as a claim of type `scope` (for both JWTs and introspection), e.g.: ```json { "typ": "at+jwt" }. { "client_id": "mobile_app", "sub": "123", "scope": "read write delete" } ``` The consumer of the access token can use that data to make sure that the client is actually allowed to invoke the corresponding functionality. See the [APIs](/identityserver/apis) section for more information on protecting APIs with access tokens. Caution Be aware, that scopes are purely for authorizing clients, not users. In other words, the `write` scope allows the client to invoke the functionality associated with the scope and is unrelated to the user’s permission to do so. This additional user-centric authorization is application logic and not covered by OAuth, yet still possibly important to implement in your API. ### Adding User Claims [Section titled “Adding User Claims”](#adding-user-claims) You can add more identity information about the user to the access token. The additional claims added are based on the scope requested. The following scope definition tells the configuration system that when a `write` scope gets granted the `user_level` claim should be added to the access token: ```csharp var writeScope = new ApiScope( name: "write", displayName: "Write your data.", userClaims: new[] { "user_level" }); ``` This will pass the `user_level` claim as a requested claim type to the profile service, so that the consumer of the access token can use this data as input for authorization decisions or business logic. ### Parameterized Scopes [Section titled “Parameterized Scopes”](#parameterized-scopes) Sometimes scopes have a certain structure, e.g. a scope name with an additional parameter: `transaction:id` or `read_patient:patientid`. In this case you would create a scope without the parameter part and assign that name to a client, but in addition provide some logic to parse the structure of the scope at runtime using the `IScopeParser` interface or by deriving from our default implementation, e.g.: ```csharp public class ParameterizedScopeParser : DefaultScopeParser { public ParameterizedScopeParser(ILogger logger) : base(logger) { } public override void ParseScopeValue(ParseScopeContext scopeContext) { const string transactionScopeName = "transaction"; const string separator = ":"; const string transactionScopePrefix = transactionScopeName + separator; var scopeValue = scopeContext.RawValue; if (scopeValue.StartsWith(transactionScopePrefix)) { // we get in here with a scope like "transaction:something" var parts = scopeValue.Split(separator, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2) { scopeContext.SetParsedValues(transactionScopeName, parts[1]); } else { scopeContext.SetError("transaction scope missing transaction parameter value"); } } else if (scopeValue != transactionScopeName) { // we get in here with a scope not like "transaction" base.ParseScopeValue(scopeContext); } else { // we get in here with a scope exactly "transaction", which is to say we're ignoring it // and not including it in the results scopeContext.SetIgnore(); } } } ``` You then have access to the parsed value throughout the pipeline, e.g. in the profile service: ```csharp public class HostProfileService : IProfileService { public override async Task GetProfileDataAsync(ProfileDataRequestContext context) { var transaction = context.RequestedResources.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction"); if (transaction?.ParsedParameter != null) { context.IssuedClaims.Add(new Claim("transaction_id", transaction.ParsedParameter)); } } } ``` ----- # Identity Resources > Learn about identity resources in Duende IdentityServer - named groups of claims about users that can be requested using scopes An identity resource is a named group of claims about a user that can be requested using the `scope` parameter. The OpenID Connect specification [suggests](https://openid.net/specs/openid-connect-core-1_0.html#scopeclaims) a couple of standard scope name to claim type mappings that might be useful to you for inspiration, but you can freely design them yourself. One of them is actually mandatory, the `openid` scope, which tells the provider to return the `sub` (subject id) claim in the identity token. This is how you could define the openid scope in code: ```csharp public static IEnumerable GetIdentityResources() { return new List { new IdentityResource( name: "openid", userClaims: new[] { "sub" }, displayName: "Your user identifier") }; } ``` But since this is one of the standard scopes from the spec you can shorten that to: ```csharp public static IEnumerable GetIdentityResources() { return new List { new IdentityResources.OpenId() }; } ``` The following example shows a custom identity resource called `profile` that represents the display name, email address and website claim: ```csharp public static IEnumerable GetIdentityResources() { return new List { new IdentityResource( name: "profile", userClaims: new[] { "name", "email", "website" }, displayName: "Your profile data") }; } ``` Once the resource is defined, you can give access to it to a client via the `AllowedScopes` option (other properties omitted): ```csharp var client = new Client { ClientId = "client", AllowedScopes = { "openid", "profile" } }; ``` The client can then request the resource using the scope parameter (other parameters omitted): ```plaintext https://demo.duendesoftware.com/connect/authorize?client_id=client&scope=openid profile ``` IdentityServer will then use the scope names to create a list of requested claim types, and present that to your implementation of the [profile service](/identityserver/reference/v8/services/profile-service/). ----- # Overview > Learn about isolating OAuth resources and using the resource parameter to control access token scope and audience OAuth itself only knows about scopes - the (API) resource concept does not exist from a pure protocol point of view. This means that all the requested scope and audience combinations get merged into a single access token. This has a couple of downsides: * Tokens can become very powerful (and large) * If such a token leaks, it allows access to multiple resources * Resources within that single token might have conflicting settings, e.g. * User claims of all resources share the same token * Resource-specific processing like signing or encryption algorithms conflict * Without sender-constraints, a resource could potentially re-use (or abuse) a token to call another contained resource directly ### Audience Ambiguity [Section titled “Audience Ambiguity”](#audience-ambiguity) In a system with multiple APIs (e.g., Shipping, Invoicing and Inventory APIs), a single token often lists all of them as valid audiences. ```json { "iss": "https://demo.duendesoftware.com", "aud": ["invoice_api", "shipping_api", "inventory_api"], "scope": ["invoice.read", "shipping.write", "inventory.read"] } ``` This violates the Principle of Least Privilege. If this token is leaked from the Inventory API, it can be used to call the Invoice API. To solve this problem [RFC 8707](https://tools.ietf.org/html/rfc8707) adds another request parameter for the authorize and token endpoint called `resource`. This allows requesting a token for a specific resource (in other words - making sure the audience claim has a single value only, and all scopes belong to that single resource). ## Using The Resource Parameter [Section titled “Using The Resource Parameter”](#using-the-resource-parameter) Let’s assume you have the following resource design and that the client is allowed access to all scopes: ApiResources.cs ```csharp var resources = new[] { new ApiResource("urn:invoices") { Scopes = { "read", "write" } }, new ApiResource("urn:products") { Scopes = { "read", "write" } } }; ``` If the client would request a token for the `read` scope, the resulting access token would contain the audience of both the invoice and the products API and thus be accepted at both APIs. ### Machine to Machine Scenarios [Section titled “Machine to Machine Scenarios”](#machine-to-machine-scenarios) If the client in addition passes the `resource` parameter specifying the name of the resource where it wants to use the access token, the token engine can `down-scope` the resulting access token to the single resource, e.g.: ```text POST /token grant_type=client_credentials& client_id=client& client_secret=...& scope=read& resource=urn:invoices ``` Thus resulting in an access token like this (some details omitted): ```json { "aud": ["urn:invoice"], "scope": "read", "client_id": "client" } ``` ### Interactive Applications [Section titled “Interactive Applications”](#interactive-applications) The authorize endpoint supports the `resource` parameter as well, e.g.: ```text GET /authorize?client_id=client&response_type=code&scope=read&resource=urn:invoices ``` Once the front-channel operations are done, the resulting code can be redeemed by passing the resource name on the token endpoint: ```text POST /token grant_type=authorization_code& client_id=client& client_secret=...& authorization_code=...& redirect_uri=...& resource=urn:invoices ``` ### Requesting Access To Multiple Resources [Section titled “Requesting Access To Multiple Resources”](#requesting-access-to-multiple-resources) It is also possible to request access to multiple resources. This will result in multiple access tokens - one for each request resource. ```text GET /authorize?client_id=client&response_type=code&scope=read offline_access&resource=urn:invoices&resource=urn:products ``` When you redeem the code, you need to specify for which resource you want to have an access token, e.g.: ```text POST /token grant_type=authorization_code& client_id=client& client_secret=...& authorization_code=...& redirect_uri=...& resource=urn:invoices ``` This will return an access token for the invoices API and a refresh token. If you want to also retrieve the access token for the products API, you use the refresh token and make another roundtrip to the token endpoint. ```text POST /token grant_type=refresh_token& client_id=client& client_secret=...& refresh_token=...& resource=urn:products ``` The end-result will be that the client has two access tokens - one for each resource and can manage their lifetime via the refresh token. ## Enforcing Resource Isolation [Section titled “Enforcing Resource Isolation”](#enforcing-resource-isolation) All examples so far used the `resource` parameter optionally. If you have API resources, where you want to make sure they are not sharing access tokens with other resources, you can enforce the resource indicator, e.g.: ApiResources.cs ```csharp var resources = new[] { new ApiResource("urn:invoices") { Scopes = { "read", "write" }, RequireResourceIndicator = true }, new ApiResource("urn:products") { Scopes = { "read", "write" }, RequireResourceIndicator = true } }; ``` The `RequireResourceIndicator` property **does not** mean that clients are forced to send the `resource` parameter when they request scopes associated with the API resource. You can still request those scopes without setting the `resource` parameter (or including the resource), and IdentityServer will issue a token as long as the client is allowed to request the scopes. Instead, `RequireResourceIndicator` controls **when** the resource’s URI is included in the **audience claim** (`aud`) of the issued access token. * When `RequireResourceIndicator` is `false` (the default): IdentityServer **automatically includes** the API’s resource URI in the token’s audience if any of the resource’s scopes are requested, even if the `resource` parameter was not sent in the request or didn’t contain the resource URI. * When `RequireResourceIndicator` is `true`: The API’s resource URI will **only** be included in the audience **if the client explicitly includes the resource URI** via the `resource` parameter when requesting the token. ## .NET Client Implementation [Section titled “.NET Client Implementation”](#net-client-implementation) While the examples above show the underlying HTTP protocol, .NET clients can use the Duende libraries to handle resource indicators easily. ### Machine-to-Machine (Worker) [Section titled “Machine-to-Machine (Worker)”](#machine-to-machine-worker) When using `Duende.IdentityModel` for client credentials, you can pass the `resource` parameter using the `Parameters` dictionary: ```csharp using Duende.IdentityModel.Client; var client = new HttpClient(); var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = "https://demo.duendesoftware.com/connect/token", ClientId = "invoice_worker", ClientSecret = "secret", // The scope defines the permission Scope = "invoice.read", // The parameter defines the target (RFC 8707) Resource = [ "urn:invoices" ] }); ``` ### ASP.NET Core [Section titled “ASP.NET Core”](#aspnet-core) For interactive applications using the standard OpenID Connect handler, use the `Resource` property on `OpenIdConnectOptions`: ```csharp .AddOpenIdConnect(options => { options.Authority = "https://demo.duendesoftware.com"; options.ClientId = "interactive_app"; options.Scope.Add("invoice.read"); // Explicitly set the target resource here options.Resource = "urn:invoices"; options.ResponseType = "code"; options.SaveTokens = true; }); ``` Note that while the RFC allows multiple `resource` parameters, the Microsoft OpenID Connect handler only supports a single resource value here. For dynamic scenarios (e.g. multi-tenant), you can set the resource parameter in the `OnRedirectToIdentityProvider` event: ```csharp options.Events.OnRedirectToIdentityProvider = context => { var tenantSpecificResource = DetermineResource(context); // Overwrite or set the 'resource' parameter context.ProtocolMessage.SetParameter("resource", tenantSpecificResource); return Task.CompletedTask; }; ``` ----- # Isolation Sample > Learn about isolating OAuth resources and using the resource parameter to control access token scope and audience Imagine a set of services with separate APIs for handling orders and tracking inventory, an Orders API and Inventory API. Each has their own distinct set of API scopes, plus a set of scopes shared between the APIs. In addition, there’s a global scope used by legacy systems that haven’t been updated yet to use Resource Isolation. The set of scopes used by each application are: | urn:orders | urn:inventory | Not Shared with any API Resource | | ------------ | --------------- | -------------------------------- | | orders.read | inventory.read | global.audit | | orders.write | inventory.write | | | shared.read | shared.read | | The below code creates in-memory scopes, API resources, and a single client (which knows about the aforementioned resources) inside a Duende IdentityServer application. Notice that all scopes are created in a single `Scopes` collection, then the `Resources` collection groups the scopes per `ApiResource`. Finally, the `Client` includes all scopes in its `AllowedScopes` property because the client will be requesting any combination of those scopes from Duende IdentityServer. The only grouping happening is when the `ApiResource` objects link an API resource to a scope. Config.cs ```csharp // All scopes used by all API Resources and Clients public static readonly IEnumerable Scopes = [ // resource specific scopes new ApiScope("orders.read"), new ApiScope("orders.write"), new ApiScope("inventory.read"), new ApiScope("inventory.write"), // a scope shared by multiple resources new ApiScope("shared.read"), // scopes without resource association new ApiScope("global.audit"), ]; // API resources with the scopes they use public static readonly IEnumerable Resources = [ new ApiResource("urn:orders", "Orders API") { Scopes = { "orders.read", "orders.write", "shared.read" } }, new ApiResource("urn:inventory", "Inventory API") { Scopes = { "inventory.read", "inventory.write", "shared.read" }, RequireResourceIndicator = true } ]; public static readonly IEnumerable Clients = [ new Client { ClientId = "resource.isolation.demo.client", ClientSecrets = { new Secret("my-secret".Sha256()) }, ClientClaimsPrefix = "", AllowedGrantTypes = GrantTypes.ClientCredentials, // Client is allowed to access all scopes for all ApiResources AllowedScopes = { "orders.read", "orders.write", "inventory.read", "inventory.write", "shared.read", "global.audit", } } ]; ``` When requesting an `ApiResource`, IdentityServer will create a token with scopes filtered to what is supported by that `ApiResource`. Scopes are not owned by any individual `ApiResource`, and are global across your applications because internally they’re an arbitrary string. An `ApiResource` doesn’t “own” scopes, it is allowed access to those scopes. The table below shows the resulting **audience claim** (`aud`) when making requests for a token with a specific scope/resource combination. | Scopes | Resource Api | Result **audience claim** (`aud`) | | ------------------------ | ------------- | --------------------------------- | | orders.read | null | urn:orders | | inventory.read | null | NOT SET | | inventory.read | urn:inventory | urn:inventory | | orders.read global.audit | null | urn:orders | | shared.read | null | urn:orders | | orders.read shared.read | null | urn:orders | ## Experimenting with a Code Sample [Section titled “Experimenting with a Code Sample”](#experimenting-with-a-code-sample) The code for the above scenario is written out in the two tabs below. Each tab is a [C# file-based app](https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/). One is a Duende IdentityServer application with scopes, API resources, and a client. The second app is a console client that makes requests to Duende IdentityServer, each request with different combinations of scopes and resources to show the result `aud` claim. To help understand how resource isolation works, feel free to run the two apps locally and make modifications as you see fit to experiment. * Duende IdentityServer IdentityServer.cs ```csharp // Run with `dotnet run IdentityServer.cs` #:sdk Microsoft.Net.Sdk.Web #:property PublishAot=false #:package Duende.IdentityServer@8.0.0 using Duende.IdentityServer.Models; var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseUrls("https://localhost:5001"); _ = builder.Services.AddIdentityServer(options => { // emits static audience if required options.EmitStaticAudienceClaim = false; // control format of scope claim options.EmitScopesAsSpaceDelimitedStringInJwt = true; }) .AddInMemoryApiScopes(InMemoryConfig.Scopes) .AddInMemoryApiResources(InMemoryConfig.Resources) .AddInMemoryClients(InMemoryConfig.Clients); var app = builder.Build(); app.UseIdentityServer(); app.Run(); public static class InMemoryConfig { // All scopes used by all API Resources and Clients public static readonly IEnumerable Scopes = [ // resource specific scopes new ApiScope("orders.read"), new ApiScope("orders.write"), new ApiScope("inventory.read"), new ApiScope("inventory.write"), // a scope shared by multiple resources new ApiScope("shared.read"), // scopes without resource association new ApiScope("global.audit"), ]; // API resources with the scopes they use public static readonly IEnumerable Resources = [ new ApiResource("urn:orders", "Orders API") { Scopes = { "orders.read", "orders.write", "shared.read" } }, new ApiResource("urn:inventory", "Inventory API") { Scopes = { "inventory.read", "inventory.write", "shared.read" }, RequireResourceIndicator = true }, ]; public static readonly IEnumerable Clients = [ new Client { ClientId = "resource.isolation.demo.client", ClientSecrets = { new Secret("my-secret".Sha256()) }, ClientClaimsPrefix = "", AllowedGrantTypes = GrantTypes.ClientCredentials, // Client is allowed to access all scopes for all ApiResources AllowedScopes = { "orders.read", "orders.write", "inventory.read", "inventory.write", "shared.read", "global.audit", } } ]; } ``` * Client ResourceIsolationClient.cs ```csharp // Run with `dotnet run ResourceIsolationClient.cs` #:property PublishAot=false // Choose your access package library // #:package Duende.IdentityModel@8.1.0 #:package Duende.AccessTokenManagement@4.2.0 using System.Buffers.Text; using System.Text; using System.Text.Json; using Duende.IdentityModel.Client; var cache = new DiscoveryCache("https://localhost:5001"); Console.WriteLine("Access Token for scope `orders.read`"); await RequestToken(cache, scope: "orders.read", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scope `inventory.read`"); await RequestToken(cache, scope: "inventory.read", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scope `inventory.read` and resource `urn:inventory`"); await RequestToken(cache, scope: "inventory.read", resource: "urn:inventory"); Console.WriteLine(); Console.WriteLine("Access Token for scopes `orders.read global.audit`"); await RequestToken(cache, scope: "orders.read global.audit", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scope `shared.read`"); await RequestToken(cache, scope: "shared.read", resource: null); Console.WriteLine(); Console.WriteLine("Access Token for scopes `orders.read and shared.read`"); await RequestToken(cache, scope: "orders.read shared.read", resource: null); static async Task RequestToken(DiscoveryCache cache, string scope, string? resource) { var client = new HttpClient(); var disco = await cache.GetAsync(); var request = new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "resource.isolation.demo.client", ClientSecret = "my-secret", Scope = scope, }; if (!string.IsNullOrEmpty(resource)) { request.Resource.Add(resource); } var response = await client.RequestClientCredentialsTokenAsync(request); Show(response); } static void Show(TokenResponse response) { if (!response.IsError) { if (response.AccessToken?.Contains('.') is true) { var parts = response.AccessToken.Split('.'); var claims = parts[1]; var raw = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(claims)); var doc = JsonDocument.Parse(raw).RootElement; var json = JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }); Console.WriteLine(json); } else { Console.WriteLine($"Token response: {response.Json}"); } } else if (response.ErrorType == ResponseErrorType.Http) { Console.WriteLine($"HTTP error: {response.Error} with HTTP status code: {response.HttpStatusCode}"); } else { Console.WriteLine($"Protocol error response: {response.Raw}"); } } ``` The code above outputs the token response for each request to Duende IdentityServer. Below is that output, but modified to be in a table to simplify | Scopes | Resource Api | Result **audience claim** (`aud`) | | ------------------------ | ------------- | --------------------------------- | | orders.read | null | urn:orders | | inventory.read | null | NOT SET | | inventory.read | urn:inventory | urn:inventory | | orders.read global.audit | null | urn:orders | | shared.read | null | urn:orders | | orders.read shared.read | null | urn:orders | ----- # Users and Logging In > Overview of user management, authentication workflows, and UI customization options in Duende IdentityServer ## Users And User Interface [Section titled “Users And User Interface”](#users-and-user-interface) The design of Duende IdentityServer allows you to use any user database and build any user interface (UI) workflow needed to satisfy your requirements. This means you have the ability to customize any UI page (registration, login, password reset, etc.), support any credential type (password, MFA, etc.), use any user database (greenfield or legacy), and/or use federated logins from any provider (social or enterprise). You have the ability to control the entire user experience while Duende IdentityServer provides the implementation of the security protocol (OpenID Connect and OAuth). ## Authorization Endpoint And Login Page Workflow [Section titled “Authorization Endpoint And Login Page Workflow”](#authorization-endpoint-and-login-page-workflow) The standard mechanism to allow users to login is for the client application to use a web browser. This is obvious if the client application is a web application, but it’s also the recommended practice for native and mobile applications. When a user must log in, the client application will redirect the user to the protocol endpoint called the [authorization endpoint](/identityserver/reference/v8/endpoints/authorize/) in your IdentityServer server to request authentication. As part of the authorize request, your IdentityServer will typically display a login page for the user to enter their credentials. Once the user has authenticated, your IdentityServer will redirect the user back to the application with the protocol response. Recall the diagram showing the relationship of your custom UI pages and the IdentityServer middleware in your IdentityServer host application: ![middleware diagram](/_astro/middleware.Cqu9tG8b_Z1LEIl7.svg) When your IdentityServer receives an authorize request, it will inspect it for a current authentication session for a user. This authentication session is based on ASP.NET Core’s authentication system and is ultimately determined by a cookie issued from your login page. If the user has never logged in there will be no cookie, and then the request to the authorize endpoint will result in a redirect to your login page. This is the entry point into your custom workflow that can take over to get the user logged in. ![sign in flow](/_astro/signin_flow.CJ2S0qPI_Zhu0yW.svg) Once the login page has finished logging in the user with the ASP.NET Core authentication system, it will redirect the user back to the authorize endpoint. This time the request to the authorize endpoint will have an authenticated session for the user, and it can then create the protocol response and redirect to the client application. ## Additional Pages [Section titled “Additional Pages”](#additional-pages) In addition to the login page, there are other pages that Duende IdentityServer expects (e.g. logout, error, consent), and you could implement custom pages as well (e.g. register, forgot password, etc.). Details about building these pages, and coverage of additional topics are in the [User Interaction](/identityserver/ui) section of this documentation. ----- # The Big Picture > An overview of modern application architecture patterns and how OpenID Connect and OAuth 2.0 protocols implemented by IdentityServer solve authentication and API access challenges Most modern applications look more or less like this: ![an architecture diagram for modern applications with clients and services](/_astro/application-architecture.BtlWYoIv_1ufBKP.svg) The most common interactions are: * Browsers communicate with web applications * Web applications communicate with web APIs (sometimes on their own, sometimes on behalf of a user) * Browser-based applications communicate with web APIs * Native applications communicate with web APIs * Server-based applications communicate with web APIs * Web APIs communicate with web APIs (sometimes on their own, sometimes on behalf of a user) Typically, each and every layer (front-end, middle-tier and back-end) has to protect resources and implement authentication and/or authorization – often against the same user store. Outsourcing these fundamental security functions to a security token service prevents duplicating that functionality across those applications and endpoints. Restructuring the application to support a security token service leads to the following architecture and protocols: ![an architecture diagram showing where OAuth 2.0 is used](/_astro/protocols.D9K09C-x_Z1VwovC.svg) Such a design divides security concerns into two parts: ## Authentication [Section titled “Authentication”](#authentication) Authentication is needed when an application needs to know the identity of the current user. Typically, these applications manage data on behalf of that user and need to make sure that this user can only access the data for which they are allowed. The most common example for that is (classic) web applications – but native and JS-based applications also have a need for authentication. The most common authentication protocols are SAML2p, WS-Federation and OpenID Connect – SAML2p being the most popular and the most widely deployed. OpenID Connect is the newest of the three, but is considered to be the future because it has the most potential for modern applications. It was built for mobile application scenarios right from the start and is designed to be API friendly. ## API Access [Section titled “API Access”](#api-access) Applications have two fundamental ways with which they communicate with APIs – using the application identity, or delegating the user’s identity. Sometimes both methods need to be combined. OAuth 2.0 is a protocol that allows applications to request access tokens from a security token service and use them to communicate with APIs. This delegation reduces complexity in both the client applications and the APIs since authentication and authorization can be centralized. ## OpenID Connect And OAuth 2.0 – Better Together! [Section titled “OpenID Connect And OAuth 2.0 – Better Together!”](#openid-connect-and-oauth-20--better-together) OpenID Connect and OAuth 2.0 are very similar – in fact OpenID Connect is an extension on top of OAuth 2.0. The two fundamental security concerns, authentication and API access, are combined into a single protocol - often with a single round trip to the security token service. We believe that the combination of OpenID Connect and OAuth 2.0 is the best approach to secure modern applications for the foreseeable future. Duende IdentityServer is an implementation of these two protocols and is highly optimized to solve the typical security problems of today’s mobile, native and web applications. ## How Duende IdentityServer Can Help [Section titled “How Duende IdentityServer Can Help”](#how-duende-identityserver-can-help) Duende IdentityServer is middleware that adds spec-compliant OpenID Connect and OAuth 2.0 endpoints to an arbitrary ASP.NET Core host. Typically, you build (or re-use) an application that contains login and logout pages (and optionally a consent page, depending on your needs) and add the IdentityServer middleware to that application. The middleware adds the necessary protocol heads to the application so that clients can talk to it using those standard protocols. ![IdentityServer middleware diagram and its relationship in the ASP.NET Core pipeline](/_astro/middleware.Cqu9tG8b_Z1LEIl7.svg) The hosting application can be as complex as you want, but we typically recommend to keep the attack surface as small as possible by including authentication/federation related UI only. ----- # Packaging and Builds > A guide to Duende IdentityServer packages, templates, UI components, and source code accessibility ## Product [Section titled “Product”](#product) The licensed and supported libraries can be accessed via NuGet: * [Duende IdentityServer](https://www.nuget.org/packages/Duende.IdentityServer) * [Duende IdentityServer EntityFramework Integration](https://www.nuget.org/packages/Duende.IdentityServer.EntityFramework) * [Duende IdentityServer ASP.NET Identity Integration](https://www.nuget.org/packages/Duende.IdentityServer.AspNetIdentity) ## Templates [Section titled “Templates”](#templates) Contains Duende templates for the `dotnet` CLI to help jump-start your Duende-powered solutions. You can install the templates using the following command: Terminal ```bash dotnet new install Duende.Templates ``` [Templates ](https://www.nuget.org/packages/Duende.Templates)NuGet Package for IdentityServer Templates [Source Code ](https://github.com/DuendeSoftware/IdentityServer.Templates)Source code for IdentityServer Templates Running the command `dotnet new list duende` should give you a list of the following templates ```bash Template Name Short Name Language Tags ---------------------------------------------------------- -------------------- -------- ------------------------- Duende BFF Host using a Remote API duende-bff-remoteapi [C#] Web/Duende/BFF Duende BFF using a Local API duende-bff-localapi [C#] Web/Duende/BFF Duende BFF with Blazor autorender duende-bff-blazor [C#] Web/Duende/BFF Duende IdentityServer Empty duende-is-empty [C#] Web/Duende/IdentityServer Duende IdentityServer Quickstart UI (UI assets only) duende-is-ui [C#] Web/IdentityServer Duende IdentityServer with ASP.NET Core Identity duende-is-aspid [C#] Web/Duende/IdentityServer Duende IdentityServer with Entity Framework Stores duende-is-ef [C#] Web/Duende/IdentityServer Duende IdentityServer with In-Memory Stores and Test Users duende-is-inmem [C#] Web/Duende/IdentityServer Duende IdentityServer duende-is [C#] Web/Duende/IdentityServer ``` ## Template Descriptions [Section titled “Template Descriptions”](#template-descriptions) In this section, we’ll discuss what each IdentityServer template offers and why you would choose to start with it. While there are similarities across templates, there are nuances that can make for better starting points depending on your particular use case. We’ll start with the simplest templates and then move to the most feature-rich ones. Many of these templates build on each other’s work, so moving from one to another is straightforward. All templates are provided as a starting point for your customization. Using the templates, you assume development responsibility for the choices, alterations, and inevitable deployment of your IdentityServer instance. ### Duende IdentityServer Empty [Section titled “Duende IdentityServer Empty”](#duende-identityserver-empty) You want to run the following command to start using the **Duende IdentityServer Empty** template. ```bash dotnet new duende-is-empty ``` Once created, this template has three essential files: `Config`, `HostingExtensions`, and `Program`. You can modify the `Config` file to add clients, scopes, and claims, as all configurations are from in-memory objects. ```csharp public static class Config { public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId() }; public static IEnumerable ApiScopes => new ApiScope[] { }; public static IEnumerable Clients => new Client[] { }; } ``` This template doesn’t include user interface elements, so it doesn’t support OpenID Connect unless you add those UI elements. You can do so by running the UI-only template of `duende-is-ui`. ```bash dotnet new duende-is-ui --project ``` The executed command will add Razor Pages to your web project. You will need to add Razor Pages to your `HostingExtensions` file. ```csharp using Serilog; internal static class HostingExtensions { public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { builder.Services.AddRazorPages(); builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) .AddLicenseSummary(); return builder.Build(); } public static WebApplication ConfigurePipeline(this WebApplication app) { app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthorization(); app.MapRazorPages().RequireAuthorization(); return app; } } ``` ### Duende IdentityServer with In-Memory Stores and Test Users [Section titled “Duende IdentityServer with In-Memory Stores and Test Users”](#duende-identityserver-with-in-memory-stores-and-test-users) The `duende-is-inmem` template is similar to the `duende-is-empty` and `duende-is-ui` templates combined into a single project template. ```bash dotnet new duende-is-inmem ``` This template differs from others in that we have defined some starting clients, scopes, and claims for common development scenarios and a speedier development experience. Config.cs ```csharp public static class Config { public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; public static IEnumerable ApiScopes => new ApiScope[] { new ApiScope("scope1"), new ApiScope("scope2"), }; public static IEnumerable Clients => new Client[] { // m2m client credentials flow client new Client { ClientId = "m2m.client", ClientName = "Client Credentials Client", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) }, AllowedScopes = { "scope1" } }, // interactive client using code flow + pkce new Client { ClientId = "interactive", ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, RedirectUris = { "https://localhost:44300/signin-oidc" }, FrontChannelLogoutUri = "https://localhost:44300/signout-oidc", PostLogoutRedirectUris = { "https://localhost:44300/signout-callback-oidc" }, AllowOfflineAccess = true, AllowedScopes = { "openid", "profile", "scope2" } }, }; } ``` This template is a great starting point for proof of concepts and a learning tool for developers experiencing OAuth 2.0 and OpenID Connect in the .NET space for the first time. ### Duende IdentityServer with Entity Framework Stores [Section titled “Duende IdentityServer with Entity Framework Stores”](#duende-identityserver-with-entity-framework-stores) For developers looking to quickly go to a production-like environment, starting with the `duende-is-ef` template is a great starting point. ```bash dotnet new duende-is-ef ``` This template stores all operational and configuration data of the IdentityServer instance in your chosen data storage, utilizing EF Core’s ability to target multiple database engines. The template targets SQLite by default, but we have included scripts to easily swap out and regenerate migrations for your database. [Read more about the Entity Framework Core setup here.](/identityserver/data/ef/) ### Duende IdentityServer [Section titled “Duende IdentityServer”](#duende-identityserver) The Duende IdentityServer template is our most feature-rich offering and a great starting point for developers who want a simple yet effective UI/UX experience. ```bash dotnet new duende-is ``` The template is built on the Entity Framework Core template but provides an administrative UI for managing clients, scopes, and claims against a database storage engine. It also has a diagnostics dashboard showing system information, including the licensing tier and features currently used in your IdentityServer deployment. #### Third-party Dependencies [Section titled “Third-party Dependencies”](#third-party-dependencies) This template includes several third-party dependencies: * [Serilog](https://serilog.net/) * [Bootstrap 5](https://getbootstrap.com) * [Bootstrap 5 tags](https://github.com/lekoala/bootstrap5-tags) * [JQuery](https://jquery.org) * [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) ### Duende IdentityServer with ASP.NET Core Identity [Section titled “Duende IdentityServer with ASP.NET Core Identity”](#duende-identityserver-with-aspnet-core-identity) The **Duende IdentityServer with ASP.NET Core Identity** template integrates with ASP.NET Identity to provide you with an instance of Duende IdentityServer that has a user store powered by the Microsoft library. [Please read our ASP.NET Identity documentation](/identityserver/aspnet-identity/), to learn more about this integration. ### BFF Templates [Section titled “BFF Templates”](#bff-templates) For Duende BFF template description, refer the [Duende BFF project templates](/bff/getting-started/templates/). ----- # More Reading Resources > Collection of learning resources including demo server access, OAuth fundamentals, and ASP.NET security guides ## Demo Server [Section titled “Demo Server”](#demo-server) You can try Duende IdentityServer with your favourite client library. We have a test instance at [demo.duendesoftware.com](https://demo.duendesoftware.com). On the main page you can find instructions on how to configure your client and how to call an API. [IdentityServer Demo Server ](https://demo.duendesoftware.com)Visit https\://demo.duendesoftware.com ## OAuth and OIDC Fundamentals [Section titled “OAuth and OIDC Fundamentals”](#oauth-and-oidc-fundamentals) * [OAuth the good Parts (video from NDC Porto 2022)](https://www.youtube.com/watch?v=Ps8ep-glDfc) * [Securing SPAs and Blazor Applications using the BFF (video from NDC Porto 2022)](https://www.youtube.com/watch?v=xzRhabmlc8M) * [Automated OAuth Access Token Management for .NET Workers and ASP.NET Web Applications](https://www.youtube.com/watch?v=zr-LAYg5BCE) ## ASP.NET Security [Section titled “ASP.NET Security”](#aspnet-security) * [Introduction to ASP.NET Core Authentication and Authorization](https://www.youtube.com/watch?v=02Yh3sxzAYI) * [ASP.NET Cookie Authentication Documentation](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie) * [Supporting External Authentication Providers Part 1](https://www.youtube.com/watch?v=HH_tw7dFhpg) * [Supporting External Authentication Providers Part 2](https://www.youtube.com/watch?v=daeVaU5CmPw) ## End-User Authorization [Section titled “End-User Authorization”](#end-user-authorization) * [Authorization for Modern Applications (video from DevConf 2018)](https://www.youtube.com/watch?v=Dlrf85NTuAU) ----- # Supported Specifications > A comprehensive list of supported OpenID Connect and OAuth 2.x specifications implemented in Duende IdentityServer Duende IdentityServer implements the following specifications: ## OpenID Connect [Section titled “OpenID Connect”](#openid-connect) * OpenID Connect Core 1.0 ([spec](https://openid.net/specs/openid-connect-core-1_0.html)) * OpenID Connect Discovery 1.0 ([spec](https://openid.net/specs/openid-connect-discovery-1_0.html)) * OpenID Connect RP-Initiated Logout 1.0 ([spec](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)) * OpenID Connect Session Management 1.0 ([spec](https://openid.net/specs/openid-connect-session-1_0.html)) * OpenID Connect Front-Channel Logout 1.0 ([spec](https://openid.net/specs/openid-connect-frontchannel-1_0.html)) * OpenID Connect Back-Channel Logout 1.0 ([spec](https://openid.net/specs/openid-connect-backchannel-1_0.html)) * Multiple Response Types ([spec](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html)) * Form Post Response Mode ([spec](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html)) * Enterprise Edition: OpenID Connect Client-Initiated Backchannel Authentication (CIBA) ([spec](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)) * FAPI 2.0 Security Profile ([spec](https://openid.net/specs/fapi-security-profile-2_0-final.html)) ## OAuth 2.x [Section titled “OAuth 2.x”](#oauth-2x) * OAuth 2.0 ([RFC 6749](https://tools.ietf.org/html/rfc6749)) * OAuth 2.0 Bearer Token Usage ([RFC 6750](https://tools.ietf.org/html/rfc6750)) * JSON Web Token ([RFC 7519](https://tools.ietf.org/html/rfc7519)) * OAuth 2.0 Token Revocation ([RFC 7009](https://tools.ietf.org/html/rfc7009)) * OAuth 2.0 Token Introspection ([RFC 7662](https://tools.ietf.org/html/rfc7662)) * Proof Key for Code Exchange by OAuth Public Clients ([RFC 7636](https://tools.ietf.org/html/rfc7636)) * OAuth 2.0 JSON Web Tokens for Client Authentication ([RFC 7523](https://tools.ietf.org/html/rfc7523)) * OAuth 2.0 Device Authorization Grant ([RFC 8628](https://tools.ietf.org/html/rfc8628)) * Proof-of-Possession Key Semantics for JSON Web Tokens ([RFC 7800](https://tools.ietf.org/html/rfc7800)) * OAuth 2.0 Mutual TLS Client Authentication and Certificate-Bound Access Tokens ([RFC 8705](https://tools.ietf.org/html/rfc8705)) * OAuth 2.0 Token Exchange ([RFC 8693](https://tools.ietf.org/html/rfc8693)) * JWT Secured Authorization Request / JAR ([RFC 9101](https://datatracker.ietf.org/doc/html/rfc9101)) * JWT Profile for OAuth 2.0 Access Tokens ([RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068)) * OAuth 2.0 Authorization Server Issuer Identifier in Authorization Response ([RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207)) * OAuth 2.0 Step-up Authentication Challenge Protocol ([RFC 9470](https://datatracker.ietf.org/doc/html/rfc9470)) * Business (legacy), Enterprise (legacy), Standard, Advanced, and Custom Edition: OAuth 2.0 Dynamic Client Registration Protocol ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)) * Business (legacy), Enterprise (legacy), Standard, Advanced, and Custom Edition: OAuth 2.0 Pushed Authorization Requests ([RFC 9126](https://www.rfc-editor.org/rfc/rfc9126)) * Enterprise (legacy), Standard, Advanced, and Custom Edition: Resource Indicators for OAuth 2.0 ([RFC 8707](https://tools.ietf.org/html/rfc8707)) * Enterprise (legacy), Standard, Advanced, and Custom Edition: OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer / DPoP ([RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)) * JSON Web Token (JWT) Response for OAuth Token Introspection ([RFC 9701](https://www.rfc-editor.org/rfc/rfc9701.html)) * OAuth 2.0 Authorization Server Metadata ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) ----- # Terminology > Learn about the key terms and concepts used in IdentityServer, including clients, resources, tokens, and user authentication flows. The specs, documentation and object model use a certain terminology that you should be aware of. ![a basic diagrams showing the relationship between users, clients, identityserver, and resources](/_astro/terminology.Du5MR-aA_vpczY.svg) ## Duende IdentityServer [Section titled “Duende IdentityServer”](#duende-identityserver) Duende IdentityServer is an OpenID Connect & OAuth engine - it implements the OpenID Connect and OAuth 2.0 family of [protocols](/identityserver/overview/specs/). Different literature uses different terms for the same role - you probably also find the terms security token service, identity provider, authorization server, IP-STS and more. But they are in a nutshell all the same: a piece of software that issues security tokens to clients. A typical implementation of Duende IdentityServer has a number of jobs and features - including: * manage access to resources * authenticate users using a local account store or via an external identity provider * provide session management and single sign-on * manage and authenticate clients * issue identity and access tokens to clients ## User [Section titled “User”](#user) A user is a human that is using a registered client to access resources. ## Client [Section titled “Client”](#client) A [client](/identityserver/fundamentals/clients/) is a piece of software that requests tokens from your IdentityServer - either for authenticating a user (requesting an identity token) or for accessing a resource (requesting an access token). A client must be first registered with your IdentityServer before it can request tokens. While there are many different client types, e.g. web applications, native mobile or desktop applications, SPAs, server processes etc., they can all be put into two high-level categories. ### Machine to Machine Communication [Section titled “Machine to Machine Communication”](#machine-to-machine-communication) In this scenario two machines talk to each other (e.g. background processes, batch jobs, server daemons), and there is no interactive user present. To authorize this communication, your IdentityServer issues a token to the caller. In protocol terms, this scenario is called *Client Credentials Flow* and you can learn more about it in the issuing tokens [section](/identityserver/tokens/requesting/#machine-to-machine-communication) and in our [Quickstart](/identityserver/quickstarts/1-client-credentials/). ### Interactive Applications [Section titled “Interactive Applications”](#interactive-applications) This is the most common type of client scenario: web applications, SPAs or native/mobile apps with interactive users. This scenario typically involves a browser for user interaction (e.g. for authentication or consent). In protocol terms, this scenario is called *Authorization Code Flow* and you can learn more about it in the issuing tokens [section](/identityserver/tokens/requesting/#interactive-applications) and in our [Quickstart](/identityserver/quickstarts/2-interactive/). ## Resources [Section titled “Resources”](#resources) [Resources](/identityserver/fundamentals/resources) are something you want to protect with your IdentityServer - either identity data of your users, or APIs. Every resource has a unique name - and clients use this name to specify to which resources they want to get access to. **Identity data** Identity information (aka claims) about a user, e.g. name or email address. *`APIs`* APIs resources represent functionality a client wants to invoke - typically modelled as Web APIs, but not necessarily. ## Identity Token [Section titled “Identity Token”](#identity-token) An identity token represents the outcome of an authentication process. It contains at a bare minimum an identifier for the user (called the `sub` aka subject claim) and information about how and when the user authenticated. It can contain additional identity data. ## Access Token [Section titled “Access Token”](#access-token) An access token allows access to an API resource. Clients request access tokens and forward them to the API. Access tokens contain information about the client and the user (if present). APIs use that information to authorize access to their data and functionality. ----- # IdentityServer Quickstarts > Step-by-step tutorials for implementing common Duende IdentityServer scenarios, from basic setup to advanced features. The quickstarts provide step-by-step instructions for various common Duende IdentityServer scenarios. They start with the absolute basics and become more complex - it is recommended you do them in order. * adding Duende IdentityServer to an ASP.NET Core application * configuring Duende IdentityServer * issuing tokens for various clients * securing web applications and APIs * adding support for EntityFramework based configuration * adding support for ASP.NET Identity Every quickstart has a reference solution. You can find the code in the [samples](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts) folder. ## Preparation [Section titled “Preparation”](#preparation) The first thing you should do is install our templates: Terminal ```bash dotnet new install Duende.Templates ``` They will be used as a starting point for the various tutorials. [YouTube video player](https://www.youtube.com/embed/cxYmODQHErM) ----- # Protecting An API With Client Credentials > Learn how to set up IdentityServer to protect an API using client credentials, implementing server-to-server authentication with access tokens. Welcome to the first quickstart for IdentityServer! To see the full list of quickstarts, please see [Quickstarts Overview](/identityserver/quickstarts/0-overview/). This first quickstart provides step-by-step instructions to set up IdentityServer in the most basic scenario: protecting APIs for server-to-server communication. You will create a solution containing three projects: * An Identity Server * An API that requires authentication * A client that accesses that API The client will request an access token from IdentityServer using its client ID and secret and then use the token to gain access to the API. ## Source Code [Section titled “Source Code”](#source-code) Finished source code for each quickstart in this series is available in the [Samples](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts) repository, and a reference implementation of this quickstart is available [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials). ## Video [Section titled “Video”](#video) In addition to the written steps below there’s also a YouTube video available: [YouTube video player](https://www.youtube.com/embed/EhuCpbH7Ad0) ## Preparation [Section titled “Preparation”](#preparation) The IdentityServer templates for the dotnet CLI are a good starting point for the quickstarts. To install the templates open a console window and type the following command: ```console dotnet new install Duende.Templates ``` ## Create The Solution And IdentityServer Project [Section titled “Create The Solution And IdentityServer Project”](#create-the-solution-and-identityserver-project) In this section, you will create a directory for the solution and use the `isempty` (IdentityServer Empty) template to create an ASP.NET Core application that includes a basic IdentityServer setup. Back in the console, run the following commands to create the directory structure for the solution. ```console mkdir quickstart cd quickstart mkdir src dotnet new sln -n Quickstart ``` This will create a quickstart directory that will serve as the root of the solution, a src subdirectory to hold your source code, and a solution file to organize your projects. Throughout the rest of the quickstart series, paths will be written relative to the quickstart directory. From the new quickstart directory, run the following commands to use the `isempty` template to create a new project. The template creates a web project named IdentityServer with the IdentityServer package installed and minimal configuration added for it. ```console cd src dotnet new duende-is-empty -n IdentityServer ``` This will create the following files within a new `src/IdentityServer` directory: * `Properties/launchSettings.json` file - launch profile * `appsettings.json` - run time settings * `Config.cs` - definitions for [resources](/identityserver/overview/terminology/#resources) and [clients](/identityserver/overview/terminology/#client) used by IdentityServer * `HostingExtensions.cs` - configuration for ASP.NET pipeline and services Notably, the IdentityServer services are configured here and the IdentityServer middleware is added to the pipeline here. * `IdentityServer.csproj` - project file with the IdentityServer NuGet package added * `Program.cs` - main application entry point Next, add the IdentityServer project to the solution. Back in the console, navigate up to the quickstart directory and add the IdentityServer project to the solution. ```console cd .. dotnet sln add ./src/IdentityServer ``` ### Defining An API Scope [Section titled “Defining An API Scope”](#defining-an-api-scope) Scope is a core feature of OAuth that allows you to express the extent or scope of access. Clients request scopes when they initiate the protocol, declaring what scope of access they want. IdentityServer then has to decide which scopes to include in the token. Just because the client has asked for something doesn’t mean they should get it! There are built-in abstractions and extensibility points that you can use to make this decision. Ultimately, IdentityServer issues a token to the client, which then uses the token to access APIs. APIs can check the scopes that were included in the token to make authorization decisions. Scopes don’t have structure imposed by the protocols - they are just space-separated strings. This allows for flexibility when designing the scopes used by a system. In this quickstart, you will create a scope that represents complete access to an API that will be created later in this quickstart. Scope definitions can be loaded in many ways. This quickstart shows how to use a “code as configuration” approach. A minimal Config.cs was created by the template at `src/IdentityServer/Config.cs`. Open it and add an `ApiScope` to the `ApiScopes` property: ```csharp public static IEnumerable ApiScopes => new ApiScope[] { new ApiScope(name: "api1", displayName: "My API") }; ``` See the full file [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/Config.cs). ### Defining The client [Section titled “Defining The client”](#defining-the-client) The next step is to configure a client application that you will use to access the API. You’ll create the client application project later in this quickstart. First, you’ll add configuration for it to your IdentityServer project. In this quickstart, the client will not have an interactive user and will authenticate with IdentityServer using a client secret. Add this client definition to `Config.cs`: ```csharp public static IEnumerable Clients => new Client[] { new Client { ClientId = "client", // no interactive user, use the clientid/secret for authentication AllowedGrantTypes = GrantTypes.ClientCredentials, // secret for authentication ClientSecrets = { new Secret("secret".Sha256()) }, // scopes that client has access to AllowedScopes = { "api1" } } }; ``` Again, see the full file [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/Config.cs). Clients can be configured with many options. Your minimal machine-to-machine client here contains: * A ClientId, which identifies the application to IdentityServer so that it knows which client is trying to connect to it. * A Secret, which you can think of as the password for the client. * The list of scopes that the client is allowed to ask for. Notice that the allowed scope here matches the name of the ApiScope above. ### Configuring IdentityServer [Section titled “Configuring IdentityServer”](#configuring-identityserver) The scope and client definitions are loaded in [HostingExtensions.cs](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/HostingExtensions.cs). The template created a ConfigureServices method there that is already loading the scopes and clients. You can take a look to see how it is done. Note that the template adds a few things that are not used in this quickstart. Here’s the minimal ConfigureServices method that is needed: Startup.cs ```csharp public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { // Can also be found in Program.cs builder.Services.AddIdentityServer() .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); return builder.Build(); } ``` That’s it - your IdentityServer is now configured. If you run the project and then navigate to `https://localhost:5001/.well-known/openid-configuration` in your browser, you should see the [discovery document](/identityserver/reference/v8/endpoints/discovery/). The discovery document is a standard endpoint in [OpenID Connect](https://openid.net/specs/openid-connect-discovery-1_0.html) and [OAuth](https://datatracker.ietf.org/doc/html/rfc8414). It is used by your clients and APIs to retrieve configuration data needed to request and validate tokens, login and logout, etc. ![Browser showing discovery endpoint JSON](/_astro/1_discovery.CglV0FSW_uefJN.webp) ## Create An API Project [Section titled “Create An API Project”](#create-an-api-project) Next, add an API project to your solution. This API will serve protected resources that will be secured by IdentityServer. You can either use the ASP.NET Core Web API template from Visual Studio or use the .NET CLI to create the API project. To use the CLI, run the following commands: ```console cd src dotnet new webapi -n Api --no-openapi ``` Then navigate back up to the root quickstart directory and add it to the solution by running the following commands: ```console cd .. dotnet sln add ./src/Api ``` ### Add JWT Bearer Authentication [Section titled “Add JWT Bearer Authentication”](#add-jwt-bearer-authentication) Now you will add JWT Bearer Authentication to the API’s ASP.NET pipeline. The goal is to authorize calls to your API using tokens issued by the IdentityServer project. To that end, you will add authentication middleware to the pipeline from the `Microsoft.AspNetCore.Authentication.JwtBearer` NuGet package. This middleware will: * Find and parse a JWT sent with incoming requests as an *Authorization: Bearer* header. * Validate the JWT’s signature to ensure that it was issued by IdentityServer. * Validate that the JWT is not expired. Run this command to add the middleware package to the API: ```console dotnet add ./src/Api package Microsoft.AspNetCore.Authentication.JwtBearer ``` Now add the authentication and authorization services to the Service Collection, and configure the JWT Bearer authentication provider as the default [Authentication Scheme](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-8.0#authentication-scheme). Program.cs ```csharp builder.Services.AddAuthentication() .AddJwtBearer(options => { options.Authority = "https://localhost:5001"; options.TokenValidationParameters.ValidateAudience = false; }); builder.Services.AddAuthorization(); ``` ### Add An Endpoint [Section titled “Add An Endpoint”](#add-an-endpoint) Replace the templated weather forecast endpoint with a new endpoint: ```csharp app.MapGet("identity", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) .RequireAuthorization(); ``` This endpoint will be used to test authorization and to display the claims identity through the eyes of the API. ### Configure API To Listen On Port 6001 [Section titled “Configure API To Listen On Port 6001”](#configure-api-to-listen-on-port-6001) Configure the API to run on `https://localhost:6001` only. You can do this by editing the [launchSettings.json](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/Api/Properties/launchSettings.json) file in the `src/Api/Properties` directory. Change these settings for the `https` profile: ```json { "launchUrl": "identity", "applicationUrl": "https://localhost:6001" } ``` ### Test The Identity Endpoint [Section titled “Test The Identity Endpoint”](#test-the-identity-endpoint) Run the API project using the `https` profile and then navigate to the identity controller at `https://localhost:6001/identity` in a browser. This should return a 401 status code, which means your API requires a credential and is now protected by IdentityServer. ## Create The Client Project [Section titled “Create The Client Project”](#create-the-client-project) The last step is to create a client that requests an access token and then uses that token to access the API. Your client will be a console project in your solution. Run the following commands: ```console cd src dotnet new console -n Client ``` Then as before, add it to your solution using: ```console cd .. dotnet sln add ./src/Client ``` ### Add The IdentityModel NuGet Package [Section titled “Add The IdentityModel NuGet Package”](#add-the-identitymodel-nuget-package) The token endpoint at IdentityServer implements the OAuth protocol, and you could use raw HTTP to access it. However, we have a client library called IdentityModel that encapsulates the protocol interaction in an easy-to-use API. Add the \*Duende.IdentityModel \* NuGet package to your client by running the following command: ```console dotnet add ./src/Client package Duende.IdentityModel ``` ### Retrieve The Discovery Document [Section titled “Retrieve The Discovery Document”](#retrieve-the-discovery-document) IdentityModel includes a client library to use with the discovery endpoint. This way you only need to know the base address of IdentityServer - the actual endpoint addresses can be read from the metadata. Add the following to the client’s Program.cs in the `src/Client/Program.cs` directory: ```csharp using Duende.IdentityModel.Client; // discovery endpoints from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); if (disco.IsError) { Console.WriteLine(disco.Error); Console.WriteLine(disco.Exception); return 1; } ``` ### Request A Token From IdentityServer [Section titled “Request A Token From IdentityServer”](#request-a-token-from-identityserver) Next you can use the information from the discovery document to request a token from `IdentityServer` to access `api1`: ```csharp // request token var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Scope = "api1" }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); Console.WriteLine(tokenResponse.ErrorDescription); return 1; } Console.WriteLine(tokenResponse.AccessToken); ``` ### Calling The API [Section titled “Calling The API”](#calling-the-api) To send the access token to the API you typically use the HTTP Authorization header. This is done using the `SetBearerToken` extension method: ```csharp // call api var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken!); // AccessToken is always non-null when IsError is false var response = await apiClient.GetAsync("https://localhost:6001/identity"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); return 1; } var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true })); return 0; ``` The completed `Program.cs` file can be found [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/Client/Program.cs). To test the flow, start the IdentityServer and API projects. Once they are running, run the Client project. The output should look like this: ![Windows console showing claims for a bearer token](/_astro/1_client_screenshot.DapOaE8B_Z21a1sv.webp) If you’re using Visual Studio, here’s how to start everything up: 1. Right-click the solution and select *Configure Startup Projects…* 2. Choose *Multiple Startup Projects* and set the action for Api and IdentityServer to Start 3. Run the solution and wait a moment for both the API and IdentityServer to start 4. Right-click the `Client` project and select Debug -> Start Without Debugging. #### Authorization At The API [Section titled “Authorization At The API”](#authorization-at-the-api) Right now, the API accepts any access token issued by your IdentityServer. In this section, you will add an [Authorization Policy](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-8.0) to the API that will check for the presence of the “api1” scope in the access token. The protocol ensures that this scope will only be in the token if the client requests it and IdentityServer allows the client to have that scope. You configured IdentityServer to allow this access by [including it in the allowedScopes property](#defining-the-client). Add the following to the `Program.cs` file of the API: Program.cs ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy("ApiScope", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "api1"); }); }); ``` You can now enforce this policy at various levels, e.g.: * globally * for all endpoints * for specific controllers, actions, or endpoints Add the policy to the identity endpoint in `src/Api/Program.cs`: ```csharp app.MapGet("identity", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) .RequireAuthorization("ApiScope"); ``` Now you can run the API again, and it will enforce that the api1 scope is present in the access token. ## Further Experiments [Section titled “Further Experiments”](#further-experiments) This quickstart focused on the success path: * The client was able to request a token. * The client could use the token to access the API. You can now try to provoke errors to learn how the system behaves, e.g.: * Try to connect to IdentityServer when it is not running (unavailable). * Try to use an invalid client id or secret to request the token. * Try to ask for an invalid scope during the token request. * Try to call the API when it is not running (unavailable). * Don’t send the token to the API. * Configure the API to require a different scope than the one in the token. ----- # Interactive Applications With ASP.NET Core > Learn how to add interactive user authentication to an ASP.NET Core application using OpenID Connect and IdentityServer, including configuring the UI, managing user login/logout, and accessing claims. Welcome to Quickstart 2 for Duende IdentityServer! In this quickstart, you will add support for interactive user authentication via the OpenID Connect protocol to the IdentityServer you built in [Quickstart 1](/identityserver/quickstarts/1-client-credentials/). Once that is in place, you will create an ASP.NET Razor Pages application that will use IdentityServer for authentication. ## Video [Section titled “Video”](#video) In addition to the written steps below there’s also a YouTube video available: [YouTube video player](https://www.youtube.com/embed/4aYj4xb7_Cg) ## Enable OIDC In IdentityServer [Section titled “Enable OIDC In IdentityServer”](#enable-oidc-in-identityserver) To enable OIDC in IdentityServer you need: * An interactive UI * Configuration for OIDC scopes * Configuration for an OIDC client * Users to log in with ### Add The UI [Section titled “Add The UI”](#add-the-ui) Support for the OpenID Connect protocol is already built into IdentityServer. You need to provide the User Interface for login, logout, consent, and error. While the look & feel and workflows will differ in each implementation, we provide a Razor Pages-based UI that you can use as a starting point. You can use the .NET CLI to add the quickstart UI to a project. Run the following command from the `src/IdentityServer` directory: ```console dotnet new duende-is-ui ``` ### Enable The UI [Section titled “Enable The UI”](#enable-the-ui) Once you have added the UI, you will need to register its services and enable it in the pipeline. In `src/IdentityServer/HostingExtensions.cs` you will find commented out code in the `ConfigureServices` and `ConfigurePipeline` methods that enable the UI. Note that there are three places to comment in - two in `ConfigurePipeline` and one in `ConfigureServices`. Comment in the service registration and pipeline configuration, run the `IdentityServer` project, and navigate to `https://localhost:5001`. You should now see a home page. Spend some time reading the pages and models, especially those in the `src/IdentityServer/Pages/Account` directory. These pages are the main UI entry points for login and logout. The better you understand them, the easier it will be to make future modifications. ### Configure OIDC Scopes [Section titled “Configure OIDC Scopes”](#configure-oidc-scopes) Similar to OAuth, OpenID Connect uses scopes to represent something you want to protect and that clients want to access. In contrast to OAuth, scopes in OIDC represent identity data like user id, name or email address rather than APIs. Add support for the standard `openid` (subject id) and `profile` (first name, last name, etc.) scopes by declaring them in `src/IdentityServer/Config.cs`: ```csharp public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; ``` Then register the identity resources in `src/IdentityServer/HostingExtensions.cs`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); ``` ### Add Test Users [Section titled “Add Test Users”](#add-test-users) The sample UI also comes with an in-memory “user database”. You can enable this by calling `AddTestUsers` in `src/IdentityServer/HostingExtensions.cs`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) .AddTestUsers(TestUsers.Users); ``` In the `TestUsers` class, you can see that two users called `alice` and `bob` are defined with some identity claims. You can use those users to login. Note that the test users’ passwords match their usernames. ### Register An OIDC client [Section titled “Register An OIDC client”](#register-an-oidc-client) The last step in the `IdentityServer` project is to add a new configuration entry for a client that will use OIDC to log in. You will create the application code for this client in the next section. For now, you will register its configuration. OpenID Connect-based clients are very similar to the OAuth clients we added in [Quickstart 1](/identityserver/quickstarts/1-client-credentials/). But since the flows in OIDC are always interactive, we need to add some redirect URLs to our configuration. The `Clients` list in `src/IdentityServer/Config.cs` should look like this: ```csharp public static IEnumerable Clients => new List { // machine to machine client (from quickstart 1) new Client { ClientId = "client", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, // scopes that client has access to AllowedScopes = { "api1" } }, // interactive ASP.NET Core Web App new Client { ClientId = "web", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } } }; ``` ## Create The OIDC client [Section titled “Create The OIDC client”](#create-the-oidc-client) Next you will create an ASP.NET web application that will allow interactive users to log in using OIDC. Use the webapp template to create the project. Run the following commands from the `src` directory: ```console dotnet new webapp -n WebClient cd .. dotnet sln add ./src/WebClient ``` ### Install The OIDC NuGet Package [Section titled “Install The OIDC NuGet Package”](#install-the-oidc-nuget-package) To add support for OpenID Connect authentication to the `WebClient` project, you need to add the NuGet package containing the OpenID Connect handler. From the `src/WebClient` directory, run the following command: ```console dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect ``` ### Configure Authentication Services [Section titled “Configure Authentication Services”](#configure-authentication-services) Then add the authentication service and register the cookie and OpenIdConnect authentication providers in `src/WebClient/Program.cs`: Program.cs ```csharp builder.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5001"; options.ClientId = "web"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.MapInboundClaims = false; // Don't rename claim types options.SaveTokens = true; }); ``` `AddAuthentication` registers the authentication services. Notice that in its options, the DefaultChallengeScheme is set to “oidc”, and the DefaultScheme is set to “Cookies”. The DefaultChallengeScheme is used when an unauthenticated user must log in. This begins the OpenID Connect protocol, redirecting the user to `IdentityServer`. After the user has logged in and been redirected back to the client, the client creates its own local cookie. Subsequent requests to the client will include this cookie and be authenticated with the default Cookie scheme. After the call to `AddAuthentication`, `AddCookie` adds the handler that can process the local cookie. Finally, `AddOpenIdConnect` is used to configure the handler that performs the OpenID Connect protocol. The `Authority` indicates where the trusted token service is located. The `ClientId` and the `ClientSecret` identify this client. The `Scope` is the collection of scopes that the client will request. By default, it includes the openid and profile scopes, but clear the collection and add them back for explicit clarity. `SaveTokens` is used to persist the tokens in the cookie (as they will be needed later). ### Configure The Pipeline [Section titled “Configure The Pipeline”](#configure-the-pipeline) Now add `UseAuthentication` to the ASP.NET pipeline in `src/WebClient/Program.cs`. Also chain a call to `RequireAuthorization` onto `MapRazorPages` to disable anonymous access for the entire application. ```csharp app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages().RequireAuthorization(); ``` ### Display The Auth Cookie [Section titled “Display The Auth Cookie”](#display-the-auth-cookie) Modify `src/WebClient/Pages/Index.cshtml` to display the claims of the user and the cookie properties: ```csharp @page @model IndexModel @using Microsoft.AspNetCore.Authentication

Claims

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

Properties

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

Hello, Blazor BFF!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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