diff --git a/.gitignore b/.gitignore index 6d280387..f7779a90 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # hugo public root/identityserver/ +.hugo_build.lock # .net [Dd]ebug/ diff --git a/IdentityServer/v5/docs/content/_index.md b/IdentityServer/v5/docs/content/_index.md index e7a38a34..d91b0640 100644 --- a/IdentityServer/v5/docs/content/_index.md +++ b/IdentityServer/v5/docs/content/_index.md @@ -6,6 +6,10 @@ weight: 1 # Duende IdentityServer v5 Documentation The most flexible & standards-compliant OpenID Connect and OAuth 2.0 framework for ASP.NET Core. +{{% notice warning %}} +Version 5.x out of support since December 13, 2022. We strongly recommend upgrading to a supported version. +{{% /notice %}} + {{% notice note %}} -This is the documentation for version 5.x. You can find the v6.x documentation [here](https://docs.duendesoftware.com/identityserver/v6). +This is the documentation for version 5.x. You can find the v6.x documentation [here](https://docs.duendesoftware.com/identityserver/v6) and the v7.x documentation [here](https://docs.duendesoftware.com/identityserver/v7). {{% /notice %}} diff --git a/IdentityServer/v5/docs/content/apis/_index.md b/IdentityServer/v5/docs/content/apis/_index.md index 1e815545..3353d061 100644 --- a/IdentityServer/v5/docs/content/apis/_index.md +++ b/IdentityServer/v5/docs/content/apis/_index.md @@ -11,5 +11,5 @@ Duende IdentityServer issues tokens for accessing resources. These resources are very often HTTP-based APIs, but could be also other "invokable" functionality like messaging endpoints, gRPC services or even good old XML Web Services. See the [issuing tokens]({{< ref "/tokens" >}}) section on more information on access tokens and how to request them. -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/apis/aspnetcore/_index.md b/IdentityServer/v5/docs/content/apis/aspnetcore/_index.md index 4c21a298..b0293e98 100644 --- a/IdentityServer/v5/docs/content/apis/aspnetcore/_index.md +++ b/IdentityServer/v5/docs/content/apis/aspnetcore/_index.md @@ -7,5 +7,5 @@ chapter = true # Protecting APIs using ASP.NET Core -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/deployment/_index.md b/IdentityServer/v5/docs/content/deployment/_index.md index 3ae5f7b6..882c5e3a 100644 --- a/IdentityServer/v5/docs/content/deployment/_index.md +++ b/IdentityServer/v5/docs/content/deployment/_index.md @@ -9,4 +9,4 @@ chapter = true Duende IdentityServer is just middleware that you host in ASP.NET Core. All [rules and advice](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) around deploying ASP.NET Core applications to various hosting environments apply here too. This section focuses on IdentityServer-specific concerns. -{{%children style="h4" %}} \ No newline at end of file +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v5/docs/content/diagnostics/_index.md b/IdentityServer/v5/docs/content/diagnostics/_index.md index 13ca024e..2189332e 100644 --- a/IdentityServer/v5/docs/content/diagnostics/_index.md +++ b/IdentityServer/v5/docs/content/diagnostics/_index.md @@ -7,4 +7,4 @@ chapter = true # Diagnostics -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/fundamentals/_index.md b/IdentityServer/v5/docs/content/fundamentals/_index.md index a058d540..d8e156fb 100644 --- a/IdentityServer/v5/docs/content/fundamentals/_index.md +++ b/IdentityServer/v5/docs/content/fundamentals/_index.md @@ -6,4 +6,4 @@ chapter = true # Fundamentals -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/fundamentals/resources/_index.md b/IdentityServer/v5/docs/content/fundamentals/resources/_index.md index 73f4be04..c750f4ce 100644 --- a/IdentityServer/v5/docs/content/fundamentals/resources/_index.md +++ b/IdentityServer/v5/docs/content/fundamentals/resources/_index.md @@ -9,4 +9,4 @@ chapter = true The ultimate job of Duende IdentityServer is to control access to resources. -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/overview/_index.md b/IdentityServer/v5/docs/content/overview/_index.md index adf71335..0d0d4a76 100644 --- a/IdentityServer/v5/docs/content/overview/_index.md +++ b/IdentityServer/v5/docs/content/overview/_index.md @@ -8,4 +8,4 @@ chapter = true # Overview -{{%children style="h4" %}} \ No newline at end of file +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v5/docs/content/quickstarts/_index.md b/IdentityServer/v5/docs/content/quickstarts/_index.md index edd0929c..4511485e 100644 --- a/IdentityServer/v5/docs/content/quickstarts/_index.md +++ b/IdentityServer/v5/docs/content/quickstarts/_index.md @@ -9,4 +9,4 @@ chapter = true The following hands-on tutorials guide you through a couple of common scenarios. -{{%children style="h4" %}} \ No newline at end of file +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v5/docs/content/reference/_index.md b/IdentityServer/v5/docs/content/reference/_index.md index ea063403..66457f8b 100644 --- a/IdentityServer/v5/docs/content/reference/_index.md +++ b/IdentityServer/v5/docs/content/reference/_index.md @@ -7,4 +7,4 @@ chapter = true # Reference -{{%children style="h4" %}} \ No newline at end of file +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v5/docs/content/reference/endpoints/_index.md b/IdentityServer/v5/docs/content/reference/endpoints/_index.md index f19eb0d4..d174b4cf 100644 --- a/IdentityServer/v5/docs/content/reference/endpoints/_index.md +++ b/IdentityServer/v5/docs/content/reference/endpoints/_index.md @@ -6,4 +6,4 @@ chapter = true # Endpoints -{{%children style="h4" %}} \ No newline at end of file +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v5/docs/content/reference/models/_index.md b/IdentityServer/v5/docs/content/reference/models/_index.md index 5c26dfc6..5c8b201d 100644 --- a/IdentityServer/v5/docs/content/reference/models/_index.md +++ b/IdentityServer/v5/docs/content/reference/models/_index.md @@ -6,4 +6,4 @@ chapter = true # Models -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/reference/response_handling/_index.md b/IdentityServer/v5/docs/content/reference/response_handling/_index.md index f0032349..8f787a95 100644 --- a/IdentityServer/v5/docs/content/reference/response_handling/_index.md +++ b/IdentityServer/v5/docs/content/reference/response_handling/_index.md @@ -6,4 +6,4 @@ chapter = true # Response Generators -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/reference/services/_index.md b/IdentityServer/v5/docs/content/reference/services/_index.md index 0af02ae3..5ca8784a 100644 --- a/IdentityServer/v5/docs/content/reference/services/_index.md +++ b/IdentityServer/v5/docs/content/reference/services/_index.md @@ -6,4 +6,4 @@ chapter = true # Services -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/reference/stores/_index.md b/IdentityServer/v5/docs/content/reference/stores/_index.md index 822e1265..6cb3c299 100644 --- a/IdentityServer/v5/docs/content/reference/stores/_index.md +++ b/IdentityServer/v5/docs/content/reference/stores/_index.md @@ -6,4 +6,4 @@ chapter = true # Stores -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/reference/validators/_index.md b/IdentityServer/v5/docs/content/reference/validators/_index.md index f9f34864..171448cf 100644 --- a/IdentityServer/v5/docs/content/reference/validators/_index.md +++ b/IdentityServer/v5/docs/content/reference/validators/_index.md @@ -6,4 +6,4 @@ chapter = true # Validators -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/tokens/_index.md b/IdentityServer/v5/docs/content/tokens/_index.md index b5a38551..fe05d637 100644 --- a/IdentityServer/v5/docs/content/tokens/_index.md +++ b/IdentityServer/v5/docs/content/tokens/_index.md @@ -9,4 +9,4 @@ chapter = true At its very heart, Duende IdentityServer is a so-called *Security Token Service* (STS). -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/tokens/authentication/_index.md b/IdentityServer/v5/docs/content/tokens/authentication/_index.md index b9719fb6..3021f162 100644 --- a/IdentityServer/v5/docs/content/tokens/authentication/_index.md +++ b/IdentityServer/v5/docs/content/tokens/authentication/_index.md @@ -7,5 +7,5 @@ chapter = true # Client Authentication -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v5/docs/content/upgrades/_index.md b/IdentityServer/v5/docs/content/upgrades/_index.md index 1588e845..aa2e7728 100644 --- a/IdentityServer/v5/docs/content/upgrades/_index.md +++ b/IdentityServer/v5/docs/content/upgrades/_index.md @@ -8,4 +8,4 @@ chapter = true Here is a list of upgrade guides. -{{%children style="h4" %}} +{{%children style="h4" /%}} diff --git a/IdentityServer/v6/docs/content/_index.md b/IdentityServer/v6/docs/content/_index.md index 90d336c7..b8a326bf 100644 --- a/IdentityServer/v6/docs/content/_index.md +++ b/IdentityServer/v6/docs/content/_index.md @@ -7,5 +7,13 @@ weight: 1 The most flexible & standards-compliant OpenID Connect and OAuth 2.0 framework for ASP.NET Core. {{% notice note %}} -This is the documentation for version 6.x. You can find the v5.x documentation [here](https://docs.duendesoftware.com/identityserver/v5). +This is the documentation for version 6.x. You can find the v5.x documentation [here](https://docs.duendesoftware.com/identityserver/v5) and the v7.x documentation [here](https://docs.duendesoftware.com/identityserver/v7). {{% /notice %}} + +{{% notice note %}} +Version 6.x is supported on .NET 6 and .NET 7. Version 6.x is supported until November 12, 2024 when .NET 6 support ends. +{{% /notice %}} + +{{% notice info %}} +Version 6.x is supported not supported on .NET 8. Please use [v7.x](https://docs.duendesoftware.com/identityserver/v7) for .NET 8. +{{% /notice %}} \ No newline at end of file diff --git a/IdentityServer/v7/docs/archetypes/default.md b/IdentityServer/v7/docs/archetypes/default.md new file mode 100644 index 00000000..00e77bd7 --- /dev/null +++ b/IdentityServer/v7/docs/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/IdentityServer/v7/docs/cheatsheet.md b/IdentityServer/v7/docs/cheatsheet.md new file mode 100644 index 00000000..82578918 --- /dev/null +++ b/IdentityServer/v7/docs/cheatsheet.md @@ -0,0 +1,15 @@ +[See part 2]({{< ref "/quickstarts/2_interactive" >}}) + +[See part 2]({{< ref "2_interactive.md" >}}) + +{{< ref "2_interactive.md" >}} + +[See part 1 - defining an API scope]({{< ref "1_client_credentials#defining-an-api-scope" >}}) + +{{< param qs_base >}} + +{{% notice note %}} +... +{{% /notice %}} + +![](../images/1_client_screenshot.png) \ No newline at end of file diff --git a/IdentityServer/v7/docs/config.toml b/IdentityServer/v7/docs/config.toml new file mode 100644 index 00000000..c79a5e45 --- /dev/null +++ b/IdentityServer/v7/docs/config.toml @@ -0,0 +1,13 @@ +baseURL = "https://docs.duendesoftware.com/identityserver/v7" +languageCode = "en-us" +title = "Duende IdentityServer Documentation" + +theme = "hugo-theme-learn" +# For search functionality +[outputs] +home = [ "HTML", "RSS", "JSON"] + +[params] +editURL = "https://github.com/DuendeSoftware/docs.duendesoftware.com/edit/main/IdentityServer/v7/docs/content/" +qs_base = "https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts" +samples_base = "https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7" \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/_index.md b/IdentityServer/v7/docs/content/_index.md new file mode 100644 index 00000000..e5a81369 --- /dev/null +++ b/IdentityServer/v7/docs/content/_index.md @@ -0,0 +1,15 @@ +--- +title: "Home" +weight: 1 +--- + +# Duende IdentityServer v7 Documentation +The most flexible & standards-compliant OpenID Connect and OAuth 2.0 framework for ASP.NET Core. + +{{% notice info %}} +Version 7.x is available as preview and not supported for production use. The RTM release is planned for January 2024. +{{% /notice %}} + +{{% notice note %}} +This is the documentation for version 7.x. You can find the v6.x documentation [here](https://docs.duendesoftware.com/identityserver/v6). +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/apis/_index.md b/IdentityServer/v7/docs/content/apis/_index.md new file mode 100644 index 00000000..483053f9 --- /dev/null +++ b/IdentityServer/v7/docs/content/apis/_index.md @@ -0,0 +1,15 @@ ++++ +title = "Protecting APIs" +date = 2020-09-10T08:20:20+02:00 +weight = 60 +chapter = true ++++ + +# Protecting APIs + +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]({{< ref "/tokens" >}}) section on more information on access tokens and how to request them. + +{{%children style="h4" /%}} + diff --git a/IdentityServer/v7/docs/content/apis/add_apis.md b/IdentityServer/v7/docs/content/apis/add_apis.md new file mode 100644 index 00000000..3cd7422c --- /dev/null +++ b/IdentityServer/v7/docs/content/apis/add_apis.md @@ -0,0 +1,111 @@ +--- +title: "Adding API Endpoints to your IdentityServer" +date: 2020-09-10T08:22:12+02:00 +weight: 20 +--- + +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. + +{{% notice note %}} +You could achieve the same by using either Microsoft's *JwtBearer* handler. But this requires more configuration and creates dependencies on external libraries that might lead to conflicts in future updates. +{{% /notice %}} + +Start by registering your API as an *ApiScope*, (or resource) e.g.: + +```cs +var scopes = new List +{ + // local API + new ApiScope(IdentityServerConstants.LocalApi.ScopeName), +}; +``` + +..and give your clients access to this API, e.g.: + +```cs +new Client +{ + // rest omitted + AllowedScopes = { IdentityServerConstants.LocalApi.ScopeName }, +} +``` + +{{% notice note %}} +The value of *IdentityServerConstants.LocalApi.ScopeName* is *IdentityServerApi*. +{{% /notice %}} + +To enable token validation for local APIs, add the following to your IdentityServer startup: + +```cs +services.AddLocalApiAuthentication(); +``` + +To protect an API controller, decorate it with an *Authorize* attribute using the *LocalApi.PolicyName* policy: + +```cs +[Route("localApi")] +[Authorize(LocalApi.PolicyName)] +public class LocalApiController : ControllerBase +{ + public IActionResult Get() + { + // omitted + } +} +``` + +Authorized clients can then request a token for the *IdentityServerApi* scope and use it to call the API. + +## Discovery +You can also add your endpoints to the discovery document if you want, e.g like this:: + +```cs +services.AddIdentityServer(options => +{ + options.Discovery.CustomEntries.Add("local_api", "~/localapi"); +}) +``` + +## Advanced +Under the covers, the *AddLocalApiAuthentication* helper does a couple of things: + +* adds an authentication handler that validates incoming tokens using IdentityServer's built-in token validation engine (the name of this handler is *IdentityServerAccessToken* or *IdentityServerConstants.LocalApi.AuthenticationScheme* +* configures the authentication handler to require a scope claim inside the access token of value *IdentityServerApi* +* sets up an authorization policy that checks for a scope claim of value *IdentityServerApi* + +This covers the most common scenarios. You can customize this behavior in the following ways: + +* Add the authentication handler yourself by calling *services.AddAuthentication().AddLocalApi(...)* + * this way you can specify the required scope name yourself, or (by specifying no scope at all) accept any token from the current IdentityServer instance +* Do your own scope validation/authorization in your controllers using custom policies or code, e.g.: + + +```cs + services.AddAuthorization(options => + { + options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy => + { + policy.AddAuthenticationSchemes(IdentityServerConstants.LocalApi.AuthenticationScheme); + policy.RequireAuthenticatedUser(); + // custom requirements + }); + }); +``` + +## Claims Transformation +You can provide a callback to transform the claims of the incoming token after validation. +Either use the helper method, e.g.: + +```cs +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. diff --git a/IdentityServer/v7/docs/content/apis/aspnetcore/_index.md b/IdentityServer/v7/docs/content/apis/aspnetcore/_index.md new file mode 100644 index 00000000..b0293e98 --- /dev/null +++ b/IdentityServer/v7/docs/content/apis/aspnetcore/_index.md @@ -0,0 +1,11 @@ ++++ +title = "Protecting APIs using ASP.NET Core" +date = 2020-09-10T08:20:20+02:00 +weight = 10 +chapter = true ++++ + +# Protecting APIs using ASP.NET Core + +{{%children style="h4" /%}} + diff --git a/IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md b/IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md new file mode 100644 index 00000000..592eb1ad --- /dev/null +++ b/IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md @@ -0,0 +1,139 @@ +--- +title: "Authorization based on Scopes and other Claims" +date: 2020-09-10T08:22:12+02:00 +weight: 30 +--- + +The access token will include additional claims that can be used for authorization, e.g. the *scope* claim will reflect the scope the client requested (and was granted) during the token request. + +In ASP.NET core, the contents of the JWT payload get transformed into claims and packaged up in a *ClaimsPrincipal*. So you can always write custom validation or authorization logic in C#: + +```cs +public IActionResult Get() +{ + var isAllowed = User.HasClaim("scope", "read"); + + // rest omitted +} +``` + +For better encapsulation and re-use, consider using the ASP.NET Core [authorization policy](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) feature. + +With this approach, you would first turn the claim requirement(s) into a named policy: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddAuthorization(options => + { + options.AddPolicy("read_access", policy => + policy.RequireClaim("scope", "read"); + }); +} +``` + +..and then enforce it, e.g. using the routing table: + +```cs + app.UseEndpoints(endpoints => + { + endpoints.MapControllers().RequireAuthorization("read_access"); + }); +``` + +...or imperatively inside the controller: + +```cs +public class DataController : ControllerBase +{ + IAuthorizationService _authz; + + public DataController(IAuthorizationService authz) + { + _authz = authz; + } + + public async Task Get() + { + var allowed = _authz.CheckAccess(User, "read_access"); + + // rest omitted + } +} +``` + +... or declaratively: + +```cs +public class DataController : ControllerBase +{ + [Authorize("read_access")] + public async Task Get() + { + var allowed = authz.CheckAccess(User, "read_access"); + + // rest omitted + } +} +``` + +#### Scope claim format +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]({{< ref "/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]({{< ref "/reference/options" >}}). But this means that the code consuming access tokens might need to be adjusted. The following code can do a conversion to the *multiple claims* format that .NET prefers: + +```cs +namespace IdentityModel.AspNetCore.AccessTokenValidation +{ + /// + /// Logic for normalizing scope claims to separate claim types + /// + public static class ScopeConverter + { + /// + /// Logic for normalizing scope claims to separate claim types + /// + /// + /// + public static ClaimsPrincipal NormalizeScopeClaims(this ClaimsPrincipal principal) + { + var identities = new List(); + + foreach (var id in principal.Identities) + { + var identity = new ClaimsIdentity(id.AuthenticationType, id.NameClaimType, id.RoleClaimType); + + foreach (var claim in id.Claims) + { + if (claim.Type == "scope") + { + if (claim.Value.Contains(' ')) + { + var scopes = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); + + foreach (var scope in scopes) + { + identity.AddClaim(new Claim("scope", scope, claim.ValueType, claim.Issuer)); + } + } + else + { + identity.AddClaim(claim); + } + } + else + { + identity.AddClaim(claim); + } + } + + identities.Add(identity); + } + + return new ClaimsPrincipal(identities); + } + } +} +``` + +The above code could then be called as an extension method or as part of [claims transformation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.iclaimstransformation). diff --git a/IdentityServer/v7/docs/content/apis/aspnetcore/confirmation.md b/IdentityServer/v7/docs/content/apis/aspnetcore/confirmation.md new file mode 100644 index 00000000..a106211c --- /dev/null +++ b/IdentityServer/v7/docs/content/apis/aspnetcore/confirmation.md @@ -0,0 +1,142 @@ +--- +title: "Validating Proof-of-Possession" +date: 2020-09-10T08:22:12+02:00 +weight: 40 +--- + +IdentityServer can [bind tokens to clients]({{< ref "/tokens/pop" >}}) 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 Proof-of-Possession + +If you are using a [mutual TLS connection]({{< ref "/tokens/pop/mtls" >}}) to establish proof-of-possession, the resulting access token will contain a *cnf* claim containing the client's certificate thumbprint. APIs validate such tokens by comparing this thumbprint to the thumbprint of the client certificate in the mTLS connection. This validation should be performed early in the pipeline, ideally immediately after the standard validation of the access token. + +You can do so with custom middleware like this: + +```cs +public void Configure(IApplicationBuilder app) +{ + // rest omitted + + // normal token validation happens here + app.UseAuthentication(); + + // This adds custom middleware to validate cnf claim + app.UseConfirmationValidation(); + + app.UseAuthorization(); + + // rest omitted +} +``` + +Here, *UseConfirmationValidation* is an extension method that registers the middleware that performs the necessary validation: + +```cs +public static class ConfirmationValidationExtensions +{ + public static IApplicationBuilder UseConfirmationValidation(this IApplicationBuilder app, ConfirmationValidationMiddlewareOptions options = default) + { + return app.UseMiddleware(options ?? new ConfirmationValidationMiddlewareOptions()); + } +} +``` + +And this is the actual middleware that validates the *cnf* claim: + +```cs +// this middleware validates the cnf claim (if present) against the thumbprint of the X.509 client certificate for the current client +public class ConfirmationValidationMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly ConfirmationValidationMiddlewareOptions _options; + + public ConfirmationValidationMiddleware( + RequestDelegate next, + ILogger logger, + ConfirmationValidationMiddlewareOptions options = null) + { + _next = next; + _logger = logger; + _options ??= new ConfirmationValidationMiddlewareOptions(); + } + + public async Task Invoke(HttpContext ctx) + { + if (ctx.User.Identity.IsAuthenticated) + { + // read the cnf claim from the validated token + var cnfJson = ctx.User.FindFirst("cnf")?.Value; + if (!String.IsNullOrWhiteSpace(cnfJson)) + { + // if present, make sure a valid certificate was presented as well + var certResult = await ctx.AuthenticateAsync(_options.CertificateSchemeName); + if (!certResult.Succeeded) + { + await ctx.ChallengeAsync(_options.CertificateSchemeName); + return; + } + + // get access to certificate from transport + var certificate = await ctx.Connection.GetClientCertificateAsync(); + var thumbprint = Base64UrlTextEncoder.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256)); + + // retrieve value of the thumbprint from cnf claim + var cnf = JObject.Parse(cnfJson); + var sha256 = cnf.Value("x5t#S256"); + + // compare thumbprint claim with thumbprint of current TLS client certificate + if (String.IsNullOrWhiteSpace(sha256) || + !thumbprint.Equals(sha256, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogError("certificate thumbprint does not match cnf claim."); + await ctx.ChallengeAsync(_options.JwtBearerSchemeName); + return; + } + + _logger.LogDebug("certificate thumbprint matches cnf claim."); + } + } + + await _next(ctx); + } +} + +public class ConfirmationValidationMiddlewareOptions +{ + public string CertificateSchemeName { get; set; } = CertificateAuthenticationDefaults.AuthenticationScheme; + public string JwtBearerSchemeName { get; set; } = JwtBearerDefaults.AuthenticationScheme; +} +``` + +### Validating DPoP Proof-of-Possession +If you are using [DPoP]({{< ref "/tokens/pop/dpop" >}}) for proof-of-possession, there is a non-trivial amount of work needed to validate the *cnf* claim. +In addition to the normal validation mechanics of the access token itself, DPoP requires additional validation of the DPoP proof token sent in the "DPoP" HTTP request header. +DPoP proof token processing involves requiring the DPoP scheme on the authorization header where the access token is sent, JWT validation of the proof token, "cnf" claim validation, HTTP method and URL validation, replay detection (which requires some storage for the replay information), nonce generation and validation, additional clock skew logic, and emitting the correct response headers in the case of the various validation errors. + +Given that there are no off-the-shelf libraries that implement this, we have developed a full-featured sample implementation. +With this sample the configuration necessary in your startup can be as simple as this: + +```cs + +public void ConfigureServices(IServiceCollection services) +{ + // adds the normal JWT bearer validation + 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 + services.ConfigureDPoPTokensForScheme("token"); +} +``` + +You can find this sample [here]({{< ref "/samples/misc#DPoP" >}}). To use the +*ConfigureDPoPTokensForScheme* shown above, copy the *~/Api/DPoP code from the +sample into you APIs. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/apis/aspnetcore/jwt.md b/IdentityServer/v7/docs/content/apis/aspnetcore/jwt.md new file mode 100644 index 00000000..e2eb6b62 --- /dev/null +++ b/IdentityServer/v7/docs/content/apis/aspnetcore/jwt.md @@ -0,0 +1,83 @@ +--- +title: "Using JWTs" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +--- + +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 token +First you need to add a reference to the authentication handler in your API project: + +```xml + +``` + +If all you care about is making sure that an access token comes from your trusted IdentityServer, the following snippet shows the typical JWT validation configuration for ASP.NET Core: + +```cs +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + 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" }; + }); + } +} +``` + +{{% notice note %}} +On .NET Core 3.1 you need to manually reference the [System.IdentityModel.Tokens.Jwt](https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/5.6.0) Nuget package version 5.6 to be able to check the type header. +{{% /notice %}} + +## 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]({{< ref "/fundamentals/resources" >}}) for more information). + +If you designed your APIs around the concept of [API resources]({{< ref "/fundamentals/resources/api_resources" >}}), your IdentityServer will emit the *aud* claim by default (*api1* in this example): + +```json +{ + "typ": "at+jwt", + "kid": "123" +}. +{ + "aud": "api1", + + "client_id": "mobile_app", + "sub": "123", + "scope": "read write delete" +} +``` + +If you want to express in your API, that only access tokens for the *api1* audience (aka API resource name) are accepted, change the above code snippet to: + +```cs +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = "https://demo.duendesoftware.com"; + options.Audience = "api1"; + + options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; + }); + } +} +``` diff --git a/IdentityServer/v7/docs/content/apis/aspnetcore/reference.md b/IdentityServer/v7/docs/content/apis/aspnetcore/reference.md new file mode 100644 index 00000000..4aba48c3 --- /dev/null +++ b/IdentityServer/v7/docs/content/apis/aspnetcore/reference.md @@ -0,0 +1,97 @@ +--- +title: "Using Reference Tokens" +date: 2020-09-10T08:22:12+02:00 +weight: 20 +--- + +If you are using [reference tokens]({{< ref "/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. [this](https://github.com/IdentityModel/IdentityModel.AspNetCore.OAuth2Introspection) one:. + +```cs +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 +It is not uncommon to use the same API with both JWTs and reference tokens. In this case you setup two authentication handlers, make one the default handler and provide some forwarding logic, e.g.: + +```cs +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: + +```cs +/// +/// 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]); +} +``` diff --git a/IdentityServer/v7/docs/content/apis/images/authorization_header.png b/IdentityServer/v7/docs/content/apis/images/authorization_header.png new file mode 100644 index 00000000..3f1de2d7 Binary files /dev/null and b/IdentityServer/v7/docs/content/apis/images/authorization_header.png differ diff --git a/IdentityServer/v7/docs/content/aspnet_identity/_index.md b/IdentityServer/v7/docs/content/aspnet_identity/_index.md new file mode 100644 index 00000000..8fc9d70a --- /dev/null +++ b/IdentityServer/v7/docs/content/aspnet_identity/_index.md @@ -0,0 +1,42 @@ ++++ +title = "ASP.NET Identity Integration" +description = "Overview" +weight = 90 +chapter = true ++++ + +# ASP.NET Identity Integration + +An ASP.NET Identity-based implementation is provided for managing the identity database for users of IdentityServer. +This implementation implements the extensibility points in IdentityServer needed to load identity data for your users to emit claims into tokens. + +To use this library, ensure that you have the NuGet package for the ASP.NET Identity integration. +It is called *Duende.IdentityServer.AspNetIdentity*. +You can install it with: + +``` +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 *Startup.cs*, use the *AddAspNetIdentity* extension method after the call to *AddIdentityServer*: + + public void ConfigureServices(IServiceCollection services) + { + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + 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]({{}}), and *IProfileService* which uses the *IUserClaimsPrincipalFactory* to add [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 properly, ensure that your implementation is registered in the DI system prior to calling the IdentityServer *AddAspNetIdentity* extension method. + +## Template +Alternatively, you can use the *isaspid* [template]({{}}) to create a starter IdentityServer host project configured to use ASP.NET Identity. See the [Quickstart Documentation]({{}}) for a detailed walkthrough. diff --git a/IdentityServer/v7/docs/content/bff/_index.md b/IdentityServer/v7/docs/content/bff/_index.md new file mode 100644 index 00000000..acab5f72 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/_index.md @@ -0,0 +1,14 @@ ++++ +title = "BFF Security Framework" +date = 2020-09-10T08:20:20+02:00 +weight = 95 +chapter = true ++++ + +# BFF Security Framework + +The Duende.BFF (Backend for Frontend) security framework packages the necessary components to secure browser-based frontends (e.g. SPAs or Blazor WASM applications) with ASP.NET Core backends. + +Duende.BFF is included in [IdentityServer](https://duendesoftware.com/products/identityserver) Business Edition or higher. The same [license](https://duendesoftware.com/products/identityserver#pricing) and [special offers](https://duendesoftware.com/specialoffers) apply. + +The source code for the BFF framework can be found [here](https://github.com/DuendeSoftware/BFF). Builds are distributed through NuGet [here](https://www.nuget.org/packages/Duende.BFF/), and samples are available [here]({{< ref "/samples/bff" >}}). \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/apis/_index.md b/IdentityServer/v7/docs/content/bff/apis/_index.md new file mode 100644 index 00000000..5417ff05 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/apis/_index.md @@ -0,0 +1,17 @@ ++++ +title = "API Endpoints" +weight = 40 +chapter = true ++++ + +# Securing and Accessing API Endpoints + +A frontend application using the BFF pattern can call two types of APIs: + +#### Remote APIs + +These APIs are deployed on a different host than the BFF, which allows them to be shared between multiple frontends or (more generally speaking) multiple clients. These APIs can only be called via the BFF host acting as a proxy. + +#### Local APIs + +These APIs only exist to support the specific frontend; they are not shared with other frontends or services. They are located in the BFF host and can be called directly by the frontend. diff --git a/IdentityServer/v7/docs/content/bff/apis/local.md b/IdentityServer/v7/docs/content/bff/apis/local.md new file mode 100644 index 00000000..04d13f76 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/apis/local.md @@ -0,0 +1,205 @@ +--- +title: "Local APIs" +weight: 30 +--- + +A _Local API_ is an API that is located within the BFF host. Local APIs are implemented with the familiar ASP.NET abstractions of API controllers or minimal API endpoints. + +There are two styles of local APIs: +- Self-contained Local APIs +- Local APIs that Make Requests using Managed Access Tokens + +#### Self-Contained Local APIs +These APIs reside within the BFF and don't make HTTP requests to other APIs. They access data controlled by the BFF itself, which can simplify the architecture of the system by reducing the number of APIs that must be deployed and managed. They are suitable for scenarios where the BFF is the sole consumer of the data. If you require data accessibility from other applications or services, this approach is probably not suitable. + +#### Local APIs that Make Requests using Managed Access Tokens +Alternatively, you can make the data available as a service and make HTTP requests to that service from your BFF's local endpoints. The benefits of this style of Local Endpoint include +- Your frontend's network access can be simplified into an aggregated call for the specific data that it needs, which reduces the amount of data that must be sent to the client. +- Your BFF endpoint can expose a subset of your remote APIs so that they are called in a more controlled manner than if the BFF proxied all requests to the endpoint. +- Your BFF endpoint can include business logic to call the appropriate endpoints, which simplifies your front end code. + +Your local endpoints can leverage services like the HTTP client factory and Duende.BFF [token management]({{< ref "/bff/tokens" >}}) to make the outgoing calls. The following is a simplified example showing how local endpoints can obtain managed access tokens and use them to make requests to remote APIs. + + +```cs +[Route("myApi")] +public class MyApiController : ControllerBase +{ + private readonly IHttpClientFactory _httpClientFactory; + + public MyApiController(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } + + public async Task Get(string id) + { + // create HTTP client + var client = _httpClientFactory.CreateClient(); + + // get current user access token and set it on HttpClient + var token = await HttpContext.GetUserAccessTokenAsync(); + client.SetBearerToken(token); + + // call remote API + var response = await client.GetAsync($"https://remoteServer/remoteApi?id={id}"); + + // maybe process response and return to frontend + return new JsonResult(await response.Content.ReadAsStringAsync()); + } +} +``` + +The example above is simplified to demonstrate the way that you might obtain a token. Real local endpoints will typically enforce constraints on the way the API is called, aggregate multiple calls, or perform other business logic. Local endpoints that merely forward requests from the frontend to the remote API may not be needed at all. Instead, you could proxy the requests through the BFF using either the [simple http forwarder]({{}}) or [YARP]({{}}). + +## Securing Local API Endpoints +Regardless of the style of data access used by a local API, it must be protected against threats such as [CSRF (Cross-Site Request Forgery)](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) attacks. To defend against such attacks and ensure that only the frontend can access these endpoints, we recommend implementing two layers of protection. + +#### SameSite cookies + +[The SameSite cookie attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value) is a feature of modern browsers that restricts cookies so that they are only sent to pages originating from the [site](https://developer.mozilla.org/en-US/docs/Glossary/Site) where the cookie was originally issued. + +This is a good first layer of defense, but makes the assumption that you can trust all subdomains of your site. All subdomains within a registrable domain are considered the same site for purposes of SameSite cookies. Thus, if another application hosted on a subdomain within your site is infected with malware, it can make CSRF attacks against your application. + + +#### Anti-forgery header + +For this reason, we recommend requiring an additional custom header on API endpoints, for example: + +``` +GET /endpoint + +x-csrf: 1 +``` + +The value of the header is not important, but its presence, combined with the cookie requirement, triggers a CORS preflight request for cross-origin calls. This effectively isolates the caller to the same origin as the backend, providing a robust security guarantee. + +Additionally, API endpoints should handle scenarios where the session has expired or authorization fails without triggering an authentication redirect to the upstream identity provider. Instead, they should return Ajax-friendly status codes. + +## Setup +Duende.BFF can automate both the pre-processing step of requiring the custom anti-forgery header and the post-processing step of converting response codes for API endpoints. To do so, first add the BFF middleware to the pipeline, and then decorate your endpoints to indicate that they should receive BFF pre and post processing. + +#### Add Middleware +Add the BFF middleware to the pipeline by calling *UseBFF*. Note that the middleware must be placed before the authorization middleware, but after routing. + +```csharp +public void Configure(IApplicationBuilder app) +{ + // rest omitted + + app.UseAuthentication(); + app.UseRouting(); + + app.UseBff(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { /* ... */ } +} +``` + +#### Decorate Endpoints +Endpoints that require the pre and post processing described above must be decorated with a call to *AsBffApiEndpoint()*. + +For minimal API endpoints, you can apply BFF pre- and post-processing when they are mapped. +```csharp +endpoints.MapPost("/foo", context => { ... }) + .RequireAuthorization() // no anonymous access + .AsBffApiEndpoint(); // BFF pre/post processing +``` + + +For MVC controllers, you can similarly apply BFF pre- and post-processing to controller actions when they are mapped. +```csharp +endpoints.MapControllers() + .RequireAuthorization() // no anonymous access + .AsBffApiEndpoint(); // BFF pre/post processing +``` + +Alternatively, you can apply the *[BffApi]* attribute directly to the controller or action. +```csharp +[Route("myApi")] +[BffApi] +public class MyApiController : ControllerBase +{ ... } +``` + +#### Disabling Anti-forgery Protection + +Disabling anti-forgery protection is possible but not recommended. Antiforgery protection defends against CSRF attacks, so opting out may cause security vulnerabilities. + +However, if you are defending against CSRF attacks with some other mechanism, you can opt-out of Duende.BFF's CSRF protection. Depending on the version of Duende.BFF, use one of the following approaches. + +For *version 1.x*, set the *requireAntiForgeryCheck* parameter to *false* when adding the endpoint. For example: + +```csharp +app.UseEndpoints(endpoints => +{ + // MVC controllers + endpoints.MapControllers() + .RequireAuthorization() + // WARNING: Disabling antiforgery protection may make + // your APIs vulnerable to CSRF attacks + .AsBffApiEndpoint(requireAntiforgeryCheck: false); + + // simple endpoint + endpoints.MapPost("/foo", context => { /* ... */ }) + .RequireAuthorization() + // WARNING: Disabling antiforgery protection may make + // your APIs vulnerable to CSRF attacks + .AsBffApiEndpoint(requireAntiforgeryCheck: false); +}); +``` + +On MVC controllers and actions you can set the *RequireAntiForgeryCheck* as a flag in the *BffApiAttribute*, like this: + +```csharp +[Route("sample")] +// WARNING: Disabling antiforgery protection may make +// your APIs vulnerable to CSRF attacks +[BffApi(requireAntiForgeryCheck: false)] +public class SampleApiController : ControllerBase +{ /* ... */ } +``` + + +In *version 2.x*, use the *SkipAntiforgery* fluent API when adding the endpoint. For example: + +```csharp +app.UseEndpoints(endpoints => +{ + // MVC controllers + endpoints.MapControllers() + .RequireAuthorization() + .AsBffApiEndpoint() + // WARNING: Disabling antiforgery protection may make + // your APIs vulnerable to CSRF attacks + .SkipAntiforgery(); + + // simple endpoint + endpoints.MapPost("/foo", context => { /* ... */ }) + .RequireAuthorization() + .AsBffApiEndpoint() + // WARNING: Disabling antiforgery protection may make + // your APIs vulnerable to CSRF attacks + .SkipAntiforgery(); +}); +``` + +MVC controllers and actions can use the *BffApiSkipAntiforgeryAttribute* (which is independent of the *BffApiAttribute*), like this: + +```csharp +[Route("sample")] +// WARNING: Disabling antiforgery protection may make +// your APIs vulnerable to CSRF attacks +[BffApiSkipAntiforgeryAttribute] +public class SampleApiController : ControllerBase +{ /* ... */ } +``` + + + + + + + diff --git a/IdentityServer/v7/docs/content/bff/apis/remote.md b/IdentityServer/v7/docs/content/bff/apis/remote.md new file mode 100644 index 00000000..a9c72147 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/apis/remote.md @@ -0,0 +1,92 @@ +--- +title: "Remote APIs" +weight: 10 +--- + +A _Remote API_ is an API that is deployed separately from the BFF host. Remote APIs use access tokens to authenticate and authorize requests, but the frontend does not possess an access token to make requests to remote APIs directly. Instead, all access to remote APIs is proxied through the BFF, which authenticates the frontend using its authentication cookie, obtains the appropriate access token, and forwards the request to the Remote API with the token attached. + +There are two different ways to set up Remote API proxying in Duende.BFF. This page describes the built-in simple HTTP forwarder. Alternatively, you can integrate Duende.BFF with Microsoft's reverse proxy [YARP]({{< ref "/bff/apis/yarp" >}}), which allows for more complex reverse proxy features provided by YARP combined with the security and identity features of Duende.BFF. + +### Simple HTTP forwarder + +Duende.BFF's simple HTTP forwarder maps routes in the BFF to a remote API surface. It uses [Microsoft YARP](https://github.com/microsoft/reverse-proxy) internally, but is much simpler to configure than YARP. The intent is to provide a developer-centric and simplified way to proxy requests from the BFF to remote APIs when more complex reverse proxy features are not needed. + +These routes receive automatic anti-forgery protection and integrate with automatic token management. + +To enable this feature, add a reference to the *Duende.BFF.Yarp* Nuget package, add the remote APIs service to DI, and call the *MapRemoteBFFApiEndpoint* method to create the mappings. + +#### Add Remote API Service to DI + +```cs +services.AddBff() + .AddRemoteApis(); +``` + + +#### Map Remote APIs +This example routes a local */api/customers* endpoint to a remote API, and forwards the user's access token in the outgoing call: + +```cs +app.UseEndpoints(endpoints => +{ + endpoints.MapRemoteBffApiEndpoint( + "/api/customers", "https://remoteHost/customers") + .RequireAccessToken(TokenType.User); +}); +``` + +{{% notice note %}} +This example opens up the complete */customers* API namespace to the frontend and thus to the outside world. Try to be as specific as possible when designing the forwarding paths. +{{% /notice %}} + +## Securing Remote APIs +Remote APIs typically require access control and must be protected against threats such as [CSRF (Cross-Site Request Forgery)](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) attacks. + +To provide access control, you can specify authorization policies on the mapped routes, and configure them with access token requirements. + +To defend against CSRF attacks, you should use SameSite cookies to authenticate calls from the frontend to the BFF. As an additional layer of defense, APIs mapped with *MapRemoteBffApiEndpoint* are automatically protected with an anti-forgery header. + + +#### SameSite cookies +[The SameSite cookie attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value) is a feature of modern browsers that restricts cookies so that they are only sent to pages originating from the [site](https://developer.mozilla.org/en-US/docs/Glossary/Site) where the cookie was originally issued. This prevents CSRF attacks, because cross site requests will no longer implicitly include the user's credentials. + +This is a good first layer of defense, but makes the assumption that you can trust all subdomains of your site. All subdomains within a registrable domain are considered the same site for purposes of SameSite cookies. Thus, if another application hosted on a subdomain within your site is infected with malware, it can make CSRF attacks against your application. + + +#### Anti-forgery header + +For this reason, remote APIs automatically require an additional custom header on API endpoints. For example: + +``` +GET /endpoint + +x-csrf: 1 +``` + +The value of the header is not important, but its presence, combined with the cookie requirement, triggers a CORS preflight request for cross-origin calls. This effectively isolates the caller to the same origin as the backend, providing a robust security guarantee. + +#### Require authorization + +The *MapRemoteBffApiEndpoint* method returns the appropriate type to integrate with the ASP.NET Core authorization system. You can attach authorization policies to remote endpoints using **RequireAuthorization**, just as you would for a standard ASP.NET core endpoint created with *MapGet*, and the authorization middleware will then enforce that policy before forwarding requests on that route to the remote endpoint. + +#### Access token requirements + +Remote APIs sometimes allow anonymous access, but usually require an access token, and the type of access token (user or client) will vary as well. You can specify access token requirements via the **RequireAccessToken** extension method. Its **TokenType** parameter has three options: + +* ***User*** + + A valid user access token is required and will be forwarded to the remote API. A user access token is an access token obtained during an OIDC flow (or subsequent refresh), and is associated with a particular user. User tokens are obtained when the user initially logs in, and will be automatically refreshed using a refresh token when they expire. + +* ***Client*** + + A valid client access token is required and will be forwarded to the remote API. A client access token is an access token obtained through the client credentials flow, and is associated with the client application, not any particular user. Client tokens can be obtained even if the user is not logged in. + +* ***UserOrClient*** + + Either a valid user access token or a valid client access token (as fallback) is required and will be forwarded to the remote API. + +You can also use the *WithOptionalUserAccessToken* extension method to specify that the API should be called with a user access token if one is available and anonymously if not. + +{{% notice note %}} +These settings only specify the logic that is applied before the API call gets proxied. The remote APIs you are calling should always specify their own authorization and token requirements. +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/bff/apis/yarp.md b/IdentityServer/v7/docs/content/bff/apis/yarp.md new file mode 100644 index 00000000..0a027c24 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/apis/yarp.md @@ -0,0 +1,247 @@ +--- +title: "YARP extensions" +weight: 20 +--- + +Duende.BFF integrates with Microsoft's full-featured reverse proxy [YARP](https://microsoft.github.io/reverse-proxy/). + +YARP includes many advanced features such as load balancing, service discovery, and session affinity. It also has its own extensibility mechanism. Duende.BFF includes YARP extensions for token management and anti-forgery protection so that you can combine the security and identity features of Duende.BFF with the flexible reverse proxy features of YARP. + +#### Adding YARP +To enable Duende.BFF's YARP integration, add a reference to the *Duende.BFF.Yarp* Nuget package to your project and add YARP and the BFF's YARP extensions to DI: + +```cs +services.AddBff(); + +// adds YARP with BFF extensions +var builder = services.AddReverseProxy() + .AddBffExtensions(); +``` + +#### Configuring YARP +YARP is most commonly configured by a config file. The following simple example forwards a local URL to a remote API: + +```json +"ReverseProxy": { + "Routes": { + "todos": { + "ClusterId": "cluster1", + "Match": { + "Path": "/todos/{**catch-all}", + } + } + }, + + "Clusters": { + "cluster1": { + "Destinations": { + "destination1": { + "Address": "https://api.mycompany.com/todos" + } + } + } + } +} +``` + +See the Microsoft [documentation](https://microsoft.github.io/reverse-proxy/articles/config-files.html) for the complete configuration schema. + +Another option is to configure YARP in code using the in-memory config provider included in the BFF extensions for YARP. The above configuration as code would look like this: + +```cs +builder.LoadFromMemory( + new[] + { + new RouteConfig() + { + RouteId = "todos", + ClusterId = "cluster1", + + Match = new() + { + Path = "/todos/{**catch-all}" + } + } + }, + new[] + { + new ClusterConfig + { + ClusterId = "cluster1", + + Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { + "destination1", new() + { + Address = "https://api.mycompany.com/todos" + } + }, + } + } + }); +``` + +### Token management +Duende.BFF's YARP extensions provide access token management and attach user or client access tokens automatically to proxied API calls. To enable this, add metadata with the name *Duende.Bff.Yarp.TokenType* to the route or cluster configuration: + +```json +"ReverseProxy": { + "Routes": { + "todos": { + "ClusterId": "cluster1", + "Match": { + "Path": "/todos/{**catch-all}", + }, + "Metadata": { + "Duende.Bff.Yarp.TokenType": "User" + } + } + }, + // rest omitted +} +``` + +Similarly to the [simple HTTP forwarder]({{}}), the allowed values for the token type are *User*, *Client*, *UserOrClient*. + +Routes that set the *Duende.Bff.Yarp.TokenType* metadata **require** the given type of access token. If it is unavailable (for example, if the *User* token type is specified but the request to the BFF is anonymous), then the proxied request will not be sent, and the BFF will return an HTTP 401: Unauthorized response. + +If you are using the code config method, call the *WithAccessToken* extension method to achieve the same thing: + +```cs +builder.LoadFromMemory( + new[] + { + new RouteConfig() + { + RouteId = "todos", + ClusterId = "cluster1", + + Match = new RouteMatch + { + Path = "/todos/{**catch-all}" + } + }.WithAccessToken(TokenType.User) + }, + // rest omitted +); +``` + +Again, the *WithAccessToken* method causes the route to require the given type of access token. If it is unavailable, the proxied request will not be made and the BFF will return an HTTP 401: Unauthorized response. + +#### Optional User Access Tokens +You can also attach user access tokens optionally by adding metadata named "Duende.Bff.Yarp.OptionalUserToken" to a YARP route. + +```json +"ReverseProxy": { + "Routes": { + "todos": { + "ClusterId": "cluster1", + "Match": { + "Path": "/todos/{**catch-all}", + }, + "Metadata": { + "Duende.Bff.Yarp.OptionalUserToken": "true" + } + } + }, + // rest omitted +} +``` + +This metadata causes the user's access token to be sent with the proxied request when the user is logged in, but makes the request anonymously when the user is not logged in. It is an error to set both *Duende.Bff.Yarp.TokenType* and *Duende.Bff.Yarp.OptionalUserToken*, since they have conflicting semantics (*TokenType* requires the token, *OptionalUserToken* makes it optional). + +If you are using the code config method, call the *WithOptionalUserAccessToken* extension method to achieve the same thing: + +```cs +builder.LoadFromMemory( + new[] + { + new RouteConfig() + { + RouteId = "todos", + ClusterId = "cluster1", + + Match = new RouteMatch + { + Path = "/todos/{**catch-all}" + } + }.WithOptionalUserAccessToken() + }, + // rest omitted +); +``` + +### Anti-forgery protection +Duende.BFF's YARP extensions can also add anti-forgery protection to proxied API calls. Anti-forgery protection defends against CSRF attacks by requiring a custom header on API endpoints, for example: + +``` +GET /endpoint + +x-csrf: 1 +``` + +The value of the header is not important, but its presence, combined with the cookie requirement, triggers a CORS preflight request for cross-origin calls. This effectively isolates the caller to the same origin as the backend, providing a robust security guarantee. + +You can add the anti-forgery protection to all YARP routes by calling the *AsBffApiEndpoint* extension method: + +```cs +endpoints.MapReverseProxy() + .AsBffApiEndpoint(); + +// or shorter +endpoints.MapBffReverseProxy(); +``` + +If you need more fine grained control over which routes should enforce the anti-forgery header, you can also annotate the route configuration by adding the *Duende.Bff.Yarp.AntiforgeryCheck* metadata to the route config: + +```json +"ReverseProxy": { + "Routes": { + "todos": { + "ClusterId": "cluster1", + "Match": { + "Path": "/todos/{**catch-all}", + }, + "Metadata": { + "Duende.Bff.Yarp.AntiforgeryCheck" : "true" + } + } + }, + // rest omitted +} +``` + +This is also possible in code: + +```cs +builder.LoadFromMemory( + new[] + { + new RouteConfig() + { + RouteId = "todos", + ClusterId = "cluster1", + + Match = new RouteMatch + { + Path = "/todos/{**catch-all}" + } + }.WithAntiforgeryCheck() + }, + // rest omitted +); +``` + +{{% notice note %}} +You can combine the token management feature with the anti-forgery check. +{{% /notice %}} + +To enforce the presence of the anti-forgery headers, you need to add a middleware to the YARP pipeline: + +```cs +endpoints.MapReverseProxy(proxyApp => +{ + proxyApp.UseAntiforgeryCheck(); +}); +``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/architecture/_index.md b/IdentityServer/v7/docs/content/bff/architecture/_index.md new file mode 100644 index 00000000..c7a7c484 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/architecture/_index.md @@ -0,0 +1,40 @@ +--- +title: "Architecture" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +chapter: true +--- + +A BFF host is an ASP.NET Core application with the following components: + + + +![](../images/BFF_blocks.png?height=30pc) + + +These components handle OIDC and OAuth protocol requests and responses, manage user sessions and tokens, secure API endpoints for the front end, and optionally serve UI assets. + +Duende.BFF builds on widely used tools and frameworks, including ASP.NET Core's OpenID Connect and cookie authentication handlers, YARP, and Duende.AccessTokenManagment. Duende.BFF combines these tools and adds additional security and application features that are useful with a BFF architecture so that you can focus on providing application logic instead of security logic: + +![](../images/DuendeBFF_blocks.png?height=30pc) + +#### ASP.NET OpenID Connect Handler +Duende.BFF uses ASP.NET's OpenID Connect handler for OIDC and OAuth protocol processing. As long-term users of and contributors to this library, we think it is a well implemented and flexible implementation of the protocols. + +#### ASP.NET Cookie Handler +Duende.BFF uses ASP.NET's Cookie handler for session management. The Cookie handler provides a claims-based identity to the application persisted in a digitally signed and encrypted cookie that is protected with modern cookie security features, including the Secure, HttpOnly and SameSite attributes. The handler also provides absolute and sliding session support, and has a flexible extensibility model, which Duende.BFF uses to implement [server-side session management](/bff/session/server_side_sessions/) and [back-channel logout support](/bff/session/management/back-channel-logout/). + +#### Duende.AccessTokenManagement +Duende.BFF uses the Duende.AccessTokenManagement library for access token management and storage. This includes storage and retrieval of tokens, refreshing tokens as needed, and revoking tokens on logout. The library provides integration with the ASP.NET HTTP client to automatically attach tokens to outgoing HTTP requests, and its underlying management actions can also be programmatically invoked through an imperative API. + +#### API Endpoints +In the BFF architecture, the frontend makes API calls to backend services via the BFF host exclusively. Typically the BFF acts as a reverse proxy to [remote APIs]({{}}), providing session and token management. Implementing local APIs within the BFF host is also [possible]({{}}). Regardless, requests to APIs are authenticated with the session cookie and need to be secured with an anti-forgery protection header. + +#### YARP +Duende.BFF proxies requests to remote APIs using Microsoft's YARP (Yet Another Reverse Proxy). You can set up YARP using a simplified developer-centric configuration API provided by Duende.BFF, or if you have more complex requirements, you can use the full YARP configuration system directly. If you are using YARP directly, Duende.BFF provides [YARP integration]({{}}) to add BFF security and identity features. + +#### UI Assets +The BFF host typically serves at least some of the UI assets of the frontend, which can be HTML/JS/CSS, WASM, and/or server-rendered content. Serving the UI assets, or at least the index page of the UI from the same origin as the backend simplifies requests from the frontend to the backend. Doing so makes the two components same-origin, so that browsers will allow requests with no need to use CORS and automatically include cookies (including the crucial authentication cookie). This also avoids issues where [third-party cookie blocking]({{}}) or the SameSite cookie attribute prevents the frontend from sending the authentication cookie to the backend. + +It is also possible to separate the BFF and UI and host them separately. See [here]({{< ref "/bff/architecture/ui-hosting">}}) for more discussion of UI hosting architecture. + diff --git a/IdentityServer/v7/docs/content/bff/architecture/third-party-cookies.md b/IdentityServer/v7/docs/content/bff/architecture/third-party-cookies.md new file mode 100644 index 00000000..f8635047 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/architecture/third-party-cookies.md @@ -0,0 +1,32 @@ +--- +title: "Third Party Cookies" +weight: 12 +--- + +If the BFF and OpenID Connect Provider (OP) are hosted on different [sites](https://developer.mozilla.org/en-US/docs/Glossary/Site), then some browsers will block cookies from being sent during navigation between those sites. Almost all browsers have the option of blocking third party cookies. Safari and Firefox are the most widely used browsers that do so by default, while Chrome is planning to do so in the future. This change is being made to protect user privacy, but it also impacts OIDC flows traditionally used by SPAs. + +A couple of particularly notable OIDC flows that don't work for SPAs when third party cookies are blocked are [OIDC Session Management](https://openid.net/specs/openid-connect-session-1_0.html) and [OIDC Silent Login via the prompt=none parameter](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + +## Session Management + +OIDC Session Management allows a client SPA to monitor the session at the OP by reading a cookie from the OP in a hidden iframe. If third party cookie blocking prevents the iframe from seeing that cookie, the SPA will not be able to monitor the session. The BFF solves this problem using [OIDC back-channel logout]({{}}). + +The BFF is able to operate server side, and is therefore able to have a back channel to the OP. When the session ends at the OP, it can send a back-channel message to the BFF, ending the session at the BFF. + +## Silent Login +OIDC Silent Login allows a client application to start its session without needing any user interaction if the OP has an ongoing session. The main benefit is that a SPA can load in the browser and then start a session without navigating away from the SPA for an OIDC flow, preventing the need to reload the SPA. + +Similarly to OIDC Session Management, OIDC Silent Login relies on a hidden iframe, though in this case, the hidden iframe makes requests to the OP, passing the *prompt=none* parameter to indicate that user interaction isn't sensible. If that request includes the OP's session cookie, the OP can respond successfully and the application can obtain tokens. But if the request does not include a session - either because no session has been started or because the cookie has been blocked - then the silent login will fail, and the user will have to be redirected to the OP for an interactive login. + +### BFF with a Federation Gateway + +The BFF supports silent login from the SPA with the /bff/silent-login [endpoint]({{}}). This endpoint is intended to be invoked in an iframe and issues a challenge to login non-interactively with *prompt=none*. Just as in a traditional SPA, this technique will be disrupted by third party cookie blocking when the BFF and OP are third parties. + +If you need silent login with a third party OP, we recommend that you use the [Federation Gateway]({{}}) pattern. In the federation gateway pattern, one identity provider (the gateway) federates with other remote identity providers. Because the client applications only interact with the gateway, the implementation details of the remote identity providers are abstracted. In this case, we shield the client application from the fact that the remote identity provider is a third party by hosting the gateway as a first party to the client. This makes the client application's requests for silent login always first party. + + +### Alternatives +Alternatively, you can accomplish a similar goal (logging in without needing to initially load the SPA, only to redirect away from it) by detecting that the user is not authenticated in the BFF and issuing a challenge before the site is ever loaded. This approach is not typically our first recommendation, because it makes allowing anonymous access to parts of the UI difficult and because it requires *samesite=lax* cookies (see below). + + + diff --git a/IdentityServer/v7/docs/content/bff/architecture/ui-hosting.md b/IdentityServer/v7/docs/content/bff/architecture/ui-hosting.md new file mode 100644 index 00000000..bf934c61 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/architecture/ui-hosting.md @@ -0,0 +1,45 @@ +--- +title: "UI Hosting" +weight: 11 +--- + +## Hosting Options for the UI +There are several options for hosting the UI assets when using a BFF. + +- Host the assets within the BFF host using the static file middleware +- Host the assets within the BFF host using the Microsoft SPA Templates +- Host the UI and BFF separately on subdomains of the same site and use CORS to allow cross origin requests +- Serves the index page of the UI from the BFF host, and all other assets are loaded from another domain, such as a CDN + +#### Static File Middleware +Hosting the UI together with the BFF is the simplest choice, as requests from the front end to the backend will automatically include the authentication cookie and not require CORS headers. If you create a BFF host using our templates, the UI will be hosted in this way: + +```sh +dotnet new bffremoteapi + +# or + +dotnet new bfflocalapi +``` + +#### Host UI in the BFF using Microsoft Templates +Many frontend applications require a build process, which complicates the use of the static file middleware at development time. Visual Studio include's SPA templates that start up a SPA and proxy requests to it during development. Samples of Duende.BFF that take this approach using [React]({{< ref "/samples/bff#reactjs-frontend" >}}) and [Angular]({{< ref "/samples/bff#angular-frontend" >}}) are available. + +Microsoft's templates are easy to use at dev time from Visual Studio. They allow you to simply run the solution, and the template proxies requests to the front end for you. At deploy time, that proxy is removed and the static assets of the site are served by the static file middleware. + + +#### Host the UI separately +You may want to host the UI outside of the BFF. At development time, UI developers might prefer to run the frontend outside of Visual Studio (e.g., using the node cli). You might also want to have separate deployments of the frontend and the BFF, and you might want your static UI assets hosted on a CDN. If that is your preference, there are a couple of options for hosting the frontend outside of the C# project. + +First, you can host the BFF and SPA entirely separately, and use CORS to make requests from the frontend to the backend. In order to include the auth cookie in those requests, the frontend code will have to [declare that it should send credentials](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included) using the *credentials: "include"* option. You'll also need to ensure that the two components are hosted on subdomains of the same domain so that [third party cookie blocking]({{}}) doesn't prevent the frontend from including cookies in its requests to the BFF host. + +A sample of this approach is [available]({{< ref "/samples/bff#separate-host-for-ui" >}}). + +#### Serve the index page from the BFF host +Secondly, you could serve the index page of the SPA from the BFF, but have all of the other static assets hosted on another host (presumably a CDN). This technique makes the UI and BFF have exactly the same origin, so the authentication cookie will be sent from the frontend to the BFF automatically, and third party cookie blocking and the SameSite cookie attribute won't present any problems. + +Setting this up for local development takes a bit of effort however. As you make changes to the frontend, the UI's build process might generate a change to the index page. If it does, you'll need to arrange for the index page being served by the BFF host to reflect that change. + +Additionally, the front end will need to be configurable so that it is able to load its assets from other hosts. The mechanism for doing so will vary depending on the technology used to build the frontend. For instance, Angular includes a number of [deployment options](https://angular.io/guide/deployment) that allow you to control where it expects to find assets. + +The added complexity of this technique is justified when there is a requirement to host the front end on a different site (typically a CDN) from the BFF. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/extensibility/_index.md b/IdentityServer/v7/docs/content/bff/extensibility/_index.md new file mode 100644 index 00000000..97833958 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/_index.md @@ -0,0 +1,13 @@ ++++ +title = "Extensibility" +weight = 100 +chapter = true ++++ + +# Extensibility + +Duende.BFF can be extended in the following areas + +* custom logic at the session management endpoints +* custom logic and configuration for HTTP forwarding +* custom data storage for server-side sessions and access/refresh tokens \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/extensibility/http_forwarder.md b/IdentityServer/v7/docs/content/bff/extensibility/http_forwarder.md new file mode 100644 index 00000000..ba6d06d6 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/http_forwarder.md @@ -0,0 +1,87 @@ +--- +title: "HTTP Forwarder" +date: 2020-09-10T08:22:12+02:00 +weight: 40 +--- + +You can customize the HTTP forwarder behavior in two ways + +* provide a customized HTTP client for outgoing calls +* provide custom request/response transformation + +### Custom HTTP clients +By default, Duende.BFF will create and cache an HTTP client per configured route or local path. + +This invoker is setup like this: + +```cs +var client = new HttpMessageInvoker(new SocketsHttpHandler +{ + UseProxy = false, + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + UseCookies = false +}); +``` + +If you want to customize the HTTP client for specific paths, you can either implement the *IHttpMessageInvokerFactory* interface or derive from the *DefaultHttpMessageInvokerFactory*, e.g.: + +```cs +public class MyInvokerFactory : DefaultHttpMessageInvokerFactory +{ + public override HttpMessageInvoker CreateClient(string localPath) + { + if (localPath == "/foo") + { + return Clients.GetOrAdd(localPath, (key) => + { + return new HttpMessageInvoker(new SocketsHttpHandler + { + // this API needs a proxy + UseProxy = true, + Proxy = new WebProxy("https://myproxy"), + + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + UseCookies = false + }); + }); + } + + return base.CreateClient(localPath); + } +} +``` + +..and override our registration: + +```cs +services.AddSingleton(); +``` + +### Custom transformations +In the standard configuration, BFF uses the YARP default behavior for forwarding HTTP requests. In addition we + +* remove the sensitive session cookie +* add the current access token + +If you want to modify this behavior you can either implement *IHttpTransformerFactory* from scratch: + +```cs +public interface IHttpTransformerFactory +{ + /// + /// Creates a HTTP transformer based on the local path + /// + /// Local path the remote API is mapped to + /// The access token to attach to the request (if present) + /// + HttpTransformer CreateTransformer(string localPath, string accessToken = null); +} +``` + +...or derive from the *DefaultHttpTransformerFactory*. + +{{% notice note %}} +The transformations are based on YARP's transform library and are extensible. See [here](https://microsoft.github.io/reverse-proxy/articles/transforms.html) for a full list of built-in transforms. +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/_index.md b/IdentityServer/v7/docs/content/bff/extensibility/management/_index.md new file mode 100644 index 00000000..bcc10a02 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/_index.md @@ -0,0 +1,36 @@ +--- +title: "BFF Management Endpoints Extensibility" +menuTitle: "Management Endpoints" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +--- + +The behavior of each [management endpoint]({{< ref "/bff/session/management" >}}) is defined in a service. When you add Duende.BFF to DI, a default implementation for every management endpoint gets registered: + +```csharp +// management endpoints +services.AddTransient(); +services.AddTransient(); +services.AddTransient(); +services.AddTransient(); +services.AddTransient(); +services.AddTransient(); +services.AddTransient(); +``` + +You can add your own implementation by overriding the default after calling *AddBff()*. + +The management endpoint services all inherit from the *IBffEndpointService*, which provides a general-purpose mechanism to add custom logic to the endpoints. + +```cs +public interface IBffEndpointService +{ + Task ProcessRequestAsync(HttpContext context); +} +``` + +None of the endpoint services contain additional members beyond *ProcessRequestAsync*. + +You can customize the behavior of the endpoints either by implementing the appropriate interface or by extending the default implementation of that interface. In many cases, extending the default implementation is preferred, as this allows you to keep most of the default behavior by calling the base *ProcessRequestAsync* from your derived class. Several of the default endpoint service implementations also define virtual methods that can be overridden to customize their behavior with more granularity. See the following pages for details on those extension points. + +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/back-channel-logout.md b/IdentityServer/v7/docs/content/bff/extensibility/management/back-channel-logout.md new file mode 100644 index 00000000..e2156f7d --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/back-channel-logout.md @@ -0,0 +1,70 @@ +--- +title: "BFF Back-Channel Logout Endpoint Extensibility" +menuTitle: "Back-Channel Logout" +linkTitle: "Back-Channel Logout" +date: 2022-12-29T10:22:12+02:00 +weight: 60 +--- + +The back-channel logout endpoint has several extensibility points organized into two interfaces and their default implementations. The *IBackchannelLogoutService* is the top level abstraction that processes requests to the endpoint. This service can be used to add custom request processing logic or to change how it validates incoming requests. When the back-channel logout endpoint receives a valid request, it revokes sessions using the *ISessionRevocationService*. + +## Request Processing +You can add custom logic to the endpoint by implementing the *IBackchannelLogoutService* or by extending its default implementation (*Duende.Bff.DefaultBackchannelLogoutService*). In most cases, extending the default implementation is preferred, as it has several virtual methods that can be overridden to customize particular aspects of how the request is processed. + +*ProcessRequestAsync* is the top level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. + +```csharp +public class CustomizedBackchannelLogoutService : DefaultBackchannelLogoutService +{ + public override Task ProcessRequestAsync(HttpContext context) + { + // Custom logic here + + return base.ProcessRequestAsync(context); + } +``` + +## Validation + +Validation of the incoming request can be customized by overriding one of several virtual methods in the *DefaultBackchannelLogoutService*. *GetTokenValidationParameters* allows you to specify the *[TokenValidationParameters](https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.tokenvalidationparameters?view=azure-dotnet)* used to validate the incoming logout token. The default implementation creates token validation parameters based on the authentication scheme's configuration. Your override could begin by calling the base method and then make changes to those parameters or completely customize how token validation parameters are created. For example: + +```csharp +public class CustomizedBackchannelLogoutService : DefaultBackchannelLogoutService +{ + protected override async Task GetTokenValidationParameters() + { + var tokenValidationParams = await base.GetTokenValidationParameters(); + + // Set custom parameters here + // For example, make clock skew more permissive than it is by default: + tokenValidationParams.ClockSkew = TimeSpan.FromMinutes(15); + + return tokenValidationParams; + } +``` +If you need more control over the validation of the logout token, you can override *ValidateJwt*. The default implementation of *ValidateJwt* validates the token and produces a *ClaimsIdentity* using a *[JsonWebTokenHandler](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/ValidatingTokens)* and the token validation parameters returned from *GetTokenValidationParameters*. Your override could call the base method and then manipulate this *ClaimsIdentity* or add a completely custom method for producing the *ClaimsIdentity* from the logout token. + +*ValidateLogoutTokenAsync* is the coarsest-grained validation method. It is is responsible for validating the incoming logout token and determining if logout should proceed, based on claims in the token. It returns a *ClaimsIdentity* if logout should proceed or null if it should not. Your override could prevent logout in certain circumstances by returning null. For example: + +```csharp +public class CustomizedBackchannelLogoutService : DefaultBackchannelLogoutService +{ + protected override async Task ValidateLogoutTokenAsync(string logoutToken) + { + var identity = await base.ValidateLogoutTokenAsync(logoutToken); + + // Perform custom logic here + // For example, prevent logout based on certain conditions + if(identity?.FindFirst("sub")?.Value == "12345") + { + return null; + } + else + { + return identity; + } + } +``` + +## Session Revocation +The back-channel logout service will call the registered session revocation service to revoke the user session when it receives a valid logout token. To customize the revocation process, implement the *ISessionRevocationService*. diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/diagnostics.md b/IdentityServer/v7/docs/content/bff/extensibility/management/diagnostics.md new file mode 100644 index 00000000..5f884e38 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/diagnostics.md @@ -0,0 +1,22 @@ +--- +title: "BFF Diagnostics Endpoint Extensibility" +menuTitle: "Diagnostics" +date: 2022-12-29T10:22:12+02:00 +weight: 70 +--- + +The BFF diagnostics endpoint can be customized by implementing the *IDiagnosticsService* or by extending *DefaultDiagnosticsService*, its default implementation. + +## Request Processing +*ProcessRequestAsync* is the top level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. + +For example, you could take whatever actions you need before normal processing of the request like this: + +```csharp +public override Task ProcessRequestAsync(HttpContext context) +{ + // Custom logic here + + return base.ProcessRequestAsync(context); +} +``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/login.md b/IdentityServer/v7/docs/content/bff/extensibility/management/login.md new file mode 100644 index 00000000..202cd974 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/login.md @@ -0,0 +1,25 @@ +--- +title: "BFF Login Endpoint Extensibility" +menuTitle: "Login" +date: 2022-12-30 10:55:24 +weight: 10 +--- + +The BFF login endpoint has extensibility points in two interfaces. The *ILoginService* is the top level abstraction that processes requests to the endpoint. This service can be used to add custom request processing logic. The *IReturnUrlValidator* ensures that the *returnUrl* parameter passed to the login endpoint is safe to use. + +## Request Processing +*ProcessRequestAsync* is the top level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. + +For example, you could take whatever actions you need before normal processing of the request like this: + +```csharp +public override Task ProcessRequestAsync(HttpContext context) +{ + // Custom logic here + + return base.ProcessRequestAsync(context); +} +``` + +## Return URL Validation +To prevent open redirector attacks, the *returnUrl* parameter to the login endpoint must be validated. You can customize this validation by implementing the *IReturnUrlValidator* interface. The default implementation enforces that return urls are local. diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/logout.md b/IdentityServer/v7/docs/content/bff/extensibility/management/logout.md new file mode 100644 index 00000000..9f679e01 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/logout.md @@ -0,0 +1,25 @@ +--- +title: "BFF Logout Endpoint Extensibility" +menuTitle: "Logout" +date: 2022-12-30 10:55:24 +weight: 40 +--- + +The BFF logout endpoint has extensibility points in two interfaces. The *IlogoutService* is the top level abstraction that processes requests to the endpoint. This service can be used to add custom request processing logic. The *IReturnUrlValidator* ensures that the *returnUrl* parameter passed to the logout endpoint is safe to use. + +## Request Processing +*ProcessRequestAsync* is the top level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. + +For example, you could take whatever actions you need before normal processing of the request like this: + +```csharp +public override Task ProcessRequestAsync(HttpContext context) +{ + // Custom logic here + + return base.ProcessRequestAsync(context); +} +``` + +## Return URL Validation +To prevent open redirector attacks, the *returnUrl* parameter to the logout endpoint must be validated. You can customize this validation by implementing the *IReturnUrlValidator* interface. The default implementation enforces that return urls are local. diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/silent-login-callback.md b/IdentityServer/v7/docs/content/bff/extensibility/management/silent-login-callback.md new file mode 100644 index 00000000..a674d7b0 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/silent-login-callback.md @@ -0,0 +1,22 @@ +--- +title: "BFF Silent Login Callback Extensibility" +menuTitle: "Silent Login Callback" +date: 2022-12-30 10:55:24 +weight: 30 +--- + +The BFF silent login callback endpoint can be customized by implementing the *ISilentLoginCallbackService* or by extending *DefaultSilentLoginCallbackService*, its default implementation. + +## Request Processing +*ProcessRequestAsync* is the top level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. + +For example, you could take whatever actions you need before normal processing of the request like this: + +```csharp +public override Task ProcessRequestAsync(HttpContext context) +{ + // Custom logic here + + return base.ProcessRequestAsync(context); +} +``` diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/silent-login.md b/IdentityServer/v7/docs/content/bff/extensibility/management/silent-login.md new file mode 100644 index 00000000..57f586b0 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/silent-login.md @@ -0,0 +1,22 @@ +--- +title: "BFF Silent Login Endpoint Extensibility" +menuTitle: "Silent Login" +date: 2022-12-30 10:55:24 +weight: 20 +--- + +The BFF silent login endpoint can be customized by implementing the *ISilentLoginService* or by extending *DefaultSilentLoginService*, its default implementation. + +## Request Processing +*ProcessRequestAsync* is the top level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. + +For example, you could take whatever actions you need before normal processing of the request like this: + +```csharp +public override Task ProcessRequestAsync(HttpContext context) +{ + // Custom logic here + + return base.ProcessRequestAsync(context); +} +``` diff --git a/IdentityServer/v7/docs/content/bff/extensibility/management/user.md b/IdentityServer/v7/docs/content/bff/extensibility/management/user.md new file mode 100644 index 00000000..7db77cbb --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/management/user.md @@ -0,0 +1,40 @@ +--- +title: "BFF User Endpoint Extensibility" +menuTitle: "User" +date: 2022-12-30 10:55:24 +weight: 50 +--- + +The BFF user endpoint can be customized by implementing the *IUserService* or by extending *DefaultUserService*, its default implementation. In most cases, extending the default implementation is preferred, as it has several virtual methods that can be overridden to customize particular aspects of how the request is processed. The *DefaultUserService*'s virtual methods are *ProcessRequestAsync*, *GetUserClaims*, and *GetManagementClaims*. + +## Request Processing +*ProcessRequestAsync* is the top level function called in the endpoint service and can be used to add arbitrary logic to the endpoint. + +For example, you could take whatever actions you need before normal processing of the request like this: + +```csharp +public override Task ProcessRequestAsync(HttpContext context) +{ + // Custom logic here + + return base.ProcessRequestAsync(context); +} +``` + +## User Claims +*GetUserClaims* produces the collection of claims that describe the user. The default implementation returns all the claims in the user's session. Your override could add claims from some other source or manipulate the claims in arbitrary ways. + +For example, you could add additional claims to the user endpoint that would not be part of the session like this: + +```csharp +protected override IEnumerable GetUserClaims(AuthenticateResult authenticateResult) +{ + var baseClaims = base.GetUserClaims(authenticateResult); + var sub = authenticateResult.Principal.FindFirstValue("sub"); + var otherClaims = getAdditionalClaims(sub); // Retrieve claims from some data store + return baseClaims.Append(otherClaims); +} +``` + +## Management Claims +*GetManagementClaims* is responsible for producing additional claims that are useful for user management. The default implementation creates *bff:session_expires_in*, *bff:session_state*, and *bff:logout_url* [claims]({{< ref "/bff/session/management/user#management-claims" >}}). Your implementation could change those claims or add additional custom claims. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/extensibility/sessions.md b/IdentityServer/v7/docs/content/bff/extensibility/sessions.md new file mode 100644 index 00000000..6d64e31d --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/sessions.md @@ -0,0 +1,96 @@ +--- +title: "Session Management" +description: "BFF Extensibility" +date: 2020-09-10T08:22:12+02:00 +weight: 20 +--- + +## User Session Store + +If using the server-side sessions feature, you will need to have a store for the session data. +An Entity Framework Core based implementation of this store is provided. +If you wish to use some other type of store, then you can implement the *IUserSessionStore* interface: + +``` +/// +/// User session store +/// +public interface IUserSessionStore +{ + /// + /// Retrieves a user session + /// + /// + /// A token that can be used to request cancellation of the asynchronous operation. + /// + Task GetUserSessionAsync(string key, CancellationToken cancellationToken = default); + + /// + /// Creates a user session + /// + /// + /// A token that can be used to request cancellation of the asynchronous operation. + /// + Task CreateUserSessionAsync(UserSession session, CancellationToken cancellationToken = default); + + /// + /// Updates a user session + /// + /// + /// + /// A token that can be used to request cancellation of the asynchronous operation. + /// + Task UpdateUserSessionAsync(string key, UserSessionUpdate session, CancellationToken cancellationToken = default); + + /// + /// Deletes a user session + /// + /// + /// A token that can be used to request cancellation of the asynchronous operation. + /// + Task DeleteUserSessionAsync(string key, CancellationToken cancellationToken = default); + + /// + /// Queries user sessions based on the filter. + /// + /// + /// A token that can be used to request cancellation of the asynchronous operation. + /// + Task> GetUserSessionsAsync(UserSessionsFilter filter, CancellationToken cancellationToken = default); + + /// + /// Deletes user sessions based on the filter. + /// + /// + /// A token that can be used to request cancellation of the asynchronous operation. + /// + Task DeleteUserSessionsAsync(UserSessionsFilter filter, CancellationToken cancellationToken = default); +} +``` + +Once you have an implementation, you can register it when you enable server-side sessions: + +``` +public void ConfigureServices(IServiceCollection services) +{ + services.AddBff() + .AddServerSideSessions(); +} +``` + +## User Session Store Cleanup + +The *IUserSessionStoreCleanup* interface is used to model cleaning up expired sessions. + +```csharp +/// +/// User session store cleanup +/// +public interface IUserSessionStoreCleanup +{ + /// + /// Deletes expired sessions + /// + Task DeleteExpiredSessionsAsync(CancellationToken cancellationToken = default); +} +``` diff --git a/IdentityServer/v7/docs/content/bff/extensibility/tokens.md b/IdentityServer/v7/docs/content/bff/extensibility/tokens.md new file mode 100644 index 00000000..99ab86a5 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/extensibility/tokens.md @@ -0,0 +1,101 @@ +--- +title: "Token Management" +description: "BFF - Customization" +date: 2020-09-10T08:22:12+02:00 +weight: 30 +--- + +The token management library does essentially two things: + +* stores access and refresh tokens in the current session +* refreshes access tokens automatically at the token service when needed + +Both aspects can be customized. + +### Token service communication +The token management library uses a named HTTP client from the HTTP client factory for all token service communication. You can provide a customized HTTP client yourself using the well-known name after calling *AddBff*: + +```cs +services.AddHttpClient(AccessTokenManagementDefaults.BackChannelHttpClientName, configureClient => { ... }); +``` + +{{% notice note %}} +You can also supply client assertions to the token management library. See this [sample]({{< ref "/samples/basics#mvc-client-with-jar-and-jwt-based-authentication" >}}) for JWT-based client authentication. +{{% /notice %}} + +### Custom token storage +We recommend that you use the default storage mechanism, as this will automatically be compatible with the Duende.BFF server-side sessions. + +If you do not use server-side sessions, then the access and refresh token will be stored in the protected session cookie. If you want to change this, you can take over token storage completely. + +This would involve two steps + +* turn off the *SaveTokens* flag on the OpenID Connect handler and handle the relevant events manually to store the tokens in your custom store +* implement and register the *IdentityModel.AspNetCore.AccessTokenManagement.IUserAccessTokenStore* interface + +The interface is responsible to storing, retrieving and clearing tokens for the automatic token management: + +```cs +public interface IUserAccessTokenStore +{ + /// + /// Stores tokens + /// + /// User the tokens belong to + /// The access token + /// The access token expiration + /// The refresh token (optional) + /// Extra optional parameters + /// + Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null); + + /// + /// Retrieves tokens from store + /// + /// User the tokens belong to + /// Extra optional parameters + /// access and refresh token and access token expiration + Task GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null); + + /// + /// Clears the stored tokens for a given user + /// + /// User the tokens belong to + /// Extra optional parameters + /// + Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null); +} +``` + +### Per-route customized token retrieval +The token store defines how tokens are retrieved globally. However, you can add custom logic that changes the way that access tokens are retrieved on a per-route basis. For example, you might need to exchange a token to perform delegation or impersonation for some API calls, depending on the remote API. The interface that describes this extension point is the *IAccessTokenRetriever*. + + +```cs +/// +/// Retrieves access tokens +/// +public interface IAccessTokenRetriever +{ + /// + /// Asynchronously gets the access token. + /// + /// Context used to retrieve the token. + /// A task that contains the access token result, which is an + /// object model that can represent various types of tokens (bearer, dpop), + /// the absence of an optional token, or an error. + Task GetAccessToken(AccessTokenRetrievalContext context); +} +``` + +You can implement this interface yourself or extend the *DefaultAccessTokenRetriever*. The *AccessTokenResult* class represents the result of this operation. It is an abstract class with concrete implementations that represent successfully retrieving a bearer token (*BearerTokenResult*), successfully retrieving a DPoP token (*DPoPTokenResult*), failing to find an optional token (*NoAccessTokenResult*), which is not an error, and failure to retrieve a token (*AccessTokenRetrievalError*). Your implementation of GetAccessToken should return one of those types. + +Implementations of the *IAccessTokenRetriever* can be added to endpoints when they are mapped using the *WithAccessTokenRetriever* extension method: + +```cs +endpoints.MapRemoteBffApiEndpoint("/api/impersonation", "https://api.example.com/endpoint/requiring/impersonation") + .RequireAccessToken(TokenType.User) + .WithAccessTokenRetriever(); +``` + +The *GetAccessToken* method will be invoked on every call to APIs that use the access token retriever. If retrieving the token is an expensive operation, you may need to cache it. It is up to your retriever code to perform caching. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/images/BFF_blocks.png b/IdentityServer/v7/docs/content/bff/images/BFF_blocks.png new file mode 100644 index 00000000..25d09db6 Binary files /dev/null and b/IdentityServer/v7/docs/content/bff/images/BFF_blocks.png differ diff --git a/IdentityServer/v7/docs/content/bff/images/DuendeBFF_blocks.png b/IdentityServer/v7/docs/content/bff/images/DuendeBFF_blocks.png new file mode 100644 index 00000000..f306f58f Binary files /dev/null and b/IdentityServer/v7/docs/content/bff/images/DuendeBFF_blocks.png differ diff --git a/IdentityServer/v7/docs/content/bff/options.md b/IdentityServer/v7/docs/content/bff/options.md new file mode 100644 index 00000000..9648d4a7 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/options.md @@ -0,0 +1,117 @@ +--- +title: "Configuration Options" +description: "BFF" +date: 2020-09-10T08:22:12+02:00 +weight: 90 +--- + +The *Duende.BFF.BffOptions* allows to configure several aspects of the BFF framework. + +You set the options at startup time in your *ConfigureServices* method: + +```cs +services.AddBff(options => +{ + // configure options here.. +}) +``` + +## General + +* ***EnforceBffMiddleware*** + + Enables checks in the user management endpoints that ensure that the BFF middleware has been added to the pipeline. Since the middleware performs important security checks, this protects from accidental configuration errors. You can disable this check if it interferes with some custom logic you might have. Defaults to true. + +* ***LicenseKey*** + + This sets the license key for Duende.BFF. A business edition or higher license key is required for production deployments. The same license key is used in IdentityServer and the BFF. Just as in the [IdentityServer host]({{}}), you can either set the license key using this option in code or include *Duende_License.key* in the same directory as your BFF host. + +* ***AnonymousSessionResponse*** (added in 2.0) + + This sets the response status code behavior on the [user endpoint]({{< ref "/bff/session/management/user">}}) to either return 401 or 200 with a *null* payload when the user is anonymous. + +* ***DiagnosticsEnvironments*** + + The ASP.NET environment names that enable the diagnostics endpoint. Defaults to "Development". + +## Paths + +* ***LoginPath*** + + Sets the path to the login endpoint. Defaults to */bff/login*. + +* ***SilentLoginPath*** + + Sets the path to the silent login endpoint. Defaults to */bff/silent-login*. + +* ***SilentLoginCallbackPath*** + + Sets the path to the silent login callback endpoint. Defaults to */bff/silent-login-callback*. + +* ***LogoutPath*** + + Sets the path to the logout endpoint. Defaults to */bff/logout*. + +* ***UserPath*** + + Sets the path to the user endpoint. Defaults to */bff/user*. + +* ***BackChannelLogoutPath*** + + Sets the path to the backchannel logout endpoint. Defaults to */bff/backchannel*. + +* ***DiagnosticsPath*** + + Sets the path to the diagnostics endpoint. Defaults to */bff/diagnostics*. + +## Session Management + +* ***ManagementBasePath*** + + Base path for management endpoints. Defaults to */bff*. + +* ***RequireLogoutSessionId*** + + Flag that specifies if the *sid* claim needs to be present in the logout request as query string parameter. + Used to prevent cross site request forgery. + Defaults to *true*. + +* ***RevokeRefreshTokenOnLogout*** + + Specifies if the user's refresh token is automatically revoked at logout time. + Defaults to *true*. + +* ***BackchannelLogoutAllUserSessions*** + + Specifies if during backchannel logout all matching user sessions are logged out. + If *true*, all sessions for the subject will be revoked. If false, just the specific session will be revoked. + Defaults to *false*. + +* ***EnableSessionCleanup*** + + Indicates if expired server side sessions should be cleaned up. + This requires an implementation of IUserSessionStoreCleanup to be registered in the DI system. + Defaults to *false*. + +* ***SessionCleanupInterval*** + + Interval at which expired sessions are cleaned up. + Defaults to *10 minutes*. + + +## APIs + +* ***AntiForgeryHeaderName*** + + Specifies the name of the header used for anti-forgery header protection. + Defaults to *X-CSRF*. + +* ***AntiForgeryHeaderValue*** + + Specifies the expected value of Anti-forgery header. + Defaults to *1*. + +* ***DPoPJsonWebKey*** + + Specifies the Json Web Key to use when creating DPoP proof tokens. + Defaults to null, which is appropriate when not using DPoP. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/overview.md b/IdentityServer/v7/docs/content/bff/overview.md new file mode 100644 index 00000000..7a78d1b5 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/overview.md @@ -0,0 +1,23 @@ +--- +title: "Overview" +description: "BFF" +date: 2020-09-10T08:22:12+02:00 +weight: 1 +--- + +Duende.BFF is a library for building services that solve security and identity problems in browser based applications such as SPAs and Blazor WASM applications. It is used to create a backend host that is paired with a frontend application. This backend is called the Backend For Frontend (BFF) host, and is responsible for all of the OAuth and OIDC protocol interactions. Moving the protocol handling out of JavaScript provides important security benefits and works around changes in browser privacy rules that increasingly disrupt OAuth and OIDC protocol flows in browser based applications. The Duende.BFF library makes it easy to build and secure BFF hosts by providing [session and token management]({{< ref "/bff/session" >}}), [API endpoint protection]({{< ref "/bff/apis" >}}), and [logout notifications]({{< ref "/bff/session/management/back-channel-logout" >}}). + +## Threats against browser based applications + +Browser based applications have a relatively large attack surface. Security risks come not only from the application's own code, which must be protected against cross site scripting, cross site request forgery, and other vulnerabilities, but also from the frameworks, libraries, and other NPM packages it uses, as well as all of their transitive dependencies. Additionally, other applications running on the same site must also be secured. The recent [Spectre](https://www.securityweek.com/google-releases-poc-exploit-browser-based-spectre-attack) attacks against browsers serve as a reminder that new threats are constantly emerging. Given all of these risks, we do not recommend storing high-value access tokens or refresh tokens in JavaScript-accessible locations. + +In Duende.BFF, tokens are only accessible server-side and sessions are managed using encrypted and signed HTTP-only cookies. This greatly simplifies the threat model and reduces risk. While content injection attacks are still possible, the BFF limits the attacker's ability to abuse APIs by constraining access through a well-defined interface to the backend which eliminates the possibility of arbitrary API calls. + +## Changes in browser privacy rules +Browsers are increasingly restricting the use of cookies across site boundaries to protect user privacy. This can be a [problem](https://leastprivilege.com/2020/03/31/spas-are-dead/) for legitimate OAuth and OpenID Connect interactions, as some interactions in these protocols are indistinguishable from common tracking mechanisms from a browser's perspective. When the identity provider and client application are hosted on 3rd party sites, this affects several flows, including: + +- Front-channel logout notifications +- [OpenID Connect Session Management](https://openid.net/specs/openid-connect-session-1_0.html) +- The "silent renew" technique for session-bound token refreshing + +Using a BFF removes or mitigates all of these problems in the design. The backend component makes backchannel logout notifications possible, while still allowing the option of front-channel notifications for 1st party clients. Robust server-side session and token management with optional server-side sessions and refresh tokens take the place of OIDC Session Management and older token refresh mechanisms. As an ASP.NET Core server-side application, the BFF has access to a full featured and stable OpenID Connect client library that supports all the necessary protocol mechanisms and provides an excellent extensibility model for advanced features like [Mutual TLS]({{}}), [DPoP]({{}}), [JWT secured authorization requests]({{}}), and [JWT-based client authentication]({{}}). diff --git a/IdentityServer/v7/docs/content/bff/session/_index.md b/IdentityServer/v7/docs/content/bff/session/_index.md new file mode 100644 index 00000000..6e9f0c92 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/_index.md @@ -0,0 +1,17 @@ ++++ +title = "Authentication & Session Management" +date = 2020-09-10T08:20:20+02:00 +weight = 20 +chapter = true ++++ + +# Authentication & Session Management + +This section deals with setting up the following components + +* the ASP.NET Core authentication system +* the OpenID Connect handler +* the cookie handler +* the BFF session management endpoints +* server-side sessions +* back-channel logout support \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/bff/session/handlers.md b/IdentityServer/v7/docs/content/bff/session/handlers.md new file mode 100644 index 00000000..c2b4b6ac --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/handlers.md @@ -0,0 +1,96 @@ +--- +title: "ASP.NET Core Authentication System" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +--- + +You typically use the following two ASP.NET Core authentication handlers to implement remote authentication: + +* the OpenID Connect authentication handler to interact with the remote OIDC / OAuth token service, e.g. Duende IdentityServer +* the cookie handler to do local session management + +Furthermore the BFF plumbing relies on the configuration of the ASP.NET Core default authentication schemes. This describes how the two handlers share the work. + +OpenID Connect for *challenge* and *signout* - cookies for all the other operations: + +```csharp +services.AddAuthentication(options => + { + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + options.DefaultSignOutScheme = "oidc"; + }) + .AddCookie("cookie", options => { ... }) + .AddOpenIdConnect("oidc", options => { ... }); +``` + +### The OpenID Connect Authentication Handler +The OIDC handler connects the application to the authentication / access token system. + +The exact settings depend on the OIDC provider and its configuration settings. We recommend: + +* use authorization code flow with PKCE +* use a *response_mode* of *query* since this plays nicer with *SameSite* cookies +* use a strong client secret. Since the BFF can be a confidential client, it is totally possible to use strong client authentication like JWT assertions, JAR or MTLS. Shared secrets work as well of course. +* turn off inbound claims mapping +* save the tokens into the authentication session so they can be automatically managed +* request a refresh token using the *offline_access* scope + +```csharp +services.AddAuthentication().AddOpenIdConnect("oidc", options => +{ + options.Authority = "https://demo.duendesoftware.com"; + + // confidential client using code flow + PKCE + options.ClientId = "spa"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + + // query response type is compatible with strict SameSite mode + options.ResponseMode = "query"; + + // get claims without mappings + options.MapInboundClaims = false; + options.GetClaimsFromUserInfoEndpoint = true; + + // save tokens into authentication session + // to enable automatic token management + options.SaveTokens = true; + + // request scopes + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api"); + + // and refresh token + options.Scope.Add("offline_access"); +}); +``` +The OIDC handler will use the default sign-in handler (the cookie handler) to establish a session after successful validation of the OIDC response. + +### The Cookie Handler +The cookie handler is responsible for establishing the session and manage authentication session related data. + +Things to consider: + +* determine the session lifetime and if the session lifetime should be sliding or absolute +* it is recommended to use a cookie name [prefix](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.1.3) if compatible with your application +* use the highest available *SameSite* mode that is compatible with your application, e.g. *strict*, but at least *lax* + +```csharp +services.AddAuthentication().AddCookie("cookie", options => +{ + // set session lifetime + options.ExpireTimeSpan = TimeSpan.FromHours(8); + + // sliding or absolute + options.SlidingExpiration = false; + + // host prefixed cookie name + options.Cookie.Name = "__Host-spa"; + + // strict SameSite handling + options.Cookie.SameSite = SameSiteMode.Strict; +}); +``` diff --git a/IdentityServer/v7/docs/content/bff/session/management/_index.md b/IdentityServer/v7/docs/content/bff/session/management/_index.md new file mode 100644 index 00000000..181efa84 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/management/_index.md @@ -0,0 +1,44 @@ +--- +title: "BFF Session Management Endpoints" +menuTitle: "Session Management Endpoints" +weight: 20 +--- + +Duende.BFF adds endpoints for performing typical session-management operations such as triggering login and logout and getting information about the currently logged-on user. These endpoint are meant to be called by the frontend. + +In addition Duende.BFF adds an implementation of the OpenID Connect back-channel notification endpoint to overcome the restrictions of third party cookies in front-channel notification in modern browsers. + +You enable the endpoints by adding the relevant services into the DI container: + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Add BFF services to DI - also add server-side session management + services.AddBff(options => + { + // default value + options.ManagementBasePath = "/bff"; + }; + + // rest omitted +} +``` + +Endpoint routing is used to map the management endpoints: + +```csharp +public void Configure(IApplicationBuilder app) +{ + // rest omitted + + app.UseEndpoints(endpoints => + { + endpoints.MapBffManagementEndpoints(); + }); +``` + +*MapBffManagementEndpoints* adds all BFF management endpoints. You can also map each endpoint individually by calling the various *MapBffManagementXxxEndpoint* methods, for example *endpoints.MapBffManagementLoginEndpoint()*. + +The following pages describe the default behavior of the management endpoints. See the [extensibility]({{< ref "/bff/extensibility" >}}) section for information about how to customize the behavior of the endpoints. + +{{%children style="h4" /%}} diff --git a/IdentityServer/v7/docs/content/bff/session/management/back-channel-logout.md b/IdentityServer/v7/docs/content/bff/session/management/back-channel-logout.md new file mode 100644 index 00000000..dd3f93e5 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/management/back-channel-logout.md @@ -0,0 +1,17 @@ +--- +title: "BFF Back-Channel Logout Endpoint" +menuTitle: "Back-Channel Logout" +date: 2022-12-29T10:22:12+02:00 +weight: 50 +--- + +The */bff/backchannel* endpoint is an implementation of the [OpenID Connect Back-Channel Logout](https://openid.net/specs/openid-connect-backchannel-1_0.html) specification. The remote identity provider can use this endpoint to end the BFF's session via a server to server call, without involving the user's browser. This design avoids problems with 3rd party cookies associated with front-channel logout. + +## Typical Usage +The back-channel logout endpoint is invoked by the remote identity provider when it determines that sessions should be ended. IdentityServer will send back-channel logout requests if you [configure]({{< ref "/reference/models/client#authentication--session-management" >}}) your client's *BackChannelLogoutUri*. When a session ends at IdentityServer, any client that was participating in that session that has a back-channel logout URI configured will be sent a back-channel logout request. This typically happens when another application signs out. [Expiration]({{< ref "/ui/server_side_sessions/session_expiration" >}}) of [IdentityServer server side sessions]({{< ref "/ui/server_side_sessions" >}}) can also be configured to send back-channel logout requests, though this is disabled by default. + +## Dependencies +The back-channel logout endpoint depends on [server-side sessions in the BFF]({{< ref "/bff/session/server_side_sessions" >}}), which must be enabled to use this endpoint. Note that such server-side sessions are distinct from server-side sessions in IdentityServer. + +## Revoke all sessions +Back-channel logout tokens include a sub (subject ID) and sid (session ID) claim to describe which session should be revoked. By default, the back-channel logout endpoint will only revoke the specific session for the given subject ID and session ID. Alternatively, you can configure the endpoint to revoke every session that belongs to the given subject ID by setting the *BackchannelLogoutAllUserSessions* [option]({{< ref "/bff/options#session-management">}}) to true. diff --git a/IdentityServer/v7/docs/content/bff/session/management/diagnostics.md b/IdentityServer/v7/docs/content/bff/session/management/diagnostics.md new file mode 100644 index 00000000..bdd69a4f --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/management/diagnostics.md @@ -0,0 +1,14 @@ +--- +title: "BFF Diagnostics Endpoint" +menuTitle: "Diagnostics" +date: 2022-12-29T10:22:12+02:00 +weight: 40 +--- + +The */bff/diagnostics* endpoint returns the current user and client access token for testing purposes. + +To use the diagnostics endpoint, make a GET request to */bff/diagnostics*. Typically this is done in a browser to diagnose a problem during development. + +{{% notice note %}} +This endpoint is only enabled in *Development* mode. +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/bff/session/management/login.md b/IdentityServer/v7/docs/content/bff/session/management/login.md new file mode 100644 index 00000000..ad73d7af --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/management/login.md @@ -0,0 +1,26 @@ +--- +title: "BFF Login Endpoint" +menuTitle: "Login" +date: 2020-09-10T08:22:12+02:00 +weight: 20 +--- + +The */bff/login* endpoint begins the authentication process. To use it, typically javascript code will navigate away from the frontend application to the login endpoint: + +```js +window.location = "/bff/login"; +``` + +In Blazor, instead use the *NavigationManager* to navigate to the login endpoint: + +```cs +Navigation.NavigateTo($"bff/login", forceLoad: true); +``` + +The login endpoint triggers an authentication challenge using the default challenge scheme, which will typically use the OpenID Connect [handler]({{< ref "handlers" >}}). + +## Return Url +After authentication is complete, the login endpoint will redirect back to your front end application. By default, this redirect goes to the root of the application. You can use a different URL instead by including a local URL as the *returnUrl* query parameter. +```js +window.location = "/bff/login?returnUrl=/logged-in"; +``` diff --git a/IdentityServer/v7/docs/content/bff/session/management/logout.md b/IdentityServer/v7/docs/content/bff/session/management/logout.md new file mode 100644 index 00000000..41572679 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/management/logout.md @@ -0,0 +1,24 @@ +--- +title: "BFF Logout Endpoint" +menuTitle: "Logout" +date: 2022-12-29T10:22:12+02:00 +weight: 30 +--- + +The */bff/logout* endpoint signs out of the appropriate ASP.NET Core [authentication schemes]({{< ref "handlers" >}}) to both delete the BFF's session cookie and to sign out from the remote identity provider. To use the logout endpoint, typically your javascript code will navigate away from your front end to the logout endpoint, similar to the login endpoint. However, unlike the login endpoint, the logout endpoint requires CSRF protection, otherwise an attacker could destroy sessions by making cross-site GET requests. The session id is used to provide this CSRF protection by requiring it as a query parameter to the logout endpoint (assuming that a session id was included during login). For convenience, the correct logout url is made available as a claim in the */bff/user* endpoint, making typical logout usage look like this: + +```js +var logoutUrl = userClaims["bff:logout_url"]; // assumes userClaims is the result of a call to /bff/user +window.location = logoutUrl; +``` + +## Return Url +After signout is complete, the logout endpoint will redirect back to your front end application. By default, this redirect goes to the root of the application. You can use a different URL instead by including a local URL as the *returnUrl* query parameter. +```js +var logoutUrl = userClaims["bff:logout_url"]; +window.location = `${logoutUrl}&returnUrl=/logged-out`; +``` + +## Revocation of Refresh Tokens +If the user has a refresh token, the logout endpoint can revoke it. This is enabled by default because revoking refresh tokens that will not be used any more is generally good practice. Normally any refresh tokens associated with the current session won't be used after logout, as the session where they are stored is deleted as part of logout. However, you can disable this revocation with the *RevokeRefreshTokenOnLogout* option. + diff --git a/IdentityServer/v7/docs/content/bff/session/management/silent-login.md b/IdentityServer/v7/docs/content/bff/session/management/silent-login.md new file mode 100644 index 00000000..1273bed9 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/management/silent-login.md @@ -0,0 +1,40 @@ +--- +title: "BFF Silent Login Endpoint" +menuTitle: "Silent Login" +date: 2022-12-29T10:22:12+02:00 +weight: 35 +--- + +Added in v1.2.0. + +The */bff/silent-login* endpoint triggers authentication similarly to the login endpoint, but in a non-interactive way. + +The expected usage pattern is that the application code loads in the browser and triggers a request to the *User Endpoint*. If that indicates that there is no BFF session, then the *Silent Login Endpoint* can be requested to attempt to automatically log the user in, using an existing session at the remote identity provider. + +This non-interactive design relies upon the use of an *iframe* to make the silent login request. +The result of the silent login request in the *iframe* will then use *postMessage* to notify the parent window of the outcome. +If the result is that a session has been established, then the application logic can either re-trigger a call to the *User Endpoint*, or simply reload the entire page (depending on the preferred design). If the result is that a session has not been established, then the application redirects to the login endpoint to log the user in interactively. + +To trigger the silent login, the application code must have an *iframe* and then set its *src* to the silent login endpoint. +For example in your HTML: + +``` + +``` + +And then in JavaScript: + +``` +document.querySelector('#bff-silent-login').src = '/bff/silent-login'; +``` + +To receive the result, the application should handle the *message* event in the browser and look for the *data.isLoggedIn* property on the event object: + +``` +window.addEventListener("message", e => { + if (e.data && e.data.source === 'bff-silent-login' && e.data.isLoggedIn) { + // we now have a user logged in silently, so reload this window + window.location.reload(); + } +}); +``` diff --git a/IdentityServer/v7/docs/content/bff/session/management/user.md b/IdentityServer/v7/docs/content/bff/session/management/user.md new file mode 100644 index 00000000..014343cc --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/management/user.md @@ -0,0 +1,103 @@ +--- +title: "BFF User Endpoint" +menuTitle: "User" +date: 2022-12-29T10:22:12+02:00 +weight: 25 +--- + +The */bff/user* endpoint returns data about the currently logged-on user and the session. It is typically invoked at application startup to check if the user has authenticated, and if so, to get profile data about the user. It can also be used to periodically query if the session is still valid. + +## Output +If there is no current session, the user endpoint returns a response indicating that the user is anonymous. By default, this is a 401 status code, but this can be [configured]({{< ref "#anonymous-session-response-option" >}}). + +If there is a current session, the user endpoint returns a JSON array containing the claims in the ASP.NET Core authentication session as well as several BFF specific claims. For example: + +```json +[ + { + "type": "sid", + "value": "173E788068FFB728806501F4F46C52D6" + }, + { + "type": "sub", + "value": "88421113" + }, + { + "type": "idp", + "value": "local" + }, + { + "type": "name", + "value": "Bob Smith" + }, + { + "type": "bff:logout_url", + "value": "/bff/logout?sid=173E788068FFB728806501F4F46C52D6" + }, + { + "type": "bff:session_expires_in", + "value": 28799 + }, + { + "type": "bff:session_state", + "value": "q-Hl1V9a7FCZE5o-vH9qpmyVKOaeVfMQBUJLrq-lDJU.013E58C33C7409C6011011B8291EF78A" + } +] +``` + +## User Claims +Since the user endpoint returns the claims that are in the ASP.NET Core session, anything that changes the session will be reflected in its output. You can customize the contents of the session via the OpenID Connect handler's [ClaimAction](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.claimactioncollectionmapextensions?view=aspnetcore-7.0) infrastructure, or by using [claims transformation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.iclaimstransformation?view=aspnetcore-7.0). For example, if you add a [claim]({{< ref "/fundamentals/claims" >}}) to the [userinfo endpoint]({{< ref "reference/endpoints/userinfo" >}}) at IdentityServer that you would like to include in the */bff/user* endpoint, you need to add a corresponding ClaimAction in the BFF's OpenID Connect Handler to include the claim in the BFF's session. + +## Management Claims +In addition to the claims in the ASP.NET Core Session, Duende.BFF adds three additional claims: + +**bff:session_expires_in** + +This is the number of seconds the current session will be valid for. + +**bff:session_state** + +This is the session state value of the upstream OIDC provider that can be use for the JavaScript *check_session* mechanism (if provided). + +**bff:logout_url** + +This is the URL to trigger logout. If the upstream provider includes a *sid* claim, the BFF logout endpoint requires this value as a query string parameter for CSRF protection. This behavior can be configured with the *RequireLogoutSessionId* in the [options]({{< ref "/bff/options" >}}). + +## Typical Usage +To use the endpoint, make an http GET request to it from your frontend javascript code. For example, your application could use the fetch api to make requests to the user endpoint like this: + +```js +var req = new Request("/bff/user", { + headers: new Headers({ + "X-CSRF": "1", + }), +}); + +var resp = await fetch(req); +if (resp.ok) { + userClaims = await resp.json(); + console.log("user logged in", userClaims); +} else if (resp.status === 401) { + console.log("user not logged in"); +} +``` + +## Cross-Site Request Forgery +To protect against cross-site request forgery, you need to add a [static header](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#use-of-custom-request-headers) to the GET request. The header's name and required value can be configured in the [options]({{< ref "/bff/options" >}}). + +## Anonymous Session Response Option +The *AnonymousSessionResponse* option allows you to change the behavior of the user endpoint to return 200 instead of 401 when the user is anonymous. If *AnonymousSessionResponse* is set to *AnonymousSessionResponse.Response200*, then the endpoint's response will set its status code to 200 and its payload will contain the literal *null* (the response body will be the characters 'n', 'u', 'l', 'l' without quotes). + +## Cookie Sliding +If your ASP.NET Core session cookie is configured to use a sliding expiration, you need to be able to query the session state without extending the session's lifetime; a periodic check for user activity shouldn't itself count as user activity. To prevent the call to the user endpoint from sliding the cookie, add the *slide=false* parameter to the request. + +```js +var req = new Request("/bff/user?slide=false", { + headers: new Headers({ + "X-CSRF": "1", + }), +}); +``` +{{% notice note %}} +The cookie sliding prevention feature requires either usage of server-side sessions or .NET 6 or higher (or both). +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/bff/session/server_side_sessions.md b/IdentityServer/v7/docs/content/bff/session/server_side_sessions.md new file mode 100644 index 00000000..0726e8c0 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/session/server_side_sessions.md @@ -0,0 +1,72 @@ +--- +title: "Server-side Sessions" +description: "BFF" +weight: 40 +--- + +By default, ASP.NET Core's cookie handler will store all user session data in a protected cookie. This works very well unless cookie size or revocation becomes an issue. + +Duende.BFF includes all the plumbing to store your sessions server-side. The cookie will then only be used to transmit the session ID between the browser and the BFF host. This has the following advantages + +* the cookie size will be very small and constant - regardless how much data (e.g. token or claims) is stored in the authentication session +* the session can be also revoked outside the context of a browser interaction, for example when receiving a back-channel logout notification from the upstream OpenID Connect provider + +## Configuring Server-side Sessions + +Server-side session can be enabled in *Startup*: + +```csharp +services.AddBff() + .AddServerSideSessions(); +``` + +The default implementation stores the session in-memory. This is useful for testing, but for production you typically want a more robust storage mechanism. We provide an implementation of the session store built with EntityFramework (EF) that can be used with any database with an EF provider (e.g. Microsoft SQL Server). You can also use a custom store. See [extensibility]({{< ref "/bff/extensibility/sessions#user-session-store" >}}) for more information. + +## Using Entity Framework for the Server-side Session Store + +To use the EF session store, register it by calling *AddEntityFrameworkServerSideSessions*, like this: + +```csharp +var cn = _configuration.GetConnectionString("db"); + +services.AddBff() + .AddEntityFrameworkServerSideSessions(options=> + { + options.UseSqlServer(cn); + }); +``` + +### Entity Framework Migrations +Most datastores that you might use with Entity Framework use a schema to define the structure of their data. *Duende.BFF.EntityFramework* doesn't make any assumptions about the underlying datastore, how (or indeed even if) it defines its schema, or how schema changes are managed by your organization. For these reasons, Duende does not directly support database creation, schema changes, or data migration by publishing database scripts. You are expected to manage your database in the way your organization sees fit. Using EF migrations is one possible approach to that, which Duende facilitates by publishing entity classes in each version of *Duende.BFF.EntityFramework*. An example project that uses those entities to create migrations is [here](https://github.com/DuendeSoftware/BFF/tree/main/migrations/UserSessionDb). + +## Session Store Cleanup + +Added in v1.2.0. + +Abandoned sessions will remain in the store unless something removes the stale entries. +If you wish to have such sessions cleaned up periodically, then you can configure the *EnableSessionCleanup* and *SessionCleanupInterval* options: + +```csharp +services.AddBff(options => { + options.EnableSessionCleanup = true; + options.SessionCleanupInterval = TimeSpan.FromMinutes(5); + }) + .AddServerSideSessions(); +``` + +This requires an implementation of [*IUserSessionStoreCleanup*]({{< ref "/bff/extensibility/sessions#user-session-store-cleanup" >}}) in the DI system. + +If using Entity Framework Core, then the *IUserSessionStoreCleanup* implementation is provided for you when you use *AddEntityFrameworkServerSideSessions*. +Just enable session cleanup: + +```csharp +var cn = _configuration.GetConnectionString("db"); + +services.AddBff(options => { + options.EnableSessionCleanup = true; + }) + .AddEntityFrameworkServerSideSessions(options=> + { + options.UseSqlServer(cn); + }); +``` diff --git a/IdentityServer/v7/docs/content/bff/tokens.md b/IdentityServer/v7/docs/content/bff/tokens.md new file mode 100644 index 00000000..c35f2fd2 --- /dev/null +++ b/IdentityServer/v7/docs/content/bff/tokens.md @@ -0,0 +1,98 @@ +--- +title: "Token Management" +description: "BFF - Overview" +date: 2020-09-10T08:22:12+02:00 +weight: 50 +--- + +Duende.BFF includes an automatic token management feature. This uses the access and refresh token stored in the authentication session to always provide a current access token for outgoing API calls. + +For most scenarios, there is no additional configuration necessary. The token management will infer the configuration and token endpoint URL from the metadata of the OpenID Connect provider. + +The easiest way to retrieve the current access token is to use an extension method on *HttpContext*: + +```cs + var token = await HttpContext.GetUserAccessTokenAsync(); +``` + +You can then use the token to set it on an *HttpClient* instance: + +```cs + var client = new HttpClient(); + client.SetBearerToken(token); +``` + +We recommend to leverage the *HttpClientFactory* to fabricate HTTP clients that are already aware of the token management plumbing. For this you would register a named client in your *startup* e.g. like this: + +```cs +// registers HTTP client that uses the managed user access token +services.AddUserAccessTokenHttpClient("apiClient", configureClient: client => +{ + client.BaseAddress = new Uri("https://remoteServer/"); +}); +``` + +And then retrieve a client instance like this: + +```cs +[Route("myApi")] +public class MyApiController : ControllerBase +{ + private readonly IHttpClientFactory _httpClientFactory; + + public MyController(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } + + public async Task Get(string id) + { + // create HTTP client with automatic token management + var client = _httpClientFactory.CreateClient("apiClient"); + + // call remote API + var response = await client.GetAsync("remoteApi"); + + // rest omitted + } +} +``` + +If you prefer to use typed clients, you can do that as well: + +```cs +// registers a typed HTTP client with token management support +services.AddHttpClient(client => +{ + client.BaseAddress = new Uri("https://remoteServer/"); +}) + .AddUserAccessTokenHandler(); +``` + +And then use that client, for example like this on a controller's action method: + +```cs +public async Task CallApiAsUserTyped( + [FromServices] MyTypedClient client) +{ + var response = await client.GetData(); + + // rest omitted +} +``` + +The client will internally always try to use a current and valid access token. If for any reason this is not possible, the 401 status code will be returned to the caller. + +### Reuse of Refresh Tokens +We recommend that you configure IdentityServer to issue reusable refresh tokens to BFF clients. Because the BFF is a confidential client, it does not need one-time use refresh tokens. Re-useable refresh tokens are desirable because they avoid performance and user experience problems associated with one time use tokens. See the discussion on [rotating refresh tokens]({{< ref "tokens/refresh#one-time-refresh-tokens" >}}) and the [OAuth 2.0 Security Best Current Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.2.2) for more details. + +### Manually revoking refresh tokens +Duende.BFF revokes refresh tokens automatically at logout time. This behavior can be disabled with the *RevokeRefreshTokenOnLogout* option. + +If you want to manually revoke the current refresh token, you can use the following code: + +```cs + await HttpContext.RevokeUserRefreshTokenAsync(); +``` + +This will invalidate the refresh token at the token service. diff --git a/IdentityServer/v7/docs/content/configuration/_index.md b/IdentityServer/v7/docs/content/configuration/_index.md new file mode 100644 index 00000000..4dfb05e6 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/_index.md @@ -0,0 +1,26 @@ +--- +title: Configuration API +description: Overview +weight: 96 +--- + +(Added in 6.3) + +The Configuration API is a collection of endpoints that allow for management and +configuration of an IdentityServer implementation. The Configuration API can be +hosted either separately or within the IdentityServer implementation, and is +distributed through the separate [Duende.IdentityServer.Configuration nuget +package](https://www.nuget.org/packages/Duende.IdentityServer.Configuration). + +In this initial release, the Configuration API supports the Dynamic Client +Registration protocol. + +The Configuration API is part of the +[IdentityServer](https://duendesoftware.com/products/identityserver) +Business Edition or higher. The same [license](https://duendesoftware.com/products/identityserver#pricing) +and [special offers](https://duendesoftware.com/specialoffers) apply. + +The Configuration API's source code is available [on +github](https://github.com/DuendeSoftware/IdentityServer/tree/main/src/Configuration). + +Samples of the Configuration API are available [here]({{< ref "/samples/configuration" >}}). \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/configuration/dcr/_index.md b/IdentityServer/v7/docs/content/configuration/dcr/_index.md new file mode 100644 index 00000000..3983b353 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/_index.md @@ -0,0 +1,16 @@ +--- +title: "Dynamic Client Registration" +weight: 95 +chapter: true +--- + +# Dynamic Client Registration +Dynamic Client Registration (DCR) is the process of registering OAuth clients +dynamically. The client provides information about itself and specifies its +desired configuration in an HTTP request to the configuration endpoint. The +endpoint will then create the necessary client configuration and return an HTTP +response describing the new client, if the request is authorized and valid. + +DCR eliminates the need for a manual registration process, making it more +efficient and less time-consuming to register new clients. + diff --git a/IdentityServer/v7/docs/content/configuration/dcr/authorization.md b/IdentityServer/v7/docs/content/configuration/dcr/authorization.md new file mode 100644 index 00000000..e670eb29 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/authorization.md @@ -0,0 +1,59 @@ +--- +title: Authorization +weight: 20 +--- + +You should consider your requirements and design authentication and +authorization policy for the Configuration API, if required. The specifications +that define DCR envision 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 in order 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 +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]({{< ref +"/reference/models/api_scope">}}) in your IdentityServer and allow the +appropriate clients to access it. An automated process running in a CI pipeline +could be configured as an OAuth client that uses the client credentials flow and +is allowed to request the "IdentityServer.Configuration" scope. It could obtain +a token using its client id and secret and then present that token when it calls +the Configuration API. You might also have an interactive web application with a +user interface that makes calls to the Configuration API. Again, you would +define the application as an OAuth client allowed to request the appropriate +scope, but this time, you'd use the authorization code flow. + +## Software Statement +The metadata within requests to the Configuration API can be bundled together +into a JWT and sent in the *software_statement* parameter. 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. + +In order 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. The configuration API doesn't make any assumptions about that +design. By default it does nothing with the *software_statement parameter*; to +make use of it, [customize]({{< ref "./customization#validation" >}}) the +*DynamicClientRegistrationValidator.ValidateSoftwareStatementAsync* extension +point. diff --git a/IdentityServer/v7/docs/content/configuration/dcr/calling-registration.md b/IdentityServer/v7/docs/content/configuration/dcr/calling-registration.md new file mode 100644 index 00000000..6dcbfa6a --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/calling-registration.md @@ -0,0 +1,8 @@ +--- +title: Calling the Registration Endpoint +weight: 20 +--- + +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]({{< ref "./reference/models.md#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 (the two grants we support) 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. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/configuration/dcr/customization.md b/IdentityServer/v7/docs/content/configuration/dcr/customization.md new file mode 100644 index 00000000..bd75dd73 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/customization.md @@ -0,0 +1,43 @@ +--- +title: Customization +weight: 50 +--- + +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 +To customize the validation process, you can either implement the *IDynamicClientRegistrationValidator* interface or extend from the default implementation of that interface, the *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. The steps are passed a context object containing 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 validation]({{< ref "./reference/validation">}}) + +## Processing +In a similar way, the request processor can be customized by implementing an *IDynamicClientRegistrationRequestProcessor* or by extending from the default *DynamicClientRegistrationRequestProcessor*. Again, the default request processor contains virtual methods that allow you to override a part of its functionality. + +For more details, see the [reference section on request processing]({{< ref "./reference/processing">}}) + +## Response Generation +Finally, to customize the HTTP responses of the Configuration API, you can implement the *IDynamicClientRegistrationResponseGenerator* or extend from the default *DynamicClientRegistrationResponseGenerator*. + +For more details, see the [reference section on response generation]({{< ref "./reference/response">}}) \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/configuration/dcr/installation.md b/IdentityServer/v7/docs/content/configuration/dcr/installation.md new file mode 100644 index 00000000..aaf5903d --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/installation.md @@ -0,0 +1,117 @@ +--- +title: Installation and Hosting +weight: 10 +--- + +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 +To host the configuration API separately from IdentityServer: + +#### Create a new empty web application +``` +dotnet new web -n Configuration +``` + +#### Add the Duende.IdentityServer.Configuration package +``` +cd Configuration +dotnet add package Duende.IdentityServer.Configuration +``` + +#### Configure Services +```cs +builder.Services.AddIdentityServerConfiguration(opt => + opt.LicenseKey = ""; +); +``` +The Configuration API feature is included in the IdentityServer Business edition +license and higher. Use the same license key for IdentityServer and the +Configuration API. + +#### Add and configure the store implementation + +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 built-in +Entity Framework based implementation, or implement the interface yourself. See +[the IClientConfigurationStore reference]({{< ref "./reference/store" >}}) for +more details. If you wish to use the built-in implementation, install its NuGet +package and add it to DI. + +``` +dotnet add package Duende.IdentityServer.Configuration.EntityFramework +``` + +```cs +builder.Services.AddIdentityServerConfiguration(opt => + opt.LicenseKey = "" +).AddClientConfigurationStore(); +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); +builder.Services.AddConfigurationDbContext(options => +{ + options.ConfigureDbContext = builder => builder.UseSqlite(connectionString); +}); +``` + +#### Map Configuration Endpoints +```cs +app.MapDynamicClientRegistration().RequireAuthorization("DCR"); +``` +*MapDynamicClientRegistration* registers the DCR endpoints and returns an +*IEndpointConventionBuilder* which you can use to define authorization +requirements for your DCR endpoint. See [Authorization]({{< ref +"./authorization" >}}) for more details. + +## Shared Host for Configuration API and IdentityServer +To host the configuration API in the same host as IdentityServer: + +#### Add the Duende.IdentityServer.Configuration package +``` +dotnet add package Duende.IdentityServer.Configuration +``` + +#### Add the Configuration API's services to the service collection: +```cs +builder.Services.AddIdentityServerConfiguration(); +``` + +#### Add and configure the store implementation + +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 built-in +Entity Framework-based implementation, or implement the interface yourself. See +[the IClientConfigurationStore reference]({{< ref "./reference/store" >}}) for +more details. If you wish to use the built-in implementation, install its NuGet +package and add it to DI. + +``` +dotnet add package Duende.IdentityServer.Configuration.EntityFramework +``` + +```cs +builder.Services.AddIdentityServerConfiguration(opt => + opt.LicenseKey = "" +).AddClientConfigurationStore(); +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); +builder.Services.AddConfigurationDbContext(options => +{ + options.ConfigureDbContext = builder => builder.UseSqlite(connectionString); +}); +``` +#### Map Configuration Endpoints: +```cs +app.MapDynamicClientRegistration().RequireAuthorization("DCR"); + +``` +*MapDynamicClientRegistration* registers the DCR endpoints and returns an +*IEndpointConventionBuilder* which you can use to define authorization +requirements for your DCR endpoint. See [Authorization]({{< ref +"./authorization" >}}) for more details. diff --git a/IdentityServer/v7/docs/content/configuration/dcr/reference/_index.md b/IdentityServer/v7/docs/content/configuration/dcr/reference/_index.md new file mode 100644 index 00000000..d3f075db --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/reference/_index.md @@ -0,0 +1,10 @@ ++++ +title = "Reference" +weight = 200 +chapter = true +description = "Dynamic Client Registration" ++++ + +# Reference + +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/configuration/dcr/reference/models.md b/IdentityServer/v7/docs/content/configuration/dcr/reference/models.md new file mode 100644 index 00000000..0fca1111 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/reference/models.md @@ -0,0 +1,152 @@ +--- +title: "Models" +description: "DCR Reference" +weight: 50 +--- + +## 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 +| name | description | +| --- | --- | +| AbsoluteRefreshTokenLifetime { get; set; } | The absolute lifetime of refresh tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | +| AccessTokenLifetime { get; set; } | The lifetime of access tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | +| AccessTokenType { get; set; } | The type of access tokens that this client will create. Either "Jwt" or "Reference". This property is an extension to the Dynamic Client Registration Protocol. | +| AllowedCorsOrigins { get; set; } | List of allowed CORS origins for JavaScript clients. This property is an extension to the Dynamic Client Registration Protocol. | +| AllowedIdentityTokenSigningAlgorithms { get; set; } | List of signing algorithms to use when signing identity tokens. If not set, will use the server default signing algorithm. This property is an extension to the Dynamic Client Registration Protocol. | +| AllowRememberConsent { get; set; } | Boolean value specifying whether a user's consent can be remembered in flows initiated by this client. This property is an extension to the Dynamic Client Registration Protocol. | +| AuthorizationCodeLifetime { get; set; } | The lifetime of authorization codes, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | +| BackChannelLogoutSessionRequired { get; set; } | Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout Token to identify the RP session with the OP when the backchannel_logout_uri is used. | +| BackChannelLogoutUri { get; set; } | RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. | +| ClientName { get; set; } | Human-readable string name of the client to be presented to the end-user during authorization. | +| ClientUri { get; set; } | Web page providing information about the client. | +| ConsentLifetime { get; set; } | The lifetime of consent, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | +| CoordinateLifetimeWithUserSession { get; set; } | When enabled, the client's token lifetimes (e.g. refresh tokens) will be tied to the user's session lifetime. This means when the user logs out, any revokable tokens will be removed. If using server-side sessions, expired sessions will also remove any revokable tokens, and backchannel logout will be triggered. This client's setting overrides the global CoordinateTokensWithUserSession configuration setting. This property is an extension to the Dynamic Client Registration Protocol. | +| DefaultMaxAge { get; set; } | Default maximum authentication age. This is stored as the UserSsoLifetime property of the IdentityServer client model. | +| EnableLocalLogin { get; set; } | Boolean value specifying if local logins are enabled when this client uses interactive flows. This property is an extension to the Dynamic Client Registration Protocol. | +| Extensions { get; set; } | Custom client metadata fields to include in the serialization. | +| FrontChannelLogoutSessionRequired { get; set; } | Boolean value specifying whether the RP requires that a sid (session ID) query parameter be included to identify the RP session with the OP when the frontchannel_logout_uri is used. | +| FrontChannelLogoutUri { get; set; } | RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. | +| GrantTypes { get; set; } | List of OAuth 2.0 grant type strings that the client can use at the token endpoint. Valid values are "authorization_code", "client_credentials", "refresh_token". | +| IdentityProviderRestrictions { get; set; } | List of external IdPs that can be used with this client. If list is empty all IdPs are allowed. Defaults to empty. This property is an extension to the Dynamic Client Registration Protocol. | +| IdentityTokenLifetime { get; set; } | The lifetime of identity tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | +| InitiateLoginUri { get; set; } | URI using the https scheme that a third party can use to initiate a login by the relying party. | +| Jwks { get; set; } | JWK Set document which contains the client's public keys. The *JwksUri* and *Jwks* parameters MUST NOT both be present in the same request or response. | +| JwksUri { get; set; } | URL to a JWK Set document which contains the client's public keys. The *JwksUri* and *Jwks* parameters MUST NOT both be present in the same request or response. The default validator must be extended to make use of the *JwksUri*. The default implementation ignores this property.| +| LogoUri { get; set; } | Logo for the client. If present, the server should display this image to the end-user during approval. | +| PostLogoutRedirectUris { get; set; } | List of post-logout redirection URIs for use in the end session endpoint. | +| RedirectUris { get; set; } | List of redirection URI strings for use in redirect-based flows such as the authorization code flow. Clients using flows with redirection must register their redirection URI values. | +| RefreshTokenExpiration { get; set; } | The type of expiration for refresh tokens. Either "sliding" or "absolute". This property is an extension to the Dynamic Client Registration Protocol. | +| RefreshTokenUsage { get; set; } | The usage type for refresh tokens. Either "OneTimeOnly" or "ReUse". This property is an extension to the Dynamic Client Registration Protocol. | +| RequireClientSecret { get; set; } | Boolean value specifying if a client secret is needed to request tokens at the token endpoint. This property is an extension to the Dynamic Client Registration Protocol. | +| RequireConsent { get; set; } | Boolean value specifying whether consent is required in user-centric flows initiated by this client. This property is an extension to the Dynamic Client Registration Protocol. | +| RequireSignedRequestObject { get; set; } | Boolean value specifying whether authorization requests must be protected as signed request objects and provided through either the request or request_uri parameters. | +| Scope { get; set; } | String containing a space-separated list of scope values that the client can use when requesting access tokens. If omitted, the configuration API will register a client with the scopes set by the *DynamicClientRegistrationValidator.SetDefaultScopes* method, which defaults to no scopes. | +| SlidingRefreshTokenLifetime { get; set; } | The sliding lifetime of refresh tokens, in seconds. This property is an extension to the Dynamic Client Registration Protocol. | +| SoftwareId { get; set; } | A unique identifier string (e.g., a Universally Unique Identifier (UUID)) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically registered. Unlike "client_id", which is issued by the authorization server and SHOULD vary between instances, the "software_id" SHOULD remain the same for all instances of the client software. The "software_id" SHOULD remain the same across multiple updates or versions of the same piece of software. The value of this field is not intended to be human readable and is usually opaque to the client and authorization server. The default validator must be extended to make use of the *SoftwareId*. The default implementation ignores this property. | +| SoftwareStatement { get; set; } | A software statement containing client metadata values about the client software as claims. This is a string value containing the entire signed JWT. The default validator must be extended to make use of the software statement. The default implementation ignores this property.| +| SoftwareVersion { get; set; } | A version identifier string for the client software identified by "software_id". The value of the "software_version" SHOULD change on any update to the client software identified by the same "software_id". The value of this field is intended to be compared using string equality matching and no other comparison semantics are defined by this specification. The default validator must be extended to make use of the *SoftwareVersion*. The default implementation ignores this property. | +| TokenEndpointAuthenticationMethod { get; set; } | Requested Client Authentication method for the Token Endpoint. The supported options are client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt. | +| UpdateAccessTokenClaimsOnRefresh { get; set; } | Boolean value specifying whether access token claims are updated during token refresh. This property is an extension to the Dynamic Client Registration Protocol. | + +## Dynamic Client Registration Response +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 +| 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 +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 +| 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 +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 +| 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 +#### 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 +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 +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 +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 +Represents a successfully validated dynamic client registration request. + +```csharp +public class DynamicClientRegistrationValidatedRequest : DynamicClientRegistrationValidationResult +``` + +## SuccessfulStep +Represents a successful validation step. + +```csharp +public class SuccessfulStep : IStepResult +``` diff --git a/IdentityServer/v7/docs/content/configuration/dcr/reference/options.md b/IdentityServer/v7/docs/content/configuration/dcr/reference/options.md new file mode 100644 index 00000000..6d97fc55 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/reference/options.md @@ -0,0 +1,32 @@ +--- +title: "Options" +weight: 60 +--- + +## IdentityServerConfigurationOptions + +Top level options for IdentityServer.Configuration. + +```csharp +public class IdentityServerConfigurationOptions +``` + +#### Public Members + +| name | description | +| --- | --- | +| [DynamicClientRegistration](#DynamicClientRegistrationOptions) { get; set; } | Options for Dynamic Client Registration | + +## DynamicClientRegistrationOptions + +Options for dynamic client registration. + +```csharp +public class DynamicClientRegistrationOptions +``` + +#### Public Members + +| 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). | diff --git a/IdentityServer/v7/docs/content/configuration/dcr/reference/processing.md b/IdentityServer/v7/docs/content/configuration/dcr/reference/processing.md new file mode 100644 index 00000000..4d32ba87 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/reference/processing.md @@ -0,0 +1,55 @@ +--- +title: "Request Processing" +weight: 20 +--- + +## 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]({{< ref +"./store.md" >}}) + +#### 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 +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 +Each of these virtual methods represents one step of request processing. +Each step is passed a [DynamicClientRegistrationContext]({{< ref +"./models.md#dynamicclientregistrationcontext" >}}) and returns a task +that returns an [*IStepResult*]({{< ref "./models.md#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 +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 more simply, 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. | \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/configuration/dcr/reference/response.md b/IdentityServer/v7/docs/content/configuration/dcr/reference/response.md new file mode 100644 index 00000000..8c0601f6 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/reference/response.md @@ -0,0 +1,35 @@ +--- +title: "Response Generation" +description: "DCR Reference" +weight: 40 +--- + +## IDynamicClientRegistrationResponseGenerator +The *IDynamicClientRegistrationResponseGenerator* interface defines the contract +for a service that generates dynamic client registration responses. + +```csharp +public interface IDynamicClientRegistrationResponseGenerator +``` + +#### 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 + +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 + +| name | description | +| --- | --- | +| SerializerOptions { get; set; } | The options used for serializing json in responses. | diff --git a/IdentityServer/v7/docs/content/configuration/dcr/reference/store.md b/IdentityServer/v7/docs/content/configuration/dcr/reference/store.md new file mode 100644 index 00000000..45e3e93c --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/reference/store.md @@ -0,0 +1,25 @@ +--- +title: "Store" +description: "DCR Reference" +weight: 30 +--- + +## IClientConfigurationStore + +The *IClientConfigurationStore* interface defines the contract for a service +that communication with the client configuration data store. It contains a +single *AddAsync* method. + +```csharp +public interface IClientConfigurationStore +``` + +#### Members + +| name | description | +| --- | --- | +| AddAsync(…) | Adds a client to the configuration store. | + +## 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. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/configuration/dcr/reference/validation.md b/IdentityServer/v7/docs/content/configuration/dcr/reference/validation.md new file mode 100644 index 00000000..7a8a08b9 --- /dev/null +++ b/IdentityServer/v7/docs/content/configuration/dcr/reference/validation.md @@ -0,0 +1,75 @@ +--- +title: "Validation" +description: "DCR Reference" +weight: 10 +--- + +## 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 + +Validates a dynamic client registration request. + +```csharp +public Task ValidateAsync( + DynamicClientRegistrationContext context) +``` + +| parameter | description | +| --- | --- | +| context | Contextual information about the DCR request. | + +#### Return Value + +A task that returns an [*IDynamicClientRegistrationValidationResult*]({{< ref "./models.md#idynamicclientregistrationvalidationresult" >}}), indicating success or failure. + +## 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 + +Each of these methods represents one step in the validation process. +Each step is passed a [*DynamicClientRegistrationContext*]({{< ref +"./models.md#dynamicclientregistrationcontext" >}}) and returns a task +that returns an [*IStepResult*]({{< ref "./models.md#istepresult" +>}}). The *DynamicClientRegistrationContext* includes the client model that will +have its properties set, the DCR request, and other contextual information. The +*IStepResult* either represents that the step succeeded or failed. + +The steps are invoked in the same order as they appear in this table. + +| name | description | +| --- | --- | +| ValidateSoftwareStatementAsync(…) | Validates the software statement of the request. The default implementation does nothing, and is included as an extension point. | +| SetGrantTypesAsync(…) | Validates requested grant types and uses them to set the allowed grant types of the client. | +| SetRedirectUrisAsync(…) | Validates requested redirect uris and uses them to set the redirect uris of the client. | +| SetScopesAsync(…) | Validates requested scopes and uses them to set the scopes of the client. | +| SetDefaultScopes(…) | Sets scopes on the client when no scopes are requested. The default implementation sets no scopes and is intended as an extension point. | +| SetSecretsAsync(…) | Validates the requested jwks to set the secrets of the client. | +| SetClientNameAsync(…) | Validates the requested client name uses it to set the name of the client. | +| SetLogoutParametersAsync(…) | Validates the requested client parameters related to logout and uses them to set the corresponding properties in the client. Those parameters include the post logout redirect uris, front channel and back channel uris, and flags for the front and back channel uris indicating if they require session ids. | +| SetMaxAgeAsync(…) | Validates the requested default max age and uses it to set the user sso lifetime of the client. | +| SetUserInterfaceProperties(…) | Validates details of the request that control the user interface, including the logo uri, client uri, initiate login uri, enable local login flag, and identity provider restrictions, and uses them to set the corresponding client properties. | +| SetPublicClientProperties(…) | Validates the requested client parameters related to public clients and uses them to set the corresponding properties in the client. Those parameters include the require client secret flag and the allowed cors origins. | +| SetAccessTokenProperties(…) | Validates the requested client parameters related to access tokens and uses them to set the corresponding properties in the client. Those parameters include the allowed access token type and access token lifetime. | +| SetIdTokenProperties(…) | Validates the requested client parameters related to id tokens and uses them to set the corresponding properties in the client. Those parameters include the id token lifetime and the allowed id token signing algorithms. | +| SetServerSideSessionProperties(…) | Validates the requested client parameters related to server side sessions and uses them to set the corresponding properties in the client. Those parameters include the coordinate lifetime with user session flag. | + diff --git a/IdentityServer/v7/docs/content/data/_index.md b/IdentityServer/v7/docs/content/data/_index.md new file mode 100644 index 00000000..aa989ee0 --- /dev/null +++ b/IdentityServer/v7/docs/content/data/_index.md @@ -0,0 +1,23 @@ ++++ +title = "Data Stores and Persistence" +weight = 80 +chapter = true ++++ + +# Data Stores and Persistence + +Duende IdentityServer is backed by two kinds of data: +* [Configuration Data]({{}}) +* [Operational Data]({{}}) + +Data access is abstracted by store interfaces that are registered in the DI system. +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]({{}}) implementations. + + +{{% notice note %}} +Given that data stores abstract the details of the data stored, strictly speaking, IdentityServer does not know or understand where the data is actually being stored. +As such, there is no built-in administrative tool to populate or manage this data. +There are third-party options (both commercial and FOSS) that provide an administrative UI for managing the data when using the EntityFramework Core implementations. +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/data/configuration.md b/IdentityServer/v7/docs/content/data/configuration.md new file mode 100644 index 00000000..530464c6 --- /dev/null +++ b/IdentityServer/v7/docs/content/data/configuration.md @@ -0,0 +1,90 @@ ++++ +title = "Configuration Data" +weight = 10 ++++ + +Configuration data models the information for [Clients]({{}}) and [Resources]({{}}). + +## Stores + +Store interfaces are designed to abstract accessing the configuration data. +The stores used in Duende IdentityServer are: +* [Client store]({{}}) for *Client* data. +* [CORS policy service]({{}}) for [CORS support]({{}}). 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]({{}}) for *IdentityResource*, *ApiResource*, and *ApiScope* data. +* [Identity Provider store]({{}}) for *IdentityProvider* data. + +## Registering Custom Stores + +Custom implementations of the stores must be registered in the DI system. +There are [convenience methods]({{}}) for registering these. +For example: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer() + .AddClientStore() + .AddCorsPolicyService() + .AddResourceStore() + .AddIdentityProviderStore(); +} +``` + +## Caching Configuration Data + +Configuration data is used frequently during request processing. +If this data is loaded from a database or other external store, then it might be expensive to frequently re-load the same data. + +Duende IdentityServer provides [convenience methods]({{}}) to enable caching data from the various stores. +The caching implementation relies upon an *ICache\* service and must also be added to DI. +For example: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer() + .AddClientStore() + .AddCorsPolicyService() + .AddResourceStore() + .AddInMemoryCaching() + .AddClientStoreCache() + .AddCorsPolicyCache() + .AddResourceStoreCache() + .AddIdentityProviderStoreCache(); +} +``` + +The duration of the data in the default cache is configurable on the [IdentityServerOptions]({{}}). +For example: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + 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 + +The various [in-memory configuration APIs]({{}}) 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. diff --git a/IdentityServer/v7/docs/content/data/ef.md b/IdentityServer/v7/docs/content/data/ef.md new file mode 100644 index 00000000..cd729e5f --- /dev/null +++ b/IdentityServer/v7/docs/content/data/ef.md @@ -0,0 +1,148 @@ +--- +title: "Entity Framework Integration" +weight: 50 +--- + +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 ASP.NET Identity integration. +It is called *Duende.IdentityServer.EntityFramework*. +You can install it with: + +``` +dotnet add package Duende.IdentityServer.EntityFramework +``` + +## Configuration Store Support +For storing [configuration data]({{}}), then 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, use the *AddConfigurationStore* extension method after the call to *AddIdentityServer*: + +```csharp +public IServiceProvider ConfigureServices(IServiceCollection services) +{ + const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; + var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; + + 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 +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 + +To enable caching for the EF configuration store implementation, use the *AddConfigurationStoreCache* extension method: + +```csharp +public IServiceProvider ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer() + .AddConfigurationStore(options => { ... }) + // this is something you will want in production to reduce load on and requests to the DB + .AddConfigurationStoreCache(); +} +``` + +## Operational Store +For storing [operational data]({{}}) 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, use the *AddOperationalStore* extension method after the call to *AddIdentityServer*: + +```csharp +public IServiceProvider ConfigureServices(IServiceCollection services) +{ + const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; + var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; + + 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 +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 will be automatically cleaned up from the database. The default is *false*. + +*RemoveConsumedTokens* [added in 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 in 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. + +{{% notice note %}} +The token cleanup feature does *not* remove persisted grants that are *consumed* (see [persisted grants]({{}})). It only removes persisted grants that are beyond their *Expiration*. +{{% /notice %}} + +## Database creation and schema changes across different versions of IdentityServer +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]({{}}) 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/IdentityServer/tree/main/migrations/IdentityServerDb) that we use internally for creating databases to test the latest database schema (this is SQL Server specific). diff --git a/IdentityServer/v7/docs/content/data/operational/_index.md b/IdentityServer/v7/docs/content/data/operational/_index.md new file mode 100644 index 00000000..46a463d9 --- /dev/null +++ b/IdentityServer/v7/docs/content/data/operational/_index.md @@ -0,0 +1,14 @@ ++++ +title = "Operational Data" +weight = 20 +chapter = true ++++ + +# Operational Data + +For certain operations, IdentityServer needs a persistence store to keep dynamically created state. +This data is collectively called *operational data*, and includes: + +* [Grants]({{}}) for authorization and device codes, reference and refresh tokens, and remembered user consent +* [Keys]({{}}) managing dynamically created signing keys +* [Server Side Sessions]({{}}) for storing authentication session data for interactive users server-side diff --git a/IdentityServer/v7/docs/content/data/operational/grants.md b/IdentityServer/v7/docs/content/data/operational/grants.md new file mode 100644 index 00000000..d685e4ca --- /dev/null +++ b/IdentityServer/v7/docs/content/data/operational/grants.md @@ -0,0 +1,46 @@ +--- +title: "Grants" +weight: 30 +--- + +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 + +The persistence for grants is abstracted behind two interfaces: +* The [persisted grant store]({{}}) is a common store for most grants. +* The [device flow store]({{}}) is a specialized store for device grants. + +## Registering Custom Stores + +Custom implementations of *IPersistedGrantStore*, and/or *IDeviceFlowStore* must be registered in the DI system. +For example: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer(); + + services.AddTransient(); + services.AddTransient(); +} +``` + +## 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]({{}}). + +## 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. With the exception of *ConsumedTime*, The other properties of the model should be treated as read-only. + +## Persisted Grant Service +Working with the grants store directly might be too low level. +As such, a higher level service called the [IPersistedGrantService]({{}}) is provided. +It abstracts and aggregates the different grant types into one concept, and allows querying and revoking the persisted grants for a user. diff --git a/IdentityServer/v7/docs/content/data/operational/keys.md b/IdentityServer/v7/docs/content/data/operational/keys.md new file mode 100644 index 00000000..d3a70a25 --- /dev/null +++ b/IdentityServer/v7/docs/content/data/operational/keys.md @@ -0,0 +1,35 @@ +--- +title: "Keys" +weight: 40 +--- + +The [automatic key management]({{}}) feature in Duende IdentityServer requires a store to persist keys that are dynamically created. + +## Signing Key Store +By default, the file system is used, but the storage of these keys is abstracted behind a extensible store interface. +The [ISigningKeyStore]({{}}) is that storage interface. + +## Registering a custom signing key store + +To register a custom signing key store in the DI container, there is a *AddSigningKeyStore* helper on the *IIdentityServerBuilder*. +For example: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer() + .AddSigningKeyStore(); +} +``` + +## 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]({{}}). +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 +The [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). + diff --git a/IdentityServer/v7/docs/content/data/operational/sessions.md b/IdentityServer/v7/docs/content/data/operational/sessions.md new file mode 100644 index 00000000..1181eb33 --- /dev/null +++ b/IdentityServer/v7/docs/content/data/operational/sessions.md @@ -0,0 +1,62 @@ +--- +title: "Server-Side Sessions" +description: "Data Store" +weight: 50 +--- + +(added in 6.1) + +The [server-side sessions]({{}}) feature in Duende IdentityServer requires a store to persist a user's session data. + +## Server-Side Session Store + +The [IServerSideSessionStore]({{}}) abstracts storing the server-side session data. +[ServerSideSession]({{}}) objects act as the storage entity, and provide several properties uses as metadata for the session. The *Ticket* property contains the actual serailized data used by the ASP.NET Cookie Authentication handler. + +The methods on the [IServerSideSessionStore]({{}}) are used to orchestrate the various management functions needed by the [server-side sessions]({{}}) feature. + +## Registering a custom store + +To register a custom server-side session store in the DI container, there is a *AddServerSideSessionStore* helper on the *IIdentityServerBuilder*. +It is still necessary to call *AddServerSideSessions* to enable the server-side session feature. +For example: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer() + .AddServerSideSessions() + .AddServerSideSessionStore(); +} +``` + +There is also an overloaded version of a *AddServerSideSessions* that will perform both registration steps in one call. +For example: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer() + .AddServerSideSessions(); +} +``` + +## EntityFramework store implementation + +An EntityFramework Core implementation of the server-side session store is included in the [Entity Framework Integration]({{}}) 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: + + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer() + .AddServerSideSessions() + .AddOperationalStore(options => + { + // ... + }); +} +``` diff --git a/IdentityServer/v7/docs/content/deployment/_index.md b/IdentityServer/v7/docs/content/deployment/_index.md new file mode 100644 index 00000000..98601489 --- /dev/null +++ b/IdentityServer/v7/docs/content/deployment/_index.md @@ -0,0 +1,25 @@ ++++ +title = "Deployment" +date = 2020-09-10T08:20:20+02:00 +weight = 100 +chapter = true ++++ + +# Deployment + +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. + +{{% notice note %}} +Our experience has been that these topics are very important. Some of our most common support requests are related to [Data Protection]({{}}) and [Load Balancing]({{}}), so we strongly encourage you to review those pages, along with the rest of this chapter before deploying IdentityServer to production. +{{% /notice %}} + +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/deployment/caching.md b/IdentityServer/v7/docs/content/deployment/caching.md new file mode 100644 index 00000000..af309fc0 --- /dev/null +++ b/IdentityServer/v7/docs/content/deployment/caching.md @@ -0,0 +1,14 @@ +--- +title: "Distributed Caching" +date: 2020-09-10T08:22:12+02:00 +weight: 40 +--- + +Some optional features rely on ASP.NET Core distributed caching: + +* [State data formatter for OpenID Connect]({{< ref "/ui/login/external#state-url-length-and-isecuredataformat" >}}) +* Replay cache (e.g. for [JWT client credentials]({{< ref "/tokens/authentication/jwt" >}})) +* [Device flow]({{< ref "reference/stores/device_flow_store" >}}) throttling service +* Authorization parameter store + +In order to work in a multi server environment, this needs to be set up correctly. Please consult the Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more details. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/deployment/data_protection.md b/IdentityServer/v7/docs/content/deployment/data_protection.md new file mode 100644 index 00000000..82083789 --- /dev/null +++ b/IdentityServer/v7/docs/content/deployment/data_protection.md @@ -0,0 +1,57 @@ +--- +title: "ASP.NET Core Data Protection" +date: 2020-09-10T08:22:12+02:00 +weight: 20 +--- + +Duende IdentityServer makes extensive use of ASP.NET's [data protection](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/) feature. It is crucial that you configure data protection correctly before you start using your IdentityServer in production. + +In local development, ASP.NET automatically creates data protection keys, but in a deployed environment, you will need to ensure that your data protection keys are stored in a persistent way and shared across all load balanced instances of your IdentityServer implementation. This means you'll need to choose where to store and how to protect the data protection keys, as appropriate for your environment. Microsoft has extensive documentation [here](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview) describing how to configure storage and protection of data protection keys. + +A typical IdentityServer implementation should include data protection configuration code, like this: + +```cs +builder.Services.AddDataProtection() + // Choose an extension method for key persistence, such as + // PersistKeysToFileSystem, PersistKeysToDbContext, + // PersistKeysToAzureBlobStorage, or PersistKeysToAWSSystemsManager + .PersistKeysToFoo() + // Choose an extension method for key protection, such as + // ProtectKeysWithCertificate, ProtectKeysWithAzureKeyVault + .ProtectKeysWithBar() + // Explicitly set an application name to prevent issues with + // key isolation. + .SetApplicationName("IdentityServer"); +``` + +## Data Protection Keys and IdentityServer's Signing Keys + +ASP.NET's data protection keys are sometimes confused with IdentityServer's signing keys, but the two are completely separate keys with different purposes. IdentityServer implementations need both to function correctly. + +#### Data Protection Keys +Data protection is a cryptographic library that is part of the ASP.NET framework. Data protection uses private key cryptography to encrypt and sign sensitive data to ensure that it is only written and read by the application. The framework uses data protection to secure data that is commonly used by IdentityServer implementations, such as authentication cookies and anti-forgery tokens. In addition, IdentityServer itself uses data protection to protect sensitive data at rest, such as persisted grants, as well as sensitive data passed through the browser, such as the context objects passed to pages in the UI. The data protection keys are critical secrets for an IdentityServer implementation because they encrypt a great deal of sensitive data at rest and prevent sensitive data that is round-tripped through the browser from being tampered with. + +#### IdentityServer Signing Key +Separately, IdentityServer needs cryptographic keys, called [signing keys]({{}}), 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]({{}}). The private key component of the signing keys are also critical secrets for IdentityServer because a valid signature provides integrity and non-repudiation guarantees that allow client applications and APIs to trust those tokens. + +## Common Problems +Common data protection problems occur when data is protected with a key that is not available when the data is later read. A common symptom is *CryptographicException*s in the IdentityServer logs. For example, when automatic key management fails to read its signing keys due to a data protection failure, IdentityServer will log an error message such as "Error unprotecting key with kid {Signing Key ID}.", and log the underlying *System.Security.Cryptography.CryptographicException*, with a message like "The key {Data Protection Key ID} was not found in the key ring." + +Failures to read automatic signing keys are often the first place where a data protection problem manifests, but any of many places where IdentityServer and ASP.NET use data protection might also throw *CryptographicException*s. + +There are several ways that data protection problems can occur: + +1. In load balanced environments, every instance of IdentityServer needs to be configured to share data protection keys. Without shared data protection keys, each load balanced instance will only be able to read the data that it writes. +2. Data protected data could be generated in a development environment and then accidentally included into the build output. This is most commonly the case for automatically managed signing keys that are stored on disk. If you are using automatic signing key management with the default file system based key store, you should exclude the *~/keys* directory from source control and make sure keys are not included in your builds. Note that if you are using our Entity Framework based implementation of the operational data stores, then the keys will instead be stored in the database. +3. Data protection creates keys isolated by application name. If you don't specify a name, the content root path of the application will be used. But, beginning in .NET 6.0 Microsoft changed how they handle the path, which can cause data protection keys to break. Their docs on the problem are [here](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview#setapplicationname), including a work-around where you de-normalize the path. Then, in .NET 7.0, this change was reverted. The solution is always to specify an explicit application name, and if you have old keys that were generated without an explicit application name, you need to set your application name to match the default behavior that produced the keys you want to be able to read. +4. If your IdentityServer is hosted by IIS, special configuration is needed for data protection. In most default deployments, IIS lacks the permissions required to persist data protection keys, and falls back to using an ephemeral key generated every time the site starts up. Microsoft's docs on this issue are [here](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/advanced?view=aspnetcore-7.0#data-protection). + +## Identity Server's Usage of Data Protection +Duende IdentityServer's features that rely on data protection include + +* protecting signing keys at rest (if [automatic key management]({{< ref "/fundamentals/keys/automatic_key_management" >}}) is used and enabled) +* protecting [persisted grants]({{< ref "/data/operational/grants" >}}) at rest (if enabled) +* protecting [server-side session]({{< ref "/ui/server_side_sessions" >}}) data at rest (if enabled) +* protecting [the state parameter]({{< ref "/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]({{< ref "/ui/logout/logout_context" >}}) and [error context]({{< ref "/ui/error" >}})). +* session management (because the ASP.NET Core cookie authentication handler requires it) \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/deployment/data_stores.md b/IdentityServer/v7/docs/content/deployment/data_stores.md new file mode 100644 index 00000000..6154b99b --- /dev/null +++ b/IdentityServer/v7/docs/content/deployment/data_stores.md @@ -0,0 +1,29 @@ +--- +title: "IdentityServer Data Stores" +description: "Deployment" +date: 2020-09-10T08:22:12+02:00 +weight: 30 +--- + +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 +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 +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]({{< ref "/data" >}}) section for more information. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/deployment/health_checks.md b/IdentityServer/v7/docs/content/deployment/health_checks.md new file mode 100644 index 00000000..a74d87c4 --- /dev/null +++ b/IdentityServer/v7/docs/content/deployment/health_checks.md @@ -0,0 +1,87 @@ +--- +title: "Health Checks" +weight: 50 +--- +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*. + +``` +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*. + +``` +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); + } +} +``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/deployment/proxies.md b/IdentityServer/v7/docs/content/deployment/proxies.md new file mode 100644 index 00000000..3bf41c00 --- /dev/null +++ b/IdentityServer/v7/docs/content/deployment/proxies.md @@ -0,0 +1,26 @@ +--- +title: "Proxy Servers and Load Balancers" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +--- + +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 +- 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 middlware 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 proxing going on, or if your proxy has a stable IP address. + +Please consult the Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer) for more details. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/diagnostics/_index.md b/IdentityServer/v7/docs/content/diagnostics/_index.md new file mode 100644 index 00000000..8c37c79e --- /dev/null +++ b/IdentityServer/v7/docs/content/diagnostics/_index.md @@ -0,0 +1,11 @@ ++++ +title = "Diagnostics" +description = "Overview" +date = 2020-09-10T08:20:20+02:00 +weight = 90 +chapter = true ++++ + +# Diagnostics + +{{%children style="h4" /%}} diff --git a/IdentityServer/v7/docs/content/diagnostics/events.md b/IdentityServer/v7/docs/content/diagnostics/events.md new file mode 100644 index 00000000..aee7065d --- /dev/null +++ b/IdentityServer/v7/docs/content/diagnostics/events.md @@ -0,0 +1,150 @@ +--- +title: "Events" +date: 2020-09-10T08:22:12+02:00 +weight: 20 +--- + +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 +Events are not turned on by default - but can be globally configured in the *ConfigureServices* method, e.g.: + +```cs +services.AddIdentityServer(options => +{ + options.Events.RaiseSuccessEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseErrorEvents = true; +}); +``` + +To emit an event use the *IEventService* from the DI container and call the *RaiseAsync* method, e.g.: + +```cs +public async Task Login(LoginInputModel model) +{ + if (_users.ValidateCredentials(model.Username, model.Password)) + { + // issue authentication cookie with subject ID and username + var user = _users.FindByUsername(model.Username); + await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); + } + else + { + await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); + } +} +``` + +### Custom sinks +Our default event sink will simply 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 DI. + +The following example uses [Seq](https://getseq.net) to emit events: + +```cs +public class SeqEventSink : IEventSink +{ + private readonly Logger _log; + + public SeqEventSink() + { + _log = new LoggerConfiguration() + .WriteTo.Seq("http://localhost:5341") + .CreateLogger(); + } + + public Task PersistAsync(Event evt) + { + if (evt.EventType == EventTypes.Success || + evt.EventType == EventTypes.Information) + { + _log.Information("{Name} ({Id}), Details: {@details}", + evt.Name, + evt.Id, + evt); + } + else + { + _log.Error("{Name} ({Id}), Details: {@details}", + evt.Name, + evt.Id, + evt); + } + + return Task.CompletedTask; + } +} +``` + +Add the *Serilog.Sinks.Seq* package to your host to make the above code work. + +## Built-in events +The following events are defined in IdentityServer: + +* ***ApiAuthenticationFailureEvent*** & ***ApiAuthenticationSuccessEvent*** + + Gets raised for successful/failed API authentication at the introspection endpoint. + +* ***ClientAuthenticationSuccessEvent*** & ***ClientAuthenticationFailureEvent*** + + Gets raised for successful/failed client authentication at the token endpoint. + +* ***TokenIssuedSuccessEvent*** & ***TokenIssuedFailureEvent*** + + Gets raised for successful/failed attempts to request identity tokens, access tokens, refresh tokens and authorization codes. + +* ***TokenIntrospectionSuccessEvent*** & ***TokenIntrospectionFailureEvent*** + + Gets raised for successful token introspection requests. + +* ***TokenRevokedSuccessEvent*** + + Gets raised for successful token revocation requests. + +* ***UserLoginSuccessEvent*** & ***UserLoginFailureEvent*** + + Gets raised by the quickstart UI for successful/failed user logins. + +* ***UserLogoutSuccessEvent*** + + Gets raised for successful logout requests. + +* ***ConsentGrantedEvent*** & ***ConsentDeniedEvent*** + + Gets raised in the consent UI. + +* ***UnhandledExceptionEvent*** + + Gets raised for unhandled exceptions. + +* ***DeviceAuthorizationFailureEvent*** & ***DeviceAuthorizationSuccessEvent*** + + Gets raised for successful/failed device authorization requests. + +### Custom events +You can create your own events and emit them via our infrastructure. + +You need to derive from our base *Event* class which injects contextual information like activity ID, timestamp, etc. +Your derived class can then add arbitrary data fields specific to the event context:: + +```cs +public class UserLoginFailureEvent : Event +{ + public UserLoginFailureEvent(string username, string error) + : base(EventCategories.Authentication, + "User Login Failure", + EventTypes.Failure, + EventIds.UserLoginFailure, + error) + { + Username = username; + } + + public string Username { get; set; } +} +``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/diagnostics/images/otel_disco.png b/IdentityServer/v7/docs/content/diagnostics/images/otel_disco.png new file mode 100644 index 00000000..5c3ca9e1 Binary files /dev/null and b/IdentityServer/v7/docs/content/diagnostics/images/otel_disco.png differ diff --git a/IdentityServer/v7/docs/content/diagnostics/images/otel_flow_1.png b/IdentityServer/v7/docs/content/diagnostics/images/otel_flow_1.png new file mode 100644 index 00000000..08da8ce5 Binary files /dev/null and b/IdentityServer/v7/docs/content/diagnostics/images/otel_flow_1.png differ diff --git a/IdentityServer/v7/docs/content/diagnostics/images/otel_flow_2.png b/IdentityServer/v7/docs/content/diagnostics/images/otel_flow_2.png new file mode 100644 index 00000000..66309a11 Binary files /dev/null and b/IdentityServer/v7/docs/content/diagnostics/images/otel_flow_2.png differ diff --git a/IdentityServer/v7/docs/content/diagnostics/logging.md b/IdentityServer/v7/docs/content/diagnostics/logging.md new file mode 100644 index 00000000..f6feb8df --- /dev/null +++ b/IdentityServer/v7/docs/content/diagnostics/logging.md @@ -0,0 +1,86 @@ +--- +title: "Logging" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +--- + +Duende IdentityServer uses the standard logging facilities provided by ASP.NET Core. You don't need to do any extra configuration. + +The Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging) has a good intro and a description of the built-in logging providers. + +We are roughly following the Microsoft guidelines for usage of log levels: + +* ***Trace*** + + For information that is valuable only to a developer troubleshooting an issue. These messages may contain sensitive application data like tokens and should not be enabled in a production environment. + +* ***Debug*** + + For following the internal flow and understanding why certain decisions are made. Has short-term usefulness during development and debugging. + +* ***Information*** + + For tracking the general flow of the application. These logs typically have some long-term value. + +* ***Warning*** + + For abnormal or unexpected events in the application flow. These may include errors or other conditions that do not cause the application to stop, but which may need to be investigated. + +* ***Error*** + + For errors and exceptions that cannot be handled. Examples: failed validation of a protocol request. + +* ***Critical*** + + For failures that require immediate attention. Examples: missing store implementation, invalid key material... + +{{% notice note %}} +In production, logging might produce too much data. It is recommended you either turn it off, or default to the *Warning* level. Have a look at [events]({{< ref "/diagnostics/events" >}}) for more high-level production instrumentation. +{{% /notice %}} + +### Setup for Serilog +We personally like [Serilog](https://serilog.net) and the [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) package a lot. Give it a try: + +```cs + public class Program + { + public static int Main(string[] args) + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) + .CreateLogger(); + + try + { + Log.Information("Starting host..."); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly."); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .UseSerilog() + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/diagnostics/otel.md b/IdentityServer/v7/docs/content/diagnostics/otel.md new file mode 100644 index 00000000..a472b475 --- /dev/null +++ b/IdentityServer/v7/docs/content/diagnostics/otel.md @@ -0,0 +1,87 @@ +--- +title: "OpenTelemetry" +date: 2020-09-10T08:22:12+02:00 +weight: 30 +--- + +(added in v6.1) + +[OpenTelemetry](https://opentelemetry.io) is a collection of tools, APIs, and SDKs for generating and collecting telemetry data (metrics, logs, and traces). This is very useful for analyzing software performance and behavior - especially in highly distributed systems. + +Now that the tracing part of OTel is finalized, we started adding instrumentation to all relevant parts of IdentityServer - especially around input validators, response generators and stores. + +The output is very useful for visualizing the control flow and finding performance bottlenecks. + +Here's e.g. the output for a request to the discovery endpoint: + +![](../images/otel_disco.png) + +When multiple applications send their traces to the same OTel 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: + +![](../images/otel_flow_1.png) + +...and then contacting the userinfo endpoint: + +![](../images/otel_flow_2.png) + +*The above screenshots are from https://www.honeycomb.io.* + +### Setup +To start emitting Otel tracing information you need + +* add the Otel libraries to your IdentityServer and client applications +* start collecting traces from the various IdentityServer sources (and other sources e.g. ASP.NET Core) + +```cs +builder.Services.AddOpenTelemetryTracing(builder => +{ + builder + .AddSource(IdentityServerConstants.Tracing.Basic) + .AddSource(IdentityServerConstants.Tracing.Cache) + .AddSource(IdentityServerConstants.Tracing.Services) + .AddSource(IdentityServerConstants.Tracing.Stores) + .AddSource(IdentityServerConstants.Tracing.Validation) + + .SetResourceBuilder( + ResourceBuilder.CreateDefault() + .AddService("MyIdentityServerHost")) + .AddHttpClientInstrumentation() + .AddAspNetCoreInstrumentation() + .AddSqlClientInstrumentation() + .AddOtlpExporter(option => + { + // wire up OTel server + }); +}); +``` + +This [sample]({{< ref "/samples/diagnostics#opentelemetry-support" >}}) uses the console exporter and can be used as a starting point. + +### 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 \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/fundamentals/_index.md b/IdentityServer/v7/docs/content/fundamentals/_index.md new file mode 100644 index 00000000..d8e156fb --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/_index.md @@ -0,0 +1,9 @@ ++++ +title = "Fundamentals" +weight = 20 +chapter = true ++++ + +# Fundamentals + +{{%children style="h4" /%}} diff --git a/IdentityServer/v7/docs/content/fundamentals/claims.md b/IdentityServer/v7/docs/content/fundamentals/claims.md new file mode 100644 index 00000000..90416e89 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/claims.md @@ -0,0 +1,112 @@ +--- +title: "Claims" +date: 2020-09-10T08:22:12+02:00 +weight: 45 +--- + +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 +User claims can be emitted in both identity and access tokens and in the [userinfo endpoint]({{< ref "/reference/endpoints/userinfo" >}}). The central extensibility point to implement to emit claims is called the [profile service]({{< ref "/reference/services/profile_service" >}}). The profile service is responsible for both gathering claim data and deciding which claims should be emitted. + +Whenever IdentityServer needs the claims for a user, it invokes the registered profile service with a [context]({{< ref "/reference/services/profile_service#duendeidentityservermodelsprofiledatarequestcontext" >}}) that presents detailed information about the current request, including + +* the client that is making the request +* the identity of the user +* the type of the request (access token, id token, or userinfo) +* the requested claim types, which are the claims types associated with requested scopes and resources + +### Strategies for Emitting Claims +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 +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]({{< ref "/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]({{< ref "/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]({{< ref "/fundamentals/resources/api_resources" >}}) by including the resource indicator parameter for the *ApiResource* requests the claims associated with the *ApiResource* in its *UserClaims* collection. + +The *RequestedClaimTypes* property of the *ProfileDataRequestContext* contains the collection of claims requested by the client. + +If your profile service extends the *DefaultProfileService*, you can use its *AddRequestedClaims* method to add only requested and approved claims. The intent is that your profile service can retrieve claim data and then filter that claim data based on what was requested by the client. For example: + +```cs +public class SampleProfileService : DefaultProfileService +{ + public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context) + { + var claims = await GetClaimsAsync(context); + + context.AddRequestedClaims(claims); + } + + + private async Task GetClaimsAsync(ProfileDataRequestContext context) + { + // Your implementation that retrieves claims goes here + } +} +``` + +#### Always emit claims +We generally recommend emitting claims based on the requested claim types, as that respects the scopes and resources requested by the client and gives the end user an opportunity to consent to this sharing of information. However, if you have claims that don't need to follow such rules, such as claims that are an integral part of the user's identity and that are needed in most scenarios, they can be added by directly updating the *context.IssuedClaims* collection. For example: + +```cs +public class SampleProfileService : DefaultProfileService +{ + public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context) + { + var claims = await GetClaimsAsync(context); + context.IssuedClaims.AddRange(claims); + } + + + private async Task GetClaimsAsync(ProfileDataRequestContext context) + { + // Your implementation that retrieves claims goes here + } +} +``` + +#### Emit claims based on the user or client identity +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 +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]({{< ref "/reference/endpoints/userinfo" >}}), the *Subject* property will not contain the principal issued during user sign-in, since userinfo calls don't happen as part of a session. Instead, the *Subject* property will contain a claims principal populated with the claims in the access token used to authorize the userinfo call. You can check the caller of the profile service by querying the *Caller* property on the context. + +## Client claims +Client claims are a set of pre-defined claims that are emitted in access tokens. They are defined on a per-client basis, meaning that each client can have its own unique set of client claims. The following shows an example of a client that is associated with a certain customer in your system: + +```cs +var client = new Client +{ + ClientId = "client", + + // rest omitted + + Claims = + { + new ClientClaim("customer_id", "123") + } +}; +``` + +To avoid accidental collision with user claims, client claims are prefixed with *client_*. For example, the above `ClientClaim` would be emitted as the *client_customer_id* claim type in access tokens. You can change or remove this prefix by setting the *ClientClaimsPrefix* on the [client definition]({{< ref "/reference/models/client#token" >}}). + +{{% notice note %}} +By default, client claims are only sent in the client credentials flow. If you want to enable them for other flows, you need to set the *AlwaysSendClientClaims* property on the client definition. +{{% /notice %}} + +### 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]({{< ref "/data" >}}) implementation), or using a [custom token request validator]({{< ref "/tokens/dynamic_validation" >}}). + + +## Claim Serialization +Claim values are serialized based on the *ClaimValueType* of the claim. Claims that don't specify a ClaimValueType are simply 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*. diff --git a/IdentityServer/v7/docs/content/fundamentals/clients.md b/IdentityServer/v7/docs/content/fundamentals/clients.md new file mode 100644 index 00000000..99e594a5 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/clients.md @@ -0,0 +1,91 @@ +--- +title: "Clients" +description: "Fundamentals" +date: 2020-09-10T08:22:12+02:00 +weight: 30 +--- + +[Clients]({{< ref "/overview/terminology#client" >}}) represent applications that can request tokens from your IdentityServer. + +The details vary, but you typically define the following common settings for a client: + +* a unique client ID +* a secret if needed +* the allowed interactions with the token service (called a grant type) +* a network location where identity and/or access token gets sent to (called a redirect URI) +* a list of scopes (aka resources) the client is allowed to access + +## Defining a client for server to server communication +In this scenario no interactive user is present - a service (i.e. the client) wants to communicate with an API (i.e. the resource that supports the scope): + +```cs +public class Clients +{ + public static IEnumerable Get() + { + return new List + { + new Client + { + ClientId = "service.client", + ClientSecrets = { new Secret("secret".Sha256()) }, + + AllowedGrantTypes = GrantTypes.ClientCredentials, + AllowedScopes = { "api1", "api2.read_only" } + } + }; + } +} +``` + +## Defining an interactive application for use authentication and delegated API access +Interactive applications (e.g. web applications or native desktop/mobile) applications use the authorization code flow. +This flow gives you the best security because the access tokens are transmitted via back-channel calls only (and gives you access to refresh tokens): + +```cs +var interactiveClient = new Client +{ + ClientId = "interactive", + + AllowedGrantTypes = GrantTypes.Code, + AllowOfflineAccess = true, + ClientSecrets = { new Secret("secret".Sha256()) }, + + RedirectUris = { "http://localhost:21402/signin-oidc" }, + PostLogoutRedirectUris = { "http://localhost:21402/" }, + FrontChannelLogoutUri = "http://localhost:21402/signout-oidc", + + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Email, + + "api1", "api2.read_only" + }, +}; +``` + +## Defining clients in appsettings.json +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: + +```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: + +```cs +AddInMemoryClients(configuration.GetSection("IdentityServer:Clients")) +``` diff --git a/IdentityServer/v7/docs/content/fundamentals/hosting.md b/IdentityServer/v7/docs/content/fundamentals/hosting.md new file mode 100644 index 00000000..455f362c --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/hosting.md @@ -0,0 +1,62 @@ +--- +title: "Hosting" +weight: 10 +--- + +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. + +{{% notice note %}} +While technically you could share the ASP.NET Core host between Duende IdentityServer, clients or APIs. We recommend putting your IdentityServer into a separate application. +{{% /notice %}} + +## DI system +You add the necessary services to the DI system by calling *AddIdentityServer* in your startup class: + +```cs +public void ConfigureServices(IServiceCollection services) +{ + var builder = services.AddIdentityServer(options => { ... }); +} +``` + +Many of the fundamental configuration settings can be set on the options. See the *[IdentityServerOptions]({{< ref "/reference/options" >}})* reference for more details. + +The builder object has a number of extension methods to add additional services to DI. +You can see the full list in the [reference]({{< ref "/reference/di" >}}) section, but very commonly you start by adding the configuration stores for clients and resources, e.g.: + +```cs +var 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]({{< ref "/data" >}}) for more information. + +## Pipeline +You need to add the Duende IdentityServer middleware to the pipeline by calling *UseIdentityServer*. + +Since ordering is important in the pipeline, you typically want to put the IdentityServer middleware after the static files, but before the UI framework like MVC. + +This would be a very typical minimal pipeline: + +```cs +public void Configure(IApplicationBuilder app) +{ + app.UseStaticFiles(); + + app.UseRouting(); + app.UseIdentityServer(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + }); +} +``` + +{{% notice note %}} +*UseIdentityServer* includes a call to *UseAuthentication*, so it’s not necessary to have both. +{{% /notice %}} + diff --git a/IdentityServer/v7/docs/content/fundamentals/keys/_index.md b/IdentityServer/v7/docs/content/fundamentals/keys/_index.md new file mode 100644 index 00000000..5593df88 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/keys/_index.md @@ -0,0 +1,18 @@ +--- +title: "Key Management" +date: 2020-09-10T08:22:12+02:00 +weight: 50 +--- + +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. + +{{%children style="h4" /%}} + diff --git a/IdentityServer/v7/docs/content/fundamentals/keys/automatic_key_management.md b/IdentityServer/v7/docs/content/fundamentals/keys/automatic_key_management.md new file mode 100644 index 00000000..fac46b0a --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/keys/automatic_key_management.md @@ -0,0 +1,134 @@ +--- +title: "Automatic Key Management" +date: 2022-11-21T11:24:12-05:00 +weight: 50 +--- + +Duende IdentityServer can manage signing keys for you using the Automatic +Key Management feature. + +Automatic Key Management follows best practices for handling signing key +material, including + +* automatic rotation of keys +* secure storage of keys at rest using data protection +* announcement of upcoming new keys +* maintenance of retired keys + +Automatic Key Management is included in [IdentityServer](https://duendesoftware.com/products/identityserver) Business Edition or higher. + +### Configuration +Automatic Key Management is configured by the options in the *KeyManagement* +property on the [*IdentityServerOptions*]({{< ref +"/reference/options#key-management" >}}). + +### Managed Key Lifecycle +Keys created by Automatic Key Management move through several phases. First, new +keys are announced, that is, they are added to the list of keys in discovery, +but not yet used for signing. After a configurable amount of *PropagationTime*, +keys are promoted to be signing credentials, and will be used by IdentityServer +to sign tokens. Eventually, enough time will pass that the key is older than the +configurable *RotationTime*, at which point the key is retired, but kept in +discovery for a configurable *RetentionDuration*. After the *RetentionDuration* +has passed, keys are removed from discovery, and optionally deleted. + +The default is to rotate keys every 90 days, announce new keys with 14 days of +propagation time, retain old keys for a duration of 14 days, and to delete keys +when they are retired. All of these options are configurable in the +*KeyManagement* options. For example: + +```cs +var 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 +Automatic Key Management stores keys through the abstraction of the +[ISigningKeyStore]({{}}). 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]({{}}) 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*. + +```cs +var builder = services.AddIdentityServer(options => +{ + // set path to store keys + options.KeyManagement.KeyPath = "/home/shared/keys"; +}); +``` + +### 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]({{< ref "/deployment" >}}) section for more information +about setting up data protection. + +### 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. + +```cs +options.KeyManagement.SigningAlgorithms = new[] +{ + // RS256 for older clients (with additional X.509 wrapping) + new SigningAlgorithmOptions(SecurityAlgorithms.RsaSha256) { UseX509Certificate = true }, + + // PS256 + new SigningAlgorithmOptions(SecurityAlgorithms.RsaSsaPssSha256), + + // ES256 + new SigningAlgorithmOptions(SecurityAlgorithms.EcdsaSha256) +}; +``` + +{{% notice note %}} + +When you register multiple signing algorithms, the first in the list will be the +default used for signing tokens. Client and API resource definitions both have +an *AllowedTokenSigningAlgorithm* property to override the default on a per +resource and client basis. + +{{% /notice %}} + + + + + + + + + diff --git a/IdentityServer/v7/docs/content/fundamentals/keys/migration.md b/IdentityServer/v7/docs/content/fundamentals/keys/migration.md new file mode 100644 index 00000000..fc7fe6dd --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/keys/migration.md @@ -0,0 +1,78 @@ +--- +title: "Migrating from Static Keys to Automatic Key Management" +date: 2022-11-21T11:24:12-05:00 +weight: 50 +--- + +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]({{}}). 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 + +First, enable automatic key management while continuing to register your old key +as the signing credential. In this phase, the new automatically managed key will be +announced so that as client apps and APIs update their caches, they get the new +key. IdentityServer will continue to sign keys with your old static key. + +```cs +var builder = services.AddIdentityServer(options => +{ + options.KeyManagement.Enabled = true; +}); + +var oldKey = LoadOldKeyFromVault(); +builder.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 + +Next, switch to using the new automatically managed keys for signing, but still +keep the old key for validation purposes. + +```cs +var builder = services.AddIdentityServer(options => +{ + options.KeyManagement.Enabled = true; +}); + +var oldKey = LoadOldKeyFromVault(); +builder.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 +Now the static key configuration can be removed entirely. + +```cs +var builder = services.AddIdentityServer(options => +{ + options.KeyManagement.Enabled = true; +}); +``` diff --git a/IdentityServer/v7/docs/content/fundamentals/keys/static_key_management.md b/IdentityServer/v7/docs/content/fundamentals/keys/static_key_management.md new file mode 100644 index 00000000..40ba11e7 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/keys/static_key_management.md @@ -0,0 +1,161 @@ +--- +title: "Manual Key Management" +date: 2022-11-21T11:24:12-05:00 +weight: 50 +--- + +Instead of using [Automatic Key Management]({{< ref "automatic_key_management.md" >}}), IdentityServer's signing keys can be set +manually. Automatic Key Management is generally recommended, but if you want to +explicitly control your keys statically, or you have a license that does not +include the feature (e.g. the Starter Edition), you will need to manually manage +your keys. With static configuration you are responsible for secure storage, +loading and rotation of keys. + +## Disabling Key Management +The automatic key management feature can be disabled by setting the *Enabled* +flag to *false* on the the *KeyManagement* property of +[*IdentityServerOptions*]({{< ref "/reference/options#key-management" >}}): + +```cs +var builder = services.AddIdentityServer(options => +{ + options.KeyManagement.Enabled = false; +}); +``` +## Key Creation +Without automatic key management, you are responsible for creating your own +cryptographic keys. Such keys can be created with many tools. Some options +include: + +- Use the PowerShell commandlet + [New-SelfSignedCertificate](https://learn.microsoft.com/en-us/powershell/module/pki/new-selfsignedcertificate?view=windowsserver2022-ps) to self-sign your own certificate +- Create certificates using [Azure KeyVault](https://learn.microsoft.com/en-us/azure/key-vault/certificates/certificate-scenarios) +- Create certificates using your Public Key Infrastructure. + +## Adding Keys +Signing keys are added with the [*AddSigningCredential*]({{< ref +"/reference/di#signing-keys" >}}) configuration method: + +```cs +var builder = services.AddIdentityServer(); +var key = LoadKeyFromVault(); // (Your code here) +builder.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 +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. + +## Manual Key Rotation {#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 +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 of the following are true: +- You have control over the deployment of all of 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 +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 + +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. + +```cs +var builder = services.AddIdentityServer(options => +{ + options.KeyManagement.Enabled = false; +}); + +var oldKey = LoadOldKeyFromVault(); +var newKey = LoadNewKeyFromVault(); +builder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); +builder.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 + +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 simply to swap +the signing credential and validation key. + +```cs +var builder = services.AddIdentityServer(options => +{ + options.KeyManagement.Enabled = false; +}); + +var oldKey = LoadOldKeyFromVault(); +var newKey = LoadNewKeyFromVault(); +builder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); +builder.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 + +Once enough time has passed that there are no unexpired tokens signed with the +old key, it is safe to completely remove the old key. + +```cs +var builder = services.AddIdentityServer(options => +{ + options.KeyManagement.Enabled = false; +}); + +var newKey = LoadNewKeyFromVault(); +builder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); +``` + diff --git a/IdentityServer/v7/docs/content/fundamentals/license_key.md b/IdentityServer/v7/docs/content/fundamentals/license_key.md new file mode 100644 index 00000000..fec27000 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/license_key.md @@ -0,0 +1,49 @@ +--- +title: "License Key" +weight: 60 +--- + +When deploying your IdentityServer to production, you will need to configure your license key. +This can be configured in one of two ways: +* Via a well-known file on the file system +* Programmatically in your startup code + +## File System + +Duende IdentityServer will look for a file called *Duende_License.key* in the same directory as your hosting application. +If present, the contents of the file will be loaded as the license key. + +## Startup + +If you prefer to load the license key dynamically (e.g. from an API or environment variable), you can in your startup code. +When calling *AddIdentityServer* from *ConfigureServices*, you can pass a lambda expression to configure various options in your IdentityServer. +The *LicenseKey* is one such setting. + +The contents of the license key file is text, and so that is the value to assign to the *LicenseKey* property. +For example: + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services.AddIdentityServer(options => + { + options.LicenseKey = "eyJhbG..."; // the contents of the license key file + }); +} +``` + +## License Validation and Logging + +All license validation is self-contained and does not leave the host (meaning there are no outbound calls related to license validation). +Any messages from the license validation layer will be emitted to the logging system. +The level of the log entry depends on the nature of the message and the type of license. + +| Type of Message | Standard License | Redistribution License (development*) | Redistribution License (production*) | +|------------------------------|-------------------------|--------------------------------------|---------------------------------------| +| Startup, missing license | Warning | Warning | Warning | +| Startup, license details | Debug | Debug | Trace | +| Startup, valid license notice| Informational | Informational | Trace | +| Startup, violations | Error | Error | Trace | +| Runtime, violations | Error | Error | Trace | + +\* as determined by *IHostEnvironment.IsDevelopment()* diff --git a/IdentityServer/v7/docs/content/fundamentals/resources/_index.md b/IdentityServer/v7/docs/content/fundamentals/resources/_index.md new file mode 100644 index 00000000..8744158f --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/resources/_index.md @@ -0,0 +1,13 @@ ++++ +title = "Resources" +date = 2020-09-10T08:20:20+02:00 +weight = 20 +chapter = true +description = "Index" ++++ + +# Resources + +The ultimate job of Duende IdentityServer is to control access to resources. + +{{%children style="h4" /%}} diff --git a/IdentityServer/v7/docs/content/fundamentals/resources/api_resources.md b/IdentityServer/v7/docs/content/fundamentals/resources/api_resources.md new file mode 100644 index 00000000..3fb21ea9 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/resources/api_resources.md @@ -0,0 +1,160 @@ +--- +title: "API Resources" +description: "Overview" +date: 2020-09-10T08:22:12+02:00 +weight: 30 +--- + +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 as well as grouping and isolation of scopes as well as providing some common settings. + +Let's use the following scope definition as an example: + +```cs +public static IEnumerable GetApiScopes() +{ + return new List + { + // invoice API specific scopes + new ApiScope(name: "invoice.read", displayName: "Reads your invoices."), + new ApiScope(name: "invoice.pay", displayName: "Pays your invoices."), + + // customer API specific scopes + new ApiScope(name: "customer.read", displayName: "Reads you customers information."), + new ApiScope(name: "customer.contact", displayName: "Allows contacting one of your customers."), + + // shared scopes + new ApiScope(name: "manage", displayName: "Provides administrative access.") + new ApiScope(name: "enumerate", displayName: "Allows enumerating data.") + }; +} +``` + +With *ApiResource* you can now create two logical APIs and their corresponding scopes: + +```cs +public static readonly IEnumerable GetApiResources() +{ + return new List + { + new ApiResource("invoice", "Invoice API") + { + Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" } + }, + + new ApiResource("customer", "Customer API") + { + Scopes = { "customer.read", "customer.contact", "manage", "enumerate" } + } + }; +} +``` + +Using the API resource grouping gives you the following additional features + +* support for the JWT *aud* claim. The value(s) of the audience claim will be the name of the API resource(s) +* support for adding common user claims across all contained scopes +* support for introspection by assigning an API secret to the resource +* support for configuring the access token signing algorithm for the resource + +Let's have a look at some example access tokens for the above resource configuration. + +Client requests: **invoice.read** and **invoice.pay**: + +```json + { + "typ": "at+jwt" + }. + { + "client_id": "client", + "sub": "123", + + "aud": "invoice", + "scope": "invoice.read invoice.pay" + } +``` + +Client requests: **invoice.read** and **customer.read**: + +```json + { + "typ": "at+jwt" + }. + { + "client_id": "client", + "sub": "123", + + "aud": [ "invoice", "customer" ], + "scope": "invoice.read customer.read" + } +``` + +Client requests: **manage**: + +```json + { + "typ": "at+jwt" + }. + { + "client_id": "client", + "sub": "123", + + "aud": [ "invoice", "customer" ], + "scope": "manage" + } +``` + +### Adding user claims +You can specify that an access token for an API resource (regardless of which scope is requested) should contain additional user claims. + +```cs +var customerResource = new ApiResource("customer", "Customer API") + { + Scopes = { "customer.read", "customer.contact", "manage", "enumerate" }, + + // additional claims to put into access token + UserClaims = + { + "department_id", + "sales_region" + } + } +``` + +If a client now requested a scope belonging to the *customer* resource, the access token would contain the additional claims (if provided by your [profile service]({{< ref "/reference/services/profile_service" >}})). + +```json + { + "typ": "at+jwt" + }. + { + "client_id": "client", + "sub": "123", + + "aud": [ "invoice", "customer" ], + "scope": "invoice.read customer.read", + + "department_id": 5, + "sales_region": "south" + } +``` + +### Setting a signing algorithm +Your APIs might have certain requirements for the cryptographic algorithm used to sign the access tokens for that resource. +An example could be regulatory requirements, or that you are starting to migrate your system to higher security algorithms. + +The following sample sets *PS256* as the required signing algorithm for the *invoices* API: + +```cs +var invoiceApi = new ApiResource("invoice", "Invoice API") + { + Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" }, + + AllowedAccessTokenSigningAlgorithms = { SecurityAlgorithms.RsaSsaPssSha256 } + } +``` + +{{% notice note %}} +Make sure that you have configured your IdentityServer for the required signing algorithm. See [here]({{< ref "../keys" >}}) for more details. +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/fundamentals/resources/api_scopes.md b/IdentityServer/v7/docs/content/fundamentals/resources/api_scopes.md new file mode 100644 index 00000000..28f3b997 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/resources/api_scopes.md @@ -0,0 +1,159 @@ +--- +title: "API Scopes" +description: "Overview" +date: 2020-09-10T08:22:12+02:00 +weight: 20 +--- + +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 +Let's model something very simple - a system that has three logical operations *read*, *write*, and *delete*. + +You can define them using the *ApiScope* class: + +```cs +public static IEnumerable GetApiScopes() +{ + return new List + { + new ApiScope(name: "read", displayName: "Read your data."), + new ApiScope(name: "write", displayName: "Write your data."), + new ApiScope(name: "delete", displayName: "Delete your data.") + }; +} +``` + +You can then assign the scopes to various clients, e.g.: + +```cs +var webViewer = new Client +{ + ClientId = "web_viewer", + + AllowedScopes = { "openid", "profile", "read" } +}; + +var mobileApp = new Client +{ + ClientId = "mobile_app", + + AllowedScopes = { "openid", "profile", "read", "write", "delete" } +} +``` + +### Authorization based on Scopes +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" +} +``` + +{{% notice note %}} +The format of the *scope* parameter can be controlled by the *EmitScopesAsSpaceDelimitedStringInJwt* setting on the options. +Historically IdentityServer emitted scopes as an array, but you can switch to a space delimited string instead. +{{% /notice %}} + +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]({{< ref "/apis" >}}) section for more information on protecting APIs with access tokens. + +{{% notice warning %}} +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. +{{% /notice %}} + +### 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: + + 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. + +{{% notice note %}} +When using the scope-only model, no aud (audience) claim will be added to the token since this concept does not apply. If you need an aud claim, you can enable the *EmitStaticAudienceClaim* setting on the options. This will emit an aud claim in the *issuer_name/resources* format. If you need more control of the aud claim, use API resources. +{{% /notice %}} + +### Parameterized Scopes +Sometimes scopes have a certain structure, e.g. a scope name with an additional parameter: *transaction:id* or *read_patient:patientid*. + +In this case you would create a scope without the parameter part and assign that name to a client, but in addition provide some logic to parse the structure +of the scope at runtime using the *IScopeParser* interface or by deriving from our default implementation, e.g.: + +```cs +public class ParameterizedScopeParser : DefaultScopeParser +{ + public ParameterizedScopeParser(ILogger logger) : base(logger) + { } + + public override void ParseScopeValue(ParseScopeContext scopeContext) + { + const string transactionScopeName = "transaction"; + const string separator = ":"; + const string transactionScopePrefix = transactionScopeName + separator; + + var scopeValue = scopeContext.RawValue; + + if (scopeValue.StartsWith(transactionScopePrefix)) + { + // we get in here with a scope like "transaction:something" + var parts = scopeValue.Split(separator, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 2) + { + scopeContext.SetParsedValues(transactionScopeName, parts[1]); + } + else + { + scopeContext.SetError("transaction scope missing transaction parameter value"); + } + } + else if (scopeValue != transactionScopeName) + { + // we get in here with a scope not like "transaction" + base.ParseScopeValue(scopeContext); + } + else + { + // we get in here with a scope exactly "transaction", which is to say we're ignoring it + // and not including it in the results + scopeContext.SetIgnore(); + } + } +} +``` + +You then have access to the parsed value throughout the pipeline, e.g. in the profile service: + +```cs +public class HostProfileService : IProfileService +{ + public override async Task GetProfileDataAsync(ProfileDataRequestContext context) + { + var transaction = context.RequestedResources.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction"); + if (transaction?.ParsedParameter != null) + { + context.IssuedClaims.Add(new Claim("transaction_id", transaction.ParsedParameter)); + } + } +} +``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/fundamentals/resources/identity.md b/IdentityServer/v7/docs/content/fundamentals/resources/identity.md new file mode 100644 index 00000000..7d1f7b96 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/resources/identity.md @@ -0,0 +1,79 @@ +--- +title: "Identity Resources" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +--- + +An identity resource is a named group of claims about a user that can be requested using the *scope* parameter. + +The OpenID Connect specification [suggests](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) a couple of standard +scope name to claim type mappings that might be useful to you for inspiration, but you can freely design them yourself. + +One of them is actually mandatory, the *openid* scope, which tells the provider to return the *sub* (subject id) claim in the identity token. + +This is how you could define the openid scope in code: + +```cs +public static IEnumerable GetIdentityResources() +{ + return new List + { + new IdentityResource( + name: "openid", + userClaims: new[] { "sub" }, + displayName: "Your user identifier") + }; +} +``` + +But since this is one of the standard scopes from the spec you can shorten that to: + +```cs +public static IEnumerable GetIdentityResources() +{ + return new List + { + new IdentityResources.OpenId() + }; +} +``` +{{% notice note %}} +See the [reference]({{< ref "/reference/models/identity_resource" >}}) section for more information on *IdentityResource*. +{{% /notice %}} + +The following example shows a custom identity resource called *profile* that represents the display name, email address and website claim: + +```cs +public static IEnumerable GetIdentityResources() +{ + return new List + { + new IdentityResource( + name: "profile", + userClaims: new[] { "name", "email", "website" }, + displayName: "Your profile data") + }; +} +``` + +Once the resource is defined, you can give access to it to a client via the *AllowedScopes* option (other properties omitted): + +```cs +var client = new Client +{ + ClientId = "client", + + AllowedScopes = { "openid", "profile" } +}; +``` + +{{% notice note %}} +See the [reference]({{< ref "/reference/models/client" >}}) section for more information on the *Client* class. +{{% /notice %}} + +The client can then request the resource using the scope parameter (other parameters omitted): + + 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]({{< ref "/reference/services/profile_service" >}}). \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/fundamentals/resources/isolation.md b/IdentityServer/v7/docs/content/fundamentals/resources/isolation.md new file mode 100644 index 00000000..07adc991 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/resources/isolation.md @@ -0,0 +1,141 @@ +--- +title: "Resource Isolation" +date: 2020-09-10T08:22:12+02:00 +weight: 40 +--- + +{{% notice note %}} +This is an Enterprise Edition feature. +{{% /notice %}} + +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 an additional 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 +Let's assume you have the following resource design and that the client is allowed access to all scopes: + +```cs +var resources = new[] +{ + new ApiResource("urn:invoices") + { + Scopes = { "read", "write" } + }, + + new ApiResource("urn:products") + { + Scopes = { "read", "write" } + } +}; +``` + +If the client would simply 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 +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.: + +``` +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 +The authorize endpoint supports the *resource* parameter as well, e.g.: + +``` +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: + +``` +POST /token + +grant_type=authorization_code& +client_id=client& +client_secret=...& +authorization_code=...& +redirect_uri=...& + +resource=urn:invoices +``` + +### 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. + +``` +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.: + +``` +POST /token + +grant_type=authorization_code& +client_id=client& +client_secret=...& +authorization_code=...& +redirect_uri=...& + +resource=urn:invoices +``` + +Which 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 round-trip to the token endpoint. + +``` +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 +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.: + +```cs +var resources = new[] +{ + new ApiResource("urn:invoices") + { + Scopes = { "read", "write" }, + + RequireResourceIndicator = true + }, + + new ApiResource("urn:products") + { + Scopes = { "read", "write" } + } +}; +``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/fundamentals/users.md b/IdentityServer/v7/docs/content/fundamentals/users.md new file mode 100644 index 00000000..0f98fc16 --- /dev/null +++ b/IdentityServer/v7/docs/content/fundamentals/users.md @@ -0,0 +1,50 @@ +--- +title: "Users and Logging In" +date: 2020-09-10T08:22:12+02:00 +weight: 40 +--- + +## 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). + +{{% notice note %}} +While you can use any custom user database or identity management library for your users, we provide [integration support]({{< ref "/aspnet_identity" >}}) for ASP.NET Identity. +{{% /notice %}} + +## 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 login, the client application will redirect the user to the protocol endpoint called the [authorization endpoint]({{< ref "/reference/endpoints/authorize" >}}) in your IdentityServer server to request authentication. +As part of the authorize request, your IdentityServer will typically display a login page for the user to enter their credentials. +Once the user has authenticated, your IdentityServer will redirect the user back to the application with the protocol response. + +{{% notice note %}} +A user's authentication session is managed using Microsoft's ASP.NET [cookie authentication framework](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie). +It is very important that you understand how it works when building the login pages in IdentityServer. +{{% /notice %}} + + +Recall the diagram showing the relationship of your custom UI pages and the IdentityServer middleware in your IdentityServer host application: + +![](../../overview/images/middleware.png?height=500px) + +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. + +![](../../ui/images/signin_flow.png?height=500px) + +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 + +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]({{< ref "/ui" >}}) +section of this documentation. diff --git a/IdentityServer/v7/docs/content/overview/_index.md b/IdentityServer/v7/docs/content/overview/_index.md new file mode 100644 index 00000000..b4937a33 --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/_index.md @@ -0,0 +1,12 @@ ++++ +title = "Overview" +description = "IdentityServer" +date = 2020-09-10T08:20:20+02:00 +weight = 1 +chapter = true ++++ + +# Overview + + +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/overview/big_picture.md b/IdentityServer/v7/docs/content/overview/big_picture.md new file mode 100644 index 00000000..ee08913f --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/big_picture.md @@ -0,0 +1,69 @@ +--- +title: "The big Picture" +date: 2020-09-10T08:22:12+02:00 +weight: 1 +--- + +Most modern applications look more or less like this: + +![](../images/appArch.png) + +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: + +![](../images/protocols.png) + +Such a design divides security concerns into two parts: + +### 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 +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. + +OAuth2 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 as well as the APIs since +authentication and authorization can be centralized. + +### OpenID Connect and OAuth 2.0 – 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 +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. + +![](../images/middleware.png?height=30pc) + +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. diff --git a/IdentityServer/v7/docs/content/overview/demo_server.md b/IdentityServer/v7/docs/content/overview/demo_server.md new file mode 100644 index 00000000..e567b2e6 --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/demo_server.md @@ -0,0 +1,10 @@ +--- +title: "Demo Server" +date: 2020-09-10T08:22:12+02:00 +weight: 60 +--- + +You can try Duende IdentityServer with your favourite client library. +We have a test instance at [demo.duendesoftware](https://demo.duendesoftware.com). + +On the main page you can find instructions on how to configure your client and how to call an API. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/overview/glossary.md b/IdentityServer/v7/docs/content/overview/glossary.md new file mode 100644 index 00000000..e4623139 --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/glossary.md @@ -0,0 +1,120 @@ +--- +title: "Glossary" +date: 2020-09-10T08:22:12+02:00 +weight: 70 +--- + +## Client + +A client is a piece of software that requests tokens from your IdentityServer - either for authenticating a user (requesting an identity token) or for accessing a resource (requesting an access token). A client must be first registered with your IdentityServer before it can request tokens and is identified by a unique client ID. + +There are many different client types, e.g. web applications, native mobile or desktop applications, SPAs, server processes, etc. + +[More details]({{}}) + +## Automatic key management +(Business Edition) + +The automatic key management feature creates and manages key material for signing tokens and follows best practices for handling this key material, including storage and rotation. + +[More details](https://blog.duendesoftware.com/posts/20201028_key_management/) + +[Documentation]({{}}) + + +## Server-side Session Management +(Business Edition) + +The server-side session management feature extends the ASP.NET Core cookie authentication handler to maintain a user's authentication session state in a server-side store, rather than putting it all into a self-contained cookie. Using server-side sessions enables more architectural features in your IdentityServer, such as: + +* query and manage active user sessions (e.g. from an administrative app). +* detect session expiration and perform cleanup both in IdentityServer as well as in client apps. +* centralize and monitor session activity in order to achieve a system-wide inactivity timeout. + +[More details](https://blog.duendesoftware.com/posts/20220406_session_management/) + +[Documentation]({{}}) + + +## BFF Security Framework +(Business Edition) + +The Duende BFF (Backend for Frontend) security framework packages up guidance and the necessary components to secure browser-based frontends (e.g. SPAs or Blazor WASM applications) with ASP.NET Core backends. + +[More details](https://blog.duendesoftware.com/posts/20210326_bff/) + +[Documentation]({{}}) + + +## Dynamic Client Registration +(Business Edition) + +Implementation of [RFC 8707](https://tools.ietf.org/html/rfc8707). Provides a standards-based endpoint to register clients and their configuration. + +[Documentation]({{}}) + +## Dynamic Authentication Providers +(Enterprise Edition) + +The dynamic configuration feature allows dynamic loading of configuration for OpenID Connect providers from a store. +This is designed to address the performance concern as well as allowing changes to the configuration to a running server. + +[More details](https://blog.duendesoftware.com/posts/20210517_dynamic_providers/) + +[Documentation]({{}}) + + +## Resource Isolation +(Enterprise Edition) + +The resource isolation feature allows a client to request access tokens for an individual resource server. +This allows API-specific features such as access token encryption and isolation of APIs that are not in the same trust boundary. + +[More details](https://blog.duendesoftware.com/posts/20201230_resource_isolation/) + +[Documentation]({{}}) + + +## CIBA +(Enterprise Edition) + +Duende IdentityServer supports the Client-Initiated Backchannel Authentication Flow (also known as CIBA). +This allows a user to login with a higher security device (e.g. their mobile phone) than the device on which they are using an application (e.g. a public kiosk). +CIBA is one of the requirements to support the Financal-grade API compliance. + +[More details](https://blog.duendesoftware.com/posts/20220107_ciba/) + +[Documentation]({{}}) + +## Proof-of-Possession at the Application Layer / DPoP +(Enterprise Edition) + +A mechanism for sender-constraining OAuth 2.0 tokens via a proof-of-possession mechanism on the application level. This mechanism allows for the detection of replay attacks with access and refresh tokens. + +[Documentation]({{}}) + +## Single Deployment +A single deployment acts as a single OpenID Connect / OAuth authority hosted at a single URL. It can consist of multiple physical or virtual nodes for load-balancing or fail-over purposes. + +## Multiple Deployment +Can be either completely independent single deployments, or a single deployment that acts as multiple authorities. + +## Multiple Authorities +A single logical deployment that acts as multiple logical token services on multiple URLs or host names (e.g. for branding, isolation or multi-tenancy reasons). + +## Standard Developer Support +Online [forum](https://github.com/DuendeSoftware/Support/issues/) for Duende Software product issues and bugs. + + +## Priority Developer Support +(Enterprise Edition) + +Helpdesk system with guaranteed response time for Duende Software product issues and bugs. + +[More details](https://duendesoftware.com/license/PrioritySupportLicense.pdf) + + +## Security Notification System +Notification system for security bugs and/or reported vulnerabilities. + +[More details](https://docs.duendesoftware.com/identityserver/v6/overview/security/#vulnerability-management-process) diff --git a/IdentityServer/v7/docs/content/overview/images/appArch.png b/IdentityServer/v7/docs/content/overview/images/appArch.png new file mode 100644 index 00000000..e0ce39d3 Binary files /dev/null and b/IdentityServer/v7/docs/content/overview/images/appArch.png differ diff --git a/IdentityServer/v7/docs/content/overview/images/middleware.png b/IdentityServer/v7/docs/content/overview/images/middleware.png new file mode 100644 index 00000000..2ae9bf17 Binary files /dev/null and b/IdentityServer/v7/docs/content/overview/images/middleware.png differ diff --git a/IdentityServer/v7/docs/content/overview/images/protocols.png b/IdentityServer/v7/docs/content/overview/images/protocols.png new file mode 100644 index 00000000..724b06e0 Binary files /dev/null and b/IdentityServer/v7/docs/content/overview/images/protocols.png differ diff --git a/IdentityServer/v7/docs/content/overview/images/terminology.png b/IdentityServer/v7/docs/content/overview/images/terminology.png new file mode 100644 index 00000000..e5e27a58 Binary files /dev/null and b/IdentityServer/v7/docs/content/overview/images/terminology.png differ diff --git a/IdentityServer/v7/docs/content/overview/packaging.md b/IdentityServer/v7/docs/content/overview/packaging.md new file mode 100644 index 00000000..fd1da56a --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/packaging.md @@ -0,0 +1,47 @@ +--- +title: "Packaging and Builds" +date: 2020-09-10T08:22:12+02:00 +weight: 40 +--- + +### 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) + +### UI +Duende IdentityServer does not contain any UI, because this is always custom to the project. +We still provide you a starting point for your modifications. + +* [standard](https://github.com/DuendeSoftware/IdentityServer.Quickstart.UI) UI +* UI with ASP.NET Identity [integration](https://github.com/DuendeSoftware/IdentityServer.Quickstart.UI.AspNetIdentity) + +### Templates +Contains templates for the dotnet CLI. + +* Nuget [package](https://www.nuget.org/packages/Duende.IdentityServer.Templates) +* [source code](https://github.com/DuendeSoftware/IdentityServer.Templates) + +You can install the templates using the following command: + +``` +dotnet new -i Duende.IdentityServer.Templates +``` + +### Dev builds +In addition we publish CI builds to our package repository. +Add the following *nuget.config* to your project to access the CI feed: + +```xml + + + + + + +``` + +### Source Code +You can find the Duende IdentityServer source code on [GitHub](https://github.com/duendesoftware/IdentityServer). diff --git a/IdentityServer/v7/docs/content/overview/resources.md b/IdentityServer/v7/docs/content/overview/resources.md new file mode 100644 index 00000000..f263a266 --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/resources.md @@ -0,0 +1,25 @@ +--- +title: "Resources" +description: "External Links" +weight: 80 +--- + +Below are links to relevant topics: + +## 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 + +* [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 + +* [Authorization for Modern Applications (video from DevConf 2018)](https://www.youtube.com/watch?v=Dlrf85NTuAU) diff --git a/IdentityServer/v7/docs/content/overview/security.md b/IdentityServer/v7/docs/content/overview/security.md new file mode 100644 index 00000000..f617bcc7 --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/security.md @@ -0,0 +1,65 @@ +--- +title: "Security best-practices" +date: 2020-09-10T08:22:12+02:00 +weight: 55 +--- + +This document describes how the integrity of software produced by Duende Software is maintained during the software development life cycle. + +### Data processing +Our products are off-the shelf downloadable developer components. They are not managed services or SaaS - nor do we store, have access to, or process any of our customers' data or their customers' data. + +### Systems access + +* Multiple systems are used in the development life cycle, including GitHub, NuGet, and Microsoft Azure Key Vault. +* Multi-factor authentication is required for all services mentioned above. +* Only a limited subset of Duende Software employees act as administrators for each system. + + +### Software development + +* All code is stored in [GitHub](https://github.com/duendesoftware). +* Any code added to a project must be added via pull request. +* At least one other staff member must review a pull request before it can be merged to a release branch. +* Static code security analysis is performed for every check-in (using GitHub [CodeQL](https://codeql.github.com/)). + + +### Testing + +* Automated test suites are run on code in every pull request branch. +* Pull requests cannot be merged if the automated test suite fails. + + +### Deployment + +* Merging a pull request does not immediately release new features to users, this requires an additional release step. +* All compiled software packages with associated source are available as GitHub releases. +* Compiled software libraries (such as Duende IdentityServer) are published to [NuGet](https://www.nuget.org/). +* Packages must be pushed to NuGet by a Duende Software staff member only after additional validation by the staff member. +* All NuGet packages are signed with a code signing certificate + * The private key (RSA 4096 bits) is stored in Azure Key Vault. + * The private key never leaves Key Vault and the signature process is performed by Key Vault. + * NuGet will validate the package signature with Duende's public key to verify they were legitimately built by Duende Software and have not been compromised or tampered with. + * NuGet client tooling can be configured to accept signed packages only. +* Once on NuGet, the package is available for end users to update their own solutions. +* End users still must take explicit action to upgrade after reviewing the package's release notes. + +### Vulnerability management process + +* Potential security vulnerabilities can be responsibly disclosed via our [contact form](https://duendesoftware.com/contact). + * We guarantee to reply within two US business days. +* All licenses includes a security notification service. + * Whenever a security vulnerability has been confirmed and fixed, customers will get a private update prior to public release. +* We will publish an official advisory + +### Dependencies + +IdentityServer has two dependencies: + +* [Microsoft .NET](https://dot.net) +* [IdentityModel](https://github.com/IdentityModel) + * maintained by Duende Software using the same principles as outlined above + +### Certification + +Duende IdentityServer is a [certified](https://openid.net/certification/) implementation of OpenID Connect. diff --git a/IdentityServer/v7/docs/content/overview/specs.md b/IdentityServer/v7/docs/content/overview/specs.md new file mode 100644 index 00000000..5e483545 --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/specs.md @@ -0,0 +1,40 @@ +--- +title: "Supported Specifications" +date: 2020-09-10T08:22:12+02:00 +weight: 30 +--- + +Duende IdentityServer implements the following specifications: + +### OpenID Connect + +* OpenID Connect Core 1.0 ([spec](http://openid.net/specs/openid-connect-core-1_0.html)) +* OpenID Connect Discovery 1.0 ([spec](http://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](http://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](http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html)) +* Form Post Response Mode ([spec](http://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)). + +### OAuth 2.x + +* OAuth 2.0 ([RFC 6749](http://tools.ietf.org/html/rfc6749)) +* OAuth 2.0 Bearer Token Usage ([RFC 6750](http://tools.ietf.org/html/rfc6750)) +* JSON Web Token ([RFC 7519](http://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.txt)) +* OAuth 2.0 Step-up Authentication Challenge Protocol ([RFC pending](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-step-up-authn-challenge)) +* Business Edition: OAuth 2.0 Dynamic Client Registration Protocol ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591.html)) +* Enterprise Edition: Resource Indicators for OAuth 2.0 ([RFC 8707](https://tools.ietf.org/html/rfc8707)) +* Enterprise Edition: OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer / DPoP ([RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)) \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/overview/support.md b/IdentityServer/v7/docs/content/overview/support.md new file mode 100644 index 00000000..66130b64 --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/support.md @@ -0,0 +1,23 @@ +--- +title: "Support and Issues" +date: 2020-09-10T08:22:12+02:00 +weight: 50 +--- + +### Source Code +You can find all source code for IdentityServer and its supporting repos in our [organization](https://github.com/duendesoftware). + +### Issue Tracker +The IdentityServer [issue tracker](https://github.com/DuendeSoftware/IdentityServer/issues) and [pull requests](https://github.com/DuendeSoftware/IdentityServer/pulls) allow you to follow the current work and [submit questions or bug reports](https://github.com/DuendeSoftware/Support/issues). + +[Milestones](https://github.com/DuendeSoftware/IdentityServer/milestones) / [release notes](https://github.com/DuendeSoftware/IdentityServer/releases) + +### Support +See [here](https://duendesoftware.com/products/support) for our support policy. + +Standard support and feature requests are handled via our public [support forum](https://github.com/DuendeSoftware/Support/issues). Please start a discussion there if you need help. + +[Priority support](https://duendesoftware.com/license/PrioritySupportLicense.pdf) is part of our Enterprise Edition. It includes a private email alias, guaranteed two US business days response time. + +### Reporting a security vulnerability +Security issues and bugs should be reported privately [here](https://duendesoftware.com/contact). You should receive a response within two business days. diff --git a/IdentityServer/v7/docs/content/overview/terminology.md b/IdentityServer/v7/docs/content/overview/terminology.md new file mode 100644 index 00000000..7cea9a2f --- /dev/null +++ b/IdentityServer/v7/docs/content/overview/terminology.md @@ -0,0 +1,64 @@ +--- +title: "Terminology" +date: 2020-09-10T08:22:12+02:00 +weight: 10 +--- +The specs, documentation and object model use a certain terminology that you should be aware of. + +![](../images/terminology.png) + +### Duende IdentityServer +Duende IdentityServer is an OpenID Connect & OAuth engine - it implements the OpenID Connect and OAuth 2.0 family of [protocols]({{< ref "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 +A user is a human that is using a registered client to access resources. + +### Client +A [client]({{< ref "/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 +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]({{< ref "/tokens/requesting#machine-to-machine-communication" >}}) as well as in our [Quickstart]({{< ref "/quickstarts/1_client_credentials" >}}). + +#### 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]({{< ref "/tokens/requesting#interactive-applications" >}}) as well as in our [Quickstart]({{< ref "/quickstarts/2_interactive" >}}). + +{{% notice note %}} +A client application can potentially have many instances - e.g. your web application might be physically deployed on multiple servers for load-balancing purposes, or your mobile application might be deployed to thousands of different phones. Logically these instances are still a single client. +{{% /notice %}} + +### Resources +[Resources]({{< ref "/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 +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 +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. \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/quickstarts/0_overview.md b/IdentityServer/v7/docs/content/quickstarts/0_overview.md new file mode 100644 index 00000000..7f4e7850 --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/0_overview.md @@ -0,0 +1,26 @@ +--- +title: "Overview" +description: "Quickstarts" +date: 2020-09-10T08:22:12+02:00 +weight: 1 +--- + +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]({{< param qs_base >}}) folder. + +## Preparation +The first thing you should do is install our templates: + +``` +dotnet new --install Duende.IdentityServer.Templates +``` + +They will be used as a starting point for the various tutorials. diff --git a/IdentityServer/v7/docs/content/quickstarts/1_client_credentials.md b/IdentityServer/v7/docs/content/quickstarts/1_client_credentials.md new file mode 100644 index 00000000..40247795 --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/1_client_credentials.md @@ -0,0 +1,517 @@ +--- +title: "Protecting an API using Client Credentials" +date: 2020-09-10T08:22:12+02:00 +weight: 2 +--- + +Welcome to the first quickstart for IdentityServer! To see the full list of +quickstarts, please see [Quickstarts Overview]({{< ref "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 +Finished source code for each quickstart in this series is available in the +[Samples]({{< param qs_base >}}) repository, and a reference implementation of +this quickstart is available [here]({{< param qs_base >}}/1_ClientCredentials). + +## 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.IdentityServer.Templates +``` + +## 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 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 isempty -n IdentityServer +``` + +This will create the following files within a new *src/IdentityServer* directory: + +* *IdentityServer.csproj* - project file with the IdentityServer nuget package + added +* *Properties/launchSettings.json* file - launch profile +* *appsettings.json* - run time settings +* *Program.cs* - main application entry point +* *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. +* *Config.cs* - definitions for [resources]({{< ref "/overview/terminology#resources" >}}) and +[clients]({{< ref "/overview/terminology#client" >}}) used by IdentityServer + + +{{% notice note %}} + +The *src/IdentityServer/Properties/launchSettings.json* file created by the +*isempty* template sets the *applicationUrl* to *https://localhost:5001*. You +can change the port that your IdentityServer host listens on by changing the +port in this url. This url also sets the protocol (http or https) that the +IdentityServer host will use. In production scenarios you should always use +*https*. + +{{% /notice %}} + +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/IdentityServer.csproj +``` + +### 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 as well as +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 List + { + new ApiScope(name: "api1", displayName: "MyAPI") + }; +``` + +See the full file [here]({{< param qs_base >}}/1_ClientCredentials/src/IdentityServer/Config.cs). + +{{% notice note %}} + +In production it is important to give your API a useful name and display name. +Use these names to describe your API in simple terms to both developers and +users. Developers will use the name to connect to your API, and end users will +see the display name on consent screens, etc. + +{{% /notice %}} + +### Defining the client {#define-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*: + +```cs +public static IEnumerable Clients => + new List + { + 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]({{< param qs_base >}}/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 +The scope and client definitions are loaded in +[HostingExtensions.cs]({{< param qs_base >}}/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: + +```csharp +public static WebApplication ConfigureServices(this WebApplicationBuilder builder) +{ + 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]({{< ref "/reference/endpoints/discovery">}}). +The discovery document is a standard endpoint in +[OpenID Connect](https://openid.net/specs/openid-connect-discovery-1_0.html) and +[OAuth](https://datatracker.ietf.org/doc/html/rfc8414). It is +used by your clients and APIs to retrieve configuration data needed to request +and validate tokens, login and logout, etc. + +![image](../images/1_discovery.png) + +{{% notice note %}} + +On first startup, IdentityServer will use its automatic key management feature +to create a signing key and store it in the *src/IdentityServer/keys* directory. +To avoid accidentally disclosing cryptographic secrets, the entire *keys* +directory should be excluded from source control. It will be recreated if it is +not present. + +{{% /notice %}} + +## 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 command from the *src* directory: + +```console +dotnet new webapi -n Api +``` + +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/Api.csproj +``` + +### 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 in the *src* directory to install the middleware +package in the Api: +```console +dotnet add ./Api/Api.csproj package Microsoft.AspNetCore.Authentication.JwtBearer +``` + +Now add JWT Bearer authentication services to the Service Collection to allow +for dependency injection (DI), and configure *Bearer* as the default +[Authentication Scheme](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-6.0#authentication-scheme). + +```csharp +builder.Services.AddAuthentication("Bearer") + .AddJwtBearer("Bearer", options => + { + options.Authority = "https://localhost:5001"; + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false + }; + }); +``` +{{% notice note %}} + +Audience validation is disabled here because access to the api is modeled with +*ApiScopes* only. By default, no audience will be emitted unless the api is +modeled with *ApiResources* instead. See +[here]({{< ref "/apis/aspnetcore/jwt#adding-audience-validation" >}}) for a +more in-depth discussion. + +{{% /notice %}} + +Add authentication middleware to the pipeline immediately before authorization: +```csharp +app.UseAuthentication(); +app.UseAuthorization(); +``` +*UseAuthentication* adds the authentication middleware to the pipeline so +authentication will be performed automatically on every call into the host. +*UseAuthorization* adds the authorization middleware to make sure your API +endpoint cannot be accessed by anonymous clients. + +### Add a controller +Add a new class called *IdentityController* in *src/Api/Controllers*: + +```csharp +[Route("identity")] +[Authorize] +public class IdentityController : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); + } +} +``` +This controller will be used to test authorization and to display the claims +identity through the eyes of the API. See the full file +[here]({{< param qs_base >}}/1_ClientCredentials/src/Api/Controllers/IdentityController.cs). + + +### 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]({{< param qs_base >}}/1_ClientCredentials/src/Api/Properties/launchSettings.json) +file in the *src/Api/Properties* directory. Change the application URL setting +to be: + +```json +"applicationUrl": "https://localhost:6001" +``` + +### Test the controller + +Run the API project 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 +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. From the *quickstart/src* directory, run the following command: + +```console +dotnet new console -n Client +``` + +Then as before, add it to your solution using: + +```console +cd .. +dotnet sln add ./src/Client/Client.csproj +``` + +### 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 *IdentityModel* NuGet package to your client. This can be done either +via Visual Studio's Nuget Package manager or dotnet CLI. From the *quickstart* +directory, run the following command: + +```console +dotnet add ./src/Client/Client.csproj package IdentityModel +``` + +### 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: + +```cs +// discover endpoints from metadata +var client = new HttpClient(); +var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); +if (disco.IsError) +{ + Console.WriteLine(disco.Error); + return; +} +``` + +{{% notice note %}} + +If you get an error connecting it may be that you are running *https* and the +development certificate for *localhost* is not trusted. You can run `dotnet +dev-certs https --trust` in order to trust the development certificate. This +only needs to be done once. + +{{% /notice %}} + +### Request a token from IdentityServer +Next you can use the information from the discovery document to request a token +from *IdentityServer* to access *api1*: + +```cs +// 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); + return; +} + +Console.WriteLine(tokenResponse.AccessToken); +``` + +{{% notice note %}} + +Copy and paste the access token from the console to [jwt.ms](https://jwt.ms) to +inspect the raw token. + +{{% /notice %}} + +### 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: + +```cs +// call api +var apiClient = new HttpClient(); +apiClient.SetBearerToken(tokenResponse.AccessToken); + +var response = await apiClient.GetAsync("https://localhost:6001/identity"); +if (!response.IsSuccessStatusCode) +{ + Console.WriteLine(response.StatusCode); +} +else +{ + var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; + Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true })); +} +``` + +The completed *Program.cs* file can be found [here]({{< param qs_base +>}}/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: + +![](../images/1_client_screenshot.png) + +{{% notice note %}} + +If you're using Visual Studio, here's how to start everything up: +1. Right click the solution and select *Set 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 and IdentityServer to start +4. Right click the *Client* project and select Debug... Start New Instance. + +{{% /notice %}} + +{{% notice note %}} + +By default an access token will contain claims about the +scope, lifetime (nbf and exp), the client ID (client_id) and the issuer name +(iss). + +{{% /notice %}} + +#### 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-6.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](#define-client). Add the following to the +*ConfigureServices* method in the API's *Program.cs* file: + +```cs +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 API endpoints +* for specific controllers/actions + +Typically you set the policy for all controllers where they are mapped in +*src/Api/Program.cs*: + +```cs +app.MapControllers().RequireAuthorization("ApiScope"); +``` + +## 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. diff --git a/IdentityServer/v7/docs/content/quickstarts/2_interactive.md b/IdentityServer/v7/docs/content/quickstarts/2_interactive.md new file mode 100644 index 00000000..d4880d10 --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/2_interactive.md @@ -0,0 +1,636 @@ +--- +title: "Interactive Applications with ASP.NET Core" +date: 2020-09-10T08:22:12+02:00 +weight: 3 +--- + +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]({{< ref "1_client_credentials" >}}). Once that is in place, you will create +an ASP.NET Razor Pages application that will use IdentityServer for +authentication. + +{{% notice note %}} + +We recommend you do the quickstarts in order. If you'd like to start here, begin +from a copy of the [reference implementation of Quickstart 1]({{< param qs_base >}}/1_ClientCredentials). +Throughout this quickstart, paths are written relative to the base *quickstart* +directory created in part 1, which is the root directory of the reference +implementation. You will also need to [install the IdentityServer templates]({{< ref "0_overview#preparation" >}}). + +{{% /notice %}} + +## 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 +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 isui +``` + +### 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*. + +{{% notice note %}} + +There is also a template called *isinmem* which combines the basic +IdentityServer from the *isempty* template with the quickstart UI from the +*isui* template. + +{{% /notice %}} + +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 +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*: + +```cs +public static IEnumerable IdentityResources => + new List + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + }; +``` + +Then register the identity resources in +*src/IdentityServer/HostingExtensions.cs*: + +```cs +builder.Services.AddIdentityServer() + .AddInMemoryIdentityResources(Config.IdentityResources) + .AddInMemoryApiScopes(Config.ApiScopes) + .AddInMemoryClients(Config.Clients); +``` + +{{% notice note %}} + +All standard scopes and their corresponding claims can be found in the OpenID +Connect +[specification](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims). + +{{% /notice %}} + +### 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*: + +```cs +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 + +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]({{< ref "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: + +```cs +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 = new List + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile + } + } + }; +``` + +## 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/WebClient.csproj +``` + +{{% notice note %}} + +This version of the quickstarts uses [Razor +Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-6.0&tabs=visual-studio) +for the web client. If you prefer MVC, the conversion is straightforward. See +the [quickstart for IdentityServer +5](https://docs.duendesoftware.com/identityserver/v5/quickstarts/2_interactive/) +that uses it. + +{{% /notice %}} + +### 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 +Then add the following to *ConfigureServices* in *src/WebClient/Program.cs*: + +```cs +using System.IdentityModel.Tokens.Jwt; + +// ... + +JwtSecurityTokenHandler.DefaultMapInboundClaims = false; + +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.SaveTokens = true; + }); +``` + +{{% notice note %}} + +If you are unfamiliar with the fundamentals of how the ASP.NET Core +authentication system works, then we recommend this recording of an +[Introduction to ASP.NET Core Authentication and +Authorization](https://www.youtube.com/watch?v=02Yh3sxzAYI). + +{{% /notice %}} + + +*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). + +{{% notice note %}} + +This uses the *authorization code* flow with PKCE to connect to the OpenID +Connect provider. See [here]({{< ref "/fundamentals/clients" >}}) for more +information on protocol flows. + +{{% /notice %}} + +### 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. + +```cs +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapRazorPages().RequireAuthorization(); + +``` + +{{% notice note %}} + +See the ASP.NET Core documentation on [Razor Pages authorization +conventions](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/razor-pages-authorization?view=aspnetcore-6.0) +for more options that allow you to specify authorization on a per page or +directory basis. + +{{% /notice %}} + +### Display the Auth Cookie + +Modify *src/WebClient/Pages/Index.cshtml* to display the claims of the user and +the cookie properties: + +```cs +@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 +Update the client's applicationUrl in +*src/WebClient/Properties/launchSettings.json* to use port 5002. + +```json +{ + "profiles": { + "WebClient": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5002", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} +``` + +## 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*. + +![](../images/2_login.png) + +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. + +![](../images/2_claims.png) + +As you can see, the cookie has two parts: the claims of the user and some +metadata. This metadata also contains the original token that was issued by +*IdentityServer*. Feel free to copy this token to [jwt.ms](https://jwt.ms) to +inspect its content. + +## 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: + +```cs +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 + + +``` + +## 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*, *family_name*, *website* 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*: + +```cs +.AddOpenIdConnect("oidc", options => +{ + // ... + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.GetClaimsFromUserInfoEndpoint = true; + // ... +}); +``` + +After restarting the client app, logging out, and logging back in, you should +see additional user claims associated with the *profile* identity scope +displayed on the page. + +![](../images/2_additional_claims.png) + +## 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 +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 = new List + { + 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-6.0) + to map the new claim returned from the userinfo endpoint onto a user claim. + ```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 +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 Google, Facebook, Twitter, Microsoft +Account and OpenID Connect. In addition, you can find handlers for many +other authentication providers +[here](https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers). + +#### Add Google support +To use Google for authentication, you need to: +- Add the *Microsoft.AspNetCore.Authentication.Google* nuget package to + the IdentityServer project. +- Register with Google and set up a client. +- 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://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-6.0#create-a-google-api-console-project-and-client-id) +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*: + +```cs +builder.Services.AddAuthentication() + .AddGoogle("Google", 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/?view=aspnetcore-6.0#authentication-scheme). +*AddGoogle* adds the Google 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 +round-trip 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. + + ![](../images/2_google_login.png) + +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. + +{{% notice note %}} + +The Google button is rendered by the login page automatically when there are +external providers registered as authentication schemes. See the +*BuildModelAsync* method in *src/IdentityServer/Pages/Login/Index.cshtml.cs* and +the corresponding Razor template for more details. + +{{% /notice %}} + +#### Adding an additional OpenID Connect-based external provider +A [cloud-hosted demo](https://demo.duendesoftware.com) version of Duende +IdentityServer can be added as an additional external provider. + +Register and configure the services for the OpenId Connect handler in +*src/IdentityServer/HostingExtensions.cs*: +```cs +builder.Services.AddAuthentication() + .AddGoogle("Google", options => { /* ... */ }) + .AddOpenIdConnect("oidc", "Demo IdentityServer", 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 button to log in to +the cloud-hosted Demo IdentityServer. If you click that button, you will be +redirected to https://demo.duendesoftware.com/. Note that the demo site is using +the same UI as your site, so there will not be very much that changes visually +when you're redirected. 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. + +{{% notice note %}} + +The quickstart UI auto-provisions external users. When an external user logs in +for the first time, a new local user is created with a copy of all the external +user's claims. This auto-provisioning process occurs in the *OnGet* method of +*src/IdentityServer/Pages/ExternalLogin/Callback.cshtml.cs*, and is completely +customizable. For example, you could modify *Callback* so that it will require +registration before provisioning the external user. + +{{% /notice %}} diff --git a/IdentityServer/v7/docs/content/quickstarts/3_api_access.md b/IdentityServer/v7/docs/content/quickstarts/3_api_access.md new file mode 100644 index 00000000..0e93282f --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/3_api_access.md @@ -0,0 +1,169 @@ +--- +title: "ASP.NET Core and API access" +date: 2020-09-10T08:22:12+02:00 +weight: 4 +--- + +Welcome to Quickstart 3 for Duende IdentityServer! + +The previous quickstarts introduced +[API access]({{< ref "1_client_credentials" >}}) and +[user authentication]({{< ref "2_interactive" >}}). This quickstart will bring +the two together. + +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 + +{{% notice note %}} + +We recommend you do the quickstarts in order. If you'd like to start here, begin +from a copy of the [reference implementation of Quickstart 2]({{< param qs_base >}}/2_InteractiveAspNetCore). +Throughout this quickstart, paths are written relative to the base *quickstart* +directory created in part 1, which is the root directory of the reference +implementation. You will also need to [install the IdentityServer templates]({{< ref "0_overview#preparation" >}}). + +{{% /notice %}} + + +## Modifying the client configuration + +The client configuration in IdentityServer requires two straightforward updates. +1. Add the *api1* resource to the allowed scopes list so that the client will + have permission to access it. +2. Enable support for refresh tokens by setting the *AllowOfflineAccess* flag. + +Update the *Client* in *src/IdentityServer/Config.cs* as follows: +```cs +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 = new List + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + "api1" + } +} +``` + +## Modifying the Web client +Now configure the client to ask for access to api1 and for a refresh token by +requesting the *api1* and *offline_access* scopes. This is done in the OpenID +Connect handler configuration in *src/WebClient/Program.cs*: + +```cs +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.SaveTokens = true; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api1"); + options.Scope.Add("offline_access"); + options.GetClaimsFromUserInfoEndpoint = true; + }); +``` + +Since *SaveTokens* is enabled, ASP.NET Core will automatically store the id, +access, and refresh 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 +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: +```cs +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
+``` + +Make sure the *IdentityServer* and *Api* projects are running, start the +*WebClient* and request */CallApi* after authentication. + +## Further Reading - Access token lifetime management +By far the most complex task for a typical client is to manage the access token. +You typically want to + +* request the access and refresh 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. +Consider using the +[IdentityModel](https://identitymodel.readthedocs.io/en/latest/aspnetcore/overview.html) +library for help with access token lifetime management. It provides abstractions +for storing tokens, automatic refresh of expired tokens, etc. diff --git a/IdentityServer/v7/docs/content/quickstarts/4_ef.md b/IdentityServer/v7/docs/content/quickstarts/4_ef.md new file mode 100644 index 00000000..2c83d34a --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/4_ef.md @@ -0,0 +1,265 @@ +--- +title: "Using EntityFramework Core for configuration and operational data" +date: 2020-09-10T08:22:12+02:00 +weight: 5 +--- + +Welcome to Quickstart 4 for Duende IdentityServer! In this quickstart you will +move configuration and other temporary data into a database using Entity +Framework. + +{{% notice note %}} + +We recommend you do the quickstarts in order. If you'd like to start here, begin +from a copy of the [reference implementation of Quickstart 3]({{< param qs_base >}}/3_AspNetCoreAndApis). +Throughout this quickstart, paths are written relative to the base *quickstart* +directory created in part 1, which is the root directory of the reference +implementation. You will also need to [install the IdentityServer templates]({{< ref "0_overview#preparation" >}}). + + +{{% /notice %}} + +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. + +{{% notice note %}} + +This quickstart shows how to add Entity Framework support to IdentityServer +manually. There is also a template that will create a new IdentityServer project +with the EntityFramework integration already added: *dotnet new isef*. + +{{% /notice %}} + +## Configure IdentityServer +### Install Duende.IdentityServer.EntityFramework +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 + +*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 +*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: + +```cs +public static WebApplication ConfigureServices(this WebApplicationBuilder builder) +{ + 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); + + //... +} +``` +{{% notice note %}} + +You will use Entity Framework migrations later on in this quickstart to manage +the database schema. The call to *MigrationsAssembly(...)* tells Entity +Framework that the host project will contain the migrations. This is necessary +since the host project is in a different assembly than the one that contains the +*DbContext* classes. + +{{% /notice %}} + +## Managing the 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 +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 +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 +*StopTheHostException* (in Entity Framework 6) or *HostAbortedException* (in Entity Framework 7) 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 +catch (Exception ex) when ( + // https://github.com/dotnet/runtime/issues/60600 + ex.GetType().Name is not "StopTheHostException" + // HostAbortedException was added in .NET 7, but since we target .NET 6 we + // need to do it this way until we target .NET 8 + && ex.GetType().Name is not "HostAbortedException") +{ + Log.Fatal(ex, "Unhandled exception"); +} +``` + +{{% notice note %}} + +When using `Microsoft.EntityFrameworkCore.Tools` version 6.x, you must use the "StopTheHostException" string here rather than catching the +*StopTheHostException* because it is a private type. +If you use version 7.x of `Microsoft.EntityFrameworkCore.Tools` and reference version 7.x of the `Microsoft.Extensions.Hosting` package, you can catch the "HostAbortedException" as expected. See +https://github.com/dotnet/runtime/issues/60600. + + +{{% /notice %}} + +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 the 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. + +{{% notice note %}} + +The approach used in this quickstart is used to make it easy to get +IdentityServer up and running. You should devise your own database creation and +maintenance strategy that is appropriate for your architecture. + +{{% /notice %}} + +In *src/IdentityServer/HostingExtensions.cs*, add this method to initialize the +database: + +```cs +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: + +```cs +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. + +![](../images/ef_database.png) + +{{% notice note %}} + +The *InitializeDatabase* method is convenient way to seed the database, but this +approach is not ideal to leave in to execute each time the application runs. +Once your database is populated, consider removing the call to the API. + +{{% /notice %}} + +## 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. diff --git a/IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md b/IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md new file mode 100644 index 00000000..829e060d --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md @@ -0,0 +1,390 @@ +--- +title: "Using ASP.NET Core Identity" +date: 2020-09-10T08:22:12+02:00 +weight: 6 +--- + +Welcome to Quickstart 5 for Duende IdentityServer! In this quickstart you will +integrate IdentityServer with ASP.NET Core Identity. + +{{% notice note %}} + +We recommend you do the quickstarts in order. If you'd like to start here, begin +from a copy of the [reference implementation of Quickstart 4]({{< param qs_base >}}/4_EntityFramework). +Throughout this quickstart, paths are written relative to the base *quickstart* +directory created in part 1, which is the root directory of the reference +implementation. You will also need to [install the IdentityServer templates]({{< ref "0_overview#preparation" >}}). + +{{% /notice %}} + +IdentityServer's flexible design allows you to use any database you want to +store users and their data, including password hashes, multifactor +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. + +{{% notice note %}} + +This quickstart assumes you are familiar with how ASP.NET Core Identity works. +If you are not, it is recommended that you first [learn about +it](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0). + +{{% /notice %}} + +## New Project for ASP.NET 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 isaspid -n IdentityServerAspNetIdentity +cd .. +dotnet sln add ./src/IdentityServerAspNetIdentity/IdentityServerAspNetIdentity.csproj +``` + +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$". + +{{% notice note %}} +The template uses Sqlite as the database for the users, and EF migrations are +pre-created in the template. If you wish to use a different database provider, +you will need to change the provider used in the code and re-create the EF +migrations. +{{% /notice %}} + +## 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 +Notice the reference to *Duende.IdentityServer.AspNetIdentity*. This NuGet +package contains the ASP.NET Core Identity integration components for +IdentityServer. + +### HostingExtensions.cs +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 +*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: + +```cs +public static class Config +{ + public static IEnumerable IdentityResources => + new List + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + }; + + + public static IEnumerable ApiScopes => + new List + { + new ApiScope("api1", "My API") + }; + + public static IEnumerable Clients => + new List + { + // machine to machine client + 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" }, + + AllowOfflineAccess = true, + + AllowedScopes = new List + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + "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/IdentityServer.csproj +rm -r ./src/IdentityServer +``` + +### Program.cs and SeedData.cs +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 +Finally, take a look at the 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 +At this point, you should be able to run all of 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. + +![](../images/aspid_claims.png) + +You should also be able to go to the [call api +page](https://localhost:5002/callapi) to invoke the API on behalf of the user: + +![](../images/aspid_api_claims.png) + +Congratulations, you're using users from ASP.NET Core Identity in +IdentityServer! + +## 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 to the *ApplicationUser* class. +```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(); +``` + +{{% notice note %}} +Caution: this will destroy +your test users when you make changes to them. While that is convenient for this +quickstart, it is not recommended in production! +{{% /notice %}} + +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]({{< ref +"/reference/services/profile_service" >}}) for more details on the profile +service. + +Create a new class called *CustomProfileService* 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*: +```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 IdentityResources.OpenId(), + new IdentityResources.Profile(), + 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: + +```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? +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-6.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*. diff --git a/IdentityServer/v7/docs/content/quickstarts/7_blazor.md b/IdentityServer/v7/docs/content/quickstarts/7_blazor.md new file mode 100644 index 00000000..a75bbd4c --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/7_blazor.md @@ -0,0 +1,398 @@ +--- +title: "Building Blazor WASM client applications" +date: 2020-09-10T08:22:12+02:00 +weight: 15 +--- + +Similar to JavaScript SPAs, you can build Blazor WASM applications with and without a backend. Not having a backend has all the security disadvantages we discussed already in the JavaScript quickstart. + +If you are building Blazor WASM apps that do not deal with sensitive data and you want to use the no-backend approach, have a look at the standard Microsoft templates, which are using this style. + +In this quickstart we will focus on how to build a Blazor WASM application using our Duende.BFF security framework. You can find the full source code [here]({{< param qs_base >}}/7_Blazor) + +{{% notice note %}} +To keep things simple, we will utilize our demo IdentityServer instance hosted at https://demo.duendesoftware.com. We will provide more details on how to configure a Blazor client in your own IdentityServer at then end. +{{% /notice %}} + +### Setting up the project +The .NET 6 CLI includes a Blazor WASM with backend template. Create the directory where you want to work in, and run the following command: + +``` +dotnet new blazorwasm --hosted +``` + +This will create three projects - server, client and shared. + +### Configuring the backend +First add the following package references to the server project: + +```xml + + +``` + +Next, we will add OpenID Connect and OAuth support to the backend. For this we are adding the Microsoft OpenID Connect authentication handler for the protocol interactions with the token service, and the cookie authentication handler for managing the resulting authentication session. See [here]({{< ref "/bff/session/handlers" >}}) for more background information. + +The BFF services provide the logic to invoke the authentication plumbing from the frontend (more about this later). + +Add the following snippet to your *Program.cs* above the call to *builder.Build();* + +```cs +builder.Services.AddBff(); + +builder.Services.AddAuthentication(options => + { + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + options.DefaultSignOutScheme = "oidc"; + }) + .AddCookie("cookie", options => + { + options.Cookie.Name = "__Host-blazor"; + options.Cookie.SameSite = SameSiteMode.Strict; + }) + .AddOpenIdConnect("oidc", options => + { + options.Authority = "https://demo.duendesoftware.com"; + + options.ClientId = "interactive.confidential"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.ResponseMode = "query"; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api"); + options.Scope.Add("offline_access"); + + options.MapInboundClaims = false; + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + }); +``` + +The last step is to add the required middleware for authentication, authorization and BFF session management. Add the following snippet after the call to *UseRouting*: + +```cs +app.UseAuthentication(); +app.UseBff(); +app.UseAuthorization(); + +app.MapBffManagementEndpoints(); +``` + +Finally you can run the server project. This will start the host, which will in turn deploy the Blazor application to your browser. + +Try to manually invoke the BFF login endpoint on */bff/login* - this should bring you to the demo IdentityServer. After login (e.g. using bob/bob), the browser will return to the Blazor application. + +In other words, the fundamental authentication plumbing is already working. Now we need to make the frontend aware of it. + +### Modifying the frontend (part 1) +A couple of steps are necessary to add the security and identity plumbing to a Blazor application. + +**a)** Add the authentication/authorization related package to the client project file: + +```xml + +``` + +**b)** Add a using statement to *_Imports.razor* to bring the above package in scope: + +```cs +@using Microsoft.AspNetCore.Components.Authorization +``` + +**c)** To propagate the current authentication state to all pages in your Blazor client, you add a special component called *CascadingAuthenticationState* to your application. This is done by wrapping the Blazor router with that component in *App.razor*: + +```xml + + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
+
+``` + +**d)** Last but not least, we will add some conditional rendering to the layout page to be able to trigger login/logout as well as displaying the current user name when logged in. This is achieved by using the *AuthorizeView* component in *MainLayout.razor*: + +```xml +
+ + +
+
+ + + 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: + +``` +crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] + Unhandled exception rendering component: Cannot provide a value for property 'AuthenticationStateProvider' on type 'Microsoft.AspNetCore.Components.Authorization.CascadingAuthenticationState'. There is no registered service of type 'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'. +``` + +*CascadingAuthenticationState* is an abstraction over an arbitrary authentication system. It internally relies on a service called *AuthenticationStateProvider* to return the required information about the current authentication state and the information about the currently logged on user. + +This component needs to be implemented, and that's what we'll do next. + +### Modifying the frontend (part 2) +The BFF library has a server-side component that allows querying the current authentication session and state (see [here]({{< ref "/bff/session/management/user" >}})). We will now add a Blazor *AuthenticationStateProvider* that will internally use this endpoint. + +Add a file with the following content: + +```cs +using System.Net; +using System.Net.Http.Json; +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; + +namespace Blazor6.Client.BFF; + +public class BffAuthenticationStateProvider + : AuthenticationStateProvider +{ + private static readonly TimeSpan UserCacheRefreshInterval + = TimeSpan.FromSeconds(60); + + private readonly HttpClient _client; + private readonly ILogger _logger; + + private DateTimeOffset _userLastCheck + = DateTimeOffset.FromUnixTimeSeconds(0); + private ClaimsPrincipal _cachedUser + = new ClaimsPrincipal(new ClaimsIdentity()); + + public BffAuthenticationStateProvider( + HttpClient client, + ILogger logger) + { + _client = client; + _logger = logger; + } + + public override async Task GetAuthenticationStateAsync() + { + return new AuthenticationState(await GetUser()); + } + + private async ValueTask GetUser(bool useCache = true) + { + var now = DateTimeOffset.Now; + if (useCache && now < _userLastCheck + UserCacheRefreshInterval) + { + _logger.LogDebug("Taking user from cache"); + return _cachedUser; + } + + _logger.LogDebug("Fetching user"); + _cachedUser = await FetchUser(); + _userLastCheck = now; + + return _cachedUser; + } + + record ClaimRecord(string Type, object Value); + + private async Task FetchUser() + { + try + { + _logger.LogInformation("Fetching user information."); + var response = await _client.GetAsync("bff/user?slide=false"); + + if (response.StatusCode == HttpStatusCode.OK) + { + var claims = await response.Content.ReadFromJsonAsync>(); + + var identity = new ClaimsIdentity( + nameof(BffAuthenticationStateProvider), + "name", + "role"); + + foreach (var claim in claims) + { + identity.AddClaim(new Claim(claim.Type, claim.Value.ToString())); + } + + return new ClaimsPrincipal(identity); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Fetching user failed."); + } + + return new ClaimsPrincipal(new ClaimsIdentity()); + } +} +``` + +..and register it in the client's *Program.cs*: + +```cs +builder.Services.AddAuthorizationCore(); +builder.Services.AddScoped(); +``` + +If you run the server app now again, you will see a different error: + +``` +fail: Duende.Bff.Endpoints.BffMiddleware[1] + Anti-forgery validation failed. local path: '/bff/user' +``` + +This is due to the antiforgery protection that is applied automatically to the management endpoints in the BFF host. To properly secure the call, you need to add a static *X-CSRF* header to the call. See [here]({{< ref "/bff/apis/local" >}}) for more background information. + +This can be easily accomplished by a delegating handler that can be plugged into the default HTTP client used by the Blazor frontend. Let's first add the handler: + +```cs +public class AntiforgeryHandler : DelegatingHandler +{ + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + request.Headers.Add("X-CSRF", "1"); + return base.SendAsync(request, cancellationToken); + } +} +```` + +..and register it in the client's *Program.cs* (overriding the standard HTTP client configuration; requires package Microsoft.Extensions.Http): + +```cs +// HTTP client configuration +builder.Services.AddTransient(); + +builder.Services.AddHttpClient("backend", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) + .AddHttpMessageHandler(); +builder.Services.AddTransient(sp => sp.GetRequiredService().CreateClient("backend")); +``` + +This requires an additional reference in the client project: + +``` + +``` + +If you restart the application again, the logon/logoff logic should work now. In addition you can display the contents of the session on the main page by adding this code to *Index.razor*: + +``` +@page "/" + +Home + +

Hello, Blazor BFF!

+ + + +
+ @foreach (var claim in @context.User.Claims) + { +
@claim.Type
+
@claim.Value
+ } +
+
+
+``` + +### Securing the local API +The standard Blazor template contains an API endpoint (*WeatherForecastController.cs*). Try invoking the weather page from the UI. It works both in logged in and anonymous state. We want to change the code to make sure, that only authenticated users can call the API. + +The standard way in ASP.NET Core would be to add an authorization requirement to the endpoint, either on the controller/action or via the endpoint routing, e.g.: + +```cs +app.MapControllers() + .RequireAuthorization(); +``` + +When you now try to invoke the API anonymously, you will see the following error in the browser console: + +``` +Access to fetch at 'https://demo.duendesoftware.com/connect/authorize?client_id=...[shortened]... (redirected from 'https://localhost:5002/WeatherForecast') from origin 'https://localhost:5002' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. +``` + +This happens because the ASP.NET Core authentication plumbing is triggering a redirect to the OpenID Connect provider for authentication. What we really want in that case is an API friendly status code - 401 in this scenario. + +This is one of the features of the BFF middleware, but you need to mark the endpoint as a BFF API endpoint for that to take effect: + +```cs +app.MapControllers() + .RequireAuthorization() + .AsBffApiEndpoint(); +``` + +After making this change, you should see a much better error message: + +``` +Response status code does not indicate success: 401 (Unauthorized). +``` + +The client code can properly respond to this, e.g. triggering a login redirect. + +When you logon now and call the API, you can put a breakpoint server-side and inspect that the API controller has access to the claims of the authenticated user via the *.User* property. + +### Setting up a Blazor BFF client in IdentityServer +In essence a BFF client is "just" a normal authorization code flow client: + +* use the code grant type +* set a client secret +* enable *AllowOfflineAccess* if you want to use refresh tokens +* enable the required identity and resource scopes +* set the redirect URIs for the OIDC handler + +Below is a typical code snippet for the client definition: + +```cs +var bffClient = new Client +{ + ClientId = "bff", + + ClientSecrets = + { + new Secret("secret".Sha256()) + }, + + AllowedGrantTypes = GrantTypes.Code, + + RedirectUris = { "https://bff_host/signin-oidc" }, + FrontChannelLogoutUri = "https://bff_host/signout-oidc", + PostLogoutRedirectUris = { "https://bff_host/signout-callback-oidc" }, + + AllowOfflineAccess = true, + + AllowedScopes = { "openid", "profile", "remote_api" } +}; +``` + +### Further experiments +Our Blazor BFF [sample]({{< ref "/samples/bff#blazor-wasm" >}}) is based on this Quickstart. In addition it shows concepts like + +* better organization with components +* reacting to logout +* using the authorize attribute to trigger automatic redirects to the login page diff --git a/IdentityServer/v7/docs/content/quickstarts/_index.md b/IdentityServer/v7/docs/content/quickstarts/_index.md new file mode 100644 index 00000000..4511485e --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/_index.md @@ -0,0 +1,12 @@ ++++ +title = "Quickstarts" +date = 2020-09-10T08:20:20+02:00 +weight = 30 +chapter = true ++++ + +# Quickstarts + +The following hands-on tutorials guide you through a couple of common scenarios. + +{{%children style="h4" /%}} \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/quickstarts/images/1_client_screenshot.png b/IdentityServer/v7/docs/content/quickstarts/images/1_client_screenshot.png new file mode 100644 index 00000000..604d5cf8 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/1_client_screenshot.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/1_discovery.png b/IdentityServer/v7/docs/content/quickstarts/images/1_discovery.png new file mode 100644 index 00000000..6ffcc8c5 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/1_discovery.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/2_additional_claims.png b/IdentityServer/v7/docs/content/quickstarts/images/2_additional_claims.png new file mode 100644 index 00000000..745e93cb Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/2_additional_claims.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/2_claims.png b/IdentityServer/v7/docs/content/quickstarts/images/2_claims.png new file mode 100644 index 00000000..73588b66 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/2_claims.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/2_google_login.png b/IdentityServer/v7/docs/content/quickstarts/images/2_google_login.png new file mode 100644 index 00000000..64aa011a Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/2_google_login.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/2_login.png b/IdentityServer/v7/docs/content/quickstarts/images/2_login.png new file mode 100644 index 00000000..1ff4d0b7 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/2_login.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/aspid_api_claims.png b/IdentityServer/v7/docs/content/quickstarts/images/aspid_api_claims.png new file mode 100644 index 00000000..e195fa1b Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/aspid_api_claims.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/aspid_claims.png b/IdentityServer/v7/docs/content/quickstarts/images/aspid_claims.png new file mode 100644 index 00000000..d6ad0d48 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/aspid_claims.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/aspid_mvc_client.png b/IdentityServer/v7/docs/content/quickstarts/images/aspid_mvc_client.png new file mode 100644 index 00000000..058e6274 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/aspid_mvc_client.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/ef_database.png b/IdentityServer/v7/docs/content/quickstarts/images/ef_database.png new file mode 100644 index 00000000..bfdec0a6 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/ef_database.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsbff_local_api.png b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_local_api.png new file mode 100644 index 00000000..ecc0095c Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_local_api.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsbff_logged_in.png b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_logged_in.png new file mode 100644 index 00000000..2af26208 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_logged_in.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsbff_not_logged_in.png b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_not_logged_in.png new file mode 100644 index 00000000..9e57dcfd Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_not_logged_in.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsbff_remote_api.png b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_remote_api.png new file mode 100644 index 00000000..365fac41 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_remote_api.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsbff_signed_out.png b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_signed_out.png new file mode 100644 index 00000000..9029c03a Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsbff_signed_out.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsclient_api_results.png b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_api_results.png new file mode 100644 index 00000000..49d38782 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_api_results.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsclient_logged_in.png b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_logged_in.png new file mode 100644 index 00000000..71f20d8f Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_logged_in.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsclient_not_logged_in.png b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_not_logged_in.png new file mode 100644 index 00000000..24978cd0 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_not_logged_in.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/images/jsclient_signed_out.png b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_signed_out.png new file mode 100644 index 00000000..16a03d04 Binary files /dev/null and b/IdentityServer/v7/docs/content/quickstarts/images/jsclient_signed_out.png differ diff --git a/IdentityServer/v7/docs/content/quickstarts/js_clients/_index.md b/IdentityServer/v7/docs/content/quickstarts/js_clients/_index.md new file mode 100644 index 00000000..ccf08f0e --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/js_clients/_index.md @@ -0,0 +1,34 @@ ++++ +title = "Building JavaScript client applications" +weight = 10 +chapter = true ++++ + +# JavaScript/SPA Client Applications + +When building JavaScript (or SPA) applications, there are two main styles: those +with a backend and those without. + +JavaScript applications **with a backend** are more secure, making it the +preferred style. This style uses the ["Backend For Frontend" +pattern](https://blog.duendesoftware.com/posts/20210326_bff/), or "BFF" for +short, which relies on the backend host to implement all of the security +protocol interactions with the token server. The *Duende.BFF* library is used in +this quickstart to easily support the BFF pattern. + +JavaScript 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 encouraged for applications dealing with sensitive data. 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]({{< ref "/bff/overview#react-to-changes-in-the-browser-security-models" >}}) +for more details. diff --git a/IdentityServer/v7/docs/content/quickstarts/js_clients/js_with_backend.md b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_with_backend.md new file mode 100644 index 00000000..fc44349d --- /dev/null +++ b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_with_backend.md @@ -0,0 +1,515 @@ +--- +title: "JavaScript applications with a backend" +weight: 10 +--- + +{{% notice note %}} + +We recommend you do the quickstarts in order. If you'd like to start here, begin +from a copy of the [reference implementation of Quickstart 3]({{< param qs_base >}}/3_AspNetCoreAndApis). +Throughout this quickstart, paths are written relative to the base *quickstart* +directory created in part 1, which is the root directory of the reference +implementation. You will also need to [install the IdentityServer templates]({{< ref "0_overview#preparation" >}}). + +{{% /notice %}} + +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 of 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 login +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 + +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: + +```console +dotnet new web -n JavaScriptClient +cd .. +dotnet sln add ./src/JavaScriptClient/JavaScriptClient.csproj +``` + +### 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: + +```console +dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect +dotnet add package Duende.BFF +dotnet add package Duende.BFF.Yarp +``` + +### Modify hosting + +Modify the *JavaScriptClient* project to run on *https://localhost:5003*. Its +*Properties/launchSettings.json* should look like this: + +```json +{ + "profiles": { + "JavaScriptClient": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5003", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} +``` + +### 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]({{}}). Additionally, the BFF services need to be added with +*AddBff()*. + +Add the following to *src/JavaScriptClient/Program.cs*: + +```cs +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(); + +JwtSecurityTokenHandler.DefaultMapInboundClaims = false; +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.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + }); + +var app = builder.Build(); +``` + +### Add middleware + +Similarly, the middleware pipeline for this application will resemble the +WebClient, with the addition of the BFF middleware and the BFF endpoints. +Continuing by adding the following to *src/JavaScriptClient/Program.cs*: + +```cs +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseRouting(); +app.UseAuthentication(); + +app.UseBff(); + +app.UseAuthorization(); + +app.UseEndpoints(endpoints => +{ + endpoints.MapBffManagementEndpoints(); +}); + +app.Run(); +``` + +### Add your 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:
+
+```
+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 a client registration to IdentityServer for the JavaScript client
+
+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. It should have the
+configuration listed below:
+
+```cs
+// JavaScript BFF client
+new Client
+{
+    ClientId = "bff",
+    ClientSecrets = { new Secret("secret".Sha256()) },
+
+    AllowedGrantTypes = GrantTypes.Code,
+    
+    // where to redirect to after login
+    RedirectUris = { "https://localhost:5003/signin-oidc" },
+
+    // where to redirect to after logout
+    PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },
+
+    AllowedScopes = new List
+    {
+        IdentityServerConstants.StandardScopes.OpenId,
+        IdentityServerConstants.StandardScopes.Profile,
+        "api1"
+    }
+}
+```
+
+## 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.
+
+![image](../../images/jsbff_not_logged_in.png)
+
+When you click the login button, you'll be redirected to IdentityServer to
+login. After you login, you'll be redirected back to the *JavaScriptClient*
+application, where you'll be signed into 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.
+ 
+![image](../../images/jsbff_logged_in.png)
+
+Finally, the logout button should successfully get the user logged out.
+
+![image](../../images/jsbff_signed_out.png)
+
+
+## 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
+
+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-6.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 the local API:
+```cs
+[Authorize] 
+static IResult LocalIdentityHandler(ClaimsPrincipal user)
+{
+    var name = user.FindFirst("name")?.Value ?? user.FindFirst("sub")?.Value;
+    return Results.Json(new { message = "Local API Success!", user = name });
+}
+
+```
+
+{{% notice note %}}
+
+Local APIs often make requests to remote APIs that are authorized with the
+user's access token. To get the access token, call the *GetUserAccessTokenAsync*
+extension method on the *HttpContext*. For example: *var token = await
+HttpContext.GetUserAccessTokenAsync();*
+
+{{% /notice %}}
+
+### 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 *UseEndpoints*
+call in *src/JavaScriptClient/Program.cs*.
+
+```cs
+app.UseEndpoints(endpoints =>
+{
+    endpoints.MapBffManagementEndpoints();
+
+    // Uncomment this for Controller support
+    // endpoints.MapControllers()
+    //     .AsBffApiEndpoint();
+
+    endpoints.MapGet("/local/identity", LocalIdentityHandler)
+        .AsBffApiEndpoint();
+
+    endpoints.MapRemoteBffApiEndpoint("/remote", "https://localhost:6001")
+        .RequireAccessToken(Duende.Bff.TokenType.User);
+
+});
+```
+The call to the *AsBffApiEndpoint()* fluent helper method adds BFF support to
+the local APIs. This includes anti-forgery protection as well as 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
+
+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 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 a *'X-CSRF': '1'* header, which acts as the
+anti-forgery token.
+
+{{% notice note %}}
+
+See the [client credentials quickstart]({{< ref
+"/quickstarts/1_client_credentials" >}}) for information on how to create the
+remote API used in the code above. 
+
+{{% /notice %}}
+
+## 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:
+
+![image](../../images/jsbff_local_api.png)
+
+And the remote API should return something like this:
+
+![image](../../images/jsbff_remote_api.png)
+
+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*.
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/quickstarts/js_clients/js_without_backend.md b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_without_backend.md
new file mode 100644
index 00000000..b48b4c45
--- /dev/null
+++ b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_without_backend.md
@@ -0,0 +1,396 @@
+---
+title: "JavaScript applications without a backend"
+weight: 20
+---
+
+{{% notice note %}}
+
+We recommend you do the quickstarts in order. If you'd like to start here, begin
+from a copy of the [reference implementation of Quickstart 3]({{< param qs_base >}}/3_AspNetCoreAndApis).
+Throughout this quickstart, paths are written relative to the base *quickstart*
+directory created in part 1, which is the root directory of the reference
+implementation. You will also need to [install the IdentityServer templates]({{< ref "0_overview#preparation" >}}).
+
+{{% /notice %}}
+
+
+This quickstart will show how to build a browser-based JavaScript client
+application without a backend. This means your application has no server-side
+code that can support the frontend application code, and thus all OpenID
+Connect/OAuth protocol interactions occur from the JavaScript code running in
+the browser. Also, invoking the API will be performed directly from the
+JavaScript in the browser.
+
+This design adds complexity (and thus security concerns) to your application, so
+consider if the ["BFF" pattern]({{}}) might be a better
+choice.
+
+In this quickstart the user will login 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
+
+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/JavaScriptClient.csproj
+```
+
+### Modify hosting
+
+Modify the *JavaScriptClient* project to run on *https://localhost:5003*. Its
+*Properties/launchSettings.json* should look like this:
+
+```json
+{
+  "profiles": {
+    "JavaScriptClient": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "applicationUrl": "https://localhost:5003",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}
+```
+
+### Add the static file middleware
+
+Given that this project is designed to run client-side, all we need ASP.NET Core
+to do is to serve up the static HTML and JavaScript files that will make up our
+application. The static file middleware is designed to do this.
+
+Register the static file middleware in *src/JavaScriptClient/Program.cs*. The
+entire file should look like this:
+
+```cs
+var builder = WebApplication.CreateBuilder(args);
+var app = builder.Build();
+
+app.UseDefaultFiles();
+app.UseStaticFiles();
+app.Run();
+```
+
+This middleware will now serve up static files from the application's
+*src/JavaScriptClient/wwwroot* directory. This is where we will put our HTML and
+JavaScript files. If that directory does not exist in your project, create it
+now.
+
+### Reference oidc-client
+
+In the prior [web application quickstart]({{}}), 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 your 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();
+}
+```
+
+{{% notice note %}}
+
+See the [client credentials quickstart]({{< ref
+"/quickstarts/1_client_credentials" >}}) for information on how to create the
+remote API used in the code above. 
+
+{{% /notice %}}
+
+**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 a client registration to IdentityServer for the JavaScript client
+
+Now that the client application is ready to go, you need to define a
+configuration entry in IdentityServer for the new JavaScript client.
+
+In the IdentityServer project locate the client configuration in
+*src/IdentityServer/Config.cs*. Add a new *Client* to the list for your new
+JavaScript application. It should have the configuration listed below:
+
+```cs
+// JavaScript Client
+new Client
+{
+    ClientId = "js",
+    ClientName = "JavaScript Client",
+    AllowedGrantTypes = GrantTypes.Code,
+    RequireClientSecret = false,
+    
+    RedirectUris =           { "https://localhost:5003/callback.html" },
+    PostLogoutRedirectUris = { "https://localhost:5003/index.html" },
+    AllowedCorsOrigins =     { "https://localhost:5003" },
+
+    AllowedScopes = 
+    {
+        IdentityServerConstants.StandardScopes.OpenId,
+        IdentityServerConstants.StandardScopes.Profile,
+        "api1"
+    }
+}
+```
+
+## Allowing Ajax calls to the Web API with CORS
+
+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 services to the dependency injection system in
+*src/Api/Program.cs*:
+
+```cs
+builder.Services.AddCors(options =>
+{
+    // this defines a CORS policy called "default"
+    options.AddPolicy("default", policy =>
+    {
+        policy.WithOrigins("https://localhost:5003")
+            .AllowAnyHeader()
+            .AllowAnyMethod();
+    });
+});
+```
+
+Then add the CORS middleware to the pipeline in *src/Api/Program.cs*. It should
+come before the call to *UseAuthentication*.
+
+```cs
+app.UseHttpsRedirection();
+
+app.UseCors("default");
+
+app.UseAuthentication();
+app.UseAuthorization();
+
+```
+
+## Run the JavaScript application
+
+Now you should be able to run the JavaScript client application:
+
+![image](../../images/jsclient_not_logged_in.png)
+
+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:
+ 
+![image](../../images/jsclient_logged_in.png)
+
+And click the "API" button to invoke the web API:
+
+![image](../../images/jsclient_api_results.png)
+
+And finally click "Logout" to sign the user out.
+
+![image](../../images/jsclient_signed_out.png)
+
+You now have the start of a JavaScript client application that uses
+IdentityServer for sign-in, sign-out, and authenticating calls to web APIs.
+
+{{% notice note %}}
+
+Some browsers limit cross-site interactions (especially in iframes). In Safari,
+Firefox, or Brave you will notice that some important features will not work
+such as silent token renewal and check session monitoring.
+
+{{% /notice %}}
diff --git a/IdentityServer/v7/docs/content/reference/_index.md b/IdentityServer/v7/docs/content/reference/_index.md
new file mode 100644
index 00000000..203718b4
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/_index.md
@@ -0,0 +1,11 @@
++++
+title = "Reference"
+date = 2020-09-10T08:20:20+02:00
+weight = 200
+chapter = true
+description = "Index"
++++
+
+# Reference
+
+{{%children style="h4" /%}}
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/di.md b/IdentityServer/v7/docs/content/reference/di.md
new file mode 100644
index 00000000..7a55d6a0
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/di.md
@@ -0,0 +1,200 @@
+---
+title: "DI Extension Methods"
+date: 2020-09-10T08:22:12+02:00
+weight: 20
+---
+
+*AddIdentityServer* return a builder object that provides many extension methods to add IdentityServer specific services to DI. Here's a list grouped by feature areas.
+
+```cs
+public void ConfigureServices(IServiceCollection services)
+{
+    var builder = services.AddIdentityServer();
+}
+```
+
+{{% notice note %}}
+Many of the fundamental configuration settings can be set on the options. See the *[IdentityServerOptions]({{< ref "reference/options" >}})* reference for more details.
+{{% /notice %}}
+
+
+## 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]({{}}) 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
+
+Extension methods to enable [caching for configuration data]({{}}):
+
+* ***AddInMemoryCaching\***
+    
+    To use any of the caches described below, an implementation of *ICache\* must be registered in DI.
+    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
+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
+Duende IdentityServer needs key material to sign tokens. This key material can
+either be created and 
+[managed automatically]({{< ref "/fundamentals/keys/automatic_key_management" >}}) 
+or 
+[configured statically]({{< ref "/fundamentals/keys/static_key_management" >}}).
+
+{{% notice note %}}
+
+We recommend that you use automatic key management. This section covers the
+configuration methods needed for manual configuration of signing keys, which are
+usually only needed if your license does not include automatic key management or
+if you are [migrating]({{< ref "/fundamentals/keys/migration" >}}) from manually
+managed keys to automatic key management.
+
+{{% /notice %}}
+
+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
+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]({{< ref "profile_service" >}})* 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 a 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.
+
diff --git a/IdentityServer/v7/docs/content/reference/efoptions/_index.md b/IdentityServer/v7/docs/content/reference/efoptions/_index.md
new file mode 100644
index 00000000..c5ff7c9a
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/efoptions/_index.md
@@ -0,0 +1,11 @@
++++
+title = "EF Options"
+weight = 11
+chapter = true
++++
+
+# Entity Framework Core Options
+
+If using the [Entity Framework Core store implementation]({{}}), you might need to configure those specific options.
+
+{{%children style="h4" /%}}
diff --git a/IdentityServer/v7/docs/content/reference/efoptions/configuration.md b/IdentityServer/v7/docs/content/reference/efoptions/configuration.md
new file mode 100644
index 00000000..1435571c
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/efoptions/configuration.md
@@ -0,0 +1,81 @@
+---
+title: "Configuration Options"
+description: "Entity Framework"
+weight: 20
+---
+
+#### Duende.IdentityServer.EntityFramework.Options.ConfigurationStoreOptions
+
+These options are configurable when using the Entity Framework Core for the [configuration store]({{}}):
+
+You set the options at startup time in your *AddConfigurationStore* method:
+
+```cs
+var builder = services.AddIdentityServer()
+    .AddConfigurationStore(options =>
+    {
+        // configure options here..
+    })
+```
+
+## 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
+
+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***
+
diff --git a/IdentityServer/v7/docs/content/reference/efoptions/operational.md b/IdentityServer/v7/docs/content/reference/efoptions/operational.md
new file mode 100644
index 00000000..83a6c90d
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/efoptions/operational.md
@@ -0,0 +1,71 @@
+---
+title: "Operational Options"
+weight: 10
+---
+
+#### Duende.IdentityServer.EntityFramework.Options.OperationalStoreOptions
+
+These options are configurable when using the Entity Framework Core for the [operational store]({{}}):
+
+You set the options at startup time in your *AddOperationalStore* method:
+
+```cs
+var builder = services.AddIdentityServer()
+    .AddOperationalStore(options =>
+    {
+        // configure options here..
+    })
+```
+
+## 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
+
+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
+
+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 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 at a time. Defaults to *100*.
+
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/_index.md b/IdentityServer/v7/docs/content/reference/endpoints/_index.md
new file mode 100644
index 00000000..d174b4cf
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/_index.md
@@ -0,0 +1,9 @@
++++
+title = "Endpoints"
+weight = 30
+chapter = true
++++
+
+# Endpoints
+
+{{%children style="h4" /%}}
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/authorize.md b/IdentityServer/v7/docs/content/reference/endpoints/authorize.md
new file mode 100644
index 00000000..e958e16b
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/authorize.md
@@ -0,0 +1,146 @@
+---
+title: "Authorize Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 2
+---
+
+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
+
+* ***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
+    
+* ***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 of them as a JWT
+
+* ***request_uri***
+
+    URL of a pre-packaged JWT containing request parameters
+
+```
+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
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically create authorize request URLs from .NET code. 
+
+```cs
+var ru = new RequestUrl("https://demo.duendesoftware.com/connect/authorize");
+
+var url = ru.CreateAuthorizeUrl(
+    clientId: "client",
+    responseType: "code",
+    redirectUri: "https://app.com/callback",
+    scope: "openid");
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/ciba.md b/IdentityServer/v7/docs/content/reference/endpoints/ciba.md
new file mode 100644
index 00000000..4ebb9b27
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/ciba.md
@@ -0,0 +1,144 @@
+---
+title: "Backchannel Authentication Endpoint"
+weight: 9
+---
+
+The backchannel authentication endpoint is used by a client to initiate a [CIBA]({{< ref "/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
+
+* ***scope***
+
+    one or more registered scopes
+
+{{% notice note %}}
+The client id and a client credential is required to authenticate to the endpoint using any valid form of authentication that has been configured for it (much like the token endpoint).
+{{% /notice %}}
+
+### Exactly one of these values is required
+
+* ***login_hint***
+    
+    hint for the end user to be authenticated. the value used is implementaiton 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 implementaiton specific.
+
+{{% notice note %}}
+To validate these implementation specific values and use them to identity the user that is to be authenticated, you are required to implement the *IBackchannelAuthenticationUserValidator* interface.
+{{% /notice %}}
+
+### 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 of them as a JWT
+
+
+```
+POST /connect/ciba
+
+    client_id=client1&
+    client_secret=secret&
+    scope=openid api1&
+    login_hint=alice
+```
+
+And a successful response will look something like:
+
+```
+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
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically interact with the protocol endpoint from .NET code.
+
+```cs
+using IdentityModel.Client;
+
+var client = new HttpClient();
+
+var cibaResponse = await client.RequestBackchannelAuthenticationAsync(new BackchannelAuthenticationRequest
+{
+    Address = "https://demo.duendesoftware.com/connect/ciba",
+    ClientId = "client1",
+    ClientSecret = "secret",
+    Scope = "openid api1",
+    LoginHint = "alice",
+});
+```
+
+And with a successful response, it can be used to poll the token endpoint:
+
+```cs
+while (true)
+{
+    var response = await client.RequestBackchannelAuthenticationTokenAsync(new BackchannelAuthenticationTokenRequest
+    {
+        Address = "https://demo.duendesoftware.com/connect/token",
+        ClientId = "client1",
+        ClientSecret = "secret",
+        AuthenticationRequestId = cibaResponse.AuthenticationRequestId
+    });
+
+    if (response.IsError)
+    {
+        if (response.Error == OidcConstants.TokenErrors.AuthorizationPending || response.Error == OidcConstants.TokenErrors.SlowDown)
+        {
+            await Task.Delay(cibaResponse.Interval.Value * 1000);
+        }
+        else
+        {
+            throw new Exception(response.Error);
+        }
+    }
+    else
+    {
+        // success! use response.IdentityToken, response.AccessToken, and response.RefreshToken (if requested)
+    }
+}
+```
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/device_authorization.md b/IdentityServer/v7/docs/content/reference/endpoints/device_authorization.md
new file mode 100644
index 00000000..d16229b3
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/device_authorization.md
@@ -0,0 +1,43 @@
+---
+title: "Device Authorization Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 8
+---
+
+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
+
+```
+POST /connect/deviceauthorization
+
+    client_id=client1&
+    client_secret=secret&
+    scope=openid api1
+```
+
+## .NET client library
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically interact with the protocol endpoint from .NET code.
+
+```cs
+using IdentityModel.Client;
+
+var client = new HttpClient();
+
+var response = await client.RequestDeviceAuthorizationAsync(new DeviceAuthorizationRequest
+{
+    Address = "https://demo.duendesoftware.com/connect/device_authorize",
+    ClientId = "device"
+});
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/discovery.md b/IdentityServer/v7/docs/content/reference/endpoints/discovery.md
new file mode 100644
index 00000000..985fa06c
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/discovery.md
@@ -0,0 +1,20 @@
+---
+title: "Discovery Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 1
+---
+
+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.:
+
+    https://demo.identityserver.io/.well-known/openid-configuration
+
+## .NET client library
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically interact with the protocol endpoint from .NET code.
+
+```cs
+var client = new HttpClient();
+
+var disco = await client.GetDiscoveryDocumentAsync("https://demo.duendesoftware.com");
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/end_session.md b/IdentityServer/v7/docs/content/reference/endpoints/end_session.md
new file mode 100644
index 00000000..037d1740
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/end_session.md
@@ -0,0 +1,46 @@
+---
+title: "End Session Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 7
+---
+
+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 round-trip state across the redirect.
+
+
+```
+    GET /connect/endsession?id_token_hint=...&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A7017%2Findex.html
+```
+
+## .NET client library
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically create end sessions request URLs from .NET code. 
+
+```cs
+var ru = new RequestUrl("https://demo.duendesoftware.com/connect/end_session");
+
+var url = ru.CreateEndSessionUrl(
+    idTokenHint: "...",
+    postLogoutRedirectUri: "...");
+```
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/introspection.md b/IdentityServer/v7/docs/content/reference/endpoints/introspection.md
new file mode 100644
index 00000000..8a19faf3
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/introspection.md
@@ -0,0 +1,54 @@
+---
+title: "Introspection Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 5
+---
+
+The introspection endpoint is an implementation of [RFC 7662](https://tools.ietf.org/html/rfc7662).
+
+It can be used to validate reference tokens (or JWTs if the consumer does not have support for appropriate JWT or cryptographic libraries).
+The introspection endpoint requires authentication - since the client of an introspection endpoint is an API, you configure the secret on the *ApiResource*.
+
+```
+POST /connect/introspect
+Authorization: Basic xxxyyy
+
+token=
+```
+
+A successful response will return a status code of 200 and either an active or inactive token::
+
+```
+{
+    "active": true,
+    "sub": "123"
+}
+```
+
+Unknown or expired tokens will be marked as inactive::
+
+```
+{
+    "active": false,
+}
+```
+
+An invalid request will return a 400, an unauthorized request 401.
+
+## .NET client library
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically interact with the protocol endpoint from .NET code. 
+
+```cs
+using IdentityModel.Client;
+
+var client = new HttpClient();
+
+var response = await client.IntrospectTokenAsync(new TokenIntrospectionRequest
+{
+    Address = "https://demo.duendesoftware.com/connect/introspect",
+    ClientId = "api1",
+    ClientSecret = "secret",
+
+    Token = accessToken
+});
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/revocation.md b/IdentityServer/v7/docs/content/reference/endpoints/revocation.md
new file mode 100644
index 00000000..cf809702
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/revocation.md
@@ -0,0 +1,43 @@
+---
+title: "Revocation Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 6
+---
+
+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)
+
+```
+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
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically interact with the protocol endpoint from .NET code.
+
+```cs
+using 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
+});
+```
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/token.md b/IdentityServer/v7/docs/content/reference/endpoints/token.md
new file mode 100644
index 00000000..a1c887f2
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/token.md
@@ -0,0 +1,111 @@
+---
+title: "Token Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 3
+---
+
+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](http://openid.net/specs/openid-connect-core-1_0.html#TokenRequest).
+
+### 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
+
+* ***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)
+
+```
+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
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically interact with the protocol endpoint from .NET code.
+
+```cs
+using 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"
+});
+```
diff --git a/IdentityServer/v7/docs/content/reference/endpoints/userinfo.md b/IdentityServer/v7/docs/content/reference/endpoints/userinfo.md
new file mode 100644
index 00000000..a9cbf160
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/endpoints/userinfo.md
@@ -0,0 +1,42 @@
+---
+title: "UserInfo Endpoint"
+date: 2020-09-10T08:22:12+02:00
+weight: 4
+---
+
+The UserInfo endpoint can be used to retrieve claims about a user (see [spec](http://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).
+
+```
+GET /connect/userinfo
+Authorization: Bearer 
+```
+
+```
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+    "sub": "248289761001",
+    "name": "Bob Smith",
+    "given_name": "Bob",
+    "family_name": "Smith"
+}
+```
+
+## .NET client library
+You can use the [IdentityModel](https://identitymodel.readthedocs.io) client library to programmatically interact with the protocol endpoint from .NET code.
+
+```cs
+using IdentityModel.Client;
+
+var client = new HttpClient();
+
+var response = await client.GetUserInfoAsync(new UserInfoRequest
+{
+    Address = disco.UserInfoEndpoint,
+    Token = token
+});
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/models/_index.md b/IdentityServer/v7/docs/content/reference/models/_index.md
new file mode 100644
index 00000000..3fbe8701
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/_index.md
@@ -0,0 +1,10 @@
++++
+title = "Models"
+description = "Reference"
+weight = 40
+chapter = true
++++
+
+# Models
+
+{{%children style="h4" /%}}
diff --git a/IdentityServer/v7/docs/content/reference/models/api_resource.md b/IdentityServer/v7/docs/content/reference/models/api_resource.md
new file mode 100644
index 00000000..b42e5c65
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/api_resource.md
@@ -0,0 +1,84 @@
+---
+title: "API Resource"
+description: "Model Reference"
+date: 2020-09-10T08:22:12+02:00
+weight: 30
+---
+
+#### Duende.IdentityServer.Models.ApiResource
+
+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]({{< ref "api_scope.md" >}}).
+
+## Defining API resources in appsettings.json
+The *AddInMemoryApiResource* extensions method also supports adding API resources from the ASP.NET Core configuration file::
+
+    "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:
+
+```cs
+builder.AddInMemoryApiResources(configuration.GetSection("IdentityServer:ApiResources"))
+```
diff --git a/IdentityServer/v7/docs/content/reference/models/api_scope.md b/IdentityServer/v7/docs/content/reference/models/api_scope.md
new file mode 100644
index 00000000..dfc8e72f
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/api_scope.md
@@ -0,0 +1,68 @@
+---
+title: "API Scope"
+description: "Model Reference"
+date: 2020-09-10T08:22:12+02:00
+weight: 25
+---
+
+#### Duende.IdentityServer.Models.ApiScope
+
+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
+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:
+
+```cs
+builder.AddInMemoryApiScopes(configuration.GetSection("IdentityServer:ApiScopes"))
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/models/ciba_login_request.md b/IdentityServer/v7/docs/content/reference/models/ciba_login_request.md
new file mode 100644
index 00000000..021f0331
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/ciba_login_request.md
@@ -0,0 +1,46 @@
+---
+title: "Backchannel User Login Request"
+weight: 80
+---
+
+#### Duende.IdentityServer.Models.BackchannelUserLoginRequest
+
+Models the information to initiate a user login request for [CIBA]({{< ref "/ui/ciba">}}).
+
+* ***InternalId***
+    
+    Ihe identifier of the request in the store.
+
+* ***Subject***
+    
+    The subject for whom the login request is intended.
+
+* ***BindingMessage***
+    
+    The binding message used in the request.
+
+* ***AuthenticationContextReferenceClasses***
+    
+    The acr_values used in the request.
+
+* ***Tenant***
+    
+    The tenant value from the acr_values used the request.
+
+* ***IdP***
+    
+    The idp value from the acr_values used in the request.
+
+* ***RequestedResourceIndicators***
+    
+    The resource indicator values used in the request.
+
+* ***Client***
+    
+    The client that initiated the request.
+
+* ***ValidatedResources***
+    
+    The validated resources (i.e. scopes) used in the request.
+
+
diff --git a/IdentityServer/v7/docs/content/reference/models/client.md b/IdentityServer/v7/docs/content/reference/models/client.md
new file mode 100644
index 00000000..2e344195
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/client.md
@@ -0,0 +1,309 @@
+---
+title: "Client"
+description: "Model Reference"
+date: 2020-09-10T08:22:12+02:00
+weight: 35
+---
+
+#### Duende.IdentityServer.Models.Client
+
+The *Client* class models an OpenID Connect or OAuth 2.0 client - 
+e.g. a native application, a web application or a JS-based application.
+
+```cs
+public static IEnumerable Get()
+{
+    return new List
+    {
+        ///////////////////////////////////////////
+        // machine to machine client
+        //////////////////////////////////////////
+        new Client
+        {
+            ClientId = "machine",
+            ClientSecrets = { Configuration["machine.secret"] },
+            
+            AllowedGrantTypes = GrantTypes.ClientCredentials,
+            
+            AllowedScopes = machineScopes
+        },
+
+        ///////////////////////////////////////////
+        // web client
+        //////////////////////////////////////////
+        new Client
+        {
+            ClientId = "web",
+            
+            ClientSecrets = { new Secret(Configuration["web.secret"]) },
+
+            AllowedGrantTypes = GrantTypes.Code,
+            
+            RedirectUris = { "https://myapp.com:/signin-oidc" },
+            PostLogoutRedirectUris = { "https://myapp.com/signout-callback-oidc" },
+
+            BackChannelLogoutUri = "https://myapp.com/backchannel-logout",
+
+            AllowOfflineAccess = true,
+            AllowedScopes = webScopes
+        }
+    }
+}
+```
+
+## Basics
+
+* ***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
+
+* ***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 *CoordinateTokensWithUserSession* configuration setting.
+
+
+## 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
+
+* ***AbsoluteRefreshTokenLifetime***
+    
+    Maximum lifetime of a refresh token in seconds. Defaults to 2592000 seconds / 30 days
+
+* ***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    
+    
+    * ***OneTime*** 
+    
+        the refresh token handle will be updated when refreshing tokens. This is the default.
+
+* ***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
+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.
+
+## 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
+Client initiated backchannel authentication specific settings.
+
+* ***CibaLifetime***
+    
+    Specifies the backchannel authentication request lifetime in seconds. Defaults to *null*.
+
+* ***PollingInterval***
+
+    Backchannel polling interval in seconds. Defaults to *null*.
+
+## DPoP
+Added in 6.3.0.
+
+Settings specific to the Demonstration of Proof-of-Possession at the Application Layer ([DPoP]({{< ref "/tokens/pop/dpop" >}})) feature.
+
+* ***RequireDPoP***
+    
+    Specifies whether a DPoP (Demonstrating Proof-of-Possession) token is requied 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
+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.
+
diff --git a/IdentityServer/v7/docs/content/reference/models/grant_validation_result.md b/IdentityServer/v7/docs/content/reference/models/grant_validation_result.md
new file mode 100644
index 00000000..c683bd47
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/grant_validation_result.md
@@ -0,0 +1,61 @@
+---
+title: "Grant Validation Result"
+date: 2020-09-10T08:22:12+02:00
+weight: 45
+---
+
+#### Duende.IdentityServer.Validation.GrantValidationResult
+
+The *GrantValidationResult* class models the outcome of grant validation for [extensions grants]({{< ref "/tokens/extension_grants" >}}) and  [resource owner password grants]({{< ref "/tokens/password_grant" >}}).
+
+It models either a successful validation result with claims (e.g. subject ID) or an invalid result with an error code and message, e.g.:
+
+```cs
+public class ExtensionGrantValidator : IExtensionGrantValidator
+{
+    public Task ValidateAsync(ExtensionGrantValidationContext context)
+    {
+        // some validation steps 
+
+        if (success)
+        {
+            context.Result = new GrantValidationResult(
+                subject: "818727", 
+                authenticationMethod: "custom",
+                claims: extraClaims);
+        }
+        else
+        {
+            // custom error message
+            context.Result = new GrantValidationResult(
+                TokenRequestErrors.InvalidGrant, 
+                "invalid custom credential");
+        }
+
+        return Task.CompletedTask;
+    }
+}
+```
+
+It also allows passing additional custom values that will be included in the token response, e.g.:
+
+```cs
+context.Result = new GrantValidationResult(
+    subject: "818727",
+    authenticationMethod: "custom",
+    customResponse: new Dictionary
+    {
+        { "some_data", "some_value" }
+    });
+```
+
+This will result in the following token response:
+
+```json
+{
+    "access_token": "...",
+    "token_type": "Bearer",
+    "expires_in": 360,
+    "some_data": "some_value"
+}
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/models/identity_resource.md b/IdentityServer/v7/docs/content/reference/models/identity_resource.md
new file mode 100644
index 00000000..37448797
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/identity_resource.md
@@ -0,0 +1,58 @@
+---
+title: "Identity Resource"
+description: "Reference"
+date: 2020-09-10T08:22:12+02:00
+weight: 20
+---
+
+#### Duende.IdentityServer.Models.IdentityResource
+
+This class models an identity resource.
+
+```cs
+public static readonly IEnumerable IdentityResources =
+    new[]
+    {
+        // some standard scopes from the OIDC spec
+        new IdentityResources.OpenId(),
+        new IdentityResources.Profile(),
+        new IdentityResources.Email(),
+
+        // custom identity resource with some associated claims
+        new IdentityResource("custom.profile", 
+            userClaims: new[] { JwtClaimTypes.Name, JwtClaimTypes.Email, "location", JwtClaimTypes.Address })
+    };
+```
+
+* ***Enabled***
+
+    Indicates if this resource is enabled and can be requested. Defaults to true.
+
+* ***Name***
+    
+    The unique name of the identity resource. This is the value a client will use for the scope parameter in the authorize request.
+
+* ***DisplayName***
+    
+    This value will be used e.g. on the consent screen.
+
+* ***Description***
+    
+    This value will be used e.g. on the consent screen.
+
+* ***Required***
+    
+    Specifies whether the user can de-select the scope on the consent screen (if the consent screen wants to implement such a feature). 
+    Defaults to false.
+
+* ***Emphasize***
+    
+    Specifies whether the consent screen will emphasize this scope (if the consent screen wants to implement such a feature). Use this setting for sensitive or important scopes. Defaults to false.
+
+* ***ShowInDiscoveryDocument***
+    
+    Specifies whether this scope is shown in the discovery document. Defaults to *true*.
+
+* ***UserClaims***
+
+    List of associated user claim types that should be included in the identity token.
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/models/idp.md b/IdentityServer/v7/docs/content/reference/models/idp.md
new file mode 100644
index 00000000..cb99e8c0
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/idp.md
@@ -0,0 +1,77 @@
+---
+title: "Identity Provider"
+date: 2020-09-10T08:22:12+02:00
+weight: 35
+---
+
+#### Duende.IdentityServer.Models.OidcProvider
+
+The *OidcProvider* models an external OpenID Connect provider for use in the [dynamic providers]({{< ref "/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
+
+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
+
+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).
diff --git a/IdentityServer/v7/docs/content/reference/models/secrets.md b/IdentityServer/v7/docs/content/reference/models/secrets.md
new file mode 100644
index 00000000..b1280aeb
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/models/secrets.md
@@ -0,0 +1,99 @@
+---
+title: "Secrets"
+date: 2020-09-10T08:22:12+02:00
+weight: 70
+---
+
+#### Duende.IdentityServer.Validation.ISecretParser
+
+Parses a secret from the raw HTTP request.
+
+```cs
+public interface ISecretParser
+{
+    /// 
+    /// Tries to find a secret on the context that can be used for authentication
+    /// 
+    /// The HTTP context.
+    /// A parsed secret
+    Task ParseAsync(HttpContext context);
+
+    /// 
+    /// Returns the authentication method name that this parser implements
+    /// 
+    /// The authentication method.
+    string AuthenticationMethod { get; }
+}
+```
+
+* ***AuthenticationMethod***
+
+    The name of the authentication method that this parser registers for. This value must be unique and will be displayed in the discovery document.
+
+* ***ParseAsync***
+
+    The job of this method is to extract the secret from the HTTP request and parse it into a *ParsedSecret*
+
+
+#### Duende.IdentityServer.Model.ParsedSecret
+
+Represents a parsed secret.
+
+```cs
+/// 
+/// Represents a secret extracted from the HttpContext
+/// 
+public class ParsedSecret
+{
+    /// 
+    /// Gets or sets the identifier associated with this secret
+    /// 
+    /// 
+    /// The identifier.
+    /// 
+    public string Id { get; set; }
+
+    /// 
+    /// Gets or sets the credential to verify the secret
+    /// 
+    /// 
+    /// The credential.
+    /// 
+    public object Credential { get; set; }
+
+    /// 
+    /// Gets or sets the type of the secret
+    /// 
+    /// 
+    /// The type.
+    /// 
+    public string Type { get; set; }
+
+    /// 
+    /// Gets or sets additional properties.
+    /// 
+    /// 
+    /// The properties.
+    /// 
+    public Dictionary Properties { get; set; } = new Dictionary();
+}
+```
+
+The parsed secret is forwarded to the registered secret validator. The validator will typically inspect the *Type* property to determine if this secret is something that can be validated by that validator instance. If yes, it will know how to cast the *Credential* object into a format that is understood.
+
+#### Duende.IdentityServer.Validation.ISecretParser
+
+Validates a parsed secret.
+
+```cs
+public interface ISecretValidator
+{
+    /// Validates a secret
+    /// The stored secrets.
+    /// The received secret.
+    /// A validation result
+    Task ValidateAsync(
+      IEnumerable secrets,
+      ParsedSecret parsedSecret);
+}
+```
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/options.md b/IdentityServer/v7/docs/content/reference/options.md
new file mode 100644
index 00000000..72475200
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/options.md
@@ -0,0 +1,690 @@
+---
+title: "IdentityServer Options"
+weight: 10
+---
+
+#### Duende.IdentityServer.Configuration.IdentityServerOptions
+
+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:
+
+```cs
+var builder = services.AddIdentityServer(options =>
+{
+    // configure options here..
+})
+```
+
+## 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.
+
+* ***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* claim in all access tokens with the format *issuer/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
+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
+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.
+
+* ***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
+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:
+
+```cs
+var builder = services.AddIdentityServer(options =>
+{
+    options.Discovery.CustomEntries.Add("my_setting", "foo");
+    options.Discovery.CustomEntries.Add("my_complex_setting",
+        new
+        {
+            foo = "foo",
+            bar = "bar"
+        });
+});
+```
+
+* ***ExpandRelativePathsInCustomEntries***
+Expands paths in custom entries that begin with "~/" into absolute paths below the IdentityServer base address. Defaults to true. In the following example, if IdentityServer's base address is *https://localhost:5001*, then *my_custom_endpoint*'s value will be expanded to *https://localhost:5001/custom*.
+
+```cs
+options.Discovery.CustomEntries.Add("my_custom_endpoint", "~/custom");
+```
+
+## Authentication
+
+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 6.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
+Configures which [events]({{< ref "/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
+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 excluded from logging. Defaults to *id_token_hint*.
+
+* ***TokenRequestSensitiveValuesFilter***
+    
+    Collection of parameter names passed to the token endpoint that are considered sensitive and will be excluded from logging. Defaults to *client_secret*, *password*, *client_assertion*, *refresh_token*, and *device_code*.
+
+* ***BackchannelAuthenticationRequestSensitiveValuesFilter***
+  
+    Collection of parameter names passed to the backchannel authentication endpoint that are considered senstivie and will be excluded from logging. Defaults to *client_secret*, *client_assertion*, and *id_token_hint*.
+
+* ***UnhandledExceptionLoggingFilter*** (Added in 6.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 *TaskCanceledException*s 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.
+
+## 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
+
+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*.
+
+
+## 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
+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
+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
+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***
+
+    Defines the minimum allowed polling interval on the token endpoint. Defaults to *5*.
+
+## Mutual TLS
+[Mutual TLS]({{< ref "/tokens/authentication/mtls" >}}) settings. Available on the *MutualTls* property of the *IdentityServerOptions* object.
+
+```cs
+var builder = services.AddIdentityServer(options =>
+{
+    options.MutualTls.Enabled = true;
+    
+    // use mtls sub-domain
+    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 sub-domain 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 sub-domain, use a full domain name (e.g. "identityserver-mtls.io") to set a full domain name.
+    When a full domain name is used, you also need to set the *IssuerName* to a fixed value.
+
+* ***AlwaysEmitConfirmationClaim***
+
+    Specifies whether a cnf claim gets emitted for access tokens if a client certificate was present.
+    Normally the cnf claims only gets emitted if the client used the client certificate for authentication,
+    setting this to true, will set the claim regardless of the authentication method. Defaults to false.
+
+## PersistentGrants
+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 6.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
+Settings for [dynamic providers]({{< ref "/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
+[CIBA]({{< ref "/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 polling interval in seconds that a client is to use when connecting to the token endpoint. Defaults to 5.
+
+## Server-side Sessions 
+Settings for [server-side sessions]({{}}). Added in 6.1.  Available on the *ServerSideSessions* property of the *IdentityServerOptions* object.
+
+* ***UserDisplayNameClaimType***
+    
+    Claim type used for the user's display name. Unset by default due to possible PII concerns. If used, this would commonly be *JwtClaimTypes.Name*, *JwtClaimType.Email* or a custom claim.
+
+* ***RemoveExpiredSessions***
+    
+   Enables periodic cleanup of expired sessions. Defaults to true.
+
+* ***RemoveExpiredSessionsFrequency***
+    
+    Frequency that expired sessions will be removed. Defaults to 10 minutes.
+
+* ***RemoveExpiredSessionsBatchSize***
+    
+    Number of expired session records to be removed at a time. Defaults to 100.
+
+* ***ExpiredSessionsTriggerBackchannelLogout***
+    
+    If enabled, when server-side sessions are removed due to expiration, back-channel logout notifications will be sent.
+    This will, in effect, tie a user's session lifetime at a client to their session lifetime at IdentityServer. Defaults to true.
+
+## 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
+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*.
diff --git a/IdentityServer/v7/docs/content/reference/response_handling/_index.md b/IdentityServer/v7/docs/content/reference/response_handling/_index.md
new file mode 100644
index 00000000..8f787a95
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/response_handling/_index.md
@@ -0,0 +1,9 @@
++++
+title = "Response Generators"
+weight = 55
+chapter = true
++++
+
+# Response Generators
+
+{{%children style="h4" /%}}
diff --git a/IdentityServer/v7/docs/content/reference/response_handling/authorize_interaction_response_generator.md b/IdentityServer/v7/docs/content/reference/response_handling/authorize_interaction_response_generator.md
new file mode 100644
index 00000000..1c5f2d1b
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/response_handling/authorize_interaction_response_generator.md
@@ -0,0 +1,55 @@
+---
+title: "Authorize Interaction Response Generator"
+weight: 10
+---
+
+#### Duende.IdentityServer.ResponseHandling.IAuthorizeInteractionResponseGenerator
+
+The *IAuthorizeInteractionResponseGenerator* interface models the logic for determining if user must login or consent when making requests to the authorization endpoint.
+
+{{% notice note %}}
+If a custom implementation of *IAuthorizeInteractionResponseGenerator* is desired, then it's [recommended]({{}}) to derive from the built-in *AuthorizeInteractionResponseGenerator* to inherit all the default logic pertaining to login and consent semantics.
+{{% /notice %}}
+
+
+## IAuthorizeInteractionResponseGenerator APIs
+
+* ***ProcessInteractionAsync***
+    
+    Returns the *InteractionResponse* based on the *ValidatedAuthorizeRequest* an and optional *ConsentResponse* if the user was shown a consent page.
+
+## InteractionResponse
+
+* ***IsLogin***
+       
+    Specifies if the user must login.
+
+* ***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.
diff --git a/IdentityServer/v7/docs/content/reference/response_handling/token_response_generator.md b/IdentityServer/v7/docs/content/reference/response_handling/token_response_generator.md
new file mode 100644
index 00000000..93d1529d
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/response_handling/token_response_generator.md
@@ -0,0 +1,91 @@
+---
+title: "Token Response Generator"
+weight: 20
+---
+
+## Duende.IdentityServer.ResponseHandling.ITokenResponseGenerator
+
+The *ITokenResponseGenerator* interface is the contract for the service that generates responses to valid requests to the token endpoint. 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 DI system.
+
+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
+
+The *ITokenResponseGenerator* contains a single method to process validated token requests and return token responses.
+
+* ***ProcessInteractionAsync***
+    
+    Returns the *TokenResponse* based on the *ValidatedTokenRequest*.
+
+## 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
+
+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.
\ No newline at end of file
diff --git a/IdentityServer/v7/docs/content/reference/services/_index.md b/IdentityServer/v7/docs/content/reference/services/_index.md
new file mode 100644
index 00000000..5ca8784a
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/services/_index.md
@@ -0,0 +1,9 @@
++++
+title = "Services"
+weight = 50
+chapter = true
++++
+
+# Services
+
+{{%children style="h4" /%}}
diff --git a/IdentityServer/v7/docs/content/reference/services/ciba_interaction_service.md b/IdentityServer/v7/docs/content/reference/services/ciba_interaction_service.md
new file mode 100644
index 00000000..2b99e948
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/services/ciba_interaction_service.md
@@ -0,0 +1,54 @@
+---
+title: "Backchannel Authentication Interaction Service"
+weight: 80
+---
+
+#### Duende.IdentityServer.Services.IBackchannelAuthenticationInteractionService
+
+The *IBackchannelAuthenticationInteractionService* interface provides services for a user to access or complete a login requests for [CIBA]({{< ref "/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
+
+* ***GetPendingLoginRequestsForCurrentUserAsync***
+    
+    Returns a collection of [BackchannelUserLoginRequest]({{< ref "/reference/models/ciba_login_request" >}}) objects which represent pending login requests for the current user.
+
+* ***GetLoginRequestByInternalIdAsync***
+    
+    Returns the [BackchannelUserLoginRequest]({{< ref "/reference/models/ciba_login_request" >}}) object for the id.
+
+* ***CompleteLoginRequestAsync***
+    
+    Completes the login request with the provided *CompleteBackchannelLoginRequest* response for the current user or the subject passed.
+
+
+### CompleteBackchannelLoginRequest
+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.
+
diff --git a/IdentityServer/v7/docs/content/reference/services/ciba_user_notification.md b/IdentityServer/v7/docs/content/reference/services/ciba_user_notification.md
new file mode 100644
index 00000000..c5a3443b
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/services/ciba_user_notification.md
@@ -0,0 +1,16 @@
+---
+title: "Backchannel Authentication User Notification Service"
+weight: 90
+---
+
+#### Duende.IdentityServer.Services.IBackchannelAuthenticationUserNotificationService
+
+The *IBackchannelAuthenticationUserNotificationService* interface is used to contact users when a [CIBA]({{< ref "/ui/ciba">}}) login request has been made.
+To use CIBA, you are expected to implement this interface and register it in the DI system.
+
+## IBackchannelAuthenticationUserNotificationService APIs
+
+* ***SendLoginRequestAsync***
+    
+    Sends a notification for the user to login via the [BackchannelUserLoginRequest]({{< ref "/reference/models/ciba_login_request" >}}) parameter.
+
diff --git a/IdentityServer/v7/docs/content/reference/services/device_flow_interaction_service.md b/IdentityServer/v7/docs/content/reference/services/device_flow_interaction_service.md
new file mode 100644
index 00000000..cd759c2d
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/services/device_flow_interaction_service.md
@@ -0,0 +1,40 @@
+---
+title: "Device Flow Interaction Service"
+date: 2020-09-10T08:22:12+02:00
+weight: 65
+---
+
+#### Duende.IdentityServer.Services.IDeviceFlowInteractionService
+
+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
+
+* ***GetAuthorizationContextAsync***
+    
+    Returns the *DeviceFlowAuthorizationRequest* based on the *userCode* passed to the login or consent pages.
+
+* ***DeviceFlowInteractionResult***
+    
+    Completes device authorization for the given *userCode*.
+
+## DeviceFlowAuthorizationRequest
+
+* ***ClientId***
+    
+    The client identifier that initiated the request.
+
+* ***ScopesRequested***
+    
+    The scopes requested from the authorization request.
+
+## DeviceFlowInteractionResult
+
+* ***IsError***
+    
+    Specifies if the authorization request errored.
+
+* ***ErrorDescription***
+    
+    Error description upon failure.
diff --git a/IdentityServer/v7/docs/content/reference/services/interaction_service.md b/IdentityServer/v7/docs/content/reference/services/interaction_service.md
new file mode 100644
index 00000000..b6a9316b
--- /dev/null
+++ b/IdentityServer/v7/docs/content/reference/services/interaction_service.md
@@ -0,0 +1,229 @@
+---
+title: "IdentityServer Interaction Service"
+date: 2020-09-10T08:22:12+02:00
+weight: 60
+---
+
+#### Duende.IdentityServer.Services.IIdentityServerInteractionService
+
+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
+
+* ***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 round-tripped 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 into during their current session.
+
+## Returned models
+The above methods return various models.
+
+### 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
+
+* ***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
+
+* ***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
+
+* ***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 *\