From ad16b63085e322a3546b3f423083f5872f1cecc0 Mon Sep 17 00:00:00 2001 From: RolandGuijt Date: Fri, 28 Jun 2024 13:38:01 +0200 Subject: [PATCH] Modernize clients in basics (#193) Co-authored-by: Roland Guijt --- .../Basics/Apis/ResourceBasedApi/Program.cs | 82 +++++--- .../Basics/Apis/ResourceBasedApi/Selector.cs | 75 ++++--- .../Basics/Apis/ResourceBasedApi/Startup.cs | 54 ----- .../Basics/ClientCredentials/src/Program.cs | 88 ++++---- .../v7/Basics/Introspection/src/Program.cs | 84 ++++---- .../src/Program.cs | 144 ++++++------- .../src/Controllers/HomeController.cs | 47 +++-- .../src/Controllers/LogoutController.cs | 141 +++++++------ .../MvcBackChannelLogout/src/Program.cs | 85 ++++++-- .../MvcBackChannelLogout/src/Startup.cs | 82 -------- .../src/AssertionConfigurationService.cs | 31 ++- .../Basics/MvcJarJwt/src/AssertionService.cs | 115 +++++------ .../src/Controllers/HomeController.cs | 49 +++-- .../v7/Basics/MvcJarJwt/src/OidcEvents.cs | 51 +++-- .../v7/Basics/MvcJarJwt/src/Program.cs | 115 +++++++++-- .../v7/Basics/MvcJarJwt/src/Startup.cs | 115 ----------- .../MvcPar/src/Controllers/HomeController.cs | 39 ++-- .../v7/Basics/MvcPar/src/ParOidcEvents.cs | 195 +++++++++--------- .../v7/Basics/MvcPar/src/Program.cs | 138 +++++++++---- .../v7/Basics/MvcPar/src/Startup.cs | 107 ---------- .../src/Controllers/HomeController.cs | 51 +++-- .../Basics/MvcTokenManagement/src/Program.cs | 97 +++++++-- .../Basics/MvcTokenManagement/src/Startup.cs | 94 --------- .../Basics/Shared/TokenResponseExtensions.cs | 130 +++++------- 24 files changed, 992 insertions(+), 1217 deletions(-) delete mode 100755 IdentityServer/v7/Basics/Apis/ResourceBasedApi/Startup.cs delete mode 100755 IdentityServer/v7/Basics/MvcBackChannelLogout/src/Startup.cs delete mode 100755 IdentityServer/v7/Basics/MvcJarJwt/src/Startup.cs delete mode 100644 IdentityServer/v7/Basics/MvcPar/src/Startup.cs delete mode 100755 IdentityServer/v7/Basics/MvcTokenManagement/src/Startup.cs diff --git a/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Program.cs b/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Program.cs index 78dc1d3a..7b19f321 100755 --- a/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Program.cs +++ b/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Program.cs @@ -1,35 +1,59 @@ using System; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; +using Client; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using ResourceBasedApi; using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; -namespace ResourceBasedApi -{ - public class Program +Console.Title = "Resource based API"; +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .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.AddCors(); +builder.Services.AddDistributedMemoryCache(); + +builder.Services.AddAuthentication("token") + // JWT tokens + .AddJwtBearer("token", options => + { + options.Authority = Urls.IdentityServer; + options.Audience = "resource2"; + + options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; + + // if token does not contain a dot, it is a reference token + options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection"); + }) + + // reference tokens + .AddOAuth2Introspection("introspection", options => { - public static void Main(string[] args) - { - Console.Title = "Simple API with Resources"; - - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .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(); - - return WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } - } -} \ No newline at end of file + options.Authority = Urls.IdentityServer; + + options.ClientId = "resource1"; + options.ClientSecret = "secret"; + }); + +var app = builder.Build(); + +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers().RequireAuthorization(); + +app.Run(); + diff --git a/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Selector.cs b/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Selector.cs index a0b70b92..15d5ba97 100755 --- a/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Selector.cs +++ b/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Selector.cs @@ -2,56 +2,55 @@ using System.Linq; using Microsoft.AspNetCore.Http; -namespace ResourceBasedApi +namespace ResourceBasedApi; + +/// +/// Provides helper functions for forwarding logic +/// +public static class Selector { /// - /// Provides helper functions for forwarding logic + /// Provides a forwarding func for JWT vs reference tokens (based on existence of dot in token) /// - public static class Selector + /// Scheme name of the introspection handler + /// + public static Func ForwardReferenceToken(string introspectionScheme = "introspection") { - /// - /// Provides a forwarding func for JWT vs reference tokens (based on existence of dot in token) - /// - /// Scheme name of the introspection handler - /// - public static Func ForwardReferenceToken(string introspectionScheme = "introspection") + string Select(HttpContext context) { - string Select(HttpContext context) + var (scheme, credential) = GetSchemeAndCredential(context); + if (scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase) && + !credential.Contains(".")) { - var (scheme, credential) = GetSchemeAndCredential(context); - if (scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase) && - !credential.Contains(".")) - { - return introspectionScheme; - } - - return null; + return introspectionScheme; } - return Select; + return null; } - - /// - /// Extracts scheme and credential from Authorization header (if present) - /// - /// - /// - public static (string, string) GetSchemeAndCredential(HttpContext context) - { - var header = context.Request.Headers["Authorization"].FirstOrDefault(); - if (string.IsNullOrEmpty(header)) - { - return ("", ""); - } + return Select; + } + + /// + /// Extracts scheme and credential from Authorization header (if present) + /// + /// + /// + public static (string, string) GetSchemeAndCredential(HttpContext context) + { + var header = context.Request.Headers["Authorization"].FirstOrDefault(); - var parts = header.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length != 2) - { - return ("", ""); - } + if (string.IsNullOrEmpty(header)) + { + return ("", ""); + } - return (parts[0], parts[1]); + var parts = header.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) + { + return ("", ""); } + + return (parts[0], parts[1]); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Startup.cs b/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Startup.cs deleted file mode 100755 index 24feeb5b..00000000 --- a/IdentityServer/v7/Basics/Apis/ResourceBasedApi/Startup.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Client; -using Serilog; - -namespace ResourceBasedApi -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddSerilog(); - services.AddControllers(); - - services.AddCors(); - services.AddDistributedMemoryCache(); - - services.AddAuthentication("token") - - // JWT tokens - .AddJwtBearer("token", options => - { - options.Authority = Urls.IdentityServer; - options.Audience = "resource2"; - - options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; - - // if token does not contain a dot, it is a reference token - options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection"); - }) - - // reference tokens - .AddOAuth2Introspection("introspection", options => - { - options.Authority = Urls.IdentityServer; - - options.ClientId = "resource1"; - options.ClientSecret = "secret"; - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers().RequireAuthorization(); - }); - } - } -} \ No newline at end of file diff --git a/IdentityServer/v7/Basics/ClientCredentials/src/Program.cs b/IdentityServer/v7/Basics/ClientCredentials/src/Program.cs index 3e0dd518..203a8a07 100755 --- a/IdentityServer/v7/Basics/ClientCredentials/src/Program.cs +++ b/IdentityServer/v7/Basics/ClientCredentials/src/Program.cs @@ -1,56 +1,48 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using Client; using IdentityModel.Client; -namespace Client +Console.Title = "Console Client Credentials Flow"; + +var response = await RequestTokenAsync(); +response.Show(); + +Console.ReadLine(); +await CallServiceAsync(response.AccessToken); + +static async Task RequestTokenAsync() { - class Program + var client = new HttpClient(); + + var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); + if (disco.IsError) throw new Exception(disco.Error); + + var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { - public static async Task Main() - { - Console.Title = "Console Client Credentials Flow"; - - var response = await RequestTokenAsync(); - response.Show(); - - Console.ReadLine(); - await CallServiceAsync(response.AccessToken); - } - - static async Task RequestTokenAsync() - { - var client = new HttpClient(); - - var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); - if (disco.IsError) throw new Exception(disco.Error); - - var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest - { - Address = disco.TokenEndpoint, - - ClientId = "client.credentials.sample", - ClientSecret = "secret", - - Scope = "scope1" - }); - - if (response.IsError) throw new Exception(response.Error); - return response; - } - - static async Task CallServiceAsync(string token) - { - var client = new HttpClient - { - BaseAddress = new Uri(Urls.SampleApi) - }; - - client.SetBearerToken(token); - var response = await client.GetStringAsync("identity"); - - "\n\nService claims:".ConsoleGreen(); - Console.WriteLine(response.PrettyPrintJson()); - } - } + Address = disco.TokenEndpoint, + + ClientId = "client.credentials.sample", + ClientSecret = "secret", + + Scope = "scope1" + }); + + if (response.IsError) throw new Exception(response.Error); + return response; } + +static async Task CallServiceAsync(string token) +{ + var client = new HttpClient + { + BaseAddress = new Uri(Urls.SampleApi) + }; + + client.SetBearerToken(token); + var response = await client.GetStringAsync("identity"); + + "\n\nService claims:".ConsoleGreen(); + Console.WriteLine(response.PrettyPrintJson()); +} \ No newline at end of file diff --git a/IdentityServer/v7/Basics/Introspection/src/Program.cs b/IdentityServer/v7/Basics/Introspection/src/Program.cs index 450f1fa2..f62a7786 100755 --- a/IdentityServer/v7/Basics/Introspection/src/Program.cs +++ b/IdentityServer/v7/Basics/Introspection/src/Program.cs @@ -1,54 +1,46 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using Client; using IdentityModel.Client; -namespace Client +var response = await RequestTokenAsync(); +response.Show(); + +Console.ReadLine(); +await CallServiceAsync(response.AccessToken); + +static async Task RequestTokenAsync() +{ + var client = new HttpClient(); + + var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); + if (disco.IsError) throw new Exception(disco.Error); + + var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest + { + Address = disco.TokenEndpoint, + + ClientId = "introspection.sample", + ClientSecret = "secret", + + Scope = "scope2" + }); + + if (response.IsError) throw new Exception(response.Error); + return response; +} + +static async Task CallServiceAsync(string token) { - class Program + var client = new HttpClient { - public static async Task Main() - { - var response = await RequestTokenAsync(); - response.Show(); - - Console.ReadLine(); - await CallServiceAsync(response.AccessToken); - } - - static async Task RequestTokenAsync() - { - var client = new HttpClient(); - - var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); - if (disco.IsError) throw new Exception(disco.Error); - - var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest - { - Address = disco.TokenEndpoint, - - ClientId = "introspection.sample", - ClientSecret = "secret", - - Scope = "scope2" - }); - - if (response.IsError) throw new Exception(response.Error); - return response; - } - - static async Task CallServiceAsync(string token) - { - var client = new HttpClient - { - BaseAddress = new Uri(Urls.SampleApi) - }; - - client.SetBearerToken(token); - var response = await client.GetStringAsync("identity"); - - "\n\nService claims:".ConsoleGreen(); - Console.WriteLine(response.PrettyPrintJson()); - } - } + BaseAddress = new Uri(Urls.SampleApi) + }; + + client.SetBearerToken(token); + var response = await client.GetStringAsync("identity"); + + "\n\nService claims:".ConsoleGreen(); + Console.WriteLine(response.PrettyPrintJson()); } diff --git a/IdentityServer/v7/Basics/JwtBasedClientAuthentication/src/Program.cs b/IdentityServer/v7/Basics/JwtBasedClientAuthentication/src/Program.cs index 777a33c9..f6f37404 100755 --- a/IdentityServer/v7/Basics/JwtBasedClientAuthentication/src/Program.cs +++ b/IdentityServer/v7/Basics/JwtBasedClientAuthentication/src/Program.cs @@ -4,99 +4,91 @@ using System.Net.Http; using System.Security.Claims; using System.Threading.Tasks; +using Client; using IdentityModel; using IdentityModel.Client; using Microsoft.IdentityModel.Tokens; -namespace Client +// would normally load from a secure data store +string rsaKey = """ { - class Program - { - // would normally load from a secure data store - private static string rsaKey = """ - { - "d":"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ", - "dp":"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE", - "dq":"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M", - "e":"AQAB", - "kid":"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA", - "kty":"RSA", - "n":"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw", - "p":"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE", - "q":"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts", - "qi":"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4" - } - """; + "d":"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ", + "dp":"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE", + "dq":"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M", + "e":"AQAB", + "kid":"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA", + "kty":"RSA", + "n":"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw", + "p":"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE", + "q":"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts", + "qi":"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4" +} +"""; - public static async Task Main() - { - var jwk = new JsonWebKey(rsaKey); - var response = await RequestTokenAsync(new SigningCredentials(jwk, "RS256")); - response.Show(); +var jwk = new JsonWebKey(rsaKey); +var response = await RequestTokenAsync(new SigningCredentials(jwk, "RS256")); +response.Show(); - Console.ReadLine(); - await CallServiceAsync(response.AccessToken); - } +Console.ReadLine(); +await CallServiceAsync(response.AccessToken); - static async Task RequestTokenAsync(SigningCredentials signingCredentials) - { - var client = new HttpClient(); +static async Task RequestTokenAsync(SigningCredentials signingCredentials) +{ + var client = new HttpClient(); - var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); - if (disco.IsError) throw new Exception(disco.Error); + var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); + if (disco.IsError) throw new Exception(disco.Error); - var clientToken = CreateClientToken(signingCredentials,"jwt.client.credentials.sample", disco.TokenEndpoint); - var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest - { - Address = disco.TokenEndpoint, + var clientToken = CreateClientToken(signingCredentials,"jwt.client.credentials.sample", disco.TokenEndpoint); + var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest + { + Address = disco.TokenEndpoint, - ClientAssertion = - { - Type = OidcConstants.ClientAssertionTypes.JwtBearer, - Value = clientToken - }, + ClientAssertion = + { + Type = OidcConstants.ClientAssertionTypes.JwtBearer, + Value = clientToken + }, - Scope = "scope1" - }); + Scope = "scope1" + }); - if (response.IsError) throw new Exception(response.Error); - return response; - } + if (response.IsError) throw new Exception(response.Error); + return response; +} - private static string CreateClientToken(SigningCredentials credential, string clientId, string audience) - { - var now = DateTime.UtcNow; +static string CreateClientToken(SigningCredentials credential, string clientId, string audience) +{ + var now = DateTime.UtcNow; - var token = new JwtSecurityToken( - clientId, - audience, - new List() - { - new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()), - new Claim(JwtClaimTypes.Subject, clientId), - new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64) - }, - now, - now.AddMinutes(1), - credential - ); + var token = new JwtSecurityToken( + clientId, + audience, + new List() + { + new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()), + new Claim(JwtClaimTypes.Subject, clientId), + new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64) + }, + now, + now.AddMinutes(1), + credential + ); - var tokenHandler = new JwtSecurityTokenHandler(); - return tokenHandler.WriteToken(token); - } + var tokenHandler = new JwtSecurityTokenHandler(); + return tokenHandler.WriteToken(token); +} - static async Task CallServiceAsync(string token) - { - var client = new HttpClient - { - BaseAddress = new Uri(Urls.SampleApi) - }; +static async Task CallServiceAsync(string token) +{ + var client = new HttpClient + { + BaseAddress = new Uri(Urls.SampleApi) + }; - client.SetBearerToken(token); - var response = await client.GetStringAsync("identity"); + client.SetBearerToken(token); + var response = await client.GetStringAsync("identity"); - "\n\nService claims:".ConsoleGreen(); - Console.WriteLine(response.PrettyPrintJson()); - } - } + "\n\nService claims:".ConsoleGreen(); + Console.WriteLine(response.PrettyPrintJson()); } diff --git a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/HomeController.cs b/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/HomeController.cs index 47b96239..05c96482 100755 --- a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/HomeController.cs +++ b/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/HomeController.cs @@ -6,36 +6,35 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Client.Controllers +namespace Client.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + private readonly IHttpClientFactory _httpClientFactory; + + public HomeController(IHttpClientFactory httpClientFactory) { - private readonly IHttpClientFactory _httpClientFactory; - - public HomeController(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } - - [AllowAnonymous] - public IActionResult Index() => View(); + _httpClientFactory = httpClientFactory; + } + + [AllowAnonymous] + public IActionResult Index() => View(); - public IActionResult Secure() => View(); + public IActionResult Secure() => View(); - public IActionResult Logout() => SignOut("oidc"); - - public async Task CallApi() - { - var token = await HttpContext.GetTokenAsync("access_token"); + public IActionResult Logout() => SignOut("oidc"); + + public async Task CallApi() + { + var token = await HttpContext.GetTokenAsync("access_token"); - var client = _httpClientFactory.CreateClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - var response = await client.GetStringAsync(Urls.SampleApi + "identity"); - var json = JsonDocument.Parse(response); + var response = await client.GetStringAsync(Urls.SampleApi + "identity"); + var json = JsonDocument.Parse(response); - ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); - return View(); - } + ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); + return View(); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/LogoutController.cs b/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/LogoutController.cs index 36610e97..f70be743 100755 --- a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/LogoutController.cs +++ b/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Controllers/LogoutController.cs @@ -11,98 +11,97 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Client.Controllers +namespace Client.Controllers; + +public class LogoutController : Controller { - public class LogoutController : Controller + public LogoutSessionManager LogoutSessions { get; } + + public LogoutController(LogoutSessionManager logoutSessions) { - public LogoutSessionManager LogoutSessions { get; } + LogoutSessions = logoutSessions; + } - public LogoutController(LogoutSessionManager logoutSessions) - { - LogoutSessions = logoutSessions; - } + [HttpPost] + [AllowAnonymous] + public async Task Index(string logout_token) + { + Response.Headers.Add("Cache-Control", "no-cache, no-store"); + Response.Headers.Add("Pragma", "no-cache"); - [HttpPost] - [AllowAnonymous] - public async Task Index(string logout_token) + try { - Response.Headers.Add("Cache-Control", "no-cache, no-store"); - Response.Headers.Add("Pragma", "no-cache"); + var user = await ValidateLogoutToken(logout_token); - try - { - var user = await ValidateLogoutToken(logout_token); + // these are the sub & sid to signout + var sub = user.FindFirst("sub")?.Value; + var sid = user.FindFirst("sid")?.Value; - // these are the sub & sid to signout - var sub = user.FindFirst("sub")?.Value; - var sid = user.FindFirst("sid")?.Value; + LogoutSessions.Add(sub, sid); - LogoutSessions.Add(sub, sid); + return Ok(); + } + catch { } - return Ok(); - } - catch { } + return BadRequest(); + } - return BadRequest(); - } + private async Task ValidateLogoutToken(string logoutToken) + { + var claims = await ValidateJwt(logoutToken); - private async Task ValidateLogoutToken(string logoutToken) - { - var claims = await ValidateJwt(logoutToken); + if (claims.FindFirst("sub") == null && claims.FindFirst("sid") == null) throw new Exception("Invalid logout token"); - if (claims.FindFirst("sub") == null && claims.FindFirst("sid") == null) throw new Exception("Invalid logout token"); + var nonce = claims.FindFirstValue("nonce"); + if (!String.IsNullOrWhiteSpace(nonce)) throw new Exception("Invalid logout token"); - var nonce = claims.FindFirstValue("nonce"); - if (!String.IsNullOrWhiteSpace(nonce)) throw new Exception("Invalid logout token"); + var eventsJson = claims.FindFirst("events")?.Value; + if (String.IsNullOrWhiteSpace(eventsJson)) throw new Exception("Invalid logout token"); - var eventsJson = claims.FindFirst("events")?.Value; - if (String.IsNullOrWhiteSpace(eventsJson)) throw new Exception("Invalid logout token"); + var events = JsonSerializer.Deserialize>(eventsJson); + var logoutEvent = events.TryGetValue("http://schemas.openid.net/event/backchannel-logout", out _); + if (logoutEvent == false) throw new Exception("Invalid logout token"); - var events = JsonSerializer.Deserialize>(eventsJson); - var logoutEvent = events.TryGetValue("http://schemas.openid.net/event/backchannel-logout", out _); - if (logoutEvent == false) throw new Exception("Invalid logout token"); + return claims; + } - return claims; - } + private static async Task ValidateJwt(string jwt) + { + // read discovery document to find issuer and key material + var client = new HttpClient(); + var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); - private static async Task ValidateJwt(string jwt) + var keys = new List(); + foreach (var webKey in disco.KeySet.Keys) { - // read discovery document to find issuer and key material - var client = new HttpClient(); - var disco = await client.GetDiscoveryDocumentAsync(Urls.IdentityServer); - - var keys = new List(); - foreach (var webKey in disco.KeySet.Keys) - { - var key = new JsonWebKey() - { - Kty = webKey.Kty, - Alg = webKey.Alg, - Kid = webKey.Kid, - X = webKey.X, - Y = webKey.Y, - Crv = webKey.Crv, - E = webKey.E, - N = webKey.N, - }; - keys.Add(key); - } - - var parameters = new TokenValidationParameters + var key = new JsonWebKey() { - ValidIssuer = disco.Issuer, - ValidAudience = "mvc.backchannel.sample", - IssuerSigningKeys = keys, - - NameClaimType = JwtClaimTypes.Name, - RoleClaimType = JwtClaimTypes.Role + Kty = webKey.Kty, + Alg = webKey.Alg, + Kid = webKey.Kid, + X = webKey.X, + Y = webKey.Y, + Crv = webKey.Crv, + E = webKey.E, + N = webKey.N, }; + keys.Add(key); + } - var handler = new JwtSecurityTokenHandler(); - handler.InboundClaimTypeMap.Clear(); + var parameters = new TokenValidationParameters + { + ValidIssuer = disco.Issuer, + ValidAudience = "mvc.backchannel.sample", + IssuerSigningKeys = keys, - var user = handler.ValidateToken(jwt, parameters, out var _); - return user; - } + NameClaimType = JwtClaimTypes.Name, + RoleClaimType = JwtClaimTypes.Role + }; + + var handler = new JwtSecurityTokenHandler(); + handler.InboundClaimTypeMap.Clear(); + + var user = handler.ValidateToken(jwt, parameters, out var _); + return user; } } diff --git a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Program.cs b/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Program.cs index 8561404f..5e0a0f7d 100755 --- a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Program.cs +++ b/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Program.cs @@ -1,20 +1,73 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; -namespace Client +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllersWithViews(); +builder.Services.AddHttpClient(); + +// implements the cookie event handler +builder.Services.AddTransient(); + +// demo version of a state management to keep track of logout notifications +builder.Services.AddSingleton(); + +builder.Services.AddAuthentication(options => { - public class Program + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = "oidc"; +}) + .AddCookie(options => { - public static void Main(string[] args) + options.EventsType = typeof(CookieEventHandler); + }) + .AddOpenIdConnect("oidc", options => + { + options.Authority = Urls.IdentityServer; + options.RequireHttpsMetadata = false; + + options.ClientId = "mvc.backchannel.sample"; + options.ClientSecret = "secret"; + + options.ResponseType = "code"; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("scope1"); + options.Scope.Add("offline_access"); + + // not mapped by default + options.ClaimActions.MapJsonKey("website", "website"); + + // keeps id_token smaller + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.MapInboundClaims = false; + options.DisableTelemetry = true; + + options.TokenValidationParameters = new TokenValidationParameters { - 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.UseDeveloperExceptionPage(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapDefaultControllerRoute() + .RequireAuthorization(); + +app.Run(); diff --git a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Startup.cs b/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Startup.cs deleted file mode 100755 index 0b82f421..00000000 --- a/IdentityServer/v7/Basics/MvcBackChannelLogout/src/Startup.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; - -namespace Client -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - - services.AddControllersWithViews(); - services.AddHttpClient(); - - // implements the cookie event handler - services.AddTransient(); - - // demo version of a state management to keep track of logout notifications - services.AddSingleton(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = "oidc"; - }) - .AddCookie(options => - { - options.EventsType = typeof(CookieEventHandler); - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = Urls.IdentityServer; - options.RequireHttpsMetadata = false; - - options.ClientId = "mvc.backchannel.sample"; - options.ClientSecret = "secret"; - - options.ResponseType = "code"; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("scope1"); - options.Scope.Add("offline_access"); - - // not mapped by default - options.ClaimActions.MapJsonKey("website", "website"); - - // keeps id_token smaller - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - - options.TokenValidationParameters = new TokenValidationParameters - { - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute() - .RequireAuthorization(); - }); - } - } -} \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionConfigurationService.cs b/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionConfigurationService.cs index 4e6c7007..942c44a1 100755 --- a/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionConfigurationService.cs +++ b/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionConfigurationService.cs @@ -3,26 +3,25 @@ using IdentityModel.Client; using Duende.AccessTokenManagement; -namespace Client +namespace Client; + +public class ClientAssertionService : IClientAssertionService { - public class ClientAssertionService : IClientAssertionService - { - private readonly AssertionService _assertionService; + private readonly AssertionService _assertionService; - public ClientAssertionService(AssertionService assertionService) - { - _assertionService = assertionService; - } + public ClientAssertionService(AssertionService assertionService) + { + _assertionService = assertionService; + } - public Task GetClientAssertionAsync(string clientName = null, TokenRequestParameters parameters = null) + public Task GetClientAssertionAsync(string clientName = null, TokenRequestParameters parameters = null) + { + var assertion = new ClientAssertion { - var assertion = new ClientAssertion - { - Type = OidcConstants.ClientAssertionTypes.JwtBearer, - Value = _assertionService.CreateClientToken() - }; + Type = OidcConstants.ClientAssertionTypes.JwtBearer, + Value = _assertionService.CreateClientToken() + }; - return Task.FromResult(assertion); - } + return Task.FromResult(assertion); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionService.cs b/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionService.cs index 21cf020c..a8d1fa50 100755 --- a/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionService.cs +++ b/IdentityServer/v7/Basics/MvcJarJwt/src/AssertionService.cs @@ -7,72 +7,71 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; -namespace Client +namespace Client; + +public class AssertionService { - public class AssertionService + private readonly IConfiguration _configuration; + + public AssertionService(IConfiguration configuration) { - private readonly IConfiguration _configuration; + _configuration = configuration; + } - public AssertionService(IConfiguration configuration) - { - _configuration = configuration; - } + public string CreateClientToken() + { + var now = DateTime.UtcNow; + var clientId = _configuration.GetValue("ClientId"); - public string CreateClientToken() - { - var now = DateTime.UtcNow; - var clientId = _configuration.GetValue("ClientId"); - - // in production you should load that key from some secure location - var key = _configuration.GetValue("Secrets:Key"); - - var token = new JwtSecurityToken( - clientId, - Urls.IdentityServer + "/connect/token", - new List() - { - new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()), - new Claim(JwtClaimTypes.Subject, clientId), - new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64) - }, - now, - now.AddMinutes(1), - new SigningCredentials(new JsonWebKey(key), "RS256") - ); + // in production you should load that key from some secure location + var key = _configuration.GetValue("Secrets:Key"); - var tokenHandler = new JwtSecurityTokenHandler(); - tokenHandler.OutboundClaimTypeMap.Clear(); - - return tokenHandler.WriteToken(token); - } + var token = new JwtSecurityToken( + clientId, + Urls.IdentityServer + "/connect/token", + new List() + { + new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()), + new Claim(JwtClaimTypes.Subject, clientId), + new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64) + }, + now, + now.AddMinutes(1), + new SigningCredentials(new JsonWebKey(key), "RS256") + ); - public string SignAuthorizationRequest(OpenIdConnectMessage message) - { - var now = DateTime.UtcNow; - var clientId = _configuration.GetValue("ClientId"); - - // in production you should load that key from some secure location - var key = _configuration.GetValue("Secrets:Key"); + var tokenHandler = new JwtSecurityTokenHandler(); + tokenHandler.OutboundClaimTypeMap.Clear(); + + return tokenHandler.WriteToken(token); + } - var claims = new List(); - foreach (var parameter in message.Parameters) - { - claims.Add(new Claim(parameter.Key, parameter.Value)); - } + public string SignAuthorizationRequest(OpenIdConnectMessage message) + { + var now = DateTime.UtcNow; + var clientId = _configuration.GetValue("ClientId"); + + // in production you should load that key from some secure location + var key = _configuration.GetValue("Secrets:Key"); - var token = new JwtSecurityToken( - clientId, - Urls.IdentityServer, - claims, - now, - now.AddMinutes(1), - new SigningCredentials(new JsonWebKey(key), "RS256") - ); - - var tokenHandler = new JwtSecurityTokenHandler(); - tokenHandler.OutboundClaimTypeMap.Clear(); - - return tokenHandler.WriteToken(token); + var claims = new List(); + foreach (var parameter in message.Parameters) + { + claims.Add(new Claim(parameter.Key, parameter.Value)); } + + var token = new JwtSecurityToken( + clientId, + Urls.IdentityServer, + claims, + now, + now.AddMinutes(1), + new SigningCredentials(new JsonWebKey(key), "RS256") + ); + + var tokenHandler = new JwtSecurityTokenHandler(); + tokenHandler.OutboundClaimTypeMap.Clear(); + + return tokenHandler.WriteToken(token); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcJarJwt/src/Controllers/HomeController.cs b/IdentityServer/v7/Basics/MvcJarJwt/src/Controllers/HomeController.cs index de8ff4e7..edcd2d30 100755 --- a/IdentityServer/v7/Basics/MvcJarJwt/src/Controllers/HomeController.cs +++ b/IdentityServer/v7/Basics/MvcJarJwt/src/Controllers/HomeController.cs @@ -4,35 +4,34 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Client.Controllers +namespace Client.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + private readonly IHttpClientFactory _httpClientFactory; + + public HomeController(IHttpClientFactory httpClientFactory) { - private readonly IHttpClientFactory _httpClientFactory; - - public HomeController(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } - - [AllowAnonymous] - public IActionResult Index() => View(); + _httpClientFactory = httpClientFactory; + } - public IActionResult Secure() => View(); + [AllowAnonymous] + public IActionResult Index() => View(); + + public IActionResult Secure() => View(); + + public IActionResult Logout() => SignOut("oidc"); + + public async Task CallApi() + { + // retrieve client with token management from HTTP client factory + // repeat the API call to see that token a requested automatically (e.g. the iat and exp values slide) + var client = _httpClientFactory.CreateClient("client"); + var response = await client.GetStringAsync("identity"); - public IActionResult Logout() => SignOut("oidc"); + var json = JsonDocument.Parse(response); + ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); - public async Task CallApi() - { - // retrieve client with token management from HTTP client factory - // repeat the API call to see that token a requested automatically (e.g. the iat and exp values slide) - var client = _httpClientFactory.CreateClient("client"); - var response = await client.GetStringAsync("identity"); - - var json = JsonDocument.Parse(response); - ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); - - return View(); - } + return View(); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcJarJwt/src/OidcEvents.cs b/IdentityServer/v7/Basics/MvcJarJwt/src/OidcEvents.cs index ab1b7b19..42b295f1 100755 --- a/IdentityServer/v7/Basics/MvcJarJwt/src/OidcEvents.cs +++ b/IdentityServer/v7/Basics/MvcJarJwt/src/OidcEvents.cs @@ -2,37 +2,36 @@ using IdentityModel; using Microsoft.AspNetCore.Authentication.OpenIdConnect; -namespace Client +namespace Client; + +public class OidcEvents : OpenIdConnectEvents { - public class OidcEvents : OpenIdConnectEvents + private readonly AssertionService _assertionService; + + public OidcEvents(AssertionService assertionService) { - private readonly AssertionService _assertionService; + _assertionService = assertionService; + } - public OidcEvents(AssertionService assertionService) - { - _assertionService = assertionService; - } - - public override Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context) - { - context.TokenEndpointRequest.ClientAssertionType = OidcConstants.ClientAssertionTypes.JwtBearer; - context.TokenEndpointRequest.ClientAssertion = _assertionService.CreateClientToken(); + public override Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + { + context.TokenEndpointRequest.ClientAssertionType = OidcConstants.ClientAssertionTypes.JwtBearer; + context.TokenEndpointRequest.ClientAssertion = _assertionService.CreateClientToken(); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - public override Task RedirectToIdentityProvider(RedirectContext context) - { - var request = _assertionService.SignAuthorizationRequest(context.ProtocolMessage); - var clientId = context.ProtocolMessage.ClientId; - var redirectUri = context.ProtocolMessage.RedirectUri; - - context.ProtocolMessage.Parameters.Clear(); - context.ProtocolMessage.ClientId = clientId; - context.ProtocolMessage.RedirectUri = redirectUri; - context.ProtocolMessage.SetParameter("request", request); + public override Task RedirectToIdentityProvider(RedirectContext context) + { + var request = _assertionService.SignAuthorizationRequest(context.ProtocolMessage); + var clientId = context.ProtocolMessage.ClientId; + var redirectUri = context.ProtocolMessage.RedirectUri; + + context.ProtocolMessage.Parameters.Clear(); + context.ProtocolMessage.ClientId = clientId; + context.ProtocolMessage.RedirectUri = redirectUri; + context.ProtocolMessage.SetParameter("request", request); - return Task.CompletedTask; - } + return Task.CompletedTask; } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcJarJwt/src/Program.cs b/IdentityServer/v7/Basics/MvcJarJwt/src/Program.cs index 8561404f..c66700b7 100755 --- a/IdentityServer/v7/Basics/MvcJarJwt/src/Program.cs +++ b/IdentityServer/v7/Basics/MvcJarJwt/src/Program.cs @@ -1,20 +1,103 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Client; +using Duende.AccessTokenManagement; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System; +using System.IdentityModel.Tokens.Jwt; -namespace Client +var builder = WebApplication.CreateBuilder(args); + +JwtSecurityTokenHandler.DefaultMapInboundClaims = false; + +builder.Services.AddControllersWithViews(); +builder.Services.AddHttpClient(); + +builder.Services.AddAuthentication(options => { - public class Program + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = "oidc"; +}) + .AddCookie(options => { - public static void Main(string[] args) + options.Cookie.Name = "mvc"; + + options.Events.OnSigningOut = async e => + { + // automatically revoke refresh token at signout time + await e.HttpContext.RevokeRefreshTokenAsync(); + }; + }) + .AddOpenIdConnect("oidc", options => + { + options.Authority = Urls.IdentityServer; + + // no static client secret + // the secret id created dynamically + options.ClientId = builder.Configuration["ClientId"]; + + // needed to add JWR / private_key_jwt support + options.EventsType = typeof(OidcEvents); + + // code flow + PKCE (PKCE is turned on by default) + options.ResponseType = "code"; + options.UsePkce = true; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("scope1"); + options.Scope.Add("offline_access"); + + // not mapped by default + options.ClaimActions.MapJsonKey("website", "website"); + + // keeps id_token smaller + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.MapInboundClaims = false; + options.DisableTelemetry = true; + + options.TokenValidationParameters = new TokenValidationParameters { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + +// add service to create JWTs +builder.Services.AddSingleton(); + +// add event handler for OIDC events +builder.Services.AddTransient(); + +// add automatic token management +builder.Services.AddOpenIdConnectAccessTokenManagement(); + +// add service to create assertions for token management +builder.Services.AddTransient(); + +// add HTTP client to call protected API +builder.Services.AddUserAccessTokenHttpClient("client", configureClient: client => +{ + client.BaseAddress = new Uri(Urls.SampleApi); +}); + +var app = builder.Build(); + +app.UseDeveloperExceptionPage(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapDefaultControllerRoute() + .RequireAuthorization(); + +app.Run(); + + diff --git a/IdentityServer/v7/Basics/MvcJarJwt/src/Startup.cs b/IdentityServer/v7/Basics/MvcJarJwt/src/Startup.cs deleted file mode 100755 index bb0b18d3..00000000 --- a/IdentityServer/v7/Basics/MvcJarJwt/src/Startup.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; -using Microsoft.Extensions.Configuration; -using Duende.AccessTokenManagement; - -namespace Client -{ - public class Startup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - - services.AddControllersWithViews(); - services.AddHttpClient(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = "oidc"; - }) - .AddCookie(options => - { - options.Cookie.Name = "mvc"; - - options.Events.OnSigningOut = async e => - { - // automatically revoke refresh token at signout time - await e.HttpContext.RevokeRefreshTokenAsync(); - }; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = Urls.IdentityServer; - - // no static client secret - // the secret id created dynamically - options.ClientId = _configuration.GetValue("ClientId"); - - // needed to add JWR / private_key_jwt support - options.EventsType = typeof(OidcEvents); - - // code flow + PKCE (PKCE is turned on by default) - options.ResponseType = "code"; - options.UsePkce = true; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("scope1"); - options.Scope.Add("offline_access"); - - // not mapped by default - options.ClaimActions.MapJsonKey("website", "website"); - - // keeps id_token smaller - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - - options.TokenValidationParameters = new TokenValidationParameters - { - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - - // add service to create JWTs - services.AddSingleton(); - - // add event handler for OIDC events - services.AddTransient(); - - // add automatic token management - services.AddOpenIdConnectAccessTokenManagement(); - - // add service to create assertions for token management - services.AddTransient(); - - // add HTTP client to call protected API - services.AddUserAccessTokenHttpClient("client", configureClient: client => - { - client.BaseAddress = new Uri(Urls.SampleApi); - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute() - .RequireAuthorization(); - }); - } - } -} \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcPar/src/Controllers/HomeController.cs b/IdentityServer/v7/Basics/MvcPar/src/Controllers/HomeController.cs index 0c0d3817..40d88413 100644 --- a/IdentityServer/v7/Basics/MvcPar/src/Controllers/HomeController.cs +++ b/IdentityServer/v7/Basics/MvcPar/src/Controllers/HomeController.cs @@ -4,33 +4,32 @@ using System.Threading.Tasks; using System.Text.Json; -namespace Client.Controllers +namespace Client.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller - { - private readonly IHttpClientFactory _httpClientFactory; + private readonly IHttpClientFactory _httpClientFactory; - public HomeController(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } + public HomeController(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } - [AllowAnonymous] - public IActionResult Index() => View(); + [AllowAnonymous] + public IActionResult Index() => View(); - public IActionResult Secure() => View(); + public IActionResult Secure() => View(); - public IActionResult Logout() => SignOut("oidc", "cookie"); + public IActionResult Logout() => SignOut("oidc", "cookie"); - public async Task CallApi() - { - var client = _httpClientFactory.CreateClient("client"); + public async Task CallApi() + { + var client = _httpClientFactory.CreateClient("client"); - var response = await client.GetStringAsync("identity"); - var json = JsonDocument.Parse(response); + var response = await client.GetStringAsync("identity"); + var json = JsonDocument.Parse(response); - ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); - return View(); - } + ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); + return View(); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcPar/src/ParOidcEvents.cs b/IdentityServer/v7/Basics/MvcPar/src/ParOidcEvents.cs index f12ed2fa..e1c70943 100644 --- a/IdentityServer/v7/Basics/MvcPar/src/ParOidcEvents.cs +++ b/IdentityServer/v7/Basics/MvcPar/src/ParOidcEvents.cs @@ -8,127 +8,126 @@ using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -namespace Client +namespace Client; + +public class ParOidcEvents(HttpClient httpClient, IDiscoveryCache discoveryCache, ILogger logger) : OpenIdConnectEvents { - public class ParOidcEvents(HttpClient httpClient, IDiscoveryCache discoveryCache, ILogger logger) : OpenIdConnectEvents + private readonly HttpClient _httpClient = httpClient; + private readonly IDiscoveryCache _discoveryCache = discoveryCache; + private readonly ILogger _logger = logger; + + public override async Task RedirectToIdentityProvider(RedirectContext context) { - private readonly HttpClient _httpClient = httpClient; - private readonly IDiscoveryCache _discoveryCache = discoveryCache; - private readonly ILogger _logger = logger; - - public override async Task RedirectToIdentityProvider(RedirectContext context) - { - var clientId = context.ProtocolMessage.ClientId; + var clientId = context.ProtocolMessage.ClientId; - // Construct the state parameter and add it to the protocol message - // so that we include it in the pushed authorization request - SetStateParameterForParRequest(context); + // Construct the state parameter and add it to the protocol message + // so that we include it in the pushed authorization request + SetStateParameterForParRequest(context); - // Make the actual pushed authorization request - var parResponse = await PushAuthorizationParameters(context, clientId); + // Make the actual pushed authorization request + var parResponse = await PushAuthorizationParameters(context, clientId); - // Now replace the parameters that would normally be sent to the - // authorize endpoint with just the client id and PAR request uri. - SetAuthorizeParameters(context, clientId, parResponse); + // Now replace the parameters that would normally be sent to the + // authorize endpoint with just the client id and PAR request uri. + SetAuthorizeParameters(context, clientId, parResponse); - // Mark the request as handled, because we don't want the normal - // behavior that attaches state to the outgoing request (we already - // did that in the PAR request). - context.HandleResponse(); + // Mark the request as handled, because we don't want the normal + // behavior that attaches state to the outgoing request (we already + // did that in the PAR request). + context.HandleResponse(); - // Finally redirect to the authorize endpoint - await RedirectToAuthorizeEndpoint(context, context.ProtocolMessage); + // Finally redirect to the authorize endpoint + await RedirectToAuthorizeEndpoint(context, context.ProtocolMessage); + } + + private const string HeaderValueEpocDate = "Thu, 01 Jan 1970 00:00:00 GMT"; + private async Task RedirectToAuthorizeEndpoint(RedirectContext context, OpenIdConnectMessage message) + { + // This code is copied from the ASP.NET handler. We want most of its + // default behavior related to redirecting to the identity provider, + // except we already pushed the state parameter, so that is left out + // here. See https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L364 + if (string.IsNullOrEmpty(message.IssuerAddress)) + { + throw new InvalidOperationException( + "Cannot redirect to the authorization endpoint, the configuration may be missing or invalid."); } - private const string HeaderValueEpocDate = "Thu, 01 Jan 1970 00:00:00 GMT"; - private async Task RedirectToAuthorizeEndpoint(RedirectContext context, OpenIdConnectMessage message) + if (context.Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet) { - // This code is copied from the ASP.NET handler. We want most of its - // default behavior related to redirecting to the identity provider, - // except we already pushed the state parameter, so that is left out - // here. See https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L364 - if (string.IsNullOrEmpty(message.IssuerAddress)) + var redirectUri = message.CreateAuthenticationRequestUrl(); + if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) { - throw new InvalidOperationException( - "Cannot redirect to the authorization endpoint, the configuration may be missing or invalid."); + _logger.LogWarning("The redirect URI is not well-formed. The URI is: '{AuthenticationRequestUrl}'.", redirectUri); } - if (context.Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet) - { - var redirectUri = message.CreateAuthenticationRequestUrl(); - if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) - { - _logger.LogWarning("The redirect URI is not well-formed. The URI is: '{AuthenticationRequestUrl}'.", redirectUri); - } - - context.Response.Redirect(redirectUri); - return; - } - else if (context.Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost) - { - var content = message.BuildFormPost(); - var buffer = Encoding.UTF8.GetBytes(content); - - context.Response.ContentLength = buffer.Length; - context.Response.ContentType = "text/html;charset=UTF-8"; + context.Response.Redirect(redirectUri); + return; + } + else if (context.Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.FormPost) + { + var content = message.BuildFormPost(); + var buffer = Encoding.UTF8.GetBytes(content); - // Emit Cache-Control=no-cache to prevent client caching. - context.Response.Headers.CacheControl = "no-cache, no-store"; - context.Response.Headers.Pragma = "no-cache"; - context.Response.Headers.Expires = HeaderValueEpocDate; + context.Response.ContentLength = buffer.Length; + context.Response.ContentType = "text/html;charset=UTF-8"; - await context.Response.Body.WriteAsync(buffer); - return; - } + // Emit Cache-Control=no-cache to prevent client caching. + context.Response.Headers.CacheControl = "no-cache, no-store"; + context.Response.Headers.Pragma = "no-cache"; + context.Response.Headers.Expires = HeaderValueEpocDate; - throw new NotImplementedException($"An unsupported authentication method has been configured: {context.Options.AuthenticationMethod}"); + await context.Response.Body.WriteAsync(buffer); + return; } - private async Task PushAuthorizationParameters(RedirectContext context, string clientId) - { - var disco = await _discoveryCache.GetAsync(); - if (disco.IsError) - { - throw new Exception(disco.Error); - } - var par = new PushedAuthorizationRequest - { - Address = disco.PushedAuthorizationRequestEndpoint, - ClientId = "mvc.par", // This has to be set here, even though it is already in the Parameters collection. We use this property to set the auth header - ClientSecret = "secret", - Parameters = new Parameters(context.ProtocolMessage.Parameters.Where(p => p.Key != "client_id")), - }; - var response = await _httpClient.PushAuthorizationAsync(par); - - if (response.IsError ) - { - throw new Exception("PAR failure", response.Exception); - } - return response; - } + throw new NotImplementedException($"An unsupported authentication method has been configured: {context.Options.AuthenticationMethod}"); + } - private static void SetAuthorizeParameters(RedirectContext context, string clientId, PushedAuthorizationResponse parResponse) + private async Task PushAuthorizationParameters(RedirectContext context, string clientId) + { + var disco = await _discoveryCache.GetAsync(); + if (disco.IsError) { - // Remove all the parameters from the protocol message, and replace with what we got from the PAR response - context.ProtocolMessage.Parameters.Clear(); - // Then, set client id and request uri as parameters - context.ProtocolMessage.ClientId = clientId; - context.ProtocolMessage.RequestUri = parResponse.RequestUri; + throw new Exception(disco.Error); } - - private static OpenIdConnectMessage SetStateParameterForParRequest(RedirectContext context) + var par = new PushedAuthorizationRequest { - // Construct State, we also need that (this chunk copied from the OIDC handler) - var message = context.ProtocolMessage; - // When redeeming a code for an AccessToken, this value is needed - context.Properties.Items.Add(OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, message.RedirectUri); - message.State = context.Options.StateDataFormat.Protect(context.Properties); - return message; - } - - public override Task TokenResponseReceived(TokenResponseReceivedContext context) + Address = disco.PushedAuthorizationRequestEndpoint, + ClientId = "mvc.par", // This has to be set here, even though it is already in the Parameters collection. We use this property to set the auth header + ClientSecret = "secret", + Parameters = new Parameters(context.ProtocolMessage.Parameters.Where(p => p.Key != "client_id")), + }; + var response = await _httpClient.PushAuthorizationAsync(par); + + if (response.IsError ) { - return base.TokenResponseReceived(context); + throw new Exception("PAR failure", response.Exception); } + return response; + } + + private static void SetAuthorizeParameters(RedirectContext context, string clientId, PushedAuthorizationResponse parResponse) + { + // Remove all the parameters from the protocol message, and replace with what we got from the PAR response + context.ProtocolMessage.Parameters.Clear(); + // Then, set client id and request uri as parameters + context.ProtocolMessage.ClientId = clientId; + context.ProtocolMessage.RequestUri = parResponse.RequestUri; + } + + private static OpenIdConnectMessage SetStateParameterForParRequest(RedirectContext context) + { + // Construct State, we also need that (this chunk copied from the OIDC handler) + var message = context.ProtocolMessage; + // When redeeming a code for an AccessToken, this value is needed + context.Properties.Items.Add(OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, message.RedirectUri); + message.State = context.Options.StateDataFormat.Protect(context.Properties); + return message; + } + + public override Task TokenResponseReceived(TokenResponseReceivedContext context) + { + return base.TokenResponseReceived(context); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcPar/src/Program.cs b/IdentityServer/v7/Basics/MvcPar/src/Program.cs index 057475b5..0f32c63e 100644 --- a/IdentityServer/v7/Basics/MvcPar/src/Program.cs +++ b/IdentityServer/v7/Basics/MvcPar/src/Program.cs @@ -1,46 +1,104 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Client; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; using Serilog; -using Serilog.Events; using System; -namespace Client +Console.Title = "MvcPar"; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") + .CreateLogger(); + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSerilog(); +builder.Services.AddTransient(); +builder.Services.AddSingleton(_ => new DiscoveryCache(Urls.IdentityServer)); + +// add MVC +builder.Services.AddControllersWithViews(); + +// add cookie-based session management with OpenID Connect authentication +builder.Services.AddAuthentication(options => { - public class Program + options.DefaultScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; +}) + .AddCookie("cookie", options => { - public static int Main(string[] args) + options.Cookie.Name = "mvc.par"; + + options.ExpireTimeSpan = TimeSpan.FromHours(8); + options.SlidingExpiration = false; + + options.Events.OnSigningOut = async e => { - Console.Title = "MvcPar"; - - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}") - .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) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseSerilog(); - } -} + // automatically revoke refresh token at signout time + await e.HttpContext.RevokeRefreshTokenAsync(); + }; + }) + .AddOpenIdConnect("oidc", options => + { + options.Authority = Urls.IdentityServer; + + options.ClientId = "mvc.par"; + options.ClientSecret = "secret"; + + // code flow + PKCE (PKCE is turned on by default) + options.ResponseType = "code"; + options.UsePkce = true; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("scope1"); + options.Scope.Add("offline_access"); + + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.MapInboundClaims = false; + + // needed to add PAR support + options.EventsType = typeof(ParOidcEvents); + + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "name", + RoleClaimType = "role" + }; + + options.DisableTelemetry = true; + }); + +// add automatic token management +builder.Services.AddOpenIdConnectAccessTokenManagement(); + +// add HTTP client to call protected API +builder.Services.AddUserAccessTokenHttpClient("client", configureClient: client => +{ + client.BaseAddress = new Uri(Urls.SampleApi); +}); + +var app = builder.Build(); + +app.UseDeveloperExceptionPage(); +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapDefaultControllerRoute() + .RequireAuthorization(); + +app.Run(); + + diff --git a/IdentityServer/v7/Basics/MvcPar/src/Startup.cs b/IdentityServer/v7/Basics/MvcPar/src/Startup.cs deleted file mode 100644 index fbcda2e5..00000000 --- a/IdentityServer/v7/Basics/MvcPar/src/Startup.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using System; -using Microsoft.Extensions.Configuration; -using IdentityModel.Client; - -namespace Client; - -public class Startup -{ - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddTransient(); - services.AddSingleton(_ => new DiscoveryCache(Urls.IdentityServer)); - - // add MVC - services.AddControllersWithViews(); - - // add cookie-based session management with OpenID Connect authentication - services.AddAuthentication(options => - { - options.DefaultScheme = "cookie"; - options.DefaultChallengeScheme = "oidc"; - }) - .AddCookie("cookie", options => - { - options.Cookie.Name = "mvc.par"; - - options.ExpireTimeSpan = TimeSpan.FromHours(8); - options.SlidingExpiration = false; - - options.Events.OnSigningOut = async e => - { - // automatically revoke refresh token at signout time - await e.HttpContext.RevokeRefreshTokenAsync(); - }; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = Urls.IdentityServer; - - options.ClientId = "mvc.par"; - options.ClientSecret = "secret"; - - // code flow + PKCE (PKCE is turned on by default) - options.ResponseType = "code"; - options.UsePkce = true; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("scope1"); - options.Scope.Add("offline_access"); - - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - options.MapInboundClaims = false; - - // needed to add PAR support - options.EventsType = typeof(ParOidcEvents); - - options.TokenValidationParameters = new TokenValidationParameters - { - NameClaimType = "name", - RoleClaimType = "role" - }; - - options.DisableTelemetry = true; - }); - - // add automatic token management - services.AddOpenIdConnectAccessTokenManagement(); - - // add HTTP client to call protected API - services.AddUserAccessTokenHttpClient("client", configureClient: client => - { - client.BaseAddress = new Uri(Urls.SampleApi); - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute() - .RequireAuthorization(); - }); - } -} \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcTokenManagement/src/Controllers/HomeController.cs b/IdentityServer/v7/Basics/MvcTokenManagement/src/Controllers/HomeController.cs index 4a0f4cd0..30570881 100755 --- a/IdentityServer/v7/Basics/MvcTokenManagement/src/Controllers/HomeController.cs +++ b/IdentityServer/v7/Basics/MvcTokenManagement/src/Controllers/HomeController.cs @@ -1,40 +1,37 @@ using System.Net.Http; -using System.Net.Http.Headers; using System.Text.Json; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Client.Controllers +namespace Client.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + private readonly IHttpClientFactory _httpClientFactory; + + public HomeController(IHttpClientFactory httpClientFactory) { - private readonly IHttpClientFactory _httpClientFactory; - - public HomeController(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } - - [AllowAnonymous] - public IActionResult Index() => View(); + _httpClientFactory = httpClientFactory; + } + + [AllowAnonymous] + public IActionResult Index() => View(); - public IActionResult Secure() => View(); + public IActionResult Secure() => View(); - public IActionResult Logout() => SignOut("oidc"); - - public async Task CallApi() - { - // retrieve client with token management from HTTP client factory - // repeat the API call to see that token a requested automatically (e.g. the iat and exp values slide) - var client = _httpClientFactory.CreateClient("client"); - var response = await client.GetStringAsync("identity"); + public IActionResult Logout() => SignOut("oidc"); + + public async Task CallApi() + { + // retrieve client with token management from HTTP client factory + // repeat the API call to see that token a requested automatically (e.g. the iat and exp values slide) + var client = _httpClientFactory.CreateClient("client"); + var response = await client.GetStringAsync("identity"); - var json = JsonDocument.Parse(response); - ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); - - return View(); - } + var json = JsonDocument.Parse(response); + ViewBag.Json = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }); + + return View(); } } \ No newline at end of file diff --git a/IdentityServer/v7/Basics/MvcTokenManagement/src/Program.cs b/IdentityServer/v7/Basics/MvcTokenManagement/src/Program.cs index 8561404f..d5cc0c54 100755 --- a/IdentityServer/v7/Basics/MvcTokenManagement/src/Program.cs +++ b/IdentityServer/v7/Basics/MvcTokenManagement/src/Program.cs @@ -1,20 +1,85 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System; -namespace Client +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllersWithViews(); +builder.Services.AddHttpClient(); + +builder.Services.AddAuthentication(options => { - public class Program + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = "oidc"; +}) + .AddCookie(options => + { + options.Cookie.Name = "mvc"; + + options.Events.OnSigningOut = async e => + { + // automatically revoke refresh token at signout time + await e.HttpContext.RevokeRefreshTokenAsync(); + }; + }) + .AddOpenIdConnect("oidc", options => { - public static void Main(string[] args) + options.Authority = Urls.IdentityServer; + options.RequireHttpsMetadata = false; + + options.ClientId = "interactive.mvc.sample.short.token.lifetime"; + options.ClientSecret = "secret"; + + // code flow + PKCE (PKCE is turned on by default) + options.ResponseType = "code"; + options.UsePkce = true; + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("profile"); + options.Scope.Add("scope1"); + options.Scope.Add("offline_access"); + + // not mapped by default + options.ClaimActions.MapJsonKey("website", "website"); + + // keeps id_token smaller + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.MapInboundClaims = false; + options.DisableTelemetry = true; + + options.TokenValidationParameters = new TokenValidationParameters { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + +// add automatic token management +builder.Services.AddOpenIdConnectAccessTokenManagement(); + +// add HTTP client to call protected API +builder.Services.AddUserAccessTokenHttpClient("client", configureClient: client => +{ + client.BaseAddress = new Uri(Urls.SampleApi); +}); + +var app = builder.Build(); + +app.UseDeveloperExceptionPage(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapDefaultControllerRoute() + .RequireAuthorization(); + +app.Run(); diff --git a/IdentityServer/v7/Basics/MvcTokenManagement/src/Startup.cs b/IdentityServer/v7/Basics/MvcTokenManagement/src/Startup.cs deleted file mode 100755 index 31e7f902..00000000 --- a/IdentityServer/v7/Basics/MvcTokenManagement/src/Startup.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; - -namespace Client -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - - services.AddControllersWithViews(); - services.AddHttpClient(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = "oidc"; - }) - .AddCookie(options => - { - options.Cookie.Name = "mvc"; - - options.Events.OnSigningOut = async e => - { - // automatically revoke refresh token at signout time - await e.HttpContext.RevokeRefreshTokenAsync(); - }; - }) - .AddOpenIdConnect("oidc", options => - { - options.Authority = Urls.IdentityServer; - options.RequireHttpsMetadata = false; - - options.ClientId = "interactive.mvc.sample.short.token.lifetime"; - options.ClientSecret = "secret"; - - // code flow + PKCE (PKCE is turned on by default) - options.ResponseType = "code"; - options.UsePkce = true; - - options.Scope.Clear(); - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("scope1"); - options.Scope.Add("offline_access"); - - // not mapped by default - options.ClaimActions.MapJsonKey("website", "website"); - - // keeps id_token smaller - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - - options.TokenValidationParameters = new TokenValidationParameters - { - NameClaimType = "name", - RoleClaimType = "role" - }; - }); - - // add automatic token management - services.AddOpenIdConnectAccessTokenManagement(); - - // add HTTP client to call protected API - services.AddUserAccessTokenHttpClient("client", configureClient: client => - { - client.BaseAddress = new Uri(Urls.SampleApi); - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute() - .RequireAuthorization(); - }); - } - } -} \ No newline at end of file diff --git a/IdentityServer/v7/Basics/Shared/TokenResponseExtensions.cs b/IdentityServer/v7/Basics/Shared/TokenResponseExtensions.cs index 4e9c844d..f4232390 100755 --- a/IdentityServer/v7/Basics/Shared/TokenResponseExtensions.cs +++ b/IdentityServer/v7/Basics/Shared/TokenResponseExtensions.cs @@ -5,97 +5,77 @@ using System.Text; using System.Text.Json; -namespace Client +namespace Client; + +public static class TokenResponseExtensions { - public static class TokenResponseExtensions + public static void Show(this TokenResponse response) { - public static void Show(this TokenResponse response) + if (!response.IsError) { - if (!response.IsError) - { - "Token response:".ConsoleGreen(); - Console.WriteLine(response.Json); + "Token response:".ConsoleGreen(); + Console.WriteLine(response.Json); - if (response.AccessToken.Contains(".")) - { - "\nAccess Token (decoded):".ConsoleGreen(); + if (response.AccessToken.Contains(".")) + { + "\nAccess Token (decoded):".ConsoleGreen(); - var parts = response.AccessToken.Split('.'); - var header = parts[0]; - var claims = parts[1]; + var parts = response.AccessToken.Split('.'); + var header = parts[0]; + var claims = parts[1]; - Console.WriteLine(PrettyPrintJson(Encoding.UTF8.GetString(Base64Url.Decode(header)))); - Console.WriteLine(PrettyPrintJson(Encoding.UTF8.GetString(Base64Url.Decode(claims)))); - } + Console.WriteLine(PrettyPrintJson(Encoding.UTF8.GetString(Base64Url.Decode(header)))); + Console.WriteLine(PrettyPrintJson(Encoding.UTF8.GetString(Base64Url.Decode(claims)))); + } + } + else + { + if (response.ErrorType == ResponseErrorType.Http) + { + "HTTP error: ".ConsoleGreen(); + Console.WriteLine(response.Error); + "HTTP status code: ".ConsoleGreen(); + Console.WriteLine(response.HttpStatusCode); } else { - if (response.ErrorType == ResponseErrorType.Http) - { - "HTTP error: ".ConsoleGreen(); - Console.WriteLine(response.Error); - "HTTP status code: ".ConsoleGreen(); - Console.WriteLine(response.HttpStatusCode); - } - else - { - "Protocol error response:".ConsoleGreen(); - Console.WriteLine(response.Raw); - } + "Protocol error response:".ConsoleGreen(); + Console.WriteLine(response.Raw); } } - - public static string PrettyPrintJson(this string raw) - { - var doc = JsonDocument.Parse(raw).RootElement; - return JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }); - } } - - public static class ConsoleExtensions + public static string PrettyPrintJson(this string raw) { - /// - /// Writes green text to the console. - /// - /// The text. - [DebuggerStepThrough] - public static void ConsoleGreen(this string text) - { - text.ColoredWriteLine(ConsoleColor.Green); - } + var doc = JsonDocument.Parse(raw).RootElement; + return JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }); + } +} - /// - /// Writes red text to the console. - /// - /// The text. - [DebuggerStepThrough] - public static void ConsoleRed(this string text) - { - text.ColoredWriteLine(ConsoleColor.Red); - } - /// - /// Writes yellow text to the console. - /// - /// The text. - [DebuggerStepThrough] - public static void ConsoleYellow(this string text) - { - text.ColoredWriteLine(ConsoleColor.Yellow); - } +public static class ConsoleExtensions +{ + /// + /// Writes green text to the console. + /// + /// The text. + [DebuggerStepThrough] + public static void ConsoleGreen(this string text) + { + text.ColoredWriteLine(ConsoleColor.Green); + } - /// - /// Writes out text with the specified ConsoleColor. - /// - /// The text. - /// The color. - [DebuggerStepThrough] - public static void ColoredWriteLine(this string text, ConsoleColor color) - { - Console.ForegroundColor = color; - Console.WriteLine(text); - Console.ResetColor(); - } + + /// + /// Writes out text with the specified ConsoleColor. + /// + /// The text. + /// The color. + [DebuggerStepThrough] + public static void ColoredWriteLine(this string text, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.WriteLine(text); + Console.ResetColor(); } }