From 06f4ed3b6868eb91c81331fe4b2a50813bf08507 Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Fri, 26 Jan 2024 16:33:16 -0600 Subject: [PATCH] Update quickstarts for v7 --- .../quickstarts/1_client_credentials.md | 99 ++++++++----------- .../docs/content/quickstarts/2_interactive.md | 42 ++++---- .../docs/content/quickstarts/3_api_access.md | 11 ++- .../v7/docs/content/quickstarts/4_ef.md | 45 +++------ .../v7/docs/content/quickstarts/5_aspnetid.md | 66 ++++++++----- .../quickstarts/js_clients/js_with_backend.md | 7 +- .../js_clients/js_without_backend.md | 18 ++-- 7 files changed, 137 insertions(+), 151 deletions(-) diff --git a/IdentityServer/v7/docs/content/quickstarts/1_client_credentials.md b/IdentityServer/v7/docs/content/quickstarts/1_client_credentials.md index 3918cc2d..6bdc97d1 100644 --- a/IdentityServer/v7/docs/content/quickstarts/1_client_credentials.md +++ b/IdentityServer/v7/docs/content/quickstarts/1_client_credentials.md @@ -92,7 +92,7 @@ the solution. ```console cd .. -dotnet sln add ./src/IdentityServer/IdentityServer.csproj +dotnet sln add ./src/IdentityServer ``` ### Defining an API Scope @@ -113,14 +113,14 @@ 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 +template at *src/IdentityServer/Config.cs*. Open it and add a couple *ApiScope*s to the *ApiScopes* property: ```csharp public static IEnumerable ApiScopes => - new List + new ApiScope[] { - new ApiScope(name: "api1", displayName: "MyAPI") + new ApiScope(name: "api1", displayName: "My API") }; ``` @@ -147,7 +147,8 @@ Add this client definition to *Config.cs*: ```cs public static IEnumerable Clients => - new List + new Client + { new Client { @@ -228,7 +229,7 @@ the .NET CLI to create the API project. To use the CLI, run the following command from the *src* directory: ```console -dotnet new webapi -n Api +dotnet new webapi -n Api --no-openapi ``` Then navigate back up to the root quickstart directory and add it to the @@ -236,7 +237,7 @@ solution by running the following commands: ```console cd .. -dotnet sln add ./src/Api/Api.csproj +dotnet sln add ./src/Api ``` ### Add JWT Bearer Authentication @@ -253,24 +254,21 @@ middleware will Run this command in the *src* directory to install the middleware package in the Api: ```console -dotnet add ./Api/Api.csproj package Microsoft.AspNetCore.Authentication.JwtBearer +dotnet add ./Api package Microsoft.AspNetCore.Authentication.JwtBearer ``` -Now add JWT Bearer authentication services to the Service Collection to allow -for dependency injection (DI), and configure *Bearer* as the default -[Authentication Scheme](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-6.0#authentication-scheme). +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). ```csharp -builder.Services.AddAuthentication("Bearer") - .AddJwtBearer("Bearer", options => +builder.Services.AddAuthentication() + .AddJwtBearer(options => { options.Authority = "https://localhost:5001"; - - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateAudience = false - }; + options.TokenValidationParameters = false; }); +builder.Services.AddAuthorization(); ``` {{% notice note %}} @@ -282,35 +280,16 @@ more in-depth discussion. {{% /notice %}} -Add authentication middleware to the pipeline immediately before authorization: -```csharp -app.UseAuthentication(); -app.UseAuthorization(); -``` -*UseAuthentication* adds the authentication middleware to the pipeline so -authentication will be performed automatically on every call into the host. -*UseAuthorization* adds the authorization middleware to make sure your API -endpoint cannot be accessed by anonymous clients. - -### Add a controller -Add a new class called *IdentityController* in *src/Api/Controllers*: +### Add an endpoint +Replace the templated weather forecast endpoint with a new endpoint: ```csharp -[Route("identity")] -[Authorize] -public class IdentityController : ControllerBase -{ - [HttpGet] - public IActionResult Get() - { - return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); - } -} +app.MapGet("identity", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) + .RequireAuthorization(); ``` -This controller will be used to test authorization and to display the claims -identity through the eyes of the API. See the full file -[here]({{< param qs_base >}}/1_ClientCredentials/src/Api/Controllers/IdentityController.cs). +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 @@ -324,7 +303,7 @@ to be: "applicationUrl": "https://localhost:6001" ``` -### Test the controller +### Test the identity endpoint Run the API project and then navigate to the identity controller at *https://localhost:6001/identity* in a browser. This should return a 401 status @@ -344,7 +323,7 @@ Then as before, add it to your solution using: ```console cd .. -dotnet sln add ./src/Client/Client.csproj +dotnet sln add ./src/Client ``` ### Add the IdentityModel nuget package @@ -357,7 +336,7 @@ via Visual Studio's Nuget Package manager or dotnet CLI. From the *quickstart* directory, run the following command: ```console -dotnet add ./src/Client/Client.csproj package IdentityModel +dotnet add ./src/Client package IdentityModel ``` ### Retrieve the discovery document @@ -367,6 +346,8 @@ endpoint addresses can be read from the metadata. Add the following to the client's Program.cs in the *src/Client/Program.cs* directory: ```cs +using IdentityModel.Client; + // discover endpoints from metadata var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); @@ -379,10 +360,9 @@ if (disco.IsError) {{% notice note %}} -If you get an error connecting it may be that you are running *https* and the -development certificate for *localhost* is not trusted. You can run `dotnet -dev-certs https --trust` in order to trust the development certificate. This -only needs to be done once. +If you get an error connecting, it may be that the development certificate for *localhost* +is not trusted. You can run `dotnet dev-certs https --trust` in order to trust the +development certificate. This only needs to be done once. {{% /notice %}} @@ -395,7 +375,6 @@ from *IdentityServer* to access *api1*: var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, - ClientId = "client", ClientSecret = "secret", Scope = "api1" @@ -404,6 +383,7 @@ var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCr if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); + Console.WriteLine(tokenResponse.ErrorDescription); return; } @@ -424,7 +404,7 @@ header. This is done using the *SetBearerToken* extension method: ```cs // call api var apiClient = new HttpClient(); -apiClient.SetBearerToken(tokenResponse.AccessToken); +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) @@ -469,7 +449,7 @@ scope, lifetime (nbf and exp), the client ID (client_id) and the issuer name #### Authorization at the API Right now, the API accepts any access token issued by your IdentityServer. In this section, you will add an [Authorization -Policy](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0) +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 @@ -491,16 +471,19 @@ builder.Services.AddAuthorization(options => You can now enforce this policy at various levels, e.g.: * globally -* for all API endpoints -* for specific controllers/actions +* for all endpoints +* for specific controllers, actions, or endpoints -Typically you set the policy for all controllers where they are mapped in -*src/Api/Program.cs*: +Add the policy to the identity endpoint in *src/Api/Program.cs*: ```cs -app.MapControllers().RequireAuthorization("ApiScope"); +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 This quickstart focused on the success path: diff --git a/IdentityServer/v7/docs/content/quickstarts/2_interactive.md b/IdentityServer/v7/docs/content/quickstarts/2_interactive.md index 78037528..7aa1a183 100644 --- a/IdentityServer/v7/docs/content/quickstarts/2_interactive.md +++ b/IdentityServer/v7/docs/content/quickstarts/2_interactive.md @@ -76,7 +76,7 @@ last name, etc) scopes by declaring them in *src/IdentityServer/Config.cs*: ```cs public static IEnumerable IdentityResources => - new List + new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), @@ -158,7 +158,7 @@ public static IEnumerable Clients => // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, - AllowedScopes = new List + AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile @@ -175,13 +175,13 @@ the following commands from the *src* directory: ```console dotnet new webapp -n WebClient cd .. -dotnet sln add ./src/WebClient/WebClient.csproj +dotnet sln add ./src/WebClient ``` {{% notice note %}} This version of the quickstarts uses [Razor -Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-6.0&tabs=visual-studio) +Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-8.0&tabs=visual-studio) for the web client. If you prefer MVC, the conversion is straightforward. See the [quickstart for IdentityServer 5](https://docs.duendesoftware.com/identityserver/v5/quickstarts/2_interactive/) @@ -199,15 +199,9 @@ dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect ``` ### Configure Authentication Services -Then add the following to *ConfigureServices* in *src/WebClient/Program.cs*: +Then add the authentication service and register the cookie and OpenIdConnect authentication providers in *src/WebClient/Program.cs*: ```cs -using System.IdentityModel.Tokens.Jwt; - -// ... - -JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - builder.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; @@ -226,6 +220,8 @@ builder.Services.AddAuthentication(options => options.Scope.Add("openid"); options.Scope.Add("profile"); + options.MapInboundClaims = false; // Don't rename claim types + options.SaveTokens = true; }); ``` @@ -285,7 +281,7 @@ app.MapRazorPages().RequireAuthorization(); {{% notice note %}} See the ASP.NET Core documentation on [Razor Pages authorization -conventions](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/razor-pages-authorization?view=aspnetcore-6.0) +conventions](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/razor-pages-authorization?view=aspnetcore-8.0) for more options that allow you to specify authorization on a per page or directory basis. @@ -315,7 +311,7 @@ the cookie properties:

Properties

- @foreach (var prop in (await HttpContext.AuthenticateAsync()).Properties.Items) + @foreach (var prop in (await HttpContext.AuthenticateAsync()).Properties!.Items) {
@prop.Key
@prop.Value
@@ -329,7 +325,8 @@ Update the client's applicationUrl in ```json { - "profiles": { + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { "WebClient": { "commandName": "Project", "dotnetRunMessages": true, @@ -417,10 +414,12 @@ within the navbar-nav list: ``` +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 You might have noticed that even though you've configured the client to be allowed to retrieve the *profile* identity scope, the claims associated with -that scope (such as *name*, *family_name*, *website* etc.) don't appear in the +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 @@ -438,9 +437,8 @@ to *ConfigureServices* in *src/WebClient/Program.cs*: }); ``` -After restarting the client app, logging out, and logging back in, you should -see additional user claims associated with the *profile* identity scope -displayed on the page. +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. ![](../images/2_additional_claims.png) @@ -485,7 +483,7 @@ To add more claims to the identity: { ClientId = "web", //... - AllowedScopes = new List + AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, @@ -495,7 +493,7 @@ To add more claims to the identity: ``` * Request the resource by adding it to the *Scopes* collection on the OpenID Connect handler configuration in *src/WebClient/Program.cs*, and add a - [ClaimAction](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions.claimactions?view=aspnetcore-6.0) + [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. ```csharp .AddOpenIdConnect("oidc", options => @@ -533,7 +531,7 @@ To use Google for authentication, you need to: it. See [Microsoft's -guide](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-6.0#create-a-google-api-console-project-and-client-id) +guide](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-8.0#create-a-google-api-console-project-and-client-id) for details on how to register with Google, create the client, and store the secrets in user-secrets. **Stop before adding the authentication middleware and Google authentication handler to the pipeline.** You will need an @@ -554,7 +552,7 @@ builder.Services.AddAuthentication() ``` When authenticating with Google, there are again two [authentication -schemes](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-6.0#authentication-scheme). +schemes](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-8.0#authentication-scheme). *AddGoogle* adds the Google scheme, which handles the protocol flow back and forth with Google. After successful login, the application needs to sign in to an additional scheme that can authenticate future requests without needing a diff --git a/IdentityServer/v7/docs/content/quickstarts/3_api_access.md b/IdentityServer/v7/docs/content/quickstarts/3_api_access.md index 0e93282f..21ec5a4e 100644 --- a/IdentityServer/v7/docs/content/quickstarts/3_api_access.md +++ b/IdentityServer/v7/docs/content/quickstarts/3_api_access.md @@ -57,10 +57,11 @@ new Client AllowOfflineAccess = true, - AllowedScopes = new List + AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, + "verification", "api1" } } @@ -93,7 +94,13 @@ builder.Services.AddAuthentication(options => options.Scope.Add("profile"); options.Scope.Add("api1"); options.Scope.Add("offline_access"); + 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; }); ``` @@ -164,6 +171,6 @@ You typically want to ASP.NET Core has built-in facilities that can help you with some of those tasks (like caching or sessions), but there is still quite some work left to do. Consider using the -[IdentityModel](https://identitymodel.readthedocs.io/en/latest/aspnetcore/overview.html) +[Duende.AccessTokenManagement](https://github.com/DuendeSoftware/Duende.AccessTokenManagement/wiki) library for help with access token lifetime management. It provides abstractions for storing tokens, automatic refresh of expired tokens, etc. diff --git a/IdentityServer/v7/docs/content/quickstarts/4_ef.md b/IdentityServer/v7/docs/content/quickstarts/4_ef.md index 2c83d34a..d785f044 100644 --- a/IdentityServer/v7/docs/content/quickstarts/4_ef.md +++ b/IdentityServer/v7/docs/content/quickstarts/4_ef.md @@ -38,7 +38,7 @@ with the EntityFramework integration already added: *dotnet new isef*. {{% /notice %}} ## Configure IdentityServer -### Install Duende.IdentityServer.EntityFramework +#### Install Duende.IdentityServer.EntityFramework IdentityServer's Entity Framework integration is provided by the *Duende.IdentityServer.EntityFramework* NuGet package. Run the following commands from the *src/IdentityServer* directory to replace the @@ -50,7 +50,7 @@ dotnet remove package Duende.IdentityServer dotnet add package Duende.IdentityServer.EntityFramework ``` -### Install Microsoft.EntityFrameworkCore.Sqlite +#### Install Microsoft.EntityFrameworkCore.Sqlite *Duende.IdentityServer.EntityFramework* can be used with any Entity Framework database provider. In this quickstart, you will use Sqlite. To add Sqlite @@ -62,7 +62,7 @@ directory: dotnet add package Microsoft.EntityFrameworkCore.Sqlite ``` -### Configuring the Stores +#### Configuring the Stores *Duende.IdentityServer.EntityFramework* stores configuration and operational data in separate stores, each with their own DbContext. @@ -79,6 +79,8 @@ To use these stores, replace the existing calls to *AddInMemoryClients*, ```cs 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"; @@ -123,7 +125,7 @@ migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations 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 +#### 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 @@ -134,36 +136,21 @@ dotnet tool install --global dotnet-ef dotnet add package Microsoft.EntityFrameworkCore.Design ``` -### Handle Expected Exception -The Entity Framework CLI internally starts up *IdentityServer* for a short time -in order to read your database configuration. After it has read the -configuration, it shuts *IdentityServer* down by throwing a -*StopTheHostException* (in Entity Framework 6) or *HostAbortedException* (in Entity Framework 7) exception. We expect this exception to be unhandled and -therefore stop *IdentityServer*. Since it is expected, you do not need to log it -as a fatal error. Update the error logging code in +#### 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 -catch (Exception ex) when ( - // https://github.com/dotnet/runtime/issues/60600 - ex.GetType().Name is not "StopTheHostException" - // HostAbortedException was added in .NET 7, but since we target .NET 6 we - // need to do it this way until we target .NET 8 - && ex.GetType().Name is not "HostAbortedException") +// 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"); } ``` -{{% notice note %}} - -When using `Microsoft.EntityFrameworkCore.Tools` version 6.x, you must use the "StopTheHostException" string here rather than catching the -*StopTheHostException* because it is a private type. -If you use version 7.x of `Microsoft.EntityFrameworkCore.Tools` and reference version 7.x of the `Microsoft.Extensions.Hosting` package, you can catch the "HostAbortedException" as expected. See -https://github.com/dotnet/runtime/issues/60600. - - -{{% /notice %}} - Now run the following two commands from the *src/IdentityServer* directory to create the migrations: @@ -175,7 +162,7 @@ dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c Config You should now see a *src/IdentityServer/Data/Migrations/IdentityServer* directory in your project containing the code for your newly created migrations. -### Initializing the Database +#### Initializing the Database Now that you have the migrations, you can write code to create the database from them and seed the database with the same configuration data used in the previous quickstarts. @@ -194,7 +181,7 @@ database: ```cs private static void InitializeDatabase(IApplicationBuilder app) { - using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) + using (var serviceScope = app.ApplicationServices.GetService()!.CreateScope()) { serviceScope.ServiceProvider.GetRequiredService().Database.Migrate(); diff --git a/IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md b/IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md index 829e060d..b6b6adb4 100644 --- a/IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md +++ b/IdentityServer/v7/docs/content/quickstarts/5_aspnetid.md @@ -35,7 +35,7 @@ this solution (for the clients and the API) will remain the same. This quickstart assumes you are familiar with how ASP.NET Core Identity works. If you are not, it is recommended that you first [learn about -it](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0). +it](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-8.0). {{% /notice %}} @@ -52,7 +52,7 @@ Identity. Run the following commands from the *src* directory: ```console dotnet new isaspid -n IdentityServerAspNetIdentity cd .. -dotnet sln add ./src/IdentityServerAspNetIdentity/IdentityServerAspNetIdentity.csproj +dotnet sln add ./src/IdentityServerAspNetIdentity ``` When prompted to "seed" the user database, choose "Y" for "yes". This populates @@ -77,12 +77,12 @@ migrating configuration from the old IdentityServer Project, including: - Entry point and seed data (*Program.cs* and *SeedData.cs*) - Login and logout pages (Pages in *Pages/Account*) -### IdentityServerAspNetIdentity.csproj +#### IdentityServerAspNetIdentity.csproj Notice the reference to *Duende.IdentityServer.AspNetIdentity*. This NuGet package contains the ASP.NET Core Identity integration components for IdentityServer. -### HostingExtensions.cs +#### HostingExtensions.cs In *ConfigureServices* notice the necessary *AddDbContext()* and *AddIdentity()* calls are done to configure ASP.NET Core Identity. @@ -100,7 +100,7 @@ claims for the users into tokens. Note that *AddIdentity()* must be invoked before *AddIdentityServer()*. -### Config.cs +#### Config.cs *Config.cs* contains the hard-coded in-memory clients and resource definitions. To keep the same clients and API working as the prior quickstarts, we need to copy over the configuration data from the old IdentityServer project into this @@ -110,33 +110,46 @@ one. Do that now, and afterwards *Config.cs* should look like this: public static class Config { public static IEnumerable IdentityResources => - new List + new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), + new IdentityResource() + { + Name = "verification", + UserClaims = new List + { + JwtClaimTypes.Email, + JwtClaimTypes.EmailVerified + } + } }; - public static IEnumerable ApiScopes => - new List - { - new ApiScope("api1", "My API") + new ApiScope[] + { + new ApiScope(name: "api1", displayName: "My API") }; public static IEnumerable Clients => - new List + new Client[] { - // machine to machine client new Client { ClientId = "client", - ClientSecrets = { new Secret("secret".Sha256()) }, + // 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 { @@ -144,19 +157,20 @@ public static class Config ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, - + // where to redirect to after login RedirectUris = { "https://localhost:5002/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, - + AllowOfflineAccess = true, - AllowedScopes = new List + AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, + "verification", "api1" } } @@ -169,11 +183,11 @@ it from the solution. From the quickstart directory, run the following commands: ```console -dotnet sln remove ./src/IdentityServer/IdentityServer.csproj +dotnet sln remove ./src/IdentityServer rm -r ./src/IdentityServer ``` -### Program.cs and SeedData.cs +#### Program.cs and SeedData.cs The application entry point in *Program.cs* is a little different than most ASP.NET Core projects. Notice that it looks for a command line argument called */seed* which is used as a flag to seed the users in the ASP.NET Core Identity @@ -183,7 +197,7 @@ 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 +#### Account Pages Finally, take a look at the the pages in the *src/IdentityServerAspNetIdentity/Pages/Account* directory. These pages contain slightly different login and logout code than the prior quickstart and templates @@ -216,7 +230,9 @@ IdentityServer! Next you will add a custom property to your user model and include it as a claim when the appropriate Identity Resource is requested. -First, add a *FavoriteColor* property to the *ApplicationUser* class. +First, add a *FavoriteColor* property in +src/IdentityServerAspNetIdentity/ApplicationUser.cs*. + ```csharp public class ApplicationUser : IdentityUser { @@ -269,7 +285,8 @@ data as a source of claims data. [See here]({{< ref "/reference/services/profile_service" >}}) for more details on the profile service. -Create a new class called *CustomProfileService* and add the following code to it: +Create a new file called *src/AspNetIdentity/CustomProfileService.cs* and add the +following code to it: ```csharp using Duende.IdentityServer.AspNetIdentity; using Duende.IdentityServer.Models; @@ -324,8 +341,7 @@ that will map the color scope onto the favorite_color claim type: public static IEnumerable IdentityResources => new IdentityResource[] { - new IdentityResources.OpenId(), - new IdentityResources.Profile(), + // ... new IdentityResource("color", new [] { "favorite_color" }) }; ``` @@ -382,7 +398,7 @@ Identity, our template deliberately does not provide those features. The intent of this template is to be a starting point to which you can add the features you need from ASP.NET Core Identity, customized according to your requirements. Alternatively, you can [create a new project based on the ASP.NET Core Identity -template](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=netcore-cli#create-a-web-app-with-authentication) +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, diff --git a/IdentityServer/v7/docs/content/quickstarts/js_clients/js_with_backend.md b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_with_backend.md index fc44349d..c3f22b34 100644 --- a/IdentityServer/v7/docs/content/quickstarts/js_clients/js_with_backend.md +++ b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_with_backend.md @@ -44,7 +44,7 @@ the following commands from the *src* directory: ```console dotnet new web -n JavaScriptClient cd .. -dotnet sln add ./src/JavaScriptClient/JavaScriptClient.csproj +dotnet sln add ./src/JavaScriptClient ``` ### Add additional NuGet packages @@ -65,6 +65,7 @@ Modify the *JavaScriptClient* project to run on *https://localhost:5003*. Its ```json { + "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "JavaScriptClient": { "commandName": "Project", @@ -130,7 +131,7 @@ var app = builder.Build(); Similarly, the middleware pipeline for this application will resemble the WebClient, with the addition of the BFF middleware and the BFF endpoints. -Continuing by adding the following to *src/JavaScriptClient/Program.cs*: +Continue by adding the following to *src/JavaScriptClient/Program.cs*: ```cs var app = builder.Build(); @@ -379,7 +380,7 @@ the access token for authentication. ### Define a local API Local APIs can be defined using controllers or with [Minimal API Route -Handlers](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0#route-handlers). +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. diff --git a/IdentityServer/v7/docs/content/quickstarts/js_clients/js_without_backend.md b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_without_backend.md index b48b4c45..e1200393 100644 --- a/IdentityServer/v7/docs/content/quickstarts/js_clients/js_without_backend.md +++ b/IdentityServer/v7/docs/content/quickstarts/js_clients/js_without_backend.md @@ -42,7 +42,7 @@ the following commands from the *src* directory: ```console dotnet new web -n JavaScriptClient cd .. -dotnet sln add ./src/JavaScriptClient/JavaScriptClient.csproj +dotnet sln add ./src/JavaScriptClient ``` ### Modify hosting @@ -52,7 +52,8 @@ Modify the *JavaScriptClient* project to run on *https://localhost:5003*. Its ```json { - "profiles": { + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { "JavaScriptClient": { "commandName": "Project", "dotnetRunMessages": true, @@ -331,13 +332,12 @@ new Client ## 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 +*Api* project. This will allow Ajax calls to be made from *https://localhost:5003* to *https://localhost:6001*. **Configure CORS** -Add the CORS services to the dependency injection system in -*src/Api/Program.cs*: +Add the CORS service to the dependency injection system in *src/Api/Program.cs*: ```cs builder.Services.AddCors(options => @@ -352,17 +352,11 @@ builder.Services.AddCors(options => }); ``` -Then add the CORS middleware to the pipeline in *src/Api/Program.cs*. It should -come before the call to *UseAuthentication*. +Then add the CORS middleware to the pipeline in *src/Api/Program.cs*. ```cs app.UseHttpsRedirection(); - app.UseCors("default"); - -app.UseAuthentication(); -app.UseAuthorization(); - ``` ## Run the JavaScript application