diff --git a/1-monolith-architecture-style/src/Api/src/appsettings.json b/1-monolith-architecture-style/src/Api/src/appsettings.json index 0ea70b1..fff2405 100644 --- a/1-monolith-architecture-style/src/Api/src/appsettings.json +++ b/1-monolith-architecture-style/src/Api/src/appsettings.json @@ -28,8 +28,7 @@ }, "Jwt": { "Authority": "https://localhost:4000", - "Audience": "booking-monolith", - "RequireHttpsMetadata": false + "Audience": "booking-monolith" }, "HealthOptions": { "Enabled": false diff --git a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Configurations/Config.cs b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Configurations/Config.cs index 09fefe6..3043823 100644 --- a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Configurations/Config.cs +++ b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Configurations/Config.cs @@ -1,6 +1,7 @@ using BookingMonolith.Identity.Identities.Constants; using Duende.IdentityServer; using Duende.IdentityServer.Models; +using IdentityModel; namespace BookingMonolith.Identity.Configurations; @@ -11,10 +12,7 @@ public static class Config { new IdentityResources.OpenId(), new IdentityResources.Profile(), - new IdentityResources.Email(), - new IdentityResources.Phone(), - new IdentityResources.Address(), - new(Constants.StandardScopes.Roles, new List {"role"}) + new IdentityResources.Email() }; @@ -25,20 +23,34 @@ public static class Config new(Constants.StandardScopes.PassengerApi), new(Constants.StandardScopes.BookingApi), new(Constants.StandardScopes.IdentityApi), - new(Constants.StandardScopes.BookingModularMonolith), new(Constants.StandardScopes.BookingMonolith), + new(JwtClaimTypes.Role, new List {"role"}) }; public static IList ApiResources => new List { - new(Constants.StandardScopes.FlightApi), - new(Constants.StandardScopes.PassengerApi), - new(Constants.StandardScopes.BookingApi), - new(Constants.StandardScopes.IdentityApi), - new(Constants.StandardScopes.BookingModularMonolith), - new(Constants.StandardScopes.BookingMonolith), + new(Constants.StandardScopes.FlightApi) + { + Scopes = { Constants.StandardScopes.FlightApi } + }, + new(Constants.StandardScopes.PassengerApi) + { + Scopes = { Constants.StandardScopes.PassengerApi } + }, + new(Constants.StandardScopes.BookingApi) + { + Scopes = { Constants.StandardScopes.BookingApi } + }, + new(Constants.StandardScopes.IdentityApi) + { + Scopes = { Constants.StandardScopes.IdentityApi } + }, + new(Constants.StandardScopes.BookingMonolith) + { + Scopes = { Constants.StandardScopes.BookingMonolith } + }, }; public static IEnumerable Clients => @@ -56,15 +68,16 @@ public static class Config { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, + JwtClaimTypes.Role, // Include roles scope Constants.StandardScopes.FlightApi, Constants.StandardScopes.PassengerApi, Constants.StandardScopes.BookingApi, Constants.StandardScopes.IdentityApi, - Constants.StandardScopes.BookingModularMonolith, Constants.StandardScopes.BookingMonolith, }, AccessTokenLifetime = 3600, // authorize the client to access protected resources - IdentityTokenLifetime = 3600 // authenticate the user + IdentityTokenLifetime = 3600, // authenticate the user, + AlwaysIncludeUserClaimsInIdToken = true // Include claims in ID token } }; } diff --git a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Data/Seed/IdentityDataSeeder.cs b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Data/Seed/IdentityDataSeeder.cs index decdb43..37af59e 100644 --- a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Data/Seed/IdentityDataSeeder.cs +++ b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Data/Seed/IdentityDataSeeder.cs @@ -1,5 +1,6 @@ using BookingMonolith.Identity.Identities.Constants; using BookingMonolith.Identity.Identities.Models; +using BuildingBlocks.Constants; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Core; using BuildingBlocks.EFCore; @@ -43,14 +44,14 @@ public class IdentityDataSeeder : IDataSeeder { if (!await _identityContext.Roles.AnyAsync()) { - if (await _roleManager.RoleExistsAsync(Constants.Role.Admin) == false) + if (await _roleManager.RoleExistsAsync(IdentityConstant.Role.Admin) == false) { - await _roleManager.CreateAsync(new Role { Name = Constants.Role.Admin }); + await _roleManager.CreateAsync(new Role { Name = IdentityConstant.Role.Admin }); } - if (await _roleManager.RoleExistsAsync(Constants.Role.User) == false) + if (await _roleManager.RoleExistsAsync(IdentityConstant.Role.User) == false) { - await _roleManager.CreateAsync(new Role { Name = Constants.Role.User }); + await _roleManager.CreateAsync(new Role { Name = IdentityConstant.Role.User }); } } } @@ -65,7 +66,7 @@ public class IdentityDataSeeder : IDataSeeder if (result.Succeeded) { - await _userManager.AddToRoleAsync(InitialData.Users.First(), Constants.Role.Admin); + await _userManager.AddToRoleAsync(InitialData.Users.First(), IdentityConstant.Role.Admin); await _eventDispatcher.SendAsync( new UserCreated( @@ -83,7 +84,7 @@ public class IdentityDataSeeder : IDataSeeder if (result.Succeeded) { - await _userManager.AddToRoleAsync(InitialData.Users.Last(), Constants.Role.User); + await _userManager.AddToRoleAsync(InitialData.Users.Last(), IdentityConstant.Role.User); await _eventDispatcher.SendAsync( new UserCreated( diff --git a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs index 6c31e85..fd78740 100644 --- a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs +++ b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs @@ -3,6 +3,7 @@ using BookingMonolith.Identity.Data; using BookingMonolith.Identity.Identities.Models; using BuildingBlocks.Web; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; @@ -43,6 +44,21 @@ public static class IdentityServerExtensions //ref: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html identityServerBuilder.AddDeveloperSigningCredential(); + builder.Services.ConfigureApplicationCookie(options => + { + options.Events.OnRedirectToLogin = context => + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.CompletedTask; + }; + + options.Events.OnRedirectToAccessDenied = context => + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return Task.CompletedTask; + }; + }); + return builder; } } diff --git a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Constants/Constants.cs b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Constants/Constants.cs index ca5f833..28f3edf 100644 --- a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Constants/Constants.cs +++ b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Constants/Constants.cs @@ -2,12 +2,6 @@ namespace BookingMonolith.Identity.Identities.Constants; public static class Constants { - public static class Role - { - public const string Admin = "admin"; - public const string User = "user"; - } - public static class StandardScopes { public const string Roles = "roles"; @@ -15,7 +9,6 @@ public static class Constants public const string PassengerApi = "passenger-api"; public const string BookingApi = "booking-api"; public const string IdentityApi = "identity-api"; - public const string BookingModularMonolith = "booking-modular-monolith"; public const string BookingMonolith = "booking-monolith"; } } diff --git a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Features/RegisteringNewUser/V1/RegisterNewUser.cs b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Features/RegisteringNewUser/V1/RegisterNewUser.cs index c578a6c..8551dc8 100644 --- a/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Features/RegisteringNewUser/V1/RegisterNewUser.cs +++ b/1-monolith-architecture-style/src/BookingMonolith/src/Identity/Identities/Features/RegisteringNewUser/V1/RegisterNewUser.cs @@ -1,6 +1,7 @@ using Ardalis.GuardClauses; using BookingMonolith.Identity.Identities.Exceptions; using BookingMonolith.Identity.Identities.Models; +using BuildingBlocks.Constants; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Core; using BuildingBlocks.Core.CQRS; @@ -109,7 +110,7 @@ internal class RegisterNewUserHandler : ICommandHandler {"role"}) + new IdentityResources.Email() }; @@ -27,17 +24,33 @@ public static class Config new(Constants.StandardScopes.BookingApi), new(Constants.StandardScopes.IdentityApi), new(Constants.StandardScopes.BookingModularMonolith), + new(JwtClaimTypes.Role, new List {"role"}) }; public static IList ApiResources => new List { - new(Constants.StandardScopes.FlightApi), - new(Constants.StandardScopes.PassengerApi), - new(Constants.StandardScopes.BookingApi), - new(Constants.StandardScopes.IdentityApi), - new(Constants.StandardScopes.BookingModularMonolith), + new(Constants.StandardScopes.FlightApi) + { + Scopes = { Constants.StandardScopes.FlightApi } + }, + new(Constants.StandardScopes.PassengerApi) + { + Scopes = { Constants.StandardScopes.PassengerApi } + }, + new(Constants.StandardScopes.BookingApi) + { + Scopes = { Constants.StandardScopes.BookingApi } + }, + new(Constants.StandardScopes.IdentityApi) + { + Scopes = { Constants.StandardScopes.IdentityApi } + }, + new(Constants.StandardScopes.BookingModularMonolith) + { + Scopes = { Constants.StandardScopes.BookingModularMonolith } + }, }; public static IEnumerable Clients => @@ -55,6 +68,7 @@ public static class Config { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, + JwtClaimTypes.Role, // Include roles scope Constants.StandardScopes.FlightApi, Constants.StandardScopes.PassengerApi, Constants.StandardScopes.BookingApi, @@ -62,7 +76,8 @@ public static class Config Constants.StandardScopes.BookingModularMonolith, }, AccessTokenLifetime = 3600, // authorize the client to access protected resources - IdentityTokenLifetime = 3600 // authenticate the user + IdentityTokenLifetime = 3600, // authenticate the user, + AlwaysIncludeUserClaimsInIdToken = true // Include claims in ID token } }; } diff --git a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Data/Seed/IdentityDataSeeder.cs b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Data/Seed/IdentityDataSeeder.cs index 069c9e2..f1780d4 100644 --- a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Data/Seed/IdentityDataSeeder.cs +++ b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Data/Seed/IdentityDataSeeder.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using BuildingBlocks.Constants; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Core; using BuildingBlocks.EFCore; @@ -47,14 +48,14 @@ public class IdentityDataSeeder : IDataSeeder { if (!await _identityContext.Roles.AnyAsync()) { - if (await _roleManager.RoleExistsAsync(Constants.Role.Admin) == false) + if (await _roleManager.RoleExistsAsync(IdentityConstant.Role.Admin) == false) { - await _roleManager.CreateAsync(new Role { Name = Constants.Role.Admin }); + await _roleManager.CreateAsync(new Role { Name = IdentityConstant.Role.Admin }); } - if (await _roleManager.RoleExistsAsync(Constants.Role.User) == false) + if (await _roleManager.RoleExistsAsync(IdentityConstant.Role.User) == false) { - await _roleManager.CreateAsync(new Role { Name = Constants.Role.User }); + await _roleManager.CreateAsync(new Role { Name = IdentityConstant.Role.User }); } } } @@ -69,7 +70,7 @@ public class IdentityDataSeeder : IDataSeeder if (result.Succeeded) { - await _userManager.AddToRoleAsync(InitialData.Users.First(), Constants.Role.Admin); + await _userManager.AddToRoleAsync(InitialData.Users.First(), IdentityConstant.Role.Admin); await _eventDispatcher.SendAsync( new UserCreated( @@ -87,7 +88,7 @@ public class IdentityDataSeeder : IDataSeeder if (result.Succeeded) { - await _userManager.AddToRoleAsync(InitialData.Users.Last(), Constants.Role.User); + await _userManager.AddToRoleAsync(InitialData.Users.Last(), IdentityConstant.Role.User); await _eventDispatcher.SendAsync( new UserCreated( diff --git a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Extensions/Infrastructure/IdentityServerExtensions.cs b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Extensions/Infrastructure/IdentityServerExtensions.cs index 5605abe..34a397e 100644 --- a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Extensions/Infrastructure/IdentityServerExtensions.cs +++ b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Extensions/Infrastructure/IdentityServerExtensions.cs @@ -1,7 +1,9 @@ +using BookingMonolith.Identity.Configurations; using BuildingBlocks.Web; using Identity.Data; using Identity.Identity.Models; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; @@ -44,6 +46,21 @@ public static class IdentityServerExtensions //ref: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html identityServerBuilder.AddDeveloperSigningCredential(); + builder.Services.ConfigureApplicationCookie(options => + { + options.Events.OnRedirectToLogin = context => + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.CompletedTask; + }; + + options.Events.OnRedirectToAccessDenied = context => + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return Task.CompletedTask; + }; + }); + return builder; } } diff --git a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Constants/Constants.cs b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Constants/Constants.cs index 35f89a5..e893f7a 100644 --- a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Constants/Constants.cs +++ b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Constants/Constants.cs @@ -2,12 +2,6 @@ namespace Identity.Identity.Constants; public static class Constants { - public static class Role - { - public const string Admin = "admin"; - public const string User = "user"; - } - public static class StandardScopes { public const string Roles = "roles"; diff --git a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs index 3ed9851..9864a06 100644 --- a/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs +++ b/2-modular-monolith-architecture-style/src/Modules/Identity/src/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs @@ -1,3 +1,4 @@ +using BuildingBlocks.Constants; using Duende.IdentityServer.EntityFramework.Entities; namespace Identity.Identity.Features.RegisteringNewUser.V1; @@ -114,7 +115,7 @@ internal class RegisterNewUserHandler : ICommandHandler {"role"}) + new IdentityResources.Email() }; @@ -25,17 +22,30 @@ public static class Config new(Constants.StandardScopes.FlightApi), new(Constants.StandardScopes.PassengerApi), new(Constants.StandardScopes.BookingApi), - new(Constants.StandardScopes.IdentityApi) + new(Constants.StandardScopes.IdentityApi), + new(JwtClaimTypes.Role, new List {"role"}) }; public static IList ApiResources => new List { - new(Constants.StandardScopes.FlightApi), - new(Constants.StandardScopes.PassengerApi), - new(Constants.StandardScopes.BookingApi), + new(Constants.StandardScopes.FlightApi) + { + Scopes = { Constants.StandardScopes.FlightApi } + }, + new(Constants.StandardScopes.PassengerApi) + { + Scopes = { Constants.StandardScopes.PassengerApi } + }, + new(Constants.StandardScopes.BookingApi) + { + Scopes = { Constants.StandardScopes.BookingApi } + }, new(Constants.StandardScopes.IdentityApi) + { + Scopes = { Constants.StandardScopes.IdentityApi } + }, }; public static IEnumerable Clients => @@ -53,13 +63,15 @@ public static class Config { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, + JwtClaimTypes.Role, // Include roles scope Constants.StandardScopes.FlightApi, Constants.StandardScopes.PassengerApi, Constants.StandardScopes.BookingApi, - Constants.StandardScopes.IdentityApi + Constants.StandardScopes.IdentityApi, }, AccessTokenLifetime = 3600, // authorize the client to access protected resources - IdentityTokenLifetime = 3600 // authenticate the user + IdentityTokenLifetime = 3600, // authenticate the user, + AlwaysIncludeUserClaimsInIdToken = true // Include claims in ID token } }; } diff --git a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Data/Seed/IdentityDataSeeder.cs b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Data/Seed/IdentityDataSeeder.cs index 29f2494..e88d45d 100644 --- a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Data/Seed/IdentityDataSeeder.cs +++ b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Data/Seed/IdentityDataSeeder.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using BuildingBlocks.Constants; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Core; using BuildingBlocks.EFCore; @@ -43,14 +44,14 @@ public class IdentityDataSeeder : IDataSeeder private async Task SeedRoles() { - if (await _roleManager.RoleExistsAsync(Constants.Role.Admin) == false) + if (await _roleManager.RoleExistsAsync(IdentityConstant.Role.Admin) == false) { - await _roleManager.CreateAsync(new Role { Name = Constants.Role.Admin }); + await _roleManager.CreateAsync(new Role { Name = IdentityConstant.Role.Admin }); } - if (await _roleManager.RoleExistsAsync(Constants.Role.User) == false) + if (await _roleManager.RoleExistsAsync(IdentityConstant.Role.User) == false) { - await _roleManager.CreateAsync(new Role { Name = Constants.Role.User }); + await _roleManager.CreateAsync(new Role { Name = IdentityConstant.Role.User }); } } @@ -62,7 +63,7 @@ public class IdentityDataSeeder : IDataSeeder if (result.Succeeded) { - await _userManager.AddToRoleAsync(InitialData.Users.First(), Constants.Role.Admin); + await _userManager.AddToRoleAsync(InitialData.Users.First(), IdentityConstant.Role.Admin); await _eventDispatcher.SendAsync(new UserCreated(InitialData.Users.First().Id, InitialData.Users.First().FirstName + " " + InitialData.Users.First().LastName, InitialData.Users.First().PassPortNumber)); } @@ -74,7 +75,7 @@ public class IdentityDataSeeder : IDataSeeder if (result.Succeeded) { - await _userManager.AddToRoleAsync(InitialData.Users.Last(), Constants.Role.User); + await _userManager.AddToRoleAsync(InitialData.Users.Last(), IdentityConstant.Role.User); await _eventDispatcher.SendAsync(new UserCreated(InitialData.Users.Last().Id, InitialData.Users.Last().FirstName + " " + InitialData.Users.Last().LastName, InitialData.Users.Last().PassPortNumber)); } diff --git a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs index 5605abe..34a397e 100644 --- a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs +++ b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Extensions/Infrastructure/IdentityServerExtensions.cs @@ -1,7 +1,9 @@ +using BookingMonolith.Identity.Configurations; using BuildingBlocks.Web; using Identity.Data; using Identity.Identity.Models; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; @@ -44,6 +46,21 @@ public static class IdentityServerExtensions //ref: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html identityServerBuilder.AddDeveloperSigningCredential(); + builder.Services.ConfigureApplicationCookie(options => + { + options.Events.OnRedirectToLogin = context => + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.CompletedTask; + }; + + options.Events.OnRedirectToAccessDenied = context => + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return Task.CompletedTask; + }; + }); + return builder; } } diff --git a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Constants/Constants.cs b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Constants/Constants.cs index d87e053..8e18f82 100644 --- a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Constants/Constants.cs +++ b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Constants/Constants.cs @@ -2,12 +2,6 @@ namespace Identity.Identity.Constants; public static class Constants { - public static class Role - { - public const string Admin = "admin"; - public const string User = "user"; - } - public static class StandardScopes { public const string Roles = "roles"; diff --git a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs index a8b5cb4..4818113 100644 --- a/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs +++ b/3-microservices-architecture-style/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUser.cs @@ -1,3 +1,5 @@ +using BuildingBlocks.Constants; + namespace Identity.Identity.Features.RegisteringNewUser.V1; using System; @@ -113,7 +115,7 @@ internal class RegisterNewUserHandler : ICommandHandler("Jwt"); - - services.AddAuthentication( - o => - { - o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddCookie(cfg => cfg.SlidingExpiration = true) - .AddJwtBearer( - JwtBearerDefaults.AuthenticationScheme, - options => - { - options.Authority = jwtOptions.Authority; - - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateAudience = false, - ClockSkew = TimeSpan.FromSeconds(2), // For prevent add default value (5min) to life time token! - ValidateLifetime = true, // Enforce token expiry - }; - - options.RequireHttpsMetadata = jwtOptions.RequireHttpsMetadata; - options.MetadataAddress = jwtOptions.MetadataAddress; - }); - - if (!string.IsNullOrEmpty(jwtOptions.Audience)) + public static IServiceCollection AddJwt(this IServiceCollection services) { + // Bind Jwt settings from configuration + var jwtOptions = services.GetOptions("Jwt"); + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = jwtOptions.Authority; + options.Audience = jwtOptions.Audience; + options.RequireHttpsMetadata = true; + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuers = [jwtOptions.Authority], + ValidateAudience = true, + ValidAudiences = [jwtOptions.Audience], + ValidateLifetime = true, + ClockSkew = TimeSpan.FromSeconds(2), // Reduce default clock skew + // For IdentityServer4/Duende, we should also validate the signing key + ValidateIssuerSigningKey = true, + NameClaimType = "name", // Map "name" claim to User.Identity.Name + RoleClaimType = "role", // Map "role" claim to User.IsInRole() + }; + + // Preserve ALL claims from the token (including "sub") + options.MapInboundClaims = false; + }); + + services.AddAuthorization( options => { - // Set JWT as the default scheme for all [Authorize] attributes - options.DefaultPolicy = - new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme) - .RequireAuthenticatedUser() - .Build(); - options.AddPolicy( nameof(ApiScope), policy => @@ -57,9 +51,27 @@ public static class JwtExtensions policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", jwtOptions.Audience); }); - }); - } - return services; + // Role-based policies + options.AddPolicy( + IdentityConstant.Role.Admin, + x => + { + x.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme); + x.RequireRole(IdentityConstant.Role.Admin); + } + ); + options.AddPolicy( + IdentityConstant.Role.User, + x => + { + x.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme); + x.RequireRole(IdentityConstant.Role.User); + } + ); + }); + + return services; + } } }