diff --git a/.gitignore b/.gitignore index 5129de1..5b80147 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ /WebCodeFlowPkceClient/WebCodeFlowPkceClient.csproj.user /PkceClientApp.txt /WebHybridClient.txt +/_logs-** diff --git a/AspNetCoreHybridFlow.sln b/AspNetCoreHybridFlow.sln index 903e201..9e61b84 100644 --- a/AspNetCoreHybridFlow.sln +++ b/AspNetCoreHybridFlow.sln @@ -11,7 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHybridFlowClient", "WebH EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{038CF360-14D7-4AF0-8BA9-5FF422D3CC7E}" ProjectSection(SolutionItems) = preProject - .github\workflows\dotnet.yml = .github\workflows\dotnet.yml README.md = README.md EndProjectSection EndProject @@ -25,7 +24,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityStandaloneMfa", "Id EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityStandaloneUserCheck", "IdentityStandaloneUserCheck\IdentityStandaloneUserCheck.csproj", "{E513EF8E-424C-4E95-945C-29073C242A8C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebExtraClaimsCached", "WebExtraClaimsCached\WebExtraClaimsCached.csproj", "{DBC546B7-C2FD-4844-9595-DA8E1FA4FA64}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebExtraClaimsCached", "WebExtraClaimsCached\WebExtraClaimsCached.csproj", "{DBC546B7-C2FD-4844-9595-DA8E1FA4FA64}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/AspNetCoreRequireMfaOidc/Program.cs b/AspNetCoreRequireMfaOidc/Program.cs index 29e6fc2..e11fea8 100644 --- a/AspNetCoreRequireMfaOidc/Program.cs +++ b/AspNetCoreRequireMfaOidc/Program.cs @@ -1,16 +1,77 @@ -namespace AspNetCoreRequireMfaOidc; +using AspNetCoreRequireMfaOidc; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Authorization; +using System.IdentityModel.Tokens.Jwt; -public class Program +var builder = WebApplication.CreateBuilder(args); + +builder.Services.ConfigureApplicationCookie(options => +{ + options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always; +}); + +builder.Services.AddSingleton(); + +builder.Services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; +}) +.AddCookie() +.AddOpenIdConnect(options => { - public static void Main(string[] args) + options.SignInScheme = "Cookies"; + options.Authority = "https://localhost:44352"; + options.RequireHttpsMetadata = true; + options.ClientId = "AspNetCoreRequireMfaOidc"; + options.ClientSecret = "AspNetCoreRequireMfaOidcSecret"; + options.ResponseType = "code id_token"; + options.Scope.Add("profile"); + options.Scope.Add("offline_access"); + options.SaveTokens = true; + options.Events = new OpenIdConnectEvents { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + OnRedirectToIdentityProvider = context => + { + context.ProtocolMessage.SetParameter("acr_values", Amr.Mfa); + + return Task.FromResult(0); + } + }; +}); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("RequireMfa", policyIsAdminRequirement => + { + policyIsAdminRequirement.Requirements.Add(new RequireMfa()); + }); +}); + +builder.Services.AddRazorPages(); + +var app = builder.Build(); + +app.UseCookiePolicy(); + +//IdentityModelEventSource.ShowPII = true; +JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); } + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapRazorPages(); + +app.Run(); diff --git a/AspNetCoreRequireMfaOidc/Properties/launchSettings.json b/AspNetCoreRequireMfaOidc/Properties/launchSettings.json index 76493c8..0614ef4 100644 --- a/AspNetCoreRequireMfaOidc/Properties/launchSettings.json +++ b/AspNetCoreRequireMfaOidc/Properties/launchSettings.json @@ -1,27 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "https://localhost:44389", - "sslPort": 44389 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "AspNetCoreRequireMfaOidc": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + "applicationUrl": "https://localhost:44389" } } } \ No newline at end of file diff --git a/AspNetCoreRequireMfaOidc/Startup.cs b/AspNetCoreRequireMfaOidc/Startup.cs deleted file mode 100644 index 10e3a2f..0000000 --- a/AspNetCoreRequireMfaOidc/Startup.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.AspNetCore.Authorization; - -namespace AspNetCoreRequireMfaOidc; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.ConfigureApplicationCookie(options => - { - options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always; - }); - - services.AddSingleton(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }) - .AddCookie() - .AddOpenIdConnect(options => - { - options.SignInScheme = "Cookies"; - options.Authority = "https://localhost:44352"; - options.RequireHttpsMetadata = true; - options.ClientId = "AspNetCoreRequireMfaOidc"; - options.ClientSecret = "AspNetCoreRequireMfaOidcSecret"; - options.ResponseType = "code id_token"; - options.Scope.Add("profile"); - options.Scope.Add("offline_access"); - options.SaveTokens = true; - options.Events = new OpenIdConnectEvents - { - OnRedirectToIdentityProvider = context => - { - context.ProtocolMessage.SetParameter("acr_values", Amr.Mfa); - - return Task.FromResult(0); - } - }; - }); - - services.AddAuthorization(options => - { - options.AddPolicy("RequireMfa", policyIsAdminRequirement => - { - policyIsAdminRequirement.Requirements.Add(new RequireMfa()); - }); - }); - - services.AddRazorPages(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseCookiePolicy(); - - //IdentityModelEventSource.ShowPII = true; - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - 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/DeviceFlowWeb/Program.cs b/DeviceFlowWeb/Program.cs index 06c9896..9e0cac6 100644 --- a/DeviceFlowWeb/Program.cs +++ b/DeviceFlowWeb/Program.cs @@ -1,16 +1,68 @@ -namespace DeviceFlowWeb; +using DeviceFlowWeb; +using Microsoft.AspNetCore.Authentication.Cookies; -public class Program +var builder = WebApplication.CreateBuilder(args); + +var services = builder.Services; +var configuration = builder.Configuration; +var env = builder.Environment; + +services.AddScoped(); +services.AddHttpClient(); +services.Configure(configuration.GetSection("AuthConfigurations")); + +services.AddSession(options => +{ + // Set a short timeout for easy testing. + options.IdleTimeout = TimeSpan.FromSeconds(60); + options.Cookie.HttpOnly = true; +}); + +services.Configure(options => +{ + options.CheckConsentNeeded = context => true; + options.MinimumSameSitePolicy = SameSiteMode.None; +}); + +var authConfigurations = configuration.GetSection("AuthConfigurations"); +var stsServer = authConfigurations["StsServer"]; + +services.AddAuthentication(options => +{ + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; +}) +.AddCookie(); + +services.AddAuthorization(); +services.AddSingleton(); + +services.AddRazorPages(); + +var app = builder.Build(); + +app.UseSecurityHeaders(SecurityHeadersDefinitions + .GetHeaderPolicyCollection(env.IsDevelopment())); + +if (env.IsDevelopment()) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + app.UseDeveloperExceptionPage(); } +else +{ + app.UseExceptionHandler("/Error"); +} + +app.UseStaticFiles(); +app.UseCookiePolicy(); +app.UseSession(); + +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapRazorPages(); + +app.Run(); diff --git a/DeviceFlowWeb/Properties/launchSettings.json b/DeviceFlowWeb/Properties/launchSettings.json index 71dbbb0..c2d83a9 100644 --- a/DeviceFlowWeb/Properties/launchSettings.json +++ b/DeviceFlowWeb/Properties/launchSettings.json @@ -1,24 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:60024", - "sslPort": 44369 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "DeviceFlowWeb": { "commandName": "Project", "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "applicationUrl": "https://localhost:44369", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/DeviceFlowWeb/Startup.cs b/DeviceFlowWeb/Startup.cs deleted file mode 100644 index 01380e4..0000000 --- a/DeviceFlowWeb/Startup.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Microsoft.AspNetCore.Authentication.Cookies; - -namespace DeviceFlowWeb; - -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.AddScoped(); - services.AddHttpClient(); - services.Configure(Configuration.GetSection("AuthConfigurations")); - - services.AddSession(options => - { - // Set a short timeout for easy testing. - options.IdleTimeout = TimeSpan.FromSeconds(60); - options.Cookie.HttpOnly = true; - }); - - services.Configure(options => - { - // This lambda determines whether user consent for non-essential cookies is needed for a given request. - options.CheckConsentNeeded = context => true; - options.MinimumSameSitePolicy = SameSiteMode.None; - }); - - var authConfigurations = Configuration.GetSection("AuthConfigurations"); - var stsServer = authConfigurations["StsServer"]; - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }) - .AddCookie(); - - services.AddAuthorization(); - services.AddSingleton(); - - services.AddRazorPages(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseSecurityHeaders( - SecurityHeadersDefinitions.GetHeaderPolicyCollection(env.IsDevelopment())); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - } - - app.UseStaticFiles(); - app.UseCookiePolicy(); - app.UseSession(); - - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - }); - } -} diff --git a/IdentityStandaloneMfa/Program.cs b/IdentityStandaloneMfa/Program.cs index 075a14d..8424d4f 100644 --- a/IdentityStandaloneMfa/Program.cs +++ b/IdentityStandaloneMfa/Program.cs @@ -6,9 +6,6 @@ using Microsoft.IdentityModel.Logging; using System.IdentityModel.Tokens.Jwt; -IdentityModelEventSource.ShowPII = true; -JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext(options => @@ -38,6 +35,9 @@ var app = builder.Build(); +IdentityModelEventSource.ShowPII = true; +JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); diff --git a/IdentityStandaloneMfa/Properties/launchSettings.json b/IdentityStandaloneMfa/Properties/launchSettings.json index b2e7f07..9ef239c 100644 --- a/IdentityStandaloneMfa/Properties/launchSettings.json +++ b/IdentityStandaloneMfa/Properties/launchSettings.json @@ -1,27 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "https://localhost:44357", - "sslPort": 44357 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "IdentityStandaloneMfa": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + "applicationUrl": "https://localhost:44357" } } } \ No newline at end of file diff --git a/IdentityStandaloneUserCheck/Program.cs b/IdentityStandaloneUserCheck/Program.cs index 6a72e12..c3cc173 100644 --- a/IdentityStandaloneUserCheck/Program.cs +++ b/IdentityStandaloneUserCheck/Program.cs @@ -1,16 +1,45 @@ -namespace IdentityStandaloneUserCheck; +using IdentityStandaloneUserCheck.Data; +using IdentityStandaloneUserCheck.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Logging; +using System.IdentityModel.Tokens.Jwt; -public class Program +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); + +builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + +builder.Services.AddIdentity( + options => options.SignIn.RequireConfirmedAccount = false) + .AddDefaultTokenProviders() + .AddDefaultUI() + .AddEntityFrameworkStores(); + +builder.Services.AddTransient(); + +builder.Services.AddRazorPages(); +var app = builder.Build(); + +//IdentityModelEventSource.ShowPII = true; +//JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + +if (!app.Environment.IsDevelopment()) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); -} \ No newline at end of file + app.UseExceptionHandler("/Error"); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapRazorPages(); + +app.Run(); diff --git a/IdentityStandaloneUserCheck/Properties/launchSettings.json b/IdentityStandaloneUserCheck/Properties/launchSettings.json index 03324e2..08a3531 100644 --- a/IdentityStandaloneUserCheck/Properties/launchSettings.json +++ b/IdentityStandaloneUserCheck/Properties/launchSettings.json @@ -1,25 +1,9 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:59194", - "sslPort": 44327 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "IdentityStandaloneUserCheck": { "commandName": "Project", - "dotnetRunMessages": "true", "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "applicationUrl": "https://localhost:44327", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/IdentityStandaloneUserCheck/Properties/serviceDependencies.json b/IdentityStandaloneUserCheck/Properties/serviceDependencies.json deleted file mode 100644 index 79b8073..0000000 --- a/IdentityStandaloneUserCheck/Properties/serviceDependencies.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "mssql1": { - "type": "mssql", - "connectionId": "DefaultConnection" - } - } -} \ No newline at end of file diff --git a/IdentityStandaloneUserCheck/Properties/serviceDependencies.local.json b/IdentityStandaloneUserCheck/Properties/serviceDependencies.local.json deleted file mode 100644 index c01b353..0000000 --- a/IdentityStandaloneUserCheck/Properties/serviceDependencies.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "mssql1": { - "type": "mssql.local", - "connectionId": "DefaultConnection" - } - } -} \ No newline at end of file diff --git a/IdentityStandaloneUserCheck/Startup.cs b/IdentityStandaloneUserCheck/Startup.cs deleted file mode 100644 index 8c927d6..0000000 --- a/IdentityStandaloneUserCheck/Startup.cs +++ /dev/null @@ -1,64 +0,0 @@ -using IdentityStandaloneUserCheck.Data; -using IdentityStandaloneUserCheck.Services; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; - -namespace IdentityStandaloneUserCheck; - -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.UseSqlServer( - Configuration.GetConnectionString("DefaultConnection"))); - services.AddDatabaseDeveloperPageExceptionFilter(); - - services.AddIdentity( - options => options.SignIn.RequireConfirmedAccount = false) - .AddDefaultTokenProviders() - .AddDefaultUI() - .AddEntityFrameworkStores(); - - services.AddTransient(); - - services.AddRazorPages(); - } - - // 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.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - }); - } -} \ No newline at end of file diff --git a/README.md b/README.md index 1fa6828..debdb7d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Update-Database ## History -- 2023-08-17 Updated packages +- 2023-08-18 Updated packages, updated startup - 2023-05-07 Updated packages - 2022-12-17 Updated packages to .NET 7 - 2022-10-22 Updated packages diff --git a/StsServer/Startup.cs b/StsServer/Startup.cs index 242f6cf..d39ab79 100644 --- a/StsServer/Startup.cs +++ b/StsServer/Startup.cs @@ -184,8 +184,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) var locOptions = app.ApplicationServices.GetService>(); app.UseRequestLocalization(locOptions.Value); - // https://nblumhardt.com/2019/10/serilog-in-aspnetcore-3/ - // https://nblumhardt.com/2019/10/serilog-mvc-logging/ app.UseSerilogRequestLogging(); app.UseStaticFiles(); diff --git a/StsServer/StsServerIdentity.csproj b/StsServer/StsServerIdentity.csproj index 8e5e7a5..6c63597 100644 --- a/StsServer/StsServerIdentity.csproj +++ b/StsServer/StsServerIdentity.csproj @@ -2,7 +2,7 @@ net7.0 5.1.2 - IdentityServer4 template with ASP.NET Core 3.1 and ASP.NET Core Identity + IdentityServer4 template with ASP.NET Core 7 and ASP.NET Core Identity https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate http://www.gravatar.com/avatar/61d005637f57b5c3da8ba662cf04a9d6.png https://github.com/damienbod/IdentityServer4AspNetCoreIdentityTemplate diff --git a/WebApi/Startup.cs b/WebApi/HostingExtensions.cs similarity index 61% rename from WebApi/Startup.cs rename to WebApi/HostingExtensions.cs index 0b7081d..088ecd4 100644 --- a/WebApi/Startup.cs +++ b/WebApi/HostingExtensions.cs @@ -1,125 +1,118 @@ -using System.IdentityModel.Tokens.Jwt; -using IdentityServer4.AccessTokenValidation; -using Microsoft.OpenApi.Models; -using Microsoft.AspNetCore.Authentication.JwtBearer; - -namespace WebApi; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public Startup(IWebHostEnvironment env) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); - - builder.AddEnvironmentVariables(); - Configuration = builder.Build(); - } - public void ConfigureServices(IServiceCollection services) - { - services.Configure(options => - { - // This lambda determines whether user consent for non-essential cookies is needed for a given request. - options.CheckConsentNeeded = context => true; - options.MinimumSameSitePolicy = SameSiteMode.None; - }); - - var stsServer = Configuration["StsServer"]; - services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(options => - { - options.Authority = stsServer; - options.ApiName = "ProtectedApi"; - options.ApiSecret = "api_in_protected_zone_secret"; - options.RequireHttpsMetadata = true; - }); - - services.AddAuthorization(options => - options.AddPolicy("protectedScope", policy => - { - policy.RequireClaim("scope", "scope_used_for_api_in_protected_zone"); - }) - ); - - services.AddSwaggerGen(c => - { - // add JWT Authentication - var securityScheme = new OpenApiSecurityScheme - { - Name = "JWT Authentication", - Description = "Enter JWT Bearer token **_only_**", - In = ParameterLocation.Header, - Type = SecuritySchemeType.Http, - Scheme = "bearer", // must be lower case - BearerFormat = "JWT", - Reference = new OpenApiReference - { - Id = JwtBearerDefaults.AuthenticationScheme, - Type = ReferenceType.SecurityScheme - } - }; - c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); - c.AddSecurityRequirement(new OpenApiSecurityRequirement - { - {securityScheme, Array.Empty()} - }); - - c.SwaggerDoc("v1", new OpenApiInfo - { - Title = "User API", - Version = "v1", - Description = "User API", - Contact = new OpenApiContact - { - Name = "damienbod", - Email = string.Empty, - Url = new Uri("https://damienbod.com/"), - }, - }); - }); - - services.AddControllers(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - app.UseSecurityHeaders( - SecurityHeadersDefinitions.GetHeaderPolicyCollection(env.IsDevelopment())); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - - app.UseSwagger(); - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); - }); - } - - app.UseCookiePolicy(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } -} \ No newline at end of file +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Logging; +using Microsoft.OpenApi.Models; +using Serilog; +using System.IdentityModel.Tokens.Jwt; + +namespace WebApi; + +internal static class HostingExtensions +{ + private static IWebHostEnvironment? _env; + + public static WebApplication ConfigureServices(this WebApplicationBuilder builder) + { + var services = builder.Services; + var configuration = builder.Configuration; + _env = builder.Environment; + + services.Configure(options => + { + // This lambda determines whether user consent for non-essential cookies is needed for a given request. + options.CheckConsentNeeded = context => true; + options.MinimumSameSitePolicy = SameSiteMode.None; + }); + + var stsServer = configuration["StsServer"]; + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => + { + options.Authority = stsServer; + options.Audience = "ProtectedApi"; + + //options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; + }); + + services.AddAuthorization(options => + options.AddPolicy("protectedScope", policy => + { + policy.RequireClaim("scope", "scope_used_for_api_in_protected_zone"); + }) + ); + + services.AddSwaggerGen(c => + { + // add JWT Authentication + var securityScheme = new OpenApiSecurityScheme + { + Name = "JWT Authentication", + Description = "Enter JWT Bearer token **_only_**", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "bearer", // must be lower case + BearerFormat = "JWT", + Reference = new OpenApiReference + { + Id = JwtBearerDefaults.AuthenticationScheme, + Type = ReferenceType.SecurityScheme + } + }; + c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + {securityScheme, Array.Empty()} + }); + + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "User API", + Version = "v1", + Description = "User API", + Contact = new OpenApiContact + { + Name = "damienbod", + Email = string.Empty, + Url = new Uri("https://damienbod.com/"), + }, + }); + }); + + services.AddControllers(); + + return builder.Build(); + } + + public static WebApplication ConfigurePipeline(this WebApplication app) + { + IdentityModelEventSource.ShowPII = true; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + app.UseSerilogRequestLogging(); + + app.UseSecurityHeaders(SecurityHeadersDefinitions.GetHeaderPolicyCollection( + _env!.IsDevelopment())); + + if (_env!.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); + }); + } + + app.UseCookiePolicy(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapControllers(); + + return app; + } +} diff --git a/WebApi/Program.cs b/WebApi/Program.cs index cec15ac..c61a9a7 100644 --- a/WebApi/Program.cs +++ b/WebApi/Program.cs @@ -1,16 +1,53 @@ -namespace WebApi; +using WebApi; +using Azure.Identity; +using Serilog; -public class Program +Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .WriteTo.AzureApp() + .CreateBootstrapLogger(); + +try { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } + Log.Information("Starting WebApi"); + + var builder = WebApplication.CreateBuilder(args); + + builder.WebHost + .ConfigureKestrel(serverOptions => { serverOptions.AddServerHeader = false; }) + .ConfigureAppConfiguration((context, configurationBuilder) => + { + var config = configurationBuilder.Build(); + var azureKeyVaultEndpoint = config["AzureKeyVaultEndpoint"]; + if (!string.IsNullOrEmpty(azureKeyVaultEndpoint)) + { + // Add Secrets from KeyVault + Log.Information("Use secrets from {AzureKeyVaultEndpoint}", azureKeyVaultEndpoint); + configurationBuilder.AddAzureKeyVault(new Uri(azureKeyVaultEndpoint), new DefaultAzureCredential()); + } + else + { + // Add Secrets from UserSecrets for local development + configurationBuilder.AddUserSecrets("9f17b08c-435a-4f50-ba7a-802e68ca8d80"); + } + }); + +builder.Host.UseSerilog((context, loggerConfiguration) => loggerConfiguration + .ReadFrom.Configuration(context.Configuration)); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); -} \ No newline at end of file +var app = builder + .ConfigureServices() + .ConfigurePipeline(); + +app.Run(); +} +catch (Exception ex) when(ex.GetType().Name is not "StopTheHostException" + && ex.GetType().Name is not "HostAbortedException") +{ + Log.Fatal(ex, "Unhandled exception"); +} +finally +{ + Log.Information("Shut down complete"); + Log.CloseAndFlush(); +} diff --git a/WebApi/Properties/launchSettings.json b/WebApi/Properties/launchSettings.json index ce953dd..02a33f8 100644 --- a/WebApi/Properties/launchSettings.json +++ b/WebApi/Properties/launchSettings.json @@ -1,29 +1,13 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "https://localhost:44342/", - "sslPort": 44342 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "WebApi": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "api/values", + "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:19039/" + "applicationUrl": "https://localhost:44342/" } } } \ No newline at end of file diff --git a/WebApi/WebApi.csproj b/WebApi/WebApi.csproj index 78d7b23..1d7e2b0 100644 --- a/WebApi/WebApi.csproj +++ b/WebApi/WebApi.csproj @@ -8,16 +8,25 @@ - + - - - - - - - + + + + + + + + + + + + + + + + diff --git a/WebApi/appsettings.Development.json b/WebApi/appsettings.Development.json index b0d3623..fafc784 100644 --- a/WebApi/appsettings.Development.json +++ b/WebApi/appsettings.Development.json @@ -1,16 +1,3 @@ { - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "LogLevel": { - "Default": "Warning" - } - } - }, "StsServer": "https://localhost:44352" } diff --git a/WebApi/appsettings.Release.json b/WebApi/appsettings.Release.json index dd64cfa..9134a85 100644 --- a/WebApi/appsettings.Release.json +++ b/WebApi/appsettings.Release.json @@ -1,16 +1,3 @@ { - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "LogLevel": { - "Default": "Warning" - } - } - }, "StsServer": "https://security-headers-sts.azurewebsites.net" } diff --git a/WebApi/appsettings.json b/WebApi/appsettings.json index 736a7e6..7b649e5 100644 --- a/WebApi/appsettings.json +++ b/WebApi/appsettings.json @@ -1,15 +1,35 @@ { - "Logging": { - "Debug": { - "LogLevel": { - "Default": "Warning" + "AzureKeyVaultEndpoint": null, + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "System": "Information" } }, - "Console": { - "LogLevel": { - "Default": "Warning" + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} ({SourceContext}){NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "../_logs-WebApi.txt", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [{SourceContext}] [{EventId}] {Message}{NewLine}{Exception}", + "rollOnFileSizeLimit": true, + "fileSizeLimitBytes": 4194304, + "retainedFileCountLimit": 5 + } } - } + ] }, "StsServer": "https://security-headers-sts.azurewebsites.net" } diff --git a/WebCodeFlowPkceClient/Startup.cs b/WebCodeFlowPkceClient/HostingExtensions.cs similarity index 75% rename from WebCodeFlowPkceClient/Startup.cs rename to WebCodeFlowPkceClient/HostingExtensions.cs index 28257da..3e7ef1e 100644 --- a/WebCodeFlowPkceClient/Startup.cs +++ b/WebCodeFlowPkceClient/HostingExtensions.cs @@ -1,77 +1,79 @@ -using System.IdentityModel.Tokens.Jwt; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.IdentityModel.Logging; - -namespace WebCodeFlowPkceClient; - -public class Startup -{ - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.AddTransient(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }) - .AddCookie() - .AddOpenIdConnect(options => - { - options.SignInScheme = "Cookies"; - options.Authority = "https://localhost:44352"; - options.RequireHttpsMetadata = true; - options.ClientId = "codeflowpkceclient"; - options.ClientSecret = "codeflow_pkce_client_secret"; - options.ResponseType = "code"; - options.UsePkce = true; - options.Scope.Add("profile"); - options.Scope.Add("offline_access"); - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username"); - options.ClaimActions.MapUniqueJsonKey("gender", "gender"); - }); - - services.AddAuthorization(); - services.AddRazorPages(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - IdentityModelEventSource.ShowPII = true; - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - }); - } -} \ No newline at end of file +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.IdentityModel.Logging; +using Serilog; +using System.IdentityModel.Tokens.Jwt; + +namespace WebCodeFlowPkceClient; + +internal static class HostingExtensions +{ + private static IWebHostEnvironment? _env; + + public static WebApplication ConfigureServices(this WebApplicationBuilder builder) + { + var services = builder.Services; + var configuration = builder.Configuration; + _env = builder.Environment; + + services.AddTransient(); + + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddOpenIdConnect(options => + { + options.SignInScheme = "Cookies"; + options.Authority = "https://localhost:44352"; + options.RequireHttpsMetadata = true; + options.ClientId = "codeflowpkceclient"; + options.ClientSecret = "codeflow_pkce_client_secret"; + options.ResponseType = "code"; + options.UsePkce = true; + options.Scope.Add("profile"); + options.Scope.Add("offline_access"); + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username"); + options.ClaimActions.MapUniqueJsonKey("gender", "gender"); + }); + + services.AddAuthorization(); + services.AddRazorPages(); + + return builder.Build(); + } + + public static WebApplication ConfigurePipeline(this WebApplication app) + { + IdentityModelEventSource.ShowPII = true; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + app.UseSerilogRequestLogging(); + + if (_env!.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapRazorPages(); + + return app; + } +} diff --git a/WebCodeFlowPkceClient/Program.cs b/WebCodeFlowPkceClient/Program.cs index 2d25060..9bb710f 100644 --- a/WebCodeFlowPkceClient/Program.cs +++ b/WebCodeFlowPkceClient/Program.cs @@ -1,59 +1,53 @@ +using WebCodeFlowPkceClient; +using Azure.Identity; using Serilog; -using Serilog.Events; -using Serilog.Sinks.SystemConsole.Themes; -namespace WebCodeFlowPkceClient; +Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .WriteTo.AzureApp() + .CreateBootstrapLogger(); -public class Program +try { - public static int Main(string[] args) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .Enrich.FromLogContext() - .WriteTo.Console() - .CreateLogger(); + Log.Information("Starting WebHybridClient"); - try - { - Log.Information("Starting web host"); - CreateHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly"); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } + var builder = WebApplication.CreateBuilder(args); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((context, config) => + builder.WebHost + .ConfigureKestrel(serverOptions => { serverOptions.AddServerHeader = false; }) + .ConfigureAppConfiguration((context, configurationBuilder) => + { + var config = configurationBuilder.Build(); + var azureKeyVaultEndpoint = config["AzureKeyVaultEndpoint"]; + if (!string.IsNullOrEmpty(azureKeyVaultEndpoint)) { - var builder = config.Build(); - var keyVaultEndpoint = builder["AzureKeyVaultEndpoint"]; - IHostEnvironment env = context.HostingEnvironment; - - config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables(); - //.AddUserSecrets("your user secret...."); - - }) - .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration - .ReadFrom.Configuration(hostingContext.Configuration) - .Enrich.FromLogContext() - .WriteTo.File("../PkceClientApp.txt") - .WriteTo.Console(theme: AnsiConsoleTheme.Code) - ) - .ConfigureWebHostDefaults(webBuilder => + // Add Secrets from KeyVault + Log.Information("Use secrets from {AzureKeyVaultEndpoint}", azureKeyVaultEndpoint); + configurationBuilder.AddAzureKeyVault(new Uri(azureKeyVaultEndpoint), new DefaultAzureCredential()); + } + else { - webBuilder.UseStartup(); - }); + // Add Secrets from UserSecrets for local development + configurationBuilder.AddUserSecrets("4bd68e8b-ec2d-42d4-899f-fa77fcd14af5"); + } + }); + + builder.Host.UseSerilog((context, loggerConfiguration) => loggerConfiguration + .ReadFrom.Configuration(context.Configuration)); + + var app = builder + .ConfigureServices() + .ConfigurePipeline(); + + app.Run(); +} +catch (Exception ex) when (ex.GetType().Name is not "StopTheHostException" + && ex.GetType().Name is not "HostAbortedException") +{ + Log.Fatal(ex, "Unhandled exception"); +} +finally +{ + Log.Information("Shut down complete"); + Log.CloseAndFlush(); } diff --git a/WebCodeFlowPkceClient/Properties/launchSettings.json b/WebCodeFlowPkceClient/Properties/launchSettings.json index 55d0dc0..014dcd3 100644 --- a/WebCodeFlowPkceClient/Properties/launchSettings.json +++ b/WebCodeFlowPkceClient/Properties/launchSettings.json @@ -1,27 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "https://localhost:44330/", - "sslPort": 44330 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "WebCodeFlowPkceClient": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + "applicationUrl": "https://localhost:44330" } } } \ No newline at end of file diff --git a/WebCodeFlowPkceClient/WebCodeFlowPkceClient.csproj b/WebCodeFlowPkceClient/WebCodeFlowPkceClient.csproj index 7092f2a..74193c9 100644 --- a/WebCodeFlowPkceClient/WebCodeFlowPkceClient.csproj +++ b/WebCodeFlowPkceClient/WebCodeFlowPkceClient.csproj @@ -8,23 +8,22 @@ - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + - - - - - - - + + + + + + + + + + + diff --git a/WebCodeFlowPkceClient/appsettings.Development.json b/WebCodeFlowPkceClient/appsettings.Development.json deleted file mode 100644 index a2880cb..0000000 --- a/WebCodeFlowPkceClient/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/WebCodeFlowPkceClient/appsettings.json b/WebCodeFlowPkceClient/appsettings.json index 81ff877..beb7a41 100644 --- a/WebCodeFlowPkceClient/appsettings.json +++ b/WebCodeFlowPkceClient/appsettings.json @@ -1,10 +1,35 @@ { - "Logging": { - "LogLevel": { + "AzureKeyVaultEndpoint": null, + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } + "Override": { + "Microsoft": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "System": "Information" + } + }, + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} ({SourceContext}){NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "../_logs-WebCodeFlowPkceClient.txt", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [{SourceContext}] [{EventId}] {Message}{NewLine}{Exception}", + "rollOnFileSizeLimit": true, + "fileSizeLimitBytes": 4194304, + "retainedFileCountLimit": 5 + } + } + ] }, "AllowedHosts": "*" } diff --git a/WebExtraClaimsCached/Program.cs b/WebExtraClaimsCached/Program.cs index 1e02474..39d0909 100644 --- a/WebExtraClaimsCached/Program.cs +++ b/WebExtraClaimsCached/Program.cs @@ -5,11 +5,6 @@ using System.IdentityModel.Tokens.Jwt; using WebExtraClaimsCached; -IServiceProvider? ApplicationServices = null; - -IdentityModelEventSource.ShowPII = true; -JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - var builder = WebApplication.CreateBuilder(args); builder.Services.AddScoped(); @@ -41,13 +36,9 @@ { options.Events.OnTokenValidated = async context => { - if(ApplicationServices != null && context.Principal != null) - { - using var scope = ApplicationServices.CreateScope(); - context.Principal = await scope.ServiceProvider - .GetRequiredService() - .TransformAsync(context.Principal); - } + await context.HttpContext.RequestServices + .GetRequiredService() + .TransformAsync(context.Principal!); }; }); @@ -55,7 +46,8 @@ var app = builder.Build(); -ApplicationServices = app.Services; +IdentityModelEventSource.ShowPII = true; +JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); if (!app.Environment.IsDevelopment()) { diff --git a/WebExtraClaimsCached/Properties/launchSettings.json b/WebExtraClaimsCached/Properties/launchSettings.json index b2c2a56..7a170c4 100644 --- a/WebExtraClaimsCached/Properties/launchSettings.json +++ b/WebExtraClaimsCached/Properties/launchSettings.json @@ -7,22 +7,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "https://localhost:5001" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - }, - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "https://localhost:44355", - "sslPort": 44355 + "applicationUrl": "https://localhost:44355" } } } \ No newline at end of file diff --git a/WebHybridFlowClient/Startup.cs b/WebHybridFlowClient/HostingExtensions.cs similarity index 62% rename from WebHybridFlowClient/Startup.cs rename to WebHybridFlowClient/HostingExtensions.cs index 6c93d94..7f4e7c7 100644 --- a/WebHybridFlowClient/Startup.cs +++ b/WebHybridFlowClient/HostingExtensions.cs @@ -1,88 +1,90 @@ -using System.IdentityModel.Tokens.Jwt; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; - -namespace WebHybridClient; - -public class Startup -{ - private string? stsServer = string.Empty; - private readonly IWebHostEnvironment _environment; - public IConfiguration Configuration { get; } - - public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) - { - Configuration = configuration; - _environment = webHostEnvironment; - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddHttpClient(); - services.Configure(Configuration.GetSection("AuthConfigurations")); - - var authConfigurations = Configuration.GetSection("AuthConfigurations"); - stsServer = authConfigurations["StsServer"]; - - services.AddDistributedMemoryCache(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; - }) - .AddCookie() - .AddOpenIdConnect(options => - { - options.SignInScheme = "Cookies"; - options.Authority = stsServer; - options.RequireHttpsMetadata = true; - options.ClientId = "hybridclient"; - options.ClientSecret = "hybrid_flow_secret"; - options.ResponseType = "code id_token"; - options.Scope.Add("scope_used_for_hybrid_flow"); - options.Scope.Add("profile"); - options.Scope.Add("offline_access"); - options.SaveTokens = true; - }); - - services.AddAuthorization(); - - services.AddControllersWithViews(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - app.UseSecurityHeaders( - SecurityHeadersDefinitions.GetHeaderPolicyCollection(env.IsDevelopment())); - - if (_environment.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - } - - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - }); - } -} \ No newline at end of file +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.IdentityModel.Logging; +using Serilog; +using System.IdentityModel.Tokens.Jwt; + +namespace WebHybridClient; + +internal static class HostingExtensions +{ + private static IWebHostEnvironment? _env; + + public static WebApplication ConfigureServices(this WebApplicationBuilder builder) + { + var services = builder.Services; + var configuration = builder.Configuration; + _env = builder.Environment; + + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddHttpClient(); + services.Configure(configuration.GetSection("AuthConfigurations")); + + var authConfigurations = configuration.GetSection("AuthConfigurations"); + var stsServer = authConfigurations["StsServer"]; + + services.AddDistributedMemoryCache(); + + services.AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddOpenIdConnect(options => + { + options.SignInScheme = "Cookies"; + options.Authority = stsServer; + options.RequireHttpsMetadata = true; + options.ClientId = "hybridclient"; + options.ClientSecret = "hybrid_flow_secret"; + options.ResponseType = "code id_token"; + options.Scope.Add("scope_used_for_hybrid_flow"); + options.Scope.Add("profile"); + options.Scope.Add("offline_access"); + options.SaveTokens = true; + }); + + services.AddAuthorization(); + + services.AddControllersWithViews(); + + return builder.Build(); + } + + public static WebApplication ConfigurePipeline(this WebApplication app) + { + IdentityModelEventSource.ShowPII = true; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + app.UseSerilogRequestLogging(); + + app.UseSecurityHeaders( + SecurityHeadersDefinitions.GetHeaderPolicyCollection(_env!.IsDevelopment())); + + if (_env!.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}" + ); + return app; + } +} diff --git a/WebHybridFlowClient/Program.cs b/WebHybridFlowClient/Program.cs index c0c0110..c924ee0 100644 --- a/WebHybridFlowClient/Program.cs +++ b/WebHybridFlowClient/Program.cs @@ -1,60 +1,53 @@ -using Serilog; -using Serilog.Events; -using Serilog.Sinks.SystemConsole.Themes; +using Azure.Identity; +using Serilog; +using WebHybridClient; -namespace WebHybridClient; +Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .WriteTo.AzureApp() + .CreateBootstrapLogger(); -public class Program +try { - public static int Main(string[] args) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .Enrich.FromLogContext() - .WriteTo.Console() - .CreateLogger(); + Log.Information("Starting WebHybridClient"); - try - { - Log.Information("Starting web host"); - CreateHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly"); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } + var builder = WebApplication.CreateBuilder(args); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((context, config) => + builder.WebHost + .ConfigureKestrel(serverOptions => { serverOptions.AddServerHeader = false; }) + .ConfigureAppConfiguration((context, configurationBuilder) => + { + var config = configurationBuilder.Build(); + var azureKeyVaultEndpoint = config["AzureKeyVaultEndpoint"]; + if (!string.IsNullOrEmpty(azureKeyVaultEndpoint)) { - var builder = config.Build(); - var keyVaultEndpoint = builder["AzureKeyVaultEndpoint"]; - IHostEnvironment env = context.HostingEnvironment; + // Add Secrets from KeyVault + Log.Information("Use secrets from {AzureKeyVaultEndpoint}", azureKeyVaultEndpoint); + configurationBuilder.AddAzureKeyVault(new Uri(azureKeyVaultEndpoint), new DefaultAzureCredential()); + } + else + { + // Add Secrets from UserSecrets for local development + configurationBuilder.AddUserSecrets("81795b76-afe1-496b-bb97-8835d573e9c4"); + } + }); - config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables(); - //.AddUserSecrets("your user secret...."); + builder.Host.UseSerilog((context, loggerConfiguration) => loggerConfiguration + .ReadFrom.Configuration(context.Configuration)); - }) - .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration - .ReadFrom.Configuration(hostingContext.Configuration) - .Enrich.FromLogContext() - .WriteTo.File("../WebHybridClient.txt") - .WriteTo.Console(theme: AnsiConsoleTheme.Code) - ) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + var app = builder + .ConfigureServices() + .ConfigurePipeline(); -} \ No newline at end of file + app.Run(); +} +catch (Exception ex) when (ex.GetType().Name is not "StopTheHostException" + && ex.GetType().Name is not "HostAbortedException") +{ + Log.Fatal(ex, "Unhandled exception"); +} +finally +{ + Log.Information("Shut down complete"); + Log.CloseAndFlush(); +} diff --git a/WebHybridFlowClient/Properties/launchSettings.json b/WebHybridFlowClient/Properties/launchSettings.json index c7909ba..df095de 100644 --- a/WebHybridFlowClient/Properties/launchSettings.json +++ b/WebHybridFlowClient/Properties/launchSettings.json @@ -1,27 +1,12 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "https://localhost:44329/", - "sslPort": 44329 - } - }, "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "WebHybridClient": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:19053/" + "applicationUrl": "https://localhost:44329/" } } } \ No newline at end of file diff --git a/WebHybridFlowClient/WebHybridFlowClient.csproj b/WebHybridFlowClient/WebHybridFlowClient.csproj index 03b6b3f..9d2077b 100644 --- a/WebHybridFlowClient/WebHybridFlowClient.csproj +++ b/WebHybridFlowClient/WebHybridFlowClient.csproj @@ -12,12 +12,18 @@ + + + + - - + + + + + - diff --git a/WebHybridFlowClient/appsettings.Development.json b/WebHybridFlowClient/appsettings.Development.json index d5fe758..50649f9 100644 --- a/WebHybridFlowClient/appsettings.Development.json +++ b/WebHybridFlowClient/appsettings.Development.json @@ -1,10 +1,4 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" - } - }, "AuthConfigurations": { "StsServer": "https://localhost:44352", "ProtectedApiUrl": "https://localhost:44342" diff --git a/WebHybridFlowClient/appsettings.Release.json b/WebHybridFlowClient/appsettings.Release.json index 5a43524..4da0762 100644 --- a/WebHybridFlowClient/appsettings.Release.json +++ b/WebHybridFlowClient/appsettings.Release.json @@ -1,10 +1,4 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" - } - }, "AuthConfigurations": { "StsServer": "https://security-headers-sts.azurewebsites.net", "ProtectedApiUrl": "https://security-headers-web-api.azurewebsites.net" diff --git a/WebHybridFlowClient/appsettings.json b/WebHybridFlowClient/appsettings.json index c00633a..6c55155 100644 --- a/WebHybridFlowClient/appsettings.json +++ b/WebHybridFlowClient/appsettings.json @@ -1,8 +1,35 @@ { - "Logging": { - "LogLevel": { - "Default": "Warning" - } + "AzureKeyVaultEndpoint": null, + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "System": "Information" + } + }, + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} ({SourceContext}){NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "../_logs-WebHybridClient.txt", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [{SourceContext}] [{EventId}] {Message}{NewLine}{Exception}", + "rollOnFileSizeLimit": true, + "fileSizeLimitBytes": 4194304, + "retainedFileCountLimit": 5 + } + } + ] }, "AuthConfigurations": { "StsServer": "https://security-headers-sts.azurewebsites.net",