From 8419caf09734c87a011059e24d79f74d956aff37 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Tue, 11 Aug 2026 12:27:08 +0200 Subject: [PATCH] AddingCustomClaimsToAccessTokenInDuende: migrate to Duende IdentityServer 8 on net10.0 Retargets every project to net10.0 and moves the sample from Duende.IdentityServer 6.3.7 to 8.0.5, which is a real v6 -> v8 migration rather than a package bump. IdentityServer v8 changes applied (per Duende's 7.4 -> 8.0 upgrade guide): * Every store/service interface method gained a CancellationToken parameter, so IProfileService.GetProfileDataAsync/IsActiveAsync take one now. * ProfileDataRequestContext.Client was renamed to .Application and retyped to IConnectedApplication, so the client id is read as Application?.Identifier rather than Client.ClientId. * The IdentityModel package/namespace became Duende.IdentityModel. * v8 requires .NET 10; nullable reference types are enabled across the assemblies. Those changes cascade through the whole stock quickstart UI, so Pages/ and wwwroot/ are regenerated from Duende's own v8 in-memory template (Duende.Templates 1.3.0) with the namespace adjusted. That also moves Pages/Home/Error to Pages/Error and swaps bootstrap4-glyphicons for bootstrap-icons. Article content added alongside the migration: * Config.ApiResources declares a "paymentsapi" resource whose UserClaims include the role claim, so Duende requests the role claim type for that audience. * CustomProfileService issues the role claim through AddRequestedClaims, because a custom IProfileService replaces the built-in one and is then the only source of user claims. * A new Api project consumes the token with AddJwtBearer. MapInboundClaims is set to false there: with the default inbound claim mapping the "role" claim arrives under the legacy schemas.microsoft.com URI and RequireRole silently fails. * One test covers the new ApiResource declaration. Verified on .NET SDK 10.0.302 / ASP.NET Core runtime 10.0.10: solution builds in Release, 7/7 tests pass, and an end-to-end authorization-code + PKCE login as alice returns an access token carrying tenant, payments.discount and role claims with paymentsapi in the audience; the role-protected API endpoint answers 200 with the token and 401 without it. --- .../.gitignore | 2 + ...ddingCustomClaimsToAccessTokenInDuende.sln | 42 + .../Api/Api.csproj | 14 + .../Api/Program.cs | 30 + .../Api/Properties/launchSettings.json | 14 + .../Api/appsettings.json | 9 + .../IdentityServer/Config.cs | 12 +- .../IdentityServer/CustomProfileService.cs | 15 +- .../IdentityServer/HostingExtensions.cs | 4 +- .../IdentityServer/IdentityServer.csproj | 9 +- .../Pages/Account/AccessDenied.cshtml.cs | 2 +- .../Pages/Account/Create/Index.cshtml | 12 +- .../Pages/Account/Create/Index.cshtml.cs | 47 +- .../Pages/Account/Create/InputModel.cs | 20 +- .../Pages/Account/Login/Index.cshtml | 10 +- .../Pages/Account/Login/Index.cshtml.cs | 148 +- .../Pages/Account/Login/InputModel.cs | 20 +- .../Pages/Account/Login/LoginOptions.cs | 14 +- .../Pages/Account/Login/ViewModel.cs | 19 +- .../Pages/Account/Logout/Index.cshtml | 6 +- .../Pages/Account/Logout/Index.cshtml.cs | 49 +- .../Pages/Account/Logout/LoggedOut.cshtml | 4 +- .../Pages/Account/Logout/LoggedOut.cshtml.cs | 21 +- .../Account/Logout/LoggedOutViewModel.cs | 15 +- .../Pages/Account/Logout/LogoutOptions.cs | 11 +- .../IdentityServer/Pages/Ciba/All.cshtml | 2 +- .../IdentityServer/Pages/Ciba/All.cshtml.cs | 29 +- .../IdentityServer/Pages/Ciba/Consent.cshtml | 14 +- .../Pages/Ciba/Consent.cshtml.cs | 129 +- .../Pages/Ciba/ConsentOptions.cs | 14 +- .../IdentityServer/Pages/Ciba/Index.cshtml.cs | 33 +- .../IdentityServer/Pages/Ciba/InputModel.cs | 13 +- .../IdentityServer/Pages/Ciba/ViewModel.cs | 33 +- .../Pages/Ciba/_ScopeListItem.cshtml | 2 +- .../Pages/Consent/ConsentOptions.cs | 14 +- .../IdentityServer/Pages/Consent/Index.cshtml | 18 +- .../Pages/Consent/Index.cshtml.cs | 138 +- .../Pages/Consent/InputModel.cs | 13 +- .../IdentityServer/Pages/Consent/ViewModel.cs | 29 +- .../Pages/Consent/_ScopeListItem.cshtml | 2 +- .../Pages/Device/DeviceOptions.cs | 14 +- .../IdentityServer/Pages/Device/Index.cshtml | 29 +- .../Pages/Device/Index.cshtml.cs | 161 +- .../IdentityServer/Pages/Device/InputModel.cs | 13 +- .../Pages/Device/Success.cshtml.cs | 2 +- .../IdentityServer/Pages/Device/ViewModel.cs | 20 +- .../Pages/Device/_ScopeListItem.cshtml | 2 +- .../Pages/Diagnostics/Index.cshtml | 26 +- .../Pages/Diagnostics/Index.cshtml.cs | 16 +- .../Pages/Diagnostics/ViewModel.cs | 27 +- .../Pages/{Home => }/Error/Index.cshtml | 0 .../Pages/Error/Index.cshtml.cs | 29 + .../IdentityServer/Pages/Error/ViewModel.cs | 14 + .../IdentityServer/Pages/Extensions.cs | 36 +- .../Pages/ExternalLogin/Callback.cshtml.cs | 71 +- .../Pages/ExternalLogin/Challenge.cshtml.cs | 30 +- .../IdentityServer/Pages/Grants/Index.cshtml | 4 +- .../Pages/Grants/Index.cshtml.cs | 50 +- .../IdentityServer/Pages/Grants/ViewModel.cs | 18 +- .../Pages/Home/Error/Index.cshtml.cs | 39 - .../Pages/Home/Error/ViewModel.cs | 20 - .../Pages/IdentityServerSuppressions.cs | 19 + .../IdentityServer/Pages/Index.cshtml | 14 +- .../IdentityServer/Pages/Index.cshtml.cs | 18 +- .../IdentityServer/Pages/Log.cs | 69 + .../Pages/Redirect/Index.cshtml.cs | 6 +- .../Pages/SecurityHeadersAttribute.cs | 20 +- .../Pages/ServerSideSessions/Index.cshtml | 13 +- .../Pages/ServerSideSessions/Index.cshtml.cs | 86 +- .../Pages/Shared/_Layout.cshtml | 2 +- .../IdentityServer/Pages/Shared/_Nav.cshtml | 7 +- .../IdentityServer/Pages/Telemetry.cs | 142 + .../IdentityServer/Pages/TestUsers.cs | 24 +- .../IdentityServer/Pages/_ViewImports.cshtml | 3 +- .../wwwroot/css/bootstrap-overrides.css | 708 + .../IdentityServer/wwwroot/duende-logo.svg | 5 +- .../IdentityServer/wwwroot/favicon.ico | Bin 15406 -> 15406 bytes .../wwwroot/js/signin-redirect.js | 8 +- .../font/bootstrap-icons.min.css | 5 + .../font/fonts/bootstrap-icons.woff | Bin 0 -> 180288 bytes .../font/fonts/bootstrap-icons.woff2 | Bin 0 -> 134044 bytes .../wwwroot/lib/bootstrap/LICENSE | 22 - .../wwwroot/lib/bootstrap/README.md | 209 - .../lib/bootstrap/dist/css/bootstrap-grid.css | 5675 +++--- .../bootstrap/dist/css/bootstrap-grid.css.map | 2 +- .../bootstrap/dist/css/bootstrap-grid.min.css | 7 +- .../dist/css/bootstrap-grid.min.css.map | 2 +- .../bootstrap/dist/css/bootstrap-reboot.css | 477 +- .../dist/css/bootstrap-reboot.css.map | 2 +- .../dist/css/bootstrap-reboot.min.css | 8 +- .../dist/css/bootstrap-reboot.min.css.map | 2 +- .../lib/bootstrap/dist/css/bootstrap.css | 16226 +++++++++------- .../lib/bootstrap/dist/css/bootstrap.css.map | 2 +- .../lib/bootstrap/dist/css/bootstrap.min.css | 9 +- .../bootstrap/dist/css/bootstrap.min.css.map | 2 +- .../lib/bootstrap/dist/js/bootstrap.bundle.js | 11086 +++++------ .../bootstrap/dist/js/bootstrap.bundle.js.map | 2 +- .../bootstrap/dist/js/bootstrap.bundle.min.js | 6 +- .../dist/js/bootstrap.bundle.min.js.map | 2 +- .../lib/bootstrap/dist/js/bootstrap.js | 7271 +++---- .../lib/bootstrap/dist/js/bootstrap.js.map | 2 +- .../lib/bootstrap/dist/js/bootstrap.min.js | 6 +- .../bootstrap/dist/js/bootstrap.min.js.map | 2 +- .../wwwroot/lib/bootstrap4-glyphicons/LICENSE | 201 - .../css/bootstrap-glyphicons.css | 809 - .../css/bootstrap-glyphicons.min.css | 6 - .../glyphicons-halflings-regular.eot | Bin 20127 -> 0 bytes .../glyphicons-halflings-regular.svg | 288 - .../glyphicons-halflings-regular.ttf | Bin 45404 -> 0 bytes .../glyphicons-halflings-regular.woff | Bin 23424 -> 0 bytes .../glyphicons-halflings-regular.woff2 | Bin 18028 -> 0 bytes .../maps/glyphicons-fontawesome.css | 2947 --- .../maps/glyphicons-fontawesome.less | 3891 ---- .../maps/glyphicons-fontawesome.min.css | 1 - .../wwwroot/lib/jquery/LICENSE.txt | 20 - .../wwwroot/lib/jquery/README.md | 62 - .../wwwroot/lib/jquery/dist/jquery.js | 2264 +-- .../wwwroot/lib/jquery/dist/jquery.min.js | 4 +- .../wwwroot/lib/jquery/dist/jquery.min.map | 2 +- .../wwwroot/lib/jquery/dist/jquery.slim.js | 2190 +-- .../lib/jquery/dist/jquery.slim.min.js | 4 +- .../lib/jquery/dist/jquery.slim.min.map | 2 +- .../Tests/CustomsProfileServiceUnitTests.cs | 30 +- .../Tests/Tests.csproj | 2 +- .../WebClient/WebClient.csproj | 4 +- 125 files changed, 25178 insertions(+), 31373 deletions(-) create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/.gitignore create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Api.csproj create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Program.cs create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Properties/launchSettings.json create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/appsettings.json rename authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/{Home => }/Error/Index.cshtml (100%) create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Error/Index.cshtml.cs create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Error/ViewModel.cs delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Home/Error/Index.cshtml.cs delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Home/Error/ViewModel.cs create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/IdentityServerSuppressions.cs create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Log.cs create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Telemetry.cs create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/css/bootstrap-overrides.css create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap-icons/font/bootstrap-icons.min.css create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap-icons/font/fonts/bootstrap-icons.woff create mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap-icons/font/fonts/bootstrap-icons.woff2 delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap/LICENSE delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap/README.md delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/LICENSE delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/css/bootstrap-glyphicons.css delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/css/bootstrap-glyphicons.min.css delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/fonts/glyphicons/glyphicons-halflings-regular.eot delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/fonts/glyphicons/glyphicons-halflings-regular.svg delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/fonts/glyphicons/glyphicons-halflings-regular.ttf delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/fonts/glyphicons/glyphicons-halflings-regular.woff delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/fonts/glyphicons/glyphicons-halflings-regular.woff2 delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/maps/glyphicons-fontawesome.css delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/maps/glyphicons-fontawesome.less delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/bootstrap4-glyphicons/maps/glyphicons-fontawesome.min.css delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/jquery/LICENSE.txt delete mode 100644 authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/wwwroot/lib/jquery/README.md diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/.gitignore b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/.gitignore new file mode 100644 index 0000000000..482bfdd002 --- /dev/null +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/.gitignore @@ -0,0 +1,2 @@ +# Duende automatic key management writes signing keys here at first run. +keys/ diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/AddingCustomClaimsToAccessTokenInDuende.sln b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/AddingCustomClaimsToAccessTokenInDuende.sln index 04698f99de..e4e1ef3262 100644 --- a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/AddingCustomClaimsToAccessTokenInDuende.sln +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/AddingCustomClaimsToAccessTokenInDuende.sln @@ -9,24 +9,66 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebClient", "WebClient\WebC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{75F68889-23DC-4EC2-8B78-F5C8DC75FB37}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "Api\Api.csproj", "{23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8D058D23-748E-4F5C-A4DA-162526B3934A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D058D23-748E-4F5C-A4DA-162526B3934A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Debug|x64.ActiveCfg = Debug|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Debug|x64.Build.0 = Debug|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Debug|x86.ActiveCfg = Debug|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Debug|x86.Build.0 = Debug|Any CPU {8D058D23-748E-4F5C-A4DA-162526B3934A}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D058D23-748E-4F5C-A4DA-162526B3934A}.Release|Any CPU.Build.0 = Release|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Release|x64.ActiveCfg = Release|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Release|x64.Build.0 = Release|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Release|x86.ActiveCfg = Release|Any CPU + {8D058D23-748E-4F5C-A4DA-162526B3934A}.Release|x86.Build.0 = Release|Any CPU {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Debug|x64.Build.0 = Debug|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Debug|x86.Build.0 = Debug|Any CPU {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Release|Any CPU.Build.0 = Release|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Release|x64.ActiveCfg = Release|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Release|x64.Build.0 = Release|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Release|x86.ActiveCfg = Release|Any CPU + {EA3E4FDE-43D5-4930-81BC-B3B2BE780714}.Release|x86.Build.0 = Release|Any CPU {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Debug|x64.ActiveCfg = Debug|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Debug|x64.Build.0 = Debug|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Debug|x86.ActiveCfg = Debug|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Debug|x86.Build.0 = Debug|Any CPU {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Release|Any CPU.ActiveCfg = Release|Any CPU {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Release|Any CPU.Build.0 = Release|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Release|x64.ActiveCfg = Release|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Release|x64.Build.0 = Release|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Release|x86.ActiveCfg = Release|Any CPU + {75F68889-23DC-4EC2-8B78-F5C8DC75FB37}.Release|x86.Build.0 = Release|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Debug|x64.ActiveCfg = Debug|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Debug|x64.Build.0 = Debug|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Debug|x86.ActiveCfg = Debug|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Debug|x86.Build.0 = Debug|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Release|Any CPU.Build.0 = Release|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Release|x64.ActiveCfg = Release|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Release|x64.Build.0 = Release|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Release|x86.ActiveCfg = Release|Any CPU + {23F6BA94-D4F4-47B9-9D9F-1FE56FF22428}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Api.csproj b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Api.csproj new file mode 100644 index 0000000000..662c1ec715 --- /dev/null +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Api.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Program.cs b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Program.cs new file mode 100644 index 0000000000..502325cce0 --- /dev/null +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Program.cs @@ -0,0 +1,30 @@ +using System.Security.Claims; +using Duende.IdentityModel; +using Microsoft.AspNetCore.Authentication.JwtBearer; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = "https://localhost:5001"; + options.Audience = "paymentsapi"; + options.MapInboundClaims = false; + options.TokenValidationParameters.RoleClaimType = JwtClaimTypes.Role; + }); + +builder.Services.AddAuthorization(); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapGet("/discounts", (ClaimsPrincipal user) => new + { + Tenant = user.FindFirstValue("tenant"), + Discount = user.FindFirstValue("payments.discount") + }) + .RequireAuthorization(policy => policy.RequireRole("admin")); + +app.Run(); diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Properties/launchSettings.json b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Properties/launchSettings.json new file mode 100644 index 0000000000..b63e1d100e --- /dev/null +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Api": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5003", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/appsettings.json b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/appsettings.json new file mode 100644 index 0000000000..10f68b8c8b --- /dev/null +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Config.cs b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Config.cs index c641853de6..da406a9e10 100644 --- a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Config.cs +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Config.cs @@ -1,4 +1,5 @@ -using Duende.IdentityServer; +using Duende.IdentityModel; +using Duende.IdentityServer; using Duende.IdentityServer.Models; namespace IdentityServer; @@ -18,6 +19,15 @@ public static class Config new(name: "payments", displayName: "Allow payments", userClaims: new[] { "payments.discount" }) }; + public static IEnumerable ApiResources => + [ + new ApiResource("paymentsapi", "Payments API") + { + Scopes = { "payments" }, + UserClaims = { JwtClaimTypes.Role } + } + ]; + public static IEnumerable Clients => new Client[] { diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/CustomProfileService.cs b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/CustomProfileService.cs index c0ccb0557c..96557caa70 100644 --- a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/CustomProfileService.cs +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/CustomProfileService.cs @@ -1,4 +1,5 @@ -using Duende.IdentityServer.Models; +using Duende.IdentityModel; +using Duende.IdentityServer.Models; using Duende.IdentityServer.Services; using System.Security.Claims; using Duende.IdentityServer.Extensions; @@ -7,22 +8,26 @@ namespace IdentityServer; public sealed class CustomProfileService : IProfileService { - public Task GetProfileDataAsync(ProfileDataRequestContext context) + public Task GetProfileDataAsync(ProfileDataRequestContext context, CancellationToken ct) { - if (context.Client.ClientId == "web") + if (context.Application?.Identifier == "web") { context.IssuedClaims.Add(new Claim("tenant", "main")); } if (context.RequestedClaimTypes.Any()) { - context.AddRequestedClaims(new[] { new Claim("payments.discount", "20") }); + context.AddRequestedClaims(new[] + { + new Claim("payments.discount", "20"), + new Claim(JwtClaimTypes.Role, "admin") + }); } return Task.CompletedTask; } - public Task IsActiveAsync(IsActiveContext context) + public Task IsActiveAsync(IsActiveContext context, CancellationToken ct) { if (context.Subject.GetSubjectId() == "3") { diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/HostingExtensions.cs b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/HostingExtensions.cs index 623e19383c..ecde387706 100644 --- a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/HostingExtensions.cs +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/HostingExtensions.cs @@ -1,3 +1,4 @@ +using IdentityServer.Pages; using Serilog; namespace IdentityServer; @@ -9,11 +10,12 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde builder.Services.AddIdentityServer(options => { - // https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/api_scopes#authorization-based-on-scopes + // https://docs.duendesoftware.com/identityserver/fundamentals/resources/api-scopes/ options.EmitStaticAudienceClaim = true; }) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) + .AddInMemoryApiResources(Config.ApiResources) .AddInMemoryClients(Config.Clients) .AddTestUsers(TestUsers.Users) .AddProfileService(); diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/IdentityServer.csproj b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/IdentityServer.csproj index 3ce069ee12..fc755165ec 100644 --- a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/IdentityServer.csproj +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/IdentityServer.csproj @@ -1,11 +1,12 @@ - + - net8.0 + net10.0 enable + enable - - + + diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/AccessDenied.cshtml.cs b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/AccessDenied.cshtml.cs index 9247dec033..867b601778 100644 --- a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/AccessDenied.cshtml.cs +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/AccessDenied.cshtml.cs @@ -7,4 +7,4 @@ public class AccessDeniedModel : PageModel public void OnGet() { } -} \ No newline at end of file +} diff --git a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/Create/Index.cshtml b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/Create/Index.cshtml index 2d26395d15..8805ee9dcd 100644 --- a/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/Create/Index.cshtml +++ b/authorization-dotnet/AddingCustomClaimsToAccessTokenInDuende/IdentityServer/Pages/Account/Create/Index.cshtml @@ -1,5 +1,5 @@ @page -@model IdentityServer.Pages.Create.Index +@model IdentityServer.Pages.Account.Create.Index