From 48993c6936c1eb2ecb0031be54fc3168a15f0605 Mon Sep 17 00:00:00 2001 From: Roland Guijt Date: Fri, 28 Jun 2024 17:09:50 +0200 Subject: [PATCH 1/4] Modernize code for AspNetIdentity and all BFF examples --- .../v7/AspNetIdentity/Client/Client.csproj | 1 + .../v7/AspNetIdentity/Client/Program.cs | 63 ++++-- .../v7/AspNetIdentity/Client/Startup.cs | 71 ------ .../Data/ApplicationDbContext.cs | 3 - .../IdentityServerAspNetIdentity.csproj | 1 + .../IdentityServerAspNetIdentity/Program.cs | 83 +++++-- .../IdentityServerAspNetIdentity/Startup.cs | 89 -------- .../v7/BFF/BlazorWasm/Server/Program.cs | 1 + .../v7/BFF/DPoP/DPoP.Api/DPoP.Api.csproj | 7 +- .../DPoP/ConfigureJwtBearerOptions.cs | 1 - .../BFF/DPoP/DPoP.Api/DPoP/DPoPExtensions.cs | 3 - .../v7/BFF/DPoP/DPoP.Api/DPoP/DPoPOptions.cs | 4 +- .../DPoP/DPoPProofValidatonContext.cs | 3 - .../DPoP.Api/DPoP/DPoPProofValidatonResult.cs | 2 - .../DPoP/DPoP.Api/DPoP/DPoPProofValidator.cs | 5 - .../DPoP/DPoPServiceCollectionExtensions.cs | 2 - .../DPoP/DPoP.Api/DPoP/DefaultReplayCache.cs | 2 - .../v7/BFF/DPoP/DPoP.Api/DPoP/IReplayCache.cs | 3 - .../v7/BFF/DPoP/DPoP.Api/EchoController.cs | 64 +++--- .../v7/BFF/DPoP/DPoP.Api/Program.cs | 128 +++++++---- .../v7/BFF/DPoP/DPoP.Api/Startup.cs | 87 -------- .../v7/BFF/DPoP/DPoP.Bff/DPoP.Bff.csproj | 5 +- .../BFF/DPoP/DPoP.Bff/LocalApiController.cs | 3 - .../v7/BFF/DPoP/DPoP.Bff/Program.cs | 160 ++++++++++---- .../v7/BFF/DPoP/DPoP.Bff/Startup.cs | 209 ------------------ .../v7/BFF/DPoP/DPoP.Bff/YarpConfigurator.cs | 87 ++++++++ .../BackendApiHost/BackendApiHost.csproj | 3 +- .../BFF/JsBffSample/BackendApiHost/Program.cs | 50 +++-- .../BFF/JsBffSample/BackendApiHost/Startup.cs | 50 ----- .../BackendApiHost/ToDoController.cs | 149 ++++++------- .../FrontendHost/FrontendHost.csproj | 3 +- .../BFF/JsBffSample/FrontendHost/Program.cs | 87 ++++++-- .../BFF/JsBffSample/FrontendHost/Startup.cs | 88 -------- .../FrontendHost/ToDoController.cs | 147 ++++++------ .../BackendApiHost/BackendApiHost.csproj | 3 +- .../JsBffYarpSample/BackendApiHost/Program.cs | 50 +++-- .../JsBffYarpSample/BackendApiHost/Startup.cs | 50 ----- .../BackendApiHost/ToDoController.cs | 149 ++++++------- .../FrontendHost/FrontendHost.csproj | 3 +- .../FrontendHost/InMemoryConfigProvider.cs | 112 +++++----- .../JsBffYarpSample/FrontendHost/Program.cs | 94 ++++++-- .../JsBffYarpSample/FrontendHost/Startup.cs | 123 ----------- .../FrontendHost/ToDoController.cs | 147 ++++++------ .../FrontendHost/YarpConfigurator.cs | 35 +++ .../v7/BFF/React/React.Bff/Program.cs | 1 + .../BackendApiHost/BackendApiHost.csproj | 1 + .../BFF/SplitHosts/BackendApiHost/Program.cs | 50 +++-- .../BFF/SplitHosts/BackendApiHost/Startup.cs | 50 ----- .../BackendApiHost/ToDoController.cs | 149 ++++++------- ...r.cs => FrontendHostReturnUrlValidator.cs} | 0 .../v7/BFF/SplitHosts/BackendHost/Program.cs | 101 +++++++-- .../v7/BFF/SplitHosts/BackendHost/Startup.cs | 96 -------- .../SplitHosts/BackendHost/ToDoController.cs | 147 ++++++------ .../FrontendHost/FrontendHost.csproj | 1 + .../v7/BFF/SplitHosts/FrontendHost/Program.cs | 2 - .../TokenExchange.Api/Program.cs | 119 ++++++---- .../TokenExchange.Api/Startup.cs | 75 ------- .../TokenExchange.Api.csproj | 7 +- .../ImpersonationAccessTokenRetriever.cs | 3 - .../TokenExchange.Bff/LocalApiController.cs | 3 - .../TokenExchange.Bff/Program.cs | 139 +++++++++--- .../TokenExchange.Bff/Startup.cs | 118 ---------- .../TokenExchange.Bff.csproj | 7 +- .../TokenExchange.IdentityServer.csproj | 5 +- 64 files changed, 1474 insertions(+), 2030 deletions(-) delete mode 100755 IdentityServer/v7/AspNetIdentity/Client/Startup.cs delete mode 100755 IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Startup.cs delete mode 100644 IdentityServer/v7/BFF/DPoP/DPoP.Api/Startup.cs delete mode 100644 IdentityServer/v7/BFF/DPoP/DPoP.Bff/Startup.cs create mode 100644 IdentityServer/v7/BFF/DPoP/DPoP.Bff/YarpConfigurator.cs delete mode 100755 IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Startup.cs delete mode 100755 IdentityServer/v7/BFF/JsBffSample/FrontendHost/Startup.cs delete mode 100755 IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Startup.cs delete mode 100755 IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Startup.cs create mode 100644 IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/YarpConfigurator.cs delete mode 100644 IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Startup.cs rename IdentityServer/v7/BFF/SplitHosts/BackendHost/{FrontendHostReturlUrlValidator.cs => FrontendHostReturnUrlValidator.cs} (100%) delete mode 100644 IdentityServer/v7/BFF/SplitHosts/BackendHost/Startup.cs delete mode 100644 IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Startup.cs delete mode 100644 IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Startup.cs diff --git a/IdentityServer/v7/AspNetIdentity/Client/Client.csproj b/IdentityServer/v7/AspNetIdentity/Client/Client.csproj index f3e670c9..12fc173b 100755 --- a/IdentityServer/v7/AspNetIdentity/Client/Client.csproj +++ b/IdentityServer/v7/AspNetIdentity/Client/Client.csproj @@ -2,6 +2,7 @@ net8.0 + true diff --git a/IdentityServer/v7/AspNetIdentity/Client/Program.cs b/IdentityServer/v7/AspNetIdentity/Client/Program.cs index 1f71b01b..07b281cc 100755 --- a/IdentityServer/v7/AspNetIdentity/Client/Program.cs +++ b/IdentityServer/v7/AspNetIdentity/Client/Program.cs @@ -1,26 +1,43 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Client +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddRazorPages(); + +builder.Services.AddAuthentication(options => { - public class Program + options.DefaultScheme = "cookies"; + options.DefaultChallengeScheme = "oidc"; +}) + .AddCookie("cookies") + .AddOpenIdConnect("oidc", options => { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + options.Authority = "https://localhost:5001"; + options.ClientId = "client"; + options.MapInboundClaims = false; + options.SaveTokens = true; + options.DisableTelemetry = true; + }); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} +else +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapRazorPages(); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/AspNetIdentity/Client/Startup.cs b/IdentityServer/v7/AspNetIdentity/Client/Startup.cs deleted file mode 100755 index a19c86bb..00000000 --- a/IdentityServer/v7/AspNetIdentity/Client/Startup.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Client -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddRazorPages(); - - services.AddAuthentication(options => - { - options.DefaultScheme = "cookies"; - options.DefaultChallengeScheme = "oidc"; - }) - .AddCookie("cookies") - .AddOpenIdConnect("oidc", options => - { - options.Authority = "https://localhost:5001"; - options.ClientId = "client"; - options.MapInboundClaims = false; - options.SaveTokens = true; - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - }); - } - } -} diff --git a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Data/ApplicationDbContext.cs b/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Data/ApplicationDbContext.cs index 783b53b7..d7c2dd89 100755 --- a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Data/ApplicationDbContext.cs +++ b/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Data/ApplicationDbContext.cs @@ -1,8 +1,5 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Text; namespace IdentityServerAspNetIdentity.Data { diff --git a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/IdentityServerAspNetIdentity.csproj b/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/IdentityServerAspNetIdentity.csproj index 7500d102..e8cd962a 100755 --- a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/IdentityServerAspNetIdentity.csproj +++ b/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/IdentityServerAspNetIdentity.csproj @@ -3,6 +3,7 @@ net8.0 aspnet-WebApplication1-28A0C71B-1513-41CA-97E3-79006F6EEC18 + true diff --git a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Program.cs b/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Program.cs index 2335c13c..583a5752 100755 --- a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Program.cs +++ b/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Program.cs @@ -1,26 +1,65 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace IdentityServerAspNetIdentity -{ - public class Program - { - public static void Main(string[] args) +using Duende.IdentityServer.Models; +using IdentityServerAspNetIdentity.Data; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDbContext(options => + options.UseSqlite( + builder.Configuration.GetConnectionString("DefaultConnection"))); +builder.Services.AddDatabaseDeveloperPageExceptionFilter(); +builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); +builder.Services.AddRazorPages(); + +builder.Services.AddIdentityServer() + .AddInMemoryClients([ + new Client { - CreateHostBuilder(args).Build().Run(); + ClientId = "client", + AllowedGrantTypes = GrantTypes.Implicit, + RedirectUris = { "https://localhost:5002/signin-oidc" }, + PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, + FrontChannelLogoutUri = "https://localhost:5002/signout-oidc", + AllowedScopes = { "openid", "profile", "email", "phone" } } + ]) + .AddInMemoryIdentityResources([ + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + new IdentityResources.Email(), + new IdentityResources.Phone(), + ]) + .AddAspNetIdentity(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } +builder.Services.AddLogging(options => +{ + options.AddFilter("Duende", LogLevel.Debug); +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); + app.UseMigrationsEndPoint(); } +else +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseIdentityServer(); +app.UseAuthorization(); + +app.MapRazorPages(); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Startup.cs b/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Startup.cs deleted file mode 100755 index 63700924..00000000 --- a/IdentityServer/v7/AspNetIdentity/IdentityServerAspNetIdentity/Startup.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Duende.IdentityServer.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Hosting; -using IdentityServerAspNetIdentity.Data; - -namespace IdentityServerAspNetIdentity -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(options => - options.UseSqlite( - Configuration.GetConnectionString("DefaultConnection"))); - services.AddDatabaseDeveloperPageExceptionFilter(); - services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) - .AddEntityFrameworkStores(); - services.AddRazorPages(); - - services.AddIdentityServer() - .AddInMemoryClients(new Client[] { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.Implicit, - RedirectUris = { "https://localhost:5002/signin-oidc" }, - PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, - FrontChannelLogoutUri = "https://localhost:5002/signout-oidc", - AllowedScopes = { "openid", "profile", "email", "phone" } - } - }) - .AddInMemoryIdentityResources(new IdentityResource[] { - new IdentityResources.OpenId(), - new IdentityResources.Profile(), - new IdentityResources.Email(), - new IdentityResources.Phone(), - }) - .AddAspNetIdentity(); - - services.AddLogging(options => - { - options.AddFilter("Duende", LogLevel.Debug); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseMigrationsEndPoint(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseIdentityServer(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - }); - } - } -} diff --git a/IdentityServer/v7/BFF/BlazorWasm/Server/Program.cs b/IdentityServer/v7/BFF/BlazorWasm/Server/Program.cs index 7fd200fe..f784dcf2 100644 --- a/IdentityServer/v7/BFF/BlazorWasm/Server/Program.cs +++ b/IdentityServer/v7/BFF/BlazorWasm/Server/Program.cs @@ -36,6 +36,7 @@ options.MapInboundClaims = false; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; + options.DisableTelemetry = true; }); var app = builder.Build(); diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP.Api.csproj b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP.Api.csproj index 55f79f8e..a28c8b2b 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP.Api.csproj +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP.Api.csproj @@ -2,11 +2,12 @@ net8.0 + true - - - + + + diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/ConfigureJwtBearerOptions.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/ConfigureJwtBearerOptions.cs index b75028c4..e35e512d 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/ConfigureJwtBearerOptions.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/ConfigureJwtBearerOptions.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; -using System; namespace DPoP.Api; diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPExtensions.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPExtensions.cs index 8cafdcb9..bddc6beb 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPExtensions.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPExtensions.cs @@ -1,9 +1,6 @@ using IdentityModel; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Tokens; -using System.Collections.Generic; -using System.Linq; using System.Text.Json; namespace DPoP.Api; diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPOptions.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPOptions.cs index 5e0f5ca1..5b5a56af 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPOptions.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPOptions.cs @@ -1,6 +1,4 @@ -using System; - -namespace DPoP.Api; +namespace DPoP.Api; public class DPoPOptions { diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonContext.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonContext.cs index f7f86d4d..89eb041e 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonContext.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonContext.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Security.Claims; - namespace DPoP.Api; public class DPoPProofValidatonContext diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonResult.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonResult.cs index 88c67934..29e9fa60 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonResult.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidatonResult.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace DPoP.Api; public class DPoPProofValidatonResult diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidator.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidator.cs index 0d36ed3e..bb4581f9 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidator.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPProofValidator.cs @@ -1,16 +1,11 @@ using IdentityModel; using Microsoft.AspNetCore.DataProtection; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace DPoP.Api; diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPServiceCollectionExtensions.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPServiceCollectionExtensions.cs index 6ff844ed..2fa072cd 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPServiceCollectionExtensions.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DPoPServiceCollectionExtensions.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System; namespace DPoP.Api; diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DefaultReplayCache.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DefaultReplayCache.cs index db098ba0..9b95a1b8 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DefaultReplayCache.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/DefaultReplayCache.cs @@ -1,6 +1,4 @@ using Microsoft.Extensions.Caching.Distributed; -using System; -using System.Threading.Tasks; namespace DPoP.Api; diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/IReplayCache.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/IReplayCache.cs index b0701901..c048e2f4 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/IReplayCache.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/DPoP/IReplayCache.cs @@ -1,6 +1,3 @@ -using System; -using System.Threading.Tasks; - namespace DPoP.Api; public interface IReplayCache diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/EchoController.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/EchoController.cs index b0a33399..1d396ee1 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/EchoController.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/EchoController.cs @@ -2,44 +2,42 @@ // See LICENSE in the project root for license information. using Microsoft.AspNetCore.Mvc; -using System; using Microsoft.AspNetCore.Authorization; -namespace DPoP.Api +namespace DPoP.Api; + +[AllowAnonymous] +public class EchoController : ControllerBase { - [AllowAnonymous] - public class EchoController : ControllerBase + [HttpGet("{**catch-all}")] + public IActionResult Get() { - [HttpGet("{**catch-all}")] - public IActionResult Get() + string message; + var sub = User.FindFirst("sub"); + + if (!User.Identity.IsAuthenticated) { - string message; - var sub = User.FindFirst("sub"); - - if (!User.Identity.IsAuthenticated) - { - message = "Hello, anonymous caller"; - } - else if (sub != null) - { - var userName = User.FindFirst("name"); - message = $"Hello user, {userName.Value}"; - } - else - { - var client = User.FindFirst("client_id"); - message = $"Hello client, {client.Value}"; - } - - var response = new - { - path = Request.Path.Value, - message = message, - time = DateTime.UtcNow.ToString(), - headers = Request.Headers - }; - - return Ok(response); + message = "Hello, anonymous caller"; + } + else if (sub != null) + { + var userName = User.FindFirst("name"); + message = $"Hello user, {userName.Value}"; } + else + { + var client = User.FindFirst("client_id"); + message = $"Hello client, {client.Value}"; + } + + var response = new + { + path = Request.Path.Value, + message = message, + time = DateTime.UtcNow.ToString(), + headers = Request.Headers + }; + + return Ok(response); } } diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/Program.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/Program.cs index f0816e96..c2932fe4 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/Program.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Api/Program.cs @@ -1,57 +1,89 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System; -using System.Diagnostics; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using DPoP.Api; +using Microsoft.IdentityModel.Tokens; using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; +using System.Diagnostics; -namespace DPoP.Api -{ - public class Program +Activity.DefaultIdFormat = ActivityIdFormat.W3C; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) + .CreateLogger(); + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSerilog(); +builder.Services.AddControllers(); + +builder.Services.AddAuthentication("token") + .AddJwtBearer("token", options => { - public static int Main(string[] args) - { - Activity.DefaultIdFormat = ActivityIdFormat.W3C; - - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) - .MinimumLevel.Override("System", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) - .CreateLogger(); - - try - { - Log.Information("Starting host..."); - CreateHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly."); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } - - public static IHostBuilder CreateHostBuilder(string[] args) + options.Authority = "https://demo.duendesoftware.com"; + options.MapInboundClaims = false; + + options.TokenValidationParameters = new TokenValidationParameters() { - return Host.CreateDefaultBuilder(args) - .UseSerilog() - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } - } -} \ No newline at end of file + ValidateAudience = false, + ValidTypes = new[] { "at+jwt" }, + + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + +// layers DPoP onto the "token" scheme above +builder.Services.ConfigureDPoPTokensForScheme("token"); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("ApiCaller", policy => + { + policy.RequireClaim("scope", "api"); + }); + + options.AddPolicy("RequireInteractiveUser", policy => + { + policy.RequireClaim("sub"); + }); +}); + +var app = builder.Build(); + +// The BFF sets the X-Forwarded-* headers to reflect that it +// forwarded the request here. Using the forwarded headers +// middleware here would therefore change the request's host to be +// the bff instead of this API, which is not what the DPoP +// validation code expects when it checks the htu value. If this API +// were hosted behind a load balancer, you might need to add back +// the forwarded headers middleware, or consider changing the DPoP +// proof validation. + +// app.UseForwardedHeaders(new ForwardedHeadersOptions +// { +// ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, +// }); + +app.UseSerilogRequestLogging(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers().RequireAuthorization("ApiCaller"); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Api/Startup.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Api/Startup.cs deleted file mode 100644 index 2554e0d5..00000000 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Api/Startup.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.IdentityModel.Tokens; -using Serilog; - -namespace DPoP.Api -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddAuthentication("token") - .AddJwtBearer("token", options => - { - options.Authority = "https://demo.duendesoftware.com"; - options.MapInboundClaims = false; - - options.TokenValidationParameters = new TokenValidationParameters() - { - ValidateAudience = false, - ValidTypes = new[] { "at+jwt" }, - - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - - // layers DPoP onto the "token" scheme above - services.ConfigureDPoPTokensForScheme("token"); - - services.AddAuthorization(options => - { - options.AddPolicy("ApiCaller", policy => - { - policy.RequireClaim("scope", "api"); - }); - - options.AddPolicy("RequireInteractiveUser", policy => - { - policy.RequireClaim("sub"); - }); - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - // The BFF sets the X-Forwarded-* headers to reflect that it - // forwarded the request here. Using the forwarded headers - // middleware here would therefore change the request's host to be - // the bff instead of this API, which is not what the DPoP - // validation code expects when it checks the htu value. If this API - // were hosted behind a load balancer, you might need to add back - // the forwarded headers middleware, or consider changing the DPoP - // proof validation. - - // app.UseForwardedHeaders(new ForwardedHeadersOptions - // { - // ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, - // }); - - app.UseSerilogRequestLogging(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers() - .RequireAuthorization("ApiCaller"); - }); - } - } -} \ No newline at end of file diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/DPoP.Bff.csproj b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/DPoP.Bff.csproj index a25aa7d5..28686e65 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/DPoP.Bff.csproj +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/DPoP.Bff.csproj @@ -4,11 +4,12 @@ net8.0 Host6 enable + true - - + + diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/LocalApiController.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/LocalApiController.cs index f5298f47..0594f422 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/LocalApiController.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/LocalApiController.cs @@ -1,10 +1,7 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System.Collections.Generic; -using System.Net.Http; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace DPoP.BFF; diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/Program.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/Program.cs index 0c4e3e54..33615b6a 100644 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/Program.cs +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/Program.cs @@ -1,54 +1,128 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Duende.Bff.Yarp; +using Microsoft.IdentityModel.Tokens; using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; +using System.Security.Cryptography; +using System.Text.Json; -namespace DPoP.BFF; +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .MinimumLevel.Override("IdentityModel", LogEventLevel.Debug) + .MinimumLevel.Override("Duende.Bff", LogEventLevel.Debug) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) + .CreateLogger(); -public class Program +var builder = WebApplication.CreateBuilder(args); + +var yarpBuilder = builder.Services.AddReverseProxy() + .AddBffExtensions(); +//Configure yarp's routes and clusters in extension method +yarpBuilder.Configure(); + +builder.Services.AddSerilog(); +// Add BFF services to DI - also add server-side session management +builder.Services.AddBff(options => +{ + var rsaKey = new RsaSecurityKey(RSA.Create(2048)); + var jwkKey = JsonWebKeyConverter.ConvertFromSecurityKey(rsaKey); + jwkKey.Alg = "PS256"; + var jwk = JsonSerializer.Serialize(jwkKey); + options.DPoPJsonWebKey = jwk; +}) +.AddRemoteApis() +.AddServerSideSessions(); + +// local APIs +builder.Services.AddControllers(); + +// cookie options +builder.Services.AddAuthentication(options => { - public static int Main(string[] args) + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + options.DefaultSignOutScheme = "oidc"; +}) + .AddCookie("cookie", options => + { + // set session lifetime + options.ExpireTimeSpan = TimeSpan.FromHours(8); + + // sliding or absolute + options.SlidingExpiration = false; + + // host prefixed cookie name + options.Cookie.Name = "__Host-bff-dpop"; + + // strict SameSite handling + options.Cookie.SameSite = SameSiteMode.Strict; + }) + .AddOpenIdConnect("oidc", options => { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Information() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) - .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) - .MinimumLevel.Override("IdentityModel", LogEventLevel.Debug) - .MinimumLevel.Override("Duende.Bff", LogEventLevel.Debug) - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) - .CreateLogger(); - - try - { - Log.Information("Starting host..."); - CreateHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly."); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } - - public static IHostBuilder CreateHostBuilder(string[] args) + options.Authority = "https://demo.duendesoftware.com"; + + // confidential client using code flow + PKCE + options.ClientId = "interactive.confidential"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.ResponseMode = "query"; + + options.MapInboundClaims = false; + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.DisableTelemetry = true; + + // request scopes + refresh tokens + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api"); + options.Scope.Add("offline_access"); + }); + +builder.Services.AddUserAccessTokenHttpClient("api", + configureClient: client => { - return Host.CreateDefaultBuilder(args) - .UseSerilog() - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + client.BaseAddress = new Uri("https://localhost:6001/api"); + }); + +var app = builder.Build(); + +app.UseSerilogRequestLogging(); +app.UseDeveloperExceptionPage(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseAuthentication(); +app.UseRouting(); + +// adds antiforgery protection for local APIs +app.UseBff(); + +// adds authorization for local and remote API endpoints +app.UseAuthorization(); + +app.MapControllers() + .RequireAuthorization() + .AsBffApiEndpoint(); + +// login, logout, user, backchannel logout... +app.MapBffManagementEndpoints(); + +// proxy endpoints using yarp +app.MapReverseProxy(proxyApp => +{ + proxyApp.UseAntiforgeryCheck(); +}); +// proxy endpoints in extension method using BFF's simplified wrapper +app.MapRemoteUrls(); + +app.Run(); diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/Startup.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/Startup.cs deleted file mode 100644 index edc022c5..00000000 --- a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/Startup.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text.Json; -using Duende.Bff; -using Duende.Bff.Yarp; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using Serilog; -using Yarp.ReverseProxy.Configuration; - - -namespace DPoP.BFF; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - var builder = services.AddReverseProxy() - .AddBffExtensions(); - - builder.LoadFromMemory( - new[] - { - new RouteConfig() - { - RouteId = "user-token", - ClusterId = "cluster1", - - Match = new() - { - Path = "/yarp/user-token/{**catch-all}" - } - }.WithAccessToken(TokenType.User).WithAntiforgeryCheck(), - new RouteConfig() - { - RouteId = "client-token", - ClusterId = "cluster1", - - Match = new() - { - Path = "/yarp/client-token/{**catch-all}" - } - }.WithAccessToken(TokenType.Client).WithAntiforgeryCheck(), - new RouteConfig() - { - RouteId = "user-or-client-token", - ClusterId = "cluster1", - - Match = new() - { - Path = "/yarp/user-or-client-token/{**catch-all}" - } - }.WithAccessToken(TokenType.UserOrClient).WithAntiforgeryCheck(), - new RouteConfig() - { - RouteId = "anonymous", - ClusterId = "cluster1", - - Match = new() - { - Path = "/yarp/anonymous/{**catch-all}" - } - }.WithAntiforgeryCheck() - }, - new[] - { - new ClusterConfig - { - ClusterId = "cluster1", - - Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "destination1", new() { Address = "https://localhost:6001" } }, - } - } - }); - - // Add BFF services to DI - also add server-side session management - services.AddBff(options => - { - var rsaKey = new RsaSecurityKey(RSA.Create(2048)); - var jwkKey = JsonWebKeyConverter.ConvertFromSecurityKey(rsaKey); - jwkKey.Alg = "PS256"; - var jwk = JsonSerializer.Serialize(jwkKey); - options.DPoPJsonWebKey = jwk; - }) - .AddRemoteApis() - .AddServerSideSessions(); - - // local APIs - services.AddControllers(); - - // cookie options - services.AddAuthentication(options => - { - options.DefaultScheme = "cookie"; - options.DefaultChallengeScheme = "oidc"; - options.DefaultSignOutScheme = "oidc"; - }) - .AddCookie("cookie", options => - { - // set session lifetime - options.ExpireTimeSpan = TimeSpan.FromHours(8); - - // sliding or absolute - options.SlidingExpiration = false; - - // host prefixed cookie name - options.Cookie.Name = "__Host-bff-dpop"; - - // strict SameSite handling - options.Cookie.SameSite = SameSiteMode.Strict; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = "https://demo.duendesoftware.com"; - - // confidential client using code flow + PKCE - options.ClientId = "interactive.confidential"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.ResponseMode = "query"; - - options.MapInboundClaims = false; - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - - // request scopes + refresh tokens - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("api"); - options.Scope.Add("offline_access"); - }); - - services.AddUserAccessTokenHttpClient("api", - configureClient: client => - { - client.BaseAddress = new Uri("https://localhost:6001/api"); - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseSerilogRequestLogging(); - app.UseDeveloperExceptionPage(); - - app.UseDefaultFiles(); - app.UseStaticFiles(); - - app.UseAuthentication(); - app.UseRouting(); - - // adds antiforgery protection for local APIs - app.UseBff(); - - // adds authorization for local and remote API endpoints - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - // local APIs - endpoints.MapControllers() - .RequireAuthorization() - .AsBffApiEndpoint(); - - // login, logout, user, backchannel logout... - endpoints.MapBffManagementEndpoints(); - - // proxy endpoints using yarp - endpoints.MapReverseProxy(proxyApp => - { - proxyApp.UseAntiforgeryCheck(); - }); - - // proxy endpoints using BFF's simplified wrapper - MapRemoteUrls(endpoints); - }); - } - - private static void MapRemoteUrls(IEndpointRouteBuilder endpoints) - { - // On this path, we use a client credentials token - endpoints.MapRemoteBffApiEndpoint("/api/client-token", "https://localhost:6001") - .RequireAccessToken(TokenType.Client); - - // On this path, we use a user token if logged in, and fall back to a client credentials token if not - endpoints.MapRemoteBffApiEndpoint("/api/user-or-client-token", "https://localhost:6001") - .RequireAccessToken(TokenType.UserOrClient); - - // On this path, we make anonymous requests - endpoints.MapRemoteBffApiEndpoint("/api/anonymous", "https://localhost:6001"); - - // On this path, we use the client token only if the user is logged in - endpoints.MapRemoteBffApiEndpoint("/api/optional-user-token", "https://localhost:6001") - .WithOptionalUserAccessToken(); - - // On this path, we require the user token - endpoints.MapRemoteBffApiEndpoint("/api/user-token", "https://localhost:6001") - .RequireAccessToken(TokenType.User); - } -} diff --git a/IdentityServer/v7/BFF/DPoP/DPoP.Bff/YarpConfigurator.cs b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/YarpConfigurator.cs new file mode 100644 index 00000000..cafb61b3 --- /dev/null +++ b/IdentityServer/v7/BFF/DPoP/DPoP.Bff/YarpConfigurator.cs @@ -0,0 +1,87 @@ +using Duende.Bff; +using Duende.Bff.Yarp; +using Yarp.ReverseProxy.Configuration; + +public static class YarpConfigurator +{ + public static void Configure(this IReverseProxyBuilder builder) + { + builder.LoadFromMemory( + [ + new RouteConfig() + { + RouteId = "user-token", + ClusterId = "cluster1", + + Match = new() + { + Path = "/yarp/user-token/{**catch-all}" + } + }.WithAccessToken(TokenType.User).WithAntiforgeryCheck(), + new RouteConfig() + { + RouteId = "client-token", + ClusterId = "cluster1", + + Match = new() + { + Path = "/yarp/client-token/{**catch-all}" + } + }.WithAccessToken(TokenType.Client).WithAntiforgeryCheck(), + new RouteConfig() + { + RouteId = "user-or-client-token", + ClusterId = "cluster1", + + Match = new() + { + Path = "/yarp/user-or-client-token/{**catch-all}" + } + }.WithAccessToken(TokenType.UserOrClient).WithAntiforgeryCheck(), + new RouteConfig() + { + RouteId = "anonymous", + ClusterId = "cluster1", + + Match = new() + { + Path = "/yarp/anonymous/{**catch-all}" + } + }.WithAntiforgeryCheck() + ], + [ + new ClusterConfig + { + ClusterId = "cluster1", + + Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "destination1", new() { Address = "https://localhost:6001" } }, + } + } + ]); + } + + public static void MapRemoteUrls(this WebApplication app) + { + // On this path, we use a client credentials token + app.MapRemoteBffApiEndpoint("/api/client-token", "https://localhost:6001") + .RequireAccessToken(TokenType.Client); + + // On this path, we use a user token if logged in, and fall back to a client credentials token if not + app.MapRemoteBffApiEndpoint("/api/user-or-client-token", "https://localhost:6001") + .RequireAccessToken(TokenType.UserOrClient); + + // On this path, we make anonymous requests + app.MapRemoteBffApiEndpoint("/api/anonymous", "https://localhost:6001"); + + // On this path, we use the client token only if the user is logged in + app.MapRemoteBffApiEndpoint("/api/optional-user-token", "https://localhost:6001") + .WithOptionalUserAccessToken(); + + // On this path, we require the user token + app.MapRemoteBffApiEndpoint("/api/user-token", "https://localhost:6001") + .RequireAccessToken(TokenType.User); + } +} + diff --git a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/BackendApiHost.csproj b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/BackendApiHost.csproj index d9ea4a9d..312e7ac0 100755 --- a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/BackendApiHost.csproj +++ b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/BackendApiHost.csproj @@ -2,10 +2,11 @@ net8.0 + true - + diff --git a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Program.cs b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Program.cs index 0a739198..d5fa6189 100755 --- a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Program.cs +++ b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Program.cs @@ -1,20 +1,36 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); -namespace BackendApiHost +builder.Services.AddControllers(); + +builder.Services.AddAuthentication("token") + .AddJwtBearer("token", options => + { + options.Authority = "https://demo.duendesoftware.com"; + options.Audience = "api"; + + options.MapInboundClaims = false; + }); + +builder.Services.AddAuthorization(options => { - public class Program + options.AddPolicy("ApiCaller", policy => + { + policy.RequireClaim("scope", "api"); + }); + + options.AddPolicy("RequireInteractiveUser", policy => { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + policy.RequireClaim("sub"); + }); +}); + +var app = builder.Build(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers().RequireAuthorization("ApiCaller"); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Startup.cs b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Startup.cs deleted file mode 100755 index 8c9642fe..00000000 --- a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/Startup.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace BackendApiHost -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddAuthentication("token") - .AddJwtBearer("token", options => - { - options.Authority = "https://demo.duendesoftware.com"; - options.Audience = "api"; - - options.MapInboundClaims = false; - }); - - services.AddAuthorization(options => - { - options.AddPolicy("ApiCaller", policy => - { - policy.RequireClaim("scope", "api"); - }); - - options.AddPolicy("RequireInteractiveUser", policy => - { - policy.RequireClaim("sub"); - }); - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers() - .RequireAuthorization("ApiCaller"); - }); - } - } -} diff --git a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs index 0417312d..bed69d42 100755 --- a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs @@ -2,99 +2,94 @@ // See LICENSE in the project root for license information. using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Logging; -namespace BackendApiHost +namespace BackendApiHost; + +[Authorize("RequireInteractiveUser")] +public class ToDoController : ControllerBase { - [Authorize("RequireInteractiveUser")] - public class ToDoController : ControllerBase - { - private readonly ILogger _logger; + private readonly ILogger _logger; - private static readonly List __data = new List() - { - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, - }; + private static readonly List __data = new List() + { + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, + }; - public ToDoController(ILogger logger) - { - _logger = logger; - } + public ToDoController(ILogger logger) + { + _logger = logger; + } - [HttpGet("todos")] - public IActionResult GetAll() - { - _logger.LogInformation("GetAll"); - - return Ok(__data.AsEnumerable()); - } + [HttpGet("todos")] + public IActionResult GetAll() + { + _logger.LogInformation("GetAll"); + + return Ok(__data.AsEnumerable()); + } - [HttpGet("todos/{id}")] - public IActionResult Get(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); - - _logger.LogInformation("Get {id}", id); - return Ok(item); - } + [HttpGet("todos/{id}")] + public IActionResult Get(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); + + _logger.LogInformation("Get {id}", id); + return Ok(item); + } - [HttpPost("todos")] - public IActionResult Post([FromBody] ToDo model) - { - model.Id = ToDo.NewId(); - model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; - - __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + [HttpPost("todos")] + public IActionResult Post([FromBody] ToDo model) + { + model.Id = ToDo.NewId(); + model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; + + __data.Add(model); + _logger.LogInformation("Add {name}", model.Name); - return Created(Url.Action(nameof(Get), new { id = model.Id }), model); - } + return Created(Url.Action(nameof(Get), new { id = model.Id }), model); + } - [HttpPut("todos/{id}")] - public IActionResult Put(int id, [FromBody] ToDo model) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + [HttpPut("todos/{id}")] + public IActionResult Put(int id, [FromBody] ToDo model) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - item.Date = model.Date; - item.Name = model.Name; + item.Date = model.Date; + item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); - - return NoContent(); - } + _logger.LogInformation("Update {name}", model.Name); - [HttpDelete("todos/{id}")] - public IActionResult Delete(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + return NoContent(); + } + + [HttpDelete("todos/{id}")] + public IActionResult Delete(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - __data.Remove(item); - _logger.LogInformation("Delete {id}", id); + __data.Remove(item); + _logger.LogInformation("Delete {id}", id); - return NoContent(); - } + return NoContent(); } - - public class ToDo +} + +public class ToDo +{ + static int _nextId = 1; + public static int NewId() { - static int _nextId = 1; - public static int NewId() - { - return _nextId++; - } - - public int Id { get; set; } - public DateTimeOffset Date { get; set; } - public string Name { get; set; } - public string User { get; set; } + return _nextId++; } + + public int Id { get; set; } + public DateTimeOffset Date { get; set; } + public string Name { get; set; } + public string User { get; set; } } diff --git a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/FrontendHost.csproj b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/FrontendHost.csproj index ec29ea05..85c826e0 100755 --- a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/FrontendHost.csproj +++ b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/FrontendHost.csproj @@ -2,13 +2,14 @@ net8.0 + true - + diff --git a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/Program.cs b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/Program.cs index 3a7f715c..782129a3 100755 --- a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/Program.cs +++ b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/Program.cs @@ -1,20 +1,75 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Duende.Bff.Yarp; -namespace FrontendHost +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +builder.Services.AddBff() + .AddRemoteApis(); + +// registers HTTP client that uses the managed user access token +builder.Services.AddUserAccessTokenHttpClient("api_client", configureClient: client => +{ + client.BaseAddress = new Uri("https://localhost:5002/"); +}); + +builder.Services.AddAuthentication(options => { - public class Program + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + options.DefaultSignOutScheme = "oidc"; +}) + .AddCookie("cookie", options => { - public static void Main(string[] args) + options.Cookie.Name = "__Host-bff"; + options.Cookie.SameSite = SameSiteMode.Strict; + }) + .AddOpenIdConnect("oidc", options => + { + options.Authority = "https://demo.duendesoftware.com"; + options.ClientId = "interactive.confidential"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.ResponseMode = "query"; + + options.GetClaimsFromUserInfoEndpoint = true; + options.MapInboundClaims = false; + options.SaveTokens = true; + options.DisableTelemetry = true; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api"); + options.Scope.Add("offline_access"); + + options.TokenValidationParameters = new() { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + +var app = builder.Build(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseBff(); +app.UseAuthorization(); + +app.MapBffManagementEndpoints(); + +// if you want the TODOs API local +app.MapControllers() + .RequireAuthorization() + .AsBffApiEndpoint(); + +// if you want the TODOs API remote +// app.MapRemoteBffApiEndpoint("/todos", "https://localhost:5020/todos") +// .RequireAccessToken(Duende.Bff.TokenType.User); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/Startup.cs b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/Startup.cs deleted file mode 100755 index 91ae1953..00000000 --- a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/Startup.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using System; -using Duende.Bff.Yarp; - -namespace FrontendHost -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddBff() - .AddRemoteApis(); - - // registers HTTP client that uses the managed user access token - services.AddUserAccessTokenHttpClient("api_client", configureClient: client => - { - client.BaseAddress = new Uri("https://localhost:5002/"); - }); - - services.AddAuthentication(options => - { - options.DefaultScheme = "cookie"; - options.DefaultChallengeScheme = "oidc"; - options.DefaultSignOutScheme = "oidc"; - }) - .AddCookie("cookie", options => - { - options.Cookie.Name = "__Host-bff"; - options.Cookie.SameSite = SameSiteMode.Strict; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = "https://demo.duendesoftware.com"; - options.ClientId = "interactive.confidential"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.ResponseMode = "query"; - - options.GetClaimsFromUserInfoEndpoint = true; - options.MapInboundClaims = false; - options.SaveTokens = true; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("api"); - options.Scope.Add("offline_access"); - - options.TokenValidationParameters = new() - { - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseDefaultFiles(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseBff(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBffManagementEndpoints(); - - // if you want the TODOs API local - endpoints.MapControllers() - .RequireAuthorization() - .AsBffApiEndpoint(); - - // if you want the TODOs API remote - // endpoints.MapRemoteBffApiEndpoint("/todos", "https://localhost:5020/todos") - // .RequireAccessToken(Duende.Bff.TokenType.User); - }); - } - } -} diff --git a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs index 86158213..e81a3623 100755 --- a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs @@ -2,97 +2,92 @@ // See LICENSE in the project root for license information. using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Logging; -namespace FrontendHost +namespace FrontendHost; + +public class ToDoController : ControllerBase { - public class ToDoController : ControllerBase - { - private readonly ILogger _logger; + private readonly ILogger _logger; - private static readonly List __data = new List() - { - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, - }; + private static readonly List __data = new List() + { + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, + }; - public ToDoController(ILogger logger) - { - _logger = logger; - } + public ToDoController(ILogger logger) + { + _logger = logger; + } - [HttpGet("todos")] - public IActionResult GetAll() - { - _logger.LogInformation("GetAll"); - - return Ok(__data.AsEnumerable()); - } + [HttpGet("todos")] + public IActionResult GetAll() + { + _logger.LogInformation("GetAll"); + + return Ok(__data.AsEnumerable()); + } - [HttpGet("todos/{id}")] - public IActionResult Get(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); - - _logger.LogInformation("Get {id}", id); - return Ok(item); - } + [HttpGet("todos/{id}")] + public IActionResult Get(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); + + _logger.LogInformation("Get {id}", id); + return Ok(item); + } - [HttpPost("todos")] - public IActionResult Post([FromBody] ToDo model) - { - model.Id = ToDo.NewId(); - model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; - - __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + [HttpPost("todos")] + public IActionResult Post([FromBody] ToDo model) + { + model.Id = ToDo.NewId(); + model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; + + __data.Add(model); + _logger.LogInformation("Add {name}", model.Name); - return Created(Url.Action(nameof(Get), new { id = model.Id }), model); - } + return Created(Url.Action(nameof(Get), new { id = model.Id }), model); + } - [HttpPut("todos/{id}")] - public IActionResult Put(int id, [FromBody] ToDo model) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + [HttpPut("todos/{id}")] + public IActionResult Put(int id, [FromBody] ToDo model) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - item.Date = model.Date; - item.Name = model.Name; + item.Date = model.Date; + item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); - - return NoContent(); - } + _logger.LogInformation("Update {name}", model.Name); - [HttpDelete("todos/{id}")] - public IActionResult Delete(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + return NoContent(); + } + + [HttpDelete("todos/{id}")] + public IActionResult Delete(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - __data.Remove(item); - _logger.LogInformation("Delete {id}", id); + __data.Remove(item); + _logger.LogInformation("Delete {id}", id); - return NoContent(); - } + return NoContent(); } - - public class ToDo +} + +public class ToDo +{ + static int _nextId = 1; + public static int NewId() { - static int _nextId = 1; - public static int NewId() - { - return _nextId++; - } - - public int Id { get; set; } - public DateTimeOffset Date { get; set; } - public string Name { get; set; } - public string User { get; set; } + return _nextId++; } + + public int Id { get; set; } + public DateTimeOffset Date { get; set; } + public string Name { get; set; } + public string User { get; set; } } diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/BackendApiHost.csproj b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/BackendApiHost.csproj index d9ea4a9d..312e7ac0 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/BackendApiHost.csproj +++ b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/BackendApiHost.csproj @@ -2,10 +2,11 @@ net8.0 + true - + diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Program.cs b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Program.cs index 0a739198..d5fa6189 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Program.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Program.cs @@ -1,20 +1,36 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); -namespace BackendApiHost +builder.Services.AddControllers(); + +builder.Services.AddAuthentication("token") + .AddJwtBearer("token", options => + { + options.Authority = "https://demo.duendesoftware.com"; + options.Audience = "api"; + + options.MapInboundClaims = false; + }); + +builder.Services.AddAuthorization(options => { - public class Program + options.AddPolicy("ApiCaller", policy => + { + policy.RequireClaim("scope", "api"); + }); + + options.AddPolicy("RequireInteractiveUser", policy => { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + policy.RequireClaim("sub"); + }); +}); + +var app = builder.Build(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers().RequireAuthorization("ApiCaller"); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Startup.cs b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Startup.cs deleted file mode 100755 index 8c9642fe..00000000 --- a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/Startup.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace BackendApiHost -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddAuthentication("token") - .AddJwtBearer("token", options => - { - options.Authority = "https://demo.duendesoftware.com"; - options.Audience = "api"; - - options.MapInboundClaims = false; - }); - - services.AddAuthorization(options => - { - options.AddPolicy("ApiCaller", policy => - { - policy.RequireClaim("scope", "api"); - }); - - options.AddPolicy("RequireInteractiveUser", policy => - { - policy.RequireClaim("sub"); - }); - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers() - .RequireAuthorization("ApiCaller"); - }); - } - } -} diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs index 0417312d..bed69d42 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs @@ -2,99 +2,94 @@ // See LICENSE in the project root for license information. using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Logging; -namespace BackendApiHost +namespace BackendApiHost; + +[Authorize("RequireInteractiveUser")] +public class ToDoController : ControllerBase { - [Authorize("RequireInteractiveUser")] - public class ToDoController : ControllerBase - { - private readonly ILogger _logger; + private readonly ILogger _logger; - private static readonly List __data = new List() - { - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, - }; + private static readonly List __data = new List() + { + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, + }; - public ToDoController(ILogger logger) - { - _logger = logger; - } + public ToDoController(ILogger logger) + { + _logger = logger; + } - [HttpGet("todos")] - public IActionResult GetAll() - { - _logger.LogInformation("GetAll"); - - return Ok(__data.AsEnumerable()); - } + [HttpGet("todos")] + public IActionResult GetAll() + { + _logger.LogInformation("GetAll"); + + return Ok(__data.AsEnumerable()); + } - [HttpGet("todos/{id}")] - public IActionResult Get(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); - - _logger.LogInformation("Get {id}", id); - return Ok(item); - } + [HttpGet("todos/{id}")] + public IActionResult Get(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); + + _logger.LogInformation("Get {id}", id); + return Ok(item); + } - [HttpPost("todos")] - public IActionResult Post([FromBody] ToDo model) - { - model.Id = ToDo.NewId(); - model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; - - __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + [HttpPost("todos")] + public IActionResult Post([FromBody] ToDo model) + { + model.Id = ToDo.NewId(); + model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; + + __data.Add(model); + _logger.LogInformation("Add {name}", model.Name); - return Created(Url.Action(nameof(Get), new { id = model.Id }), model); - } + return Created(Url.Action(nameof(Get), new { id = model.Id }), model); + } - [HttpPut("todos/{id}")] - public IActionResult Put(int id, [FromBody] ToDo model) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + [HttpPut("todos/{id}")] + public IActionResult Put(int id, [FromBody] ToDo model) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - item.Date = model.Date; - item.Name = model.Name; + item.Date = model.Date; + item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); - - return NoContent(); - } + _logger.LogInformation("Update {name}", model.Name); - [HttpDelete("todos/{id}")] - public IActionResult Delete(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + return NoContent(); + } + + [HttpDelete("todos/{id}")] + public IActionResult Delete(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - __data.Remove(item); - _logger.LogInformation("Delete {id}", id); + __data.Remove(item); + _logger.LogInformation("Delete {id}", id); - return NoContent(); - } + return NoContent(); } - - public class ToDo +} + +public class ToDo +{ + static int _nextId = 1; + public static int NewId() { - static int _nextId = 1; - public static int NewId() - { - return _nextId++; - } - - public int Id { get; set; } - public DateTimeOffset Date { get; set; } - public string Name { get; set; } - public string User { get; set; } + return _nextId++; } + + public int Id { get; set; } + public DateTimeOffset Date { get; set; } + public string Name { get; set; } + public string User { get; set; } } diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/FrontendHost.csproj b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/FrontendHost.csproj index ec29ea05..755b92d0 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/FrontendHost.csproj +++ b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/FrontendHost.csproj @@ -2,13 +2,14 @@ net8.0 + true - + diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/InMemoryConfigProvider.cs b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/InMemoryConfigProvider.cs index c03faeed..5014d2c1 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/InMemoryConfigProvider.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/InMemoryConfigProvider.cs @@ -1,89 +1,85 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.Threading; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; using Yarp.ReverseProxy.Configuration; -namespace FrontendHost +namespace FrontendHost; + +/// +/// Extends the IReverseProxyBuilder to support the InMemoryConfigProvider +/// +public static class InMemoryConfigProviderExtensions +{ + public static IReverseProxyBuilder LoadFromMemory(this IReverseProxyBuilder builder, IReadOnlyList routes, IReadOnlyList clusters) + { + builder.Services.AddSingleton(new InMemoryConfigProvider(routes, clusters)); + return builder; + } +} + +/// +/// Provides an implementation of IProxyConfigProvider to support config being generated by code. +/// +public class InMemoryConfigProvider : IProxyConfigProvider { + // Marked as volatile so that updates are atomic + private volatile InMemoryConfig _config; + + public InMemoryConfigProvider(IReadOnlyList routes, IReadOnlyList clusters) + { + _config = new InMemoryConfig(routes, clusters); + } + + /// + /// Implementation of the IProxyConfigProvider.GetConfig method to supply the current snapshot of configuration + /// + /// An immutable snapshot of the current configuration state + public IProxyConfig GetConfig() => _config; + /// - /// Extends the IReverseProxyBuilder to support the InMemoryConfigProvider + /// Swaps the config state with a new snapshot of the configuration, then signals the change /// - public static class InMemoryConfigProviderExtensions + public void Update(IReadOnlyList routes, IReadOnlyList clusters) { - public static IReverseProxyBuilder LoadFromMemory(this IReverseProxyBuilder builder, IReadOnlyList routes, IReadOnlyList clusters) - { - builder.Services.AddSingleton(new InMemoryConfigProvider(routes, clusters)); - return builder; - } + var oldConfig = _config; + _config = new InMemoryConfig(routes, clusters); + oldConfig.SignalChange(); } /// - /// Provides an implementation of IProxyConfigProvider to support config being generated by code. + /// Implementation of IProxyConfig which is a snapshot of the current config state. The data for this class should be immutable. /// - public class InMemoryConfigProvider : IProxyConfigProvider + private class InMemoryConfig : IProxyConfig { - // Marked as volatile so that updates are atomic - private volatile InMemoryConfig _config; + // Used to implement the change token for the state + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); - public InMemoryConfigProvider(IReadOnlyList routes, IReadOnlyList clusters) + public InMemoryConfig(IReadOnlyList routes, IReadOnlyList clusters) { - _config = new InMemoryConfig(routes, clusters); + Routes = routes; + Clusters = clusters; + ChangeToken = new CancellationChangeToken(_cts.Token); } /// - /// Implementation of the IProxyConfigProvider.GetConfig method to supply the current snapshot of configuration + /// A snapshot of the list of routes for the proxy /// - /// An immutable snapshot of the current configuration state - public IProxyConfig GetConfig() => _config; + public IReadOnlyList Routes { get; } /// - /// Swaps the config state with a new snapshot of the configuration, then signals the change + /// A snapshot of the list of Clusters which are collections of interchangable destination endpoints /// - public void Update(IReadOnlyList routes, IReadOnlyList clusters) - { - var oldConfig = _config; - _config = new InMemoryConfig(routes, clusters); - oldConfig.SignalChange(); - } + public IReadOnlyList Clusters { get; } /// - /// Implementation of IProxyConfig which is a snapshot of the current config state. The data for this class should be immutable. + /// Fired to indicate the the proxy state has changed, and that this snapshot is now stale /// - private class InMemoryConfig : IProxyConfig - { - // Used to implement the change token for the state - private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + public IChangeToken ChangeToken { get; } - public InMemoryConfig(IReadOnlyList routes, IReadOnlyList clusters) - { - Routes = routes; - Clusters = clusters; - ChangeToken = new CancellationChangeToken(_cts.Token); - } - - /// - /// A snapshot of the list of routes for the proxy - /// - public IReadOnlyList Routes { get; } - - /// - /// A snapshot of the list of Clusters which are collections of interchangable destination endpoints - /// - public IReadOnlyList Clusters { get; } - - /// - /// Fired to indicate the the proxy state has changed, and that this snapshot is now stale - /// - public IChangeToken ChangeToken { get; } - - internal void SignalChange() - { - _cts.Cancel(); - } + internal void SignalChange() + { + _cts.Cancel(); } } } \ No newline at end of file diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Program.cs b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Program.cs index 3a7f715c..579aadbd 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Program.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Program.cs @@ -1,20 +1,82 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Duende.Bff.Yarp; -namespace FrontendHost +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +builder.Services.AddBff(); + +var yarpBuilder = builder.Services.AddReverseProxy() + .AddTransforms(); +//Configure from included extension method +yarpBuilder.Configure(); + +// registers HTTP client that uses the managed user access token +builder.Services.AddUserAccessTokenHttpClient("api_client", configureClient: client => +{ + client.BaseAddress = new Uri("https://localhost:5002/"); +}); + +builder.Services.AddAuthentication(options => { - public class Program + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + options.DefaultSignOutScheme = "oidc"; +}) + .AddCookie("cookie", options => + { + options.Cookie.Name = "__Host-bff"; + options.Cookie.SameSite = SameSiteMode.Strict; + }) + .AddOpenIdConnect("oidc", options => { - public static void Main(string[] args) + options.Authority = "https://demo.duendesoftware.com"; + options.ClientId = "interactive.confidential"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.ResponseMode = "query"; + + options.GetClaimsFromUserInfoEndpoint = true; + options.MapInboundClaims = false; + options.SaveTokens = true; + options.DisableTelemetry = true; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api"); + options.Scope.Add("offline_access"); + + options.TokenValidationParameters = new() { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + +var app = builder.Build(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseBff(); +app.UseAuthorization(); + +app.MapBffManagementEndpoints(); + +// if you want the TODOs API local +// endpoints.MapControllers() +// .RequireAuthorization() +// .AsBffApiEndpoint(); + +// if you want the TODOs API remote +app.MapBffReverseProxy(); + +// which is equivalent to +//endpoints.MapReverseProxy() +// .AsBffApiEndpoint(); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Startup.cs b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Startup.cs deleted file mode 100755 index 1dc35dce..00000000 --- a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/Startup.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using Duende.Bff; -using Duende.Bff.Yarp; -using Yarp.ReverseProxy.Configuration; - -namespace FrontendHost -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddBff(); - - var builder = services.AddReverseProxy() - .AddTransforms(); - - builder.LoadFromMemory( - new[] - { - new RouteConfig() - { - RouteId = "todos", - ClusterId = "cluster1", - - Match = new RouteMatch - { - Path = "/todos/{**catch-all}" - } - }.WithAccessToken(TokenType.User), - }, - new[] - { - new ClusterConfig - { - ClusterId = "cluster1", - - Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "destination1", new DestinationConfig() { Address = "https://localhost:5020" } }, - } - } - }); - - // registers HTTP client that uses the managed user access token - services.AddUserAccessTokenHttpClient("api_client", configureClient: client => - { - client.BaseAddress = new Uri("https://localhost:5002/"); - }); - - services.AddAuthentication(options => - { - options.DefaultScheme = "cookie"; - options.DefaultChallengeScheme = "oidc"; - options.DefaultSignOutScheme = "oidc"; - }) - .AddCookie("cookie", options => - { - options.Cookie.Name = "__Host-bff"; - options.Cookie.SameSite = SameSiteMode.Strict; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = "https://demo.duendesoftware.com"; - options.ClientId = "interactive.confidential"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.ResponseMode = "query"; - - options.GetClaimsFromUserInfoEndpoint = true; - options.MapInboundClaims = false; - options.SaveTokens = true; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("api"); - options.Scope.Add("offline_access"); - - options.TokenValidationParameters = new() - { - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseDefaultFiles(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseBff(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBffManagementEndpoints(); - - // if you want the TODOs API local - // endpoints.MapControllers() - // .RequireAuthorization() - // .AsBffApiEndpoint(); - - // if you want the TODOs API remote - endpoints.MapBffReverseProxy(); - - // which is equivalent to - //endpoints.MapReverseProxy() - // .AsBffApiEndpoint(); - }); - } - } -} diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs index 86158213..e81a3623 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs @@ -2,97 +2,92 @@ // See LICENSE in the project root for license information. using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Logging; -namespace FrontendHost +namespace FrontendHost; + +public class ToDoController : ControllerBase { - public class ToDoController : ControllerBase - { - private readonly ILogger _logger; + private readonly ILogger _logger; - private static readonly List __data = new List() - { - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, - }; + private static readonly List __data = new List() + { + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, + }; - public ToDoController(ILogger logger) - { - _logger = logger; - } + public ToDoController(ILogger logger) + { + _logger = logger; + } - [HttpGet("todos")] - public IActionResult GetAll() - { - _logger.LogInformation("GetAll"); - - return Ok(__data.AsEnumerable()); - } + [HttpGet("todos")] + public IActionResult GetAll() + { + _logger.LogInformation("GetAll"); + + return Ok(__data.AsEnumerable()); + } - [HttpGet("todos/{id}")] - public IActionResult Get(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); - - _logger.LogInformation("Get {id}", id); - return Ok(item); - } + [HttpGet("todos/{id}")] + public IActionResult Get(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); + + _logger.LogInformation("Get {id}", id); + return Ok(item); + } - [HttpPost("todos")] - public IActionResult Post([FromBody] ToDo model) - { - model.Id = ToDo.NewId(); - model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; - - __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + [HttpPost("todos")] + public IActionResult Post([FromBody] ToDo model) + { + model.Id = ToDo.NewId(); + model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; + + __data.Add(model); + _logger.LogInformation("Add {name}", model.Name); - return Created(Url.Action(nameof(Get), new { id = model.Id }), model); - } + return Created(Url.Action(nameof(Get), new { id = model.Id }), model); + } - [HttpPut("todos/{id}")] - public IActionResult Put(int id, [FromBody] ToDo model) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + [HttpPut("todos/{id}")] + public IActionResult Put(int id, [FromBody] ToDo model) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - item.Date = model.Date; - item.Name = model.Name; + item.Date = model.Date; + item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); - - return NoContent(); - } + _logger.LogInformation("Update {name}", model.Name); - [HttpDelete("todos/{id}")] - public IActionResult Delete(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + return NoContent(); + } + + [HttpDelete("todos/{id}")] + public IActionResult Delete(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - __data.Remove(item); - _logger.LogInformation("Delete {id}", id); + __data.Remove(item); + _logger.LogInformation("Delete {id}", id); - return NoContent(); - } + return NoContent(); } - - public class ToDo +} + +public class ToDo +{ + static int _nextId = 1; + public static int NewId() { - static int _nextId = 1; - public static int NewId() - { - return _nextId++; - } - - public int Id { get; set; } - public DateTimeOffset Date { get; set; } - public string Name { get; set; } - public string User { get; set; } + return _nextId++; } + + public int Id { get; set; } + public DateTimeOffset Date { get; set; } + public string Name { get; set; } + public string User { get; set; } } diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/YarpConfigurator.cs b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/YarpConfigurator.cs new file mode 100644 index 00000000..b4f96291 --- /dev/null +++ b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/YarpConfigurator.cs @@ -0,0 +1,35 @@ +using Duende.Bff; +using Duende.Bff.Yarp; +using Yarp.ReverseProxy.Configuration; + +public static class YarpConfigurator +{ + public static void Configure(this IReverseProxyBuilder builder) + { + builder.LoadFromMemory( + [ + new RouteConfig() + { + RouteId = "todos", + ClusterId = "cluster1", + + Match = new RouteMatch + { + Path = "/todos/{**catch-all}" + } + }.WithAccessToken(TokenType.User), + ], + [ + new ClusterConfig + { + ClusterId = "cluster1", + + Destinations = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "destination1", new DestinationConfig() { Address = "https://localhost:5020" } }, + } + } + ]); + } +} + diff --git a/IdentityServer/v7/BFF/React/React.Bff/Program.cs b/IdentityServer/v7/BFF/React/React.Bff/Program.cs index b2a5b7ea..89a02fce 100644 --- a/IdentityServer/v7/BFF/React/React.Bff/Program.cs +++ b/IdentityServer/v7/BFF/React/React.Bff/Program.cs @@ -26,6 +26,7 @@ options.GetClaimsFromUserInfoEndpoint = true; options.MapInboundClaims = false; options.SaveTokens = true; + options.DisableTelemetry = true; options.Scope.Clear(); options.Scope.Add("openid"); diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/BackendApiHost.csproj b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/BackendApiHost.csproj index d9ea4a9d..7bb1fef3 100644 --- a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/BackendApiHost.csproj +++ b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/BackendApiHost.csproj @@ -2,6 +2,7 @@ net8.0 + true diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Program.cs b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Program.cs index 0a739198..59e563a2 100644 --- a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Program.cs +++ b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Program.cs @@ -1,20 +1,36 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); -namespace BackendApiHost +builder.Services.AddControllers(); + +builder.Services.AddAuthentication("token") + .AddJwtBearer("token", options => + { + options.Authority = "https://demo.duendesoftware.com"; + options.Audience = "api"; + + options.MapInboundClaims = false; + }); + +builder.Services.AddAuthorization(options => { - public class Program + options.AddPolicy("ApiCaller", policy => + { + policy.RequireClaim("scope", "api"); + }); + + options.AddPolicy("RequireInteractiveUser", policy => { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + policy.RequireClaim("sub"); + }); +}); + +var app = builder.Build(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers().RequireAuthorization("ApiCaller"); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Startup.cs b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Startup.cs deleted file mode 100644 index 8c9642fe..00000000 --- a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/Startup.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace BackendApiHost -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddAuthentication("token") - .AddJwtBearer("token", options => - { - options.Authority = "https://demo.duendesoftware.com"; - options.Audience = "api"; - - options.MapInboundClaims = false; - }); - - services.AddAuthorization(options => - { - options.AddPolicy("ApiCaller", policy => - { - policy.RequireClaim("scope", "api"); - }); - - options.AddPolicy("RequireInteractiveUser", policy => - { - policy.RequireClaim("sub"); - }); - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers() - .RequireAuthorization("ApiCaller"); - }); - } - } -} diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs index 0417312d..bed69d42 100644 --- a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs @@ -2,99 +2,94 @@ // See LICENSE in the project root for license information. using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Logging; -namespace BackendApiHost +namespace BackendApiHost; + +[Authorize("RequireInteractiveUser")] +public class ToDoController : ControllerBase { - [Authorize("RequireInteractiveUser")] - public class ToDoController : ControllerBase - { - private readonly ILogger _logger; + private readonly ILogger _logger; - private static readonly List __data = new List() - { - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, - }; + private static readonly List __data = new List() + { + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, + }; - public ToDoController(ILogger logger) - { - _logger = logger; - } + public ToDoController(ILogger logger) + { + _logger = logger; + } - [HttpGet("todos")] - public IActionResult GetAll() - { - _logger.LogInformation("GetAll"); - - return Ok(__data.AsEnumerable()); - } + [HttpGet("todos")] + public IActionResult GetAll() + { + _logger.LogInformation("GetAll"); + + return Ok(__data.AsEnumerable()); + } - [HttpGet("todos/{id}")] - public IActionResult Get(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); - - _logger.LogInformation("Get {id}", id); - return Ok(item); - } + [HttpGet("todos/{id}")] + public IActionResult Get(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); + + _logger.LogInformation("Get {id}", id); + return Ok(item); + } - [HttpPost("todos")] - public IActionResult Post([FromBody] ToDo model) - { - model.Id = ToDo.NewId(); - model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; - - __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + [HttpPost("todos")] + public IActionResult Post([FromBody] ToDo model) + { + model.Id = ToDo.NewId(); + model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; + + __data.Add(model); + _logger.LogInformation("Add {name}", model.Name); - return Created(Url.Action(nameof(Get), new { id = model.Id }), model); - } + return Created(Url.Action(nameof(Get), new { id = model.Id }), model); + } - [HttpPut("todos/{id}")] - public IActionResult Put(int id, [FromBody] ToDo model) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + [HttpPut("todos/{id}")] + public IActionResult Put(int id, [FromBody] ToDo model) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - item.Date = model.Date; - item.Name = model.Name; + item.Date = model.Date; + item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); - - return NoContent(); - } + _logger.LogInformation("Update {name}", model.Name); - [HttpDelete("todos/{id}")] - public IActionResult Delete(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + return NoContent(); + } + + [HttpDelete("todos/{id}")] + public IActionResult Delete(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - __data.Remove(item); - _logger.LogInformation("Delete {id}", id); + __data.Remove(item); + _logger.LogInformation("Delete {id}", id); - return NoContent(); - } + return NoContent(); } - - public class ToDo +} + +public class ToDo +{ + static int _nextId = 1; + public static int NewId() { - static int _nextId = 1; - public static int NewId() - { - return _nextId++; - } - - public int Id { get; set; } - public DateTimeOffset Date { get; set; } - public string Name { get; set; } - public string User { get; set; } + return _nextId++; } + + public int Id { get; set; } + public DateTimeOffset Date { get; set; } + public string Name { get; set; } + public string User { get; set; } } diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendHost/FrontendHostReturlUrlValidator.cs b/IdentityServer/v7/BFF/SplitHosts/BackendHost/FrontendHostReturnUrlValidator.cs similarity index 100% rename from IdentityServer/v7/BFF/SplitHosts/BackendHost/FrontendHostReturlUrlValidator.cs rename to IdentityServer/v7/BFF/SplitHosts/BackendHost/FrontendHostReturnUrlValidator.cs diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendHost/Program.cs b/IdentityServer/v7/BFF/SplitHosts/BackendHost/Program.cs index b82485ee..48cd679c 100644 --- a/IdentityServer/v7/BFF/SplitHosts/BackendHost/Program.cs +++ b/IdentityServer/v7/BFF/SplitHosts/BackendHost/Program.cs @@ -1,18 +1,89 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using BackendHost; +using Duende.Bff; +using Duende.Bff.Yarp; -namespace BackendHost; -public class Program +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddCors(opt => { - public static void Main(string[] args) + opt.AddDefaultPolicy(policy => { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); -} + policy + .WithOrigins("https://localhost:5011") + .WithHeaders("x-csrf", "content-type") + .WithMethods("DELETE") + .AllowCredentials(); + }); +}); +builder.Services.AddControllers(); +builder.Services.AddBff() + .AddRemoteApis(); +builder.Services.AddTransient(); + +// registers HTTP client that uses the managed user access token +builder.Services.AddUserAccessTokenHttpClient("api_client", configureClient: client => +{ + client.BaseAddress = new Uri("https://localhost:5002/"); +}); + +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + options.DefaultSignOutScheme = "oidc"; +}) + .AddCookie("cookie", options => + { + options.Cookie.Name = "__Host-bff"; + options.Cookie.SameSite = SameSiteMode.Strict; + }) + .AddOpenIdConnect("oidc", options => + { + options.Authority = "https://demo.duendesoftware.com"; + options.ClientId = "interactive.confidential"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.ResponseMode = "query"; + + options.GetClaimsFromUserInfoEndpoint = true; + options.MapInboundClaims = false; + options.SaveTokens = true; + options.DisableTelemetry = true; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api"); + options.Scope.Add("offline_access"); + + options.TokenValidationParameters = new() + { + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + +var app = builder.Build(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseRouting(); +app.UseCors(); + +app.UseAuthentication(); +app.UseBff(); +app.UseAuthorization(); + +app.MapBffManagementEndpoints(); + +// if you want the TODOs API local +app.MapControllers() + .RequireAuthorization() + .AsBffApiEndpoint(); + +// if you want the TODOs API remote +// app.MapRemoteBffApiEndpoint("/todos", "https://localhost:5020/todos") +// .RequireAccessToken(Duende.Bff.TokenType.User); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendHost/Startup.cs b/IdentityServer/v7/BFF/SplitHosts/BackendHost/Startup.cs deleted file mode 100644 index bf63650c..00000000 --- a/IdentityServer/v7/BFF/SplitHosts/BackendHost/Startup.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Duende.Bff.Yarp; -using Duende.Bff; - -namespace BackendHost -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddCors(opt => - { - opt.AddDefaultPolicy(policy => - { - policy - .WithOrigins("https://localhost:5011") - .WithHeaders("x-csrf", "content-type") - .WithMethods("DELETE") - .AllowCredentials(); - }); - }); - services.AddControllers(); - services.AddBff() - .AddRemoteApis(); - services.AddTransient(); - - // registers HTTP client that uses the managed user access token - services.AddUserAccessTokenHttpClient("api_client", configureClient: client => - { - client.BaseAddress = new Uri("https://localhost:5002/"); - }); - - services.AddAuthentication(options => - { - options.DefaultScheme = "cookie"; - options.DefaultChallengeScheme = "oidc"; - options.DefaultSignOutScheme = "oidc"; - }) - .AddCookie("cookie", options => - { - options.Cookie.Name = "__Host-bff"; - options.Cookie.SameSite = SameSiteMode.Strict; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = "https://demo.duendesoftware.com"; - options.ClientId = "interactive.confidential"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.ResponseMode = "query"; - - options.GetClaimsFromUserInfoEndpoint = true; - options.MapInboundClaims = false; - options.SaveTokens = true; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("api"); - options.Scope.Add("offline_access"); - - options.TokenValidationParameters = new() - { - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseDefaultFiles(); - app.UseStaticFiles(); - - app.UseRouting(); - app.UseCors(); - - app.UseAuthentication(); - app.UseBff(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBffManagementEndpoints(); - - // if you want the TODOs API local - endpoints.MapControllers() - .RequireAuthorization() - .AsBffApiEndpoint(); - - // if you want the TODOs API remote - // endpoints.MapRemoteBffApiEndpoint("/todos", "https://localhost:5020/todos") - // .RequireAccessToken(Duende.Bff.TokenType.User); - }); - } - } -} diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs b/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs index fa4231f8..cb3873d3 100644 --- a/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs @@ -2,97 +2,92 @@ // See LICENSE in the project root for license information. using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Logging; -namespace BackendHost +namespace BackendHost; + +public class ToDoController : ControllerBase { - public class ToDoController : ControllerBase - { - private readonly ILogger _logger; + private readonly ILogger _logger; - private static readonly List __data = new List() - { - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, - new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, - }; + private static readonly List __data = new List() + { + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow, Name = "Demo ToDo API", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(1), Name = "Stop Demo", User = "bob" }, + new ToDo { Id = ToDo.NewId(), Date = DateTimeOffset.UtcNow.AddHours(4), Name = "Have Dinner", User = "alice" }, + }; - public ToDoController(ILogger logger) - { - _logger = logger; - } + public ToDoController(ILogger logger) + { + _logger = logger; + } - [HttpGet("todos")] - public IActionResult GetAll() - { - _logger.LogInformation("GetAll"); - - return Ok(__data.AsEnumerable()); - } + [HttpGet("todos")] + public IActionResult GetAll() + { + _logger.LogInformation("GetAll"); + + return Ok(__data.AsEnumerable()); + } - [HttpGet("todos/{id}")] - public IActionResult Get(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); - - _logger.LogInformation("Get {id}", id); - return Ok(item); - } + [HttpGet("todos/{id}")] + public IActionResult Get(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); + + _logger.LogInformation("Get {id}", id); + return Ok(item); + } - [HttpPost("todos")] - public IActionResult Post([FromBody] ToDo model) - { - model.Id = ToDo.NewId(); - model.User = $"{User.FindFirst("sub")?.Value} ({User.FindFirst("name")?.Value})"; - - __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + [HttpPost("todos")] + public IActionResult Post([FromBody] ToDo model) + { + model.Id = ToDo.NewId(); + model.User = $"{User.FindFirst("sub")?.Value} ({User.FindFirst("name")?.Value})"; + + __data.Add(model); + _logger.LogInformation("Add {name}", model.Name); - return Created(Url.Action(nameof(Get), new { id = model.Id }), model); - } + return Created(Url.Action(nameof(Get), new { id = model.Id }), model); + } - [HttpPut("todos/{id}")] - public IActionResult Put(int id, [FromBody] ToDo model) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + [HttpPut("todos/{id}")] + public IActionResult Put(int id, [FromBody] ToDo model) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - item.Date = model.Date; - item.Name = model.Name; + item.Date = model.Date; + item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); - - return NoContent(); - } + _logger.LogInformation("Update {name}", model.Name); - [HttpDelete("todos/{id}")] - public IActionResult Delete(int id) - { - var item = __data.FirstOrDefault(x => x.Id == id); - if (item == null) return NotFound(); + return NoContent(); + } + + [HttpDelete("todos/{id}")] + public IActionResult Delete(int id) + { + var item = __data.FirstOrDefault(x => x.Id == id); + if (item == null) return NotFound(); - __data.Remove(item); - _logger.LogInformation("Delete {id}", id); + __data.Remove(item); + _logger.LogInformation("Delete {id}", id); - return NoContent(); - } + return NoContent(); } - - public class ToDo +} + +public class ToDo +{ + static int _nextId = 1; + public static int NewId() { - static int _nextId = 1; - public static int NewId() - { - return _nextId++; - } - - public int Id { get; set; } - public DateTimeOffset Date { get; set; } - public string? Name { get; set; } - public string? User { get; set; } + return _nextId++; } + + public int Id { get; set; } + public DateTimeOffset Date { get; set; } + public string? Name { get; set; } + public string? User { get; set; } } diff --git a/IdentityServer/v7/BFF/SplitHosts/FrontendHost/FrontendHost.csproj b/IdentityServer/v7/BFF/SplitHosts/FrontendHost/FrontendHost.csproj index c70916d3..7c8c992a 100644 --- a/IdentityServer/v7/BFF/SplitHosts/FrontendHost/FrontendHost.csproj +++ b/IdentityServer/v7/BFF/SplitHosts/FrontendHost/FrontendHost.csproj @@ -2,5 +2,6 @@ net8.0 + true diff --git a/IdentityServer/v7/BFF/SplitHosts/FrontendHost/Program.cs b/IdentityServer/v7/BFF/SplitHosts/FrontendHost/Program.cs index c61a0354..651fe508 100644 --- a/IdentityServer/v7/BFF/SplitHosts/FrontendHost/Program.cs +++ b/IdentityServer/v7/BFF/SplitHosts/FrontendHost/Program.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.Builder; - var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Program.cs b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Program.cs index 295d0f86..98f0ec49 100644 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Program.cs +++ b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Program.cs @@ -1,58 +1,79 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System; -using System.Diagnostics; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.IdentityModel.Tokens; using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; +using System.Diagnostics; -namespace TokenExchange.Api -{ - public class Program +Console.Title = "Simple API"; +Activity.DefaultIdFormat = ActivityIdFormat.W3C; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) + .CreateLogger(); + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSerilog(); +builder.Services.AddControllers(); + +builder.Services.AddAuthentication("token") + .AddJwtBearer("token", options => { - public static int Main(string[] args) - { - Console.Title = "Simple API"; - Activity.DefaultIdFormat = ActivityIdFormat.W3C; - - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) - .MinimumLevel.Override("System", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) - .CreateLogger(); - - try - { - Log.Information("Starting host..."); - CreateHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly."); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } - - public static IHostBuilder CreateHostBuilder(string[] args) + options.Authority = "https://localhost:5001"; + options.MapInboundClaims = false; + + options.TokenValidationParameters = new TokenValidationParameters() { - return Host.CreateDefaultBuilder(args) - .UseSerilog() - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } - } -} \ No newline at end of file + ValidateAudience = false, + ValidTypes = new[] { "at+jwt" }, + + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("ApiCaller", policy => + { + policy.RequireClaim("scope", "api"); + }); + + options.AddPolicy("RequireInteractiveUser", policy => + { + policy.RequireClaim("sub"); + }); +}); + +var app = builder.Build(); + +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, +}); + +app.UseSerilogRequestLogging(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} + +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers() + .RequireAuthorization("ApiCaller"); + +app.Run(); diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Startup.cs b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Startup.cs deleted file mode 100644 index 3f28739a..00000000 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/Startup.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.IdentityModel.Tokens; -using Serilog; - -namespace TokenExchange.Api -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - - services.AddAuthentication("token") - .AddJwtBearer("token", options => - { - options.Authority = "https://localhost:5001"; - options.MapInboundClaims = false; - - options.TokenValidationParameters = new TokenValidationParameters() - { - ValidateAudience = false, - ValidTypes = new[] { "at+jwt" }, - - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - - services.AddAuthorization(options => - { - options.AddPolicy("ApiCaller", policy => - { - policy.RequireClaim("scope", "api"); - }); - - options.AddPolicy("RequireInteractiveUser", policy => - { - policy.RequireClaim("sub"); - }); - }); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseForwardedHeaders(new ForwardedHeadersOptions - { - ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, - }); - - app.UseSerilogRequestLogging(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers() - .RequireAuthorization("ApiCaller"); - }); - } - } -} \ No newline at end of file diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/TokenExchange.Api.csproj b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/TokenExchange.Api.csproj index 150e3691..ca2943fd 100644 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/TokenExchange.Api.csproj +++ b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Api/TokenExchange.Api.csproj @@ -2,11 +2,12 @@ net8.0 + true - - - + + + diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/ImpersonationAccessTokenRetriever.cs b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/ImpersonationAccessTokenRetriever.cs index 386d7bb4..bf428f58 100644 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/ImpersonationAccessTokenRetriever.cs +++ b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/ImpersonationAccessTokenRetriever.cs @@ -1,12 +1,9 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System.Net.Http; -using System.Threading.Tasks; using Duende.Bff; using IdentityModel; using IdentityModel.Client; -using Microsoft.Extensions.Logging; namespace TokenExchange.Bff; diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/LocalApiController.cs b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/LocalApiController.cs index 53345d00..bf87f453 100644 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/LocalApiController.cs +++ b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/LocalApiController.cs @@ -1,10 +1,7 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System.Collections.Generic; -using System.Net.Http; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace TokenExchange.Bff; diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Program.cs b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Program.cs index 76dc1603..05014882 100644 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Program.cs +++ b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Program.cs @@ -1,20 +1,14 @@ // Copyright (c) Duende Software. All rights reserved. // See LICENSE in the project root for license information. -using System; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Duende.Bff; +using Duende.Bff.Yarp; using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; +using TokenExchange.Bff; -namespace TokenExchange.Bff; - -public class Program -{ - public static int Main(string[] args) - { - Log.Logger = new LoggerConfiguration() +Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) @@ -25,30 +19,103 @@ public static int Main(string[] args) .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) .CreateLogger(); - try - { - Log.Information("Starting host..."); - CreateHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly."); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } - - public static IHostBuilder CreateHostBuilder(string[] args) +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSerilog(); +// Add BFF services to DI - also add server-side session management +builder.Services.AddBff(options => +{ + //options.UserEndpointReturnNullForAnonymousUser = true; +}) + .AddRemoteApis() + .AddServerSideSessions(); + +// local APIs +builder.Services.AddControllers(); + +// cookie options +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + options.DefaultSignOutScheme = "oidc"; +}) + .AddCookie("cookie", options => + { + // set session lifetime + options.ExpireTimeSpan = TimeSpan.FromHours(8); + + // sliding or absolute + options.SlidingExpiration = false; + + // host prefixed cookie name + options.Cookie.Name = "__Host-bff-token-exchange"; + + // strict SameSite handling + options.Cookie.SameSite = SameSiteMode.Strict; + }) + .AddOpenIdConnect("oidc", options => + { + options.Authority = "https://localhost:5001"; + + // confidential client using code flow + PKCE + options.ClientId = "spa"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + options.ResponseMode = "query"; + + options.MapInboundClaims = false; + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.DisableTelemetry = true; + + // request scopes + refresh tokens + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("api"); + options.Scope.Add("offline_access"); + }); +builder.Services.AddSingleton(); + +builder.Services.AddUserAccessTokenHttpClient("api", + configureClient: client => { - return Host.CreateDefaultBuilder(args) - .UseSerilog() - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + client.BaseAddress = new Uri("https://localhost:7001/api"); + }); + +var app = builder.Build(); + +app.UseSerilogRequestLogging(); +app.UseDeveloperExceptionPage(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseAuthentication(); +app.UseRouting(); + +// adds antiforgery protection for local APIs +app.UseBff(); + +// adds authorization for local and remote API endpoints +app.UseAuthorization(); + +app.MapControllers() + .RequireAuthorization() + .AsBffApiEndpoint(); + +// login, logout, user, backchannel logout... +app.MapBffManagementEndpoints(); + +// On this path, we require the user token +app.MapRemoteBffApiEndpoint("/api/user-token", "https://localhost:7001") + .RequireAccessToken(TokenType.User); + +// On this path, we perform token exchange to impersonate a different user +// before making the api request +app.MapRemoteBffApiEndpoint("/api/impersonation", "https://localhost:7001") + .RequireAccessToken(TokenType.User) + .WithAccessTokenRetriever(); + +app.Run(); \ No newline at end of file diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Startup.cs b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Startup.cs deleted file mode 100644 index ddf614d4..00000000 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/Startup.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Duende Software. All rights reserved. -// See LICENSE in the project root for license information. - -using System; -using Duende.Bff; -using Duende.Bff.Yarp; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Serilog; - -namespace TokenExchange.Bff; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - // Add BFF services to DI - also add server-side session management - services.AddBff(options => - { - //options.UserEndpointReturnNullForAnonymousUser = true; - }) - .AddRemoteApis() - .AddServerSideSessions(); - - // local APIs - services.AddControllers(); - - // cookie options - services.AddAuthentication(options => - { - options.DefaultScheme = "cookie"; - options.DefaultChallengeScheme = "oidc"; - options.DefaultSignOutScheme = "oidc"; - }) - .AddCookie("cookie", options => - { - // set session lifetime - options.ExpireTimeSpan = TimeSpan.FromHours(8); - - // sliding or absolute - options.SlidingExpiration = false; - - // host prefixed cookie name - options.Cookie.Name = "__Host-bff-token-exchange"; - - // strict SameSite handling - options.Cookie.SameSite = SameSiteMode.Strict; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = "https://localhost:5001"; - - // confidential client using code flow + PKCE - options.ClientId = "spa"; - options.ClientSecret = "secret"; - options.ResponseType = "code"; - options.ResponseMode = "query"; - - options.MapInboundClaims = false; - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - - // request scopes + refresh tokens - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("api"); - options.Scope.Add("offline_access"); - }); - services.AddSingleton(); - - services.AddUserAccessTokenHttpClient("api", - configureClient: client => - { - client.BaseAddress = new Uri("https://localhost:7001/api"); - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseSerilogRequestLogging(); - app.UseDeveloperExceptionPage(); - - app.UseDefaultFiles(); - app.UseStaticFiles(); - - app.UseAuthentication(); - app.UseRouting(); - - // adds antiforgery protection for local APIs - app.UseBff(); - - // adds authorization for local and remote API endpoints - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - // local APIs - endpoints.MapControllers() - .RequireAuthorization() - .AsBffApiEndpoint(); - - // login, logout, user, backchannel logout... - endpoints.MapBffManagementEndpoints(); - - // On this path, we require the user token - endpoints.MapRemoteBffApiEndpoint("/api/user-token", "https://localhost:7001") - .RequireAccessToken(TokenType.User); - - // On this path, we perform token exchange to impersonate a different user - // before making the api request - endpoints.MapRemoteBffApiEndpoint("/api/impersonation", "https://localhost:7001") - .RequireAccessToken(TokenType.User) - .WithAccessTokenRetriever(); - }); - } -} diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/TokenExchange.Bff.csproj b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/TokenExchange.Bff.csproj index 6f61d3d7..5b56ef29 100644 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/TokenExchange.Bff.csproj +++ b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.Bff/TokenExchange.Bff.csproj @@ -4,12 +4,13 @@ net8.0 Host6 enable + true - - - + + + diff --git a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.IdentityServer/TokenExchange.IdentityServer.csproj b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.IdentityServer/TokenExchange.IdentityServer.csproj index 7d151cfc..9ad7fb50 100644 --- a/IdentityServer/v7/BFF/TokenExchange/TokenExchange.IdentityServer/TokenExchange.IdentityServer.csproj +++ b/IdentityServer/v7/BFF/TokenExchange/TokenExchange.IdentityServer/TokenExchange.IdentityServer.csproj @@ -3,11 +3,12 @@ net8.0 true + true - - + + \ No newline at end of file From 00122f3bd3ca7a564b97e7abbb444ee220153be8 Mon Sep 17 00:00:00 2001 From: Roland Guijt Date: Wed, 3 Jul 2024 16:30:11 +0200 Subject: [PATCH 2/4] Fixed log issues --- .../v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs | 4 ++-- .../v7/BFF/JsBffSample/FrontendHost/ToDoController.cs | 4 ++-- .../v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs | 4 ++-- .../v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs | 4 ++-- .../v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs index bed69d42..6b9faa00 100755 --- a/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffSample/BackendApiHost/ToDoController.cs @@ -48,7 +48,7 @@ public IActionResult Post([FromBody] ToDo model) model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + _logger.LogInformation("Added todo"); return Created(Url.Action(nameof(Get), new { id = model.Id }), model); } @@ -62,7 +62,7 @@ public IActionResult Put(int id, [FromBody] ToDo model) item.Date = model.Date; item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); + _logger.LogInformation("Updated todo"); return NoContent(); } diff --git a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs index e81a3623..f479d619 100755 --- a/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffSample/FrontendHost/ToDoController.cs @@ -46,7 +46,7 @@ public IActionResult Post([FromBody] ToDo model) model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + _logger.LogInformation("Added todo"); return Created(Url.Action(nameof(Get), new { id = model.Id }), model); } @@ -60,7 +60,7 @@ public IActionResult Put(int id, [FromBody] ToDo model) item.Date = model.Date; item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); + _logger.LogInformation("Updated todo"); return NoContent(); } diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs index bed69d42..6b9faa00 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/BackendApiHost/ToDoController.cs @@ -48,7 +48,7 @@ public IActionResult Post([FromBody] ToDo model) model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + _logger.LogInformation("Added todo"); return Created(Url.Action(nameof(Get), new { id = model.Id }), model); } @@ -62,7 +62,7 @@ public IActionResult Put(int id, [FromBody] ToDo model) item.Date = model.Date; item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); + _logger.LogInformation("Updated todo"); return NoContent(); } diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs index e81a3623..eb437ae7 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs @@ -46,7 +46,7 @@ public IActionResult Post([FromBody] ToDo model) model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + _logger.LogInformation("Added todo"; return Created(Url.Action(nameof(Get), new { id = model.Id }), model); } @@ -60,7 +60,7 @@ public IActionResult Put(int id, [FromBody] ToDo model) item.Date = model.Date; item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); + _logger.LogInformation("Updated todo"); return NoContent(); } diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs index bed69d42..6b9faa00 100644 --- a/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/SplitHosts/BackendApiHost/ToDoController.cs @@ -48,7 +48,7 @@ public IActionResult Post([FromBody] ToDo model) model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + _logger.LogInformation("Added todo"); return Created(Url.Action(nameof(Get), new { id = model.Id }), model); } @@ -62,7 +62,7 @@ public IActionResult Put(int id, [FromBody] ToDo model) item.Date = model.Date; item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); + _logger.LogInformation("Updated todo"); return NoContent(); } From 26d0328a088dc8c835283a5c232dee5a50ce47e3 Mon Sep 17 00:00:00 2001 From: Roland Guijt Date: Wed, 3 Jul 2024 16:42:37 +0200 Subject: [PATCH 3/4] Fixed more log issues --- .../v7/BFF/SplitHosts/BackendHost/ToDoController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs b/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs index cb3873d3..08cff035 100644 --- a/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/SplitHosts/BackendHost/ToDoController.cs @@ -46,7 +46,7 @@ public IActionResult Post([FromBody] ToDo model) model.User = $"{User.FindFirst("sub")?.Value} ({User.FindFirst("name")?.Value})"; __data.Add(model); - _logger.LogInformation("Add {name}", model.Name); + _logger.LogInformation("Added todo"); return Created(Url.Action(nameof(Get), new { id = model.Id }), model); } @@ -60,7 +60,7 @@ public IActionResult Put(int id, [FromBody] ToDo model) item.Date = model.Date; item.Name = model.Name; - _logger.LogInformation("Update {name}", model.Name); + _logger.LogInformation("Updated todo"); return NoContent(); } From 0b4afe45eb150f755d05e49315247f2b2cdeca3f Mon Sep 17 00:00:00 2001 From: Roland Guijt Date: Wed, 3 Jul 2024 17:05:33 +0200 Subject: [PATCH 4/4] Added missing parenthesis --- .../v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs index eb437ae7..f479d619 100755 --- a/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs +++ b/IdentityServer/v7/BFF/JsBffYarpSample/FrontendHost/ToDoController.cs @@ -46,7 +46,7 @@ public IActionResult Post([FromBody] ToDo model) model.User = $"{User.FindFirst("sub").Value} ({User.FindFirst("name").Value})"; __data.Add(model); - _logger.LogInformation("Added todo"; + _logger.LogInformation("Added todo"); return Created(Url.Action(nameof(Get), new { id = model.Id }), model); }