IdentityServer Quickstarts: Step-by-step tutorials to get started with Duende IdentityServer ----- # IdentityServer Quickstarts > Step-by-step tutorials for implementing common Duende IdentityServer scenarios, from basic setup to advanced features. The quickstarts provide step-by-step instructions for various common Duende IdentityServer scenarios. They start with the absolute basics and become more complex - it is recommended you do them in order. * adding Duende IdentityServer to an ASP.NET Core application * configuring Duende IdentityServer * issuing tokens for various clients * securing web applications and APIs * adding support for EntityFramework based configuration * adding support for ASP.NET Identity Every quickstart has a reference solution. You can find the code in the [samples](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts) folder. ## Preparation [Section titled “Preparation”](#preparation) The first thing you should do is install our templates: Terminal ```bash dotnet new install Duende.Templates ``` They will be used as a starting point for the various tutorials. [YouTube video player](https://www.youtube.com/embed/cxYmODQHErM) ----- # Protecting An API With Client Credentials > Learn how to set up IdentityServer to protect an API using client credentials, implementing server-to-server authentication with access tokens. Welcome to the first quickstart for IdentityServer! To see the full list of quickstarts, please see [Quickstarts Overview](/identityserver/quickstarts/0-overview/). This first quickstart provides step-by-step instructions to set up IdentityServer in the most basic scenario: protecting APIs for server-to-server communication. You will create a solution containing three projects: * An Identity Server * An API that requires authentication * A client that accesses that API The client will request an access token from IdentityServer using its client ID and secret and then use the token to gain access to the API. ## Source Code [Section titled “Source Code”](#source-code) Finished source code for each quickstart in this series is available in the [Samples](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts) repository, and a reference implementation of this quickstart is available [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials). ## Video [Section titled “Video”](#video) In addition to the written steps below there’s also a YouTube video available: [YouTube video player](https://www.youtube.com/embed/EhuCpbH7Ad0) ## Preparation [Section titled “Preparation”](#preparation) The IdentityServer templates for the dotnet CLI are a good starting point for the quickstarts. To install the templates open a console window and type the following command: ```console dotnet new install Duende.Templates ``` ## Create The Solution And IdentityServer Project [Section titled “Create The Solution And IdentityServer Project”](#create-the-solution-and-identityserver-project) In this section, you will create a directory for the solution and use the `isempty` (IdentityServer Empty) template to create an ASP.NET Core application that includes a basic IdentityServer setup. Back in the console, run the following commands to create the directory structure for the solution. ```console mkdir quickstart cd quickstart mkdir src dotnet new sln -n Quickstart ``` This will create a quickstart directory that will serve as the root of the solution, a src subdirectory to hold your source code, and a solution file to organize your projects. Throughout the rest of the quickstart series, paths will be written relative to the quickstart directory. From the new quickstart directory, run the following commands to use the `isempty` template to create a new project. The template creates a web project named IdentityServer with the IdentityServer package installed and minimal configuration added for it. ```console cd src dotnet new duende-is-empty -n IdentityServer ``` This will create the following files within a new `src/IdentityServer` directory: * `Properties/launchSettings.json` file - launch profile * `appsettings.json` - run time settings * `Config.cs` - definitions for [resources](/identityserver/overview/terminology/#resources) and [clients](/identityserver/overview/terminology/#client) used by IdentityServer * `HostingExtensions.cs` - configuration for ASP.NET pipeline and services Notably, the IdentityServer services are configured here and the IdentityServer middleware is added to the pipeline here. * `IdentityServer.csproj` - project file with the IdentityServer NuGet package added * `Program.cs` - main application entry point Next, add the IdentityServer project to the solution. Back in the console, navigate up to the quickstart directory and add the IdentityServer project to the solution. ```console cd .. dotnet sln add ./src/IdentityServer ``` ### Defining An API Scope [Section titled “Defining An API Scope”](#defining-an-api-scope) Scope is a core feature of OAuth that allows you to express the extent or scope of access. Clients request scopes when they initiate the protocol, declaring what scope of access they want. IdentityServer then has to decide which scopes to include in the token. Just because the client has asked for something doesn’t mean they should get it! There are built-in abstractions and extensibility points that you can use to make this decision. Ultimately, IdentityServer issues a token to the client, which then uses the token to access APIs. APIs can check the scopes that were included in the token to make authorization decisions. Scopes don’t have structure imposed by the protocols - they are just space-separated strings. This allows for flexibility when designing the scopes used by a system. In this quickstart, you will create a scope that represents complete access to an API that will be created later in this quickstart. Scope definitions can be loaded in many ways. This quickstart shows how to use a “code as configuration” approach. A minimal Config.cs was created by the template at `src/IdentityServer/Config.cs`. Open it and add an `ApiScope` to the `ApiScopes` property: ```csharp public static IEnumerable ApiScopes => new ApiScope[] { new ApiScope(name: "api1", displayName: "My API") }; ``` See the full file [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/Config.cs). ### Defining The client [Section titled “Defining The client”](#defining-the-client) The next step is to configure a client application that you will use to access the API. You’ll create the client application project later in this quickstart. First, you’ll add configuration for it to your IdentityServer project. In this quickstart, the client will not have an interactive user and will authenticate with IdentityServer using a client secret. Add this client definition to `Config.cs`: ```csharp public static IEnumerable Clients => new Client[] { new Client { ClientId = "client", // no interactive user, use the clientid/secret for authentication AllowedGrantTypes = GrantTypes.ClientCredentials, // secret for authentication ClientSecrets = { new Secret("secret".Sha256()) }, // scopes that client has access to AllowedScopes = { "api1" } } }; ``` Again, see the full file [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/Config.cs). Clients can be configured with many options. Your minimal machine-to-machine client here contains: * A ClientId, which identifies the application to IdentityServer so that it knows which client is trying to connect to it. * A Secret, which you can think of as the password for the client. * The list of scopes that the client is allowed to ask for. Notice that the allowed scope here matches the name of the ApiScope above. ### Configuring IdentityServer [Section titled “Configuring IdentityServer”](#configuring-identityserver) The scope and client definitions are loaded in [HostingExtensions.cs](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/IdentityServer/HostingExtensions.cs). The template created a ConfigureServices method there that is already loading the scopes and clients. You can take a look to see how it is done. Note that the template adds a few things that are not used in this quickstart. Here’s the minimal ConfigureServices method that is needed: Startup.cs ```csharp public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { // Can also be found in Program.cs builder.Services.AddIdentityServer() .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); return builder.Build(); } ``` That’s it - your IdentityServer is now configured. If you run the project and then navigate to `https://localhost:5001/.well-known/openid-configuration` in your browser, you should see the [discovery document](/identityserver/reference/v8/endpoints/discovery/). The discovery document is a standard endpoint in [OpenID Connect](https://openid.net/specs/openid-connect-discovery-1_0.html) and [OAuth](https://datatracker.ietf.org/doc/html/rfc8414). It is used by your clients and APIs to retrieve configuration data needed to request and validate tokens, login and logout, etc. ![Browser showing discovery endpoint JSON](/_astro/1_discovery.CglV0FSW_uefJN.webp) ## Create An API Project [Section titled “Create An API Project”](#create-an-api-project) Next, add an API project to your solution. This API will serve protected resources that will be secured by IdentityServer. You can either use the ASP.NET Core Web API template from Visual Studio or use the .NET CLI to create the API project. To use the CLI, run the following commands: ```console cd src dotnet new webapi -n Api --no-openapi ``` Then navigate back up to the root quickstart directory and add it to the solution by running the following commands: ```console cd .. dotnet sln add ./src/Api ``` ### Add JWT Bearer Authentication [Section titled “Add JWT Bearer Authentication”](#add-jwt-bearer-authentication) Now you will add JWT Bearer Authentication to the API’s ASP.NET pipeline. The goal is to authorize calls to your API using tokens issued by the IdentityServer project. To that end, you will add authentication middleware to the pipeline from the `Microsoft.AspNetCore.Authentication.JwtBearer` NuGet package. This middleware will: * Find and parse a JWT sent with incoming requests as an *Authorization: Bearer* header. * Validate the JWT’s signature to ensure that it was issued by IdentityServer. * Validate that the JWT is not expired. Run this command to add the middleware package to the API: ```console dotnet add ./src/Api package Microsoft.AspNetCore.Authentication.JwtBearer ``` Now add the authentication and authorization services to the Service Collection, and configure the JWT Bearer authentication provider as the default [Authentication Scheme](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-8.0#authentication-scheme). Program.cs ```csharp builder.Services.AddAuthentication() .AddJwtBearer(options => { options.Authority = "https://localhost:5001"; options.TokenValidationParameters.ValidateAudience = false; }); builder.Services.AddAuthorization(); ``` ### Add An Endpoint [Section titled “Add An Endpoint”](#add-an-endpoint) Replace the templated weather forecast endpoint with a new endpoint: ```csharp app.MapGet("identity", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) .RequireAuthorization(); ``` This endpoint will be used to test authorization and to display the claims identity through the eyes of the API. ### Configure API To Listen On Port 6001 [Section titled “Configure API To Listen On Port 6001”](#configure-api-to-listen-on-port-6001) Configure the API to run on `https://localhost:6001` only. You can do this by editing the [launchSettings.json](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/Api/Properties/launchSettings.json) file in the `src/Api/Properties` directory. Change these settings for the `https` profile: ```json { "launchUrl": "identity", "applicationUrl": "https://localhost:6001" } ``` ### Test The Identity Endpoint [Section titled “Test The Identity Endpoint”](#test-the-identity-endpoint) Run the API project using the `https` profile and then navigate to the identity controller at `https://localhost:6001/identity` in a browser. This should return a 401 status code, which means your API requires a credential and is now protected by IdentityServer. ## Create The Client Project [Section titled “Create The Client Project”](#create-the-client-project) The last step is to create a client that requests an access token and then uses that token to access the API. Your client will be a console project in your solution. Run the following commands: ```console cd src dotnet new console -n Client ``` Then as before, add it to your solution using: ```console cd .. dotnet sln add ./src/Client ``` ### Add The IdentityModel NuGet Package [Section titled “Add The IdentityModel NuGet Package”](#add-the-identitymodel-nuget-package) The token endpoint at IdentityServer implements the OAuth protocol, and you could use raw HTTP to access it. However, we have a client library called IdentityModel that encapsulates the protocol interaction in an easy-to-use API. Add the \*Duende.IdentityModel \* NuGet package to your client by running the following command: ```console dotnet add ./src/Client package Duende.IdentityModel ``` ### Retrieve The Discovery Document [Section titled “Retrieve The Discovery Document”](#retrieve-the-discovery-document) IdentityModel includes a client library to use with the discovery endpoint. This way you only need to know the base address of IdentityServer - the actual endpoint addresses can be read from the metadata. Add the following to the client’s Program.cs in the `src/Client/Program.cs` directory: ```csharp using Duende.IdentityModel.Client; // discovery endpoints from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); if (disco.IsError) { Console.WriteLine(disco.Error); Console.WriteLine(disco.Exception); return 1; } ``` ### Request A Token From IdentityServer [Section titled “Request A Token From IdentityServer”](#request-a-token-from-identityserver) Next you can use the information from the discovery document to request a token from `IdentityServer` to access `api1`: ```csharp // request token var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Scope = "api1" }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); Console.WriteLine(tokenResponse.ErrorDescription); return 1; } Console.WriteLine(tokenResponse.AccessToken); ``` ### Calling The API [Section titled “Calling The API”](#calling-the-api) To send the access token to the API you typically use the HTTP Authorization header. This is done using the `SetBearerToken` extension method: ```csharp // call api var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken!); // AccessToken is always non-null when IsError is false var response = await apiClient.GetAsync("https://localhost:6001/identity"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); return 1; } var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true })); return 0; ``` The completed `Program.cs` file can be found [here](https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v8/Quickstarts/1_ClientCredentials/src/Client/Program.cs). To test the flow, start the IdentityServer and API projects. Once they are running, run the Client project. The output should look like this: ![Windows console showing claims for a bearer token](/_astro/1_client_screenshot.DapOaE8B_Z21a1sv.webp) If you’re using Visual Studio, here’s how to start everything up: 1. Right-click the solution and select *Configure Startup Projects…* 2. Choose *Multiple Startup Projects* and set the action for Api and IdentityServer to Start 3. Run the solution and wait a moment for both the API and IdentityServer to start 4. Right-click the `Client` project and select Debug -> Start Without Debugging. #### Authorization At The API [Section titled “Authorization At The API”](#authorization-at-the-api) Right now, the API accepts any access token issued by your IdentityServer. In this section, you will add an [Authorization Policy](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-8.0) to the API that will check for the presence of the “api1” scope in the access token. The protocol ensures that this scope will only be in the token if the client requests it and IdentityServer allows the client to have that scope. You configured IdentityServer to allow this access by [including it in the allowedScopes property](#defining-the-client). Add the following to the `Program.cs` file of the API: Program.cs ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy("ApiScope", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "api1"); }); }); ``` You can now enforce this policy at various levels, e.g.: * globally * for all endpoints * for specific controllers, actions, or endpoints Add the policy to the identity endpoint in `src/Api/Program.cs`: ```csharp app.MapGet("identity", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) .RequireAuthorization("ApiScope"); ``` Now you can run the API again, and it will enforce that the api1 scope is present in the access token. ## Further Experiments [Section titled “Further Experiments”](#further-experiments) This quickstart focused on the success path: * The client was able to request a token. * The client could use the token to access the API. You can now try to provoke errors to learn how the system behaves, e.g.: * Try to connect to IdentityServer when it is not running (unavailable). * Try to use an invalid client id or secret to request the token. * Try to ask for an invalid scope during the token request. * Try to call the API when it is not running (unavailable). * Don’t send the token to the API. * Configure the API to require a different scope than the one in the token. ----- # Interactive Applications With ASP.NET Core > Learn how to add interactive user authentication to an ASP.NET Core application using OpenID Connect and IdentityServer, including configuring the UI, managing user login/logout, and accessing claims. Welcome to Quickstart 2 for Duende IdentityServer! In this quickstart, you will add support for interactive user authentication via the OpenID Connect protocol to the IdentityServer you built in [Quickstart 1](/identityserver/quickstarts/1-client-credentials/). Once that is in place, you will create an ASP.NET Razor Pages application that will use IdentityServer for authentication. ## Video [Section titled “Video”](#video) In addition to the written steps below there’s also a YouTube video available: [YouTube video player](https://www.youtube.com/embed/4aYj4xb7_Cg) ## Enable OIDC In IdentityServer [Section titled “Enable OIDC In IdentityServer”](#enable-oidc-in-identityserver) To enable OIDC in IdentityServer you need: * An interactive UI * Configuration for OIDC scopes * Configuration for an OIDC client * Users to log in with ### Add The UI [Section titled “Add The UI”](#add-the-ui) Support for the OpenID Connect protocol is already built into IdentityServer. You need to provide the User Interface for login, logout, consent, and error. While the look & feel and workflows will differ in each implementation, we provide a Razor Pages-based UI that you can use as a starting point. You can use the .NET CLI to add the quickstart UI to a project. Run the following command from the `src/IdentityServer` directory: ```console dotnet new duende-is-ui ``` ### Enable The UI [Section titled “Enable The UI”](#enable-the-ui) Once you have added the UI, you will need to register its services and enable it in the pipeline. In `src/IdentityServer/HostingExtensions.cs` you will find commented out code in the `ConfigureServices` and `ConfigurePipeline` methods that enable the UI. Note that there are three places to comment in - two in `ConfigurePipeline` and one in `ConfigureServices`. Comment in the service registration and pipeline configuration, run the `IdentityServer` project, and navigate to `https://localhost:5001`. You should now see a home page. Spend some time reading the pages and models, especially those in the `src/IdentityServer/Pages/Account` directory. These pages are the main UI entry points for login and logout. The better you understand them, the easier it will be to make future modifications. ### Configure OIDC Scopes [Section titled “Configure OIDC Scopes”](#configure-oidc-scopes) Similar to OAuth, OpenID Connect uses scopes to represent something you want to protect and that clients want to access. In contrast to OAuth, scopes in OIDC represent identity data like user id, name or email address rather than APIs. Add support for the standard `openid` (subject id) and `profile` (first name, last name, etc.) scopes by declaring them in `src/IdentityServer/Config.cs`: ```csharp public static IEnumerable IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; ``` Then register the identity resources in `src/IdentityServer/HostingExtensions.cs`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients); ``` ### Add Test Users [Section titled “Add Test Users”](#add-test-users) The sample UI also comes with an in-memory “user database”. You can enable this by calling `AddTestUsers` in `src/IdentityServer/HostingExtensions.cs`: Program.cs ```csharp builder.Services.AddIdentityServer() .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryClients(Config.Clients) .AddTestUsers(TestUsers.Users); ``` In the `TestUsers` class, you can see that two users called `alice` and `bob` are defined with some identity claims. You can use those users to login. Note that the test users’ passwords match their usernames. ### Register An OIDC client [Section titled “Register An OIDC client”](#register-an-oidc-client) The last step in the `IdentityServer` project is to add a new configuration entry for a client that will use OIDC to log in. You will create the application code for this client in the next section. For now, you will register its configuration. OpenID Connect-based clients are very similar to the OAuth clients we added in [Quickstart 1](/identityserver/quickstarts/1-client-credentials/). But since the flows in OIDC are always interactive, we need to add some redirect URLs to our configuration. The `Clients` list in `src/IdentityServer/Config.cs` should look like this: ```csharp public static IEnumerable Clients => new List { // machine to machine client (from quickstart 1) new Client { ClientId = "client", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, // scopes that client has access to AllowedScopes = { "api1" } }, // interactive ASP.NET Core Web App new Client { ClientId = "web", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } } }; ``` ## Create The OIDC client [Section titled “Create The OIDC client”](#create-the-oidc-client) Next you will create an ASP.NET web application that will allow interactive users to log in using OIDC. Use the webapp template to create the project. Run the following commands from the `src` directory: ```console dotnet new webapp -n WebClient cd .. dotnet sln add ./src/WebClient ``` ### Install The OIDC NuGet Package [Section titled “Install The OIDC NuGet Package”](#install-the-oidc-nuget-package) To add support for OpenID Connect authentication to the `WebClient` project, you need to add the NuGet package containing the OpenID Connect handler. From the `src/WebClient` directory, run the following command: ```console dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect ``` ### Configure Authentication Services [Section titled “Configure Authentication Services”](#configure-authentication-services) Then add the authentication service and register the cookie and OpenIdConnect authentication providers in `src/WebClient/Program.cs`: Program.cs ```csharp builder.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5001"; options.ClientId = "web"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.MapInboundClaims = false; // Don't rename claim types options.SaveTokens = true; }); ``` `AddAuthentication` registers the authentication services. Notice that in its options, the DefaultChallengeScheme is set to “oidc”, and the DefaultScheme is set to “Cookies”. The DefaultChallengeScheme is used when an unauthenticated user must log in. This begins the OpenID Connect protocol, redirecting the user to `IdentityServer`. After the user has logged in and been redirected back to the client, the client creates its own local cookie. Subsequent requests to the client will include this cookie and be authenticated with the default Cookie scheme. After the call to `AddAuthentication`, `AddCookie` adds the handler that can process the local cookie. Finally, `AddOpenIdConnect` is used to configure the handler that performs the OpenID Connect protocol. The `Authority` indicates where the trusted token service is located. The `ClientId` and the `ClientSecret` identify this client. The `Scope` is the collection of scopes that the client will request. By default, it includes the openid and profile scopes, but clear the collection and add them back for explicit clarity. `SaveTokens` is used to persist the tokens in the cookie (as they will be needed later). ### Configure The Pipeline [Section titled “Configure The Pipeline”](#configure-the-pipeline) Now add `UseAuthentication` to the ASP.NET pipeline in `src/WebClient/Program.cs`. Also chain a call to `RequireAuthorization` onto `MapRazorPages` to disable anonymous access for the entire application. ```csharp app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages().RequireAuthorization(); ``` ### Display The Auth Cookie [Section titled “Display The Auth Cookie”](#display-the-auth-cookie) Modify `src/WebClient/Pages/Index.cshtml` to display the claims of the user and the cookie properties: ```csharp @page @model IndexModel @using Microsoft.AspNetCore.Authentication

Claims

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

Properties

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

Hello, Blazor BFF!

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

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

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