From f3b361b1bd94626106f3cfbd502eb393b4c34119 Mon Sep 17 00:00:00 2001 From: enkodellc Date: Sun, 13 Oct 2019 01:52:48 -0700 Subject: [PATCH] Fix for #47, Claims / Policies for Roles --- README.md | 1 - .../Controllers/AccountController.cs | 27 +++---------------- .../Controllers/ToDoController.cs | 7 ++--- .../Controllers/UserProfileController.cs | 5 +--- .../Data/IdentityServerConfig.cs | 22 ++++++++++----- .../Properties/launchSettings.json | 8 +++--- src/BlazorBoilerplate.Server/Startup.cs | 25 +++++++---------- 7 files changed, 36 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 2e03b505a..1ede1d9cf 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,6 @@ This project is licensed under the terms of the [MIT license](LICENSE). - .NET Core 3.0.100 / Blazor 3.0.0-preview9.19457.4 update - Known Issues: - IAuditable Shadow Properties not getting UserId - - Authorization is broken / doesn't work. I need help sorting out IS4 / Asp.net Identity working together. Do ### 0.2.3 - .Net Core Authentication / Authorization (Stable Version) - .NET Core 3.0.100 / Blazor 3.0.0-preview9.19457.4 update diff --git a/src/BlazorBoilerplate.Server/Controllers/AccountController.cs b/src/BlazorBoilerplate.Server/Controllers/AccountController.cs index a2035d555..e13479d6b 100644 --- a/src/BlazorBoilerplate.Server/Controllers/AccountController.cs +++ b/src/BlazorBoilerplate.Server/Controllers/AccountController.cs @@ -21,7 +21,6 @@ namespace BlazorBoilerplate.Server.Controllers { [Route("api/[controller]")] [ApiController] - public class AccountController : ControllerBase { private static readonly UserInfoDto LoggedOutUser = new UserInfoDto { IsAuthenticated = false, Roles = new List() }; @@ -33,7 +32,6 @@ public class AccountController : ControllerBase private readonly IEmailService _emailService; private readonly IConfiguration _configuration; private readonly ApplicationDbContext _db; - public AccountController(UserManager userManager, ApplicationDbContext db, SignInManager signInManager, ILogger logger, @@ -94,17 +92,6 @@ public async Task Login(LoginDto parameters) return new ApiResponse(401, "Login Failed"); } - - // This is a policy test endpoint, feel free to delete it - [Authorize(Policies.IsAdmin)] - [HttpGet("amiadmin")] - public async Task Testing() - { - var claims = HttpContext.User.Claims.ToList(); - var claimsResponse = claims.Distinct().Select(x=>(x.Type.ToString(), x.Value.ToString())).ToList(); - return new ApiResponse(200, $"policy {Policies.IsAdmin} has allowed you through", claimsResponse); - } - [AllowAnonymous] // POST: api/Account/Register [HttpPost("Register")] @@ -132,6 +119,7 @@ public async Task Register(RegisterDto parameters) else { var claimsResult = _userManager.AddClaimsAsync(user, new Claim[]{ + new Claim(Policies.IsUser,""), new Claim(JwtClaimTypes.Name, parameters.UserName), new Claim(JwtClaimTypes.Email, parameters.Email), new Claim(JwtClaimTypes.EmailVerified, "false", ClaimValueTypes.Boolean) @@ -139,9 +127,7 @@ public async Task Register(RegisterDto parameters) } //Role - Here we tie the new user to the "User" role - await _userManager.AddToRoleAsync(user, "User"); - await _userManager.AddClaimAsync(user, new Claim(Policies.IsUser, "")); // Replacing roles with claims, if IsUser is present we interpret it as the equivalent of having the User role - + await _userManager.AddToRoleAsync(user, "User"); if (Convert.ToBoolean(_configuration["BlazorBoilerplate:RequireConfirmedEmail"] ?? "false")) { @@ -439,6 +425,7 @@ public async Task Create(RegisterDto parameters) else { var claimsResult = _userManager.AddClaimsAsync(user, new Claim[]{ + new Claim(Policies.IsUser,""), new Claim(JwtClaimTypes.Name, parameters.UserName), new Claim(JwtClaimTypes.Email, parameters.Email), new Claim(JwtClaimTypes.EmailVerified, "false", ClaimValueTypes.Boolean) @@ -446,11 +433,7 @@ public async Task Create(RegisterDto parameters) } //Role - Here we tie the new user to the "User" role - await _userManager.AddToRoleAsync(user, "User"); - await _userManager.AddClaimAsync(user, new Claim(Policies.IsUser, "")); // Replacing roles with claims, if IsUser is present we interpret it as the equivalent of having the User role - - - + await _userManager.AddToRoleAsync(user, "User"); if (Convert.ToBoolean(_configuration["BlazorBoilerplate:RequireConfirmedEmail"] ?? "false")) { @@ -600,7 +583,6 @@ public async Task Get([FromQuery] int pageSize = 10, [FromQuery] in public async Task ListRoles() { var roleList = _roleManager.Roles.Select(x => x.Name).ToList(); - return new ApiResponse(200, "", roleList); } @@ -646,7 +628,6 @@ public async Task Update([FromBody] UserInfoDto userInfo) rolesToAdd.Add(newUserRole); } } - await _userManager.AddToRolesAsync(appUser, rolesToAdd).ConfigureAwait(true); //HACK to switch to claims auth foreach (var role in rolesToAdd) diff --git a/src/BlazorBoilerplate.Server/Controllers/ToDoController.cs b/src/BlazorBoilerplate.Server/Controllers/ToDoController.cs index d0845ce69..03c5ce7de 100644 --- a/src/BlazorBoilerplate.Server/Controllers/ToDoController.cs +++ b/src/BlazorBoilerplate.Server/Controllers/ToDoController.cs @@ -65,11 +65,8 @@ public async Task Put([FromBody] TodoDto todo) } return await _todoService.Update(todo); } - - //Todo find out why this doesn't work! - //[Authorize(Roles = "User")] - [Authorize(Policy = Policies.IsUser)] - //[AllowAnonymous] + + [Authorize(Policy = Policies.IsAdmin)] // DELETE: api/Todo/5 [HttpDelete("{id}")] public async Task Delete(long id) diff --git a/src/BlazorBoilerplate.Server/Controllers/UserProfileController.cs b/src/BlazorBoilerplate.Server/Controllers/UserProfileController.cs index 58af4caf5..51f93b026 100644 --- a/src/BlazorBoilerplate.Server/Controllers/UserProfileController.cs +++ b/src/BlazorBoilerplate.Server/Controllers/UserProfileController.cs @@ -7,14 +7,13 @@ using BlazorBoilerplate.Shared.Dto; using BlazorBoilerplate.Server.Middleware.Wrappers; using Microsoft.AspNetCore.Http; -using System.Security.Claims; using IdentityModel; namespace BlazorBoilerplate.Server.Controllers { [Route("api/[controller]")] - [Authorize] [ApiController] + [Authorize] public class UserProfileController : ControllerBase { private readonly ILogger _logger; @@ -29,8 +28,6 @@ public UserProfileController(IUserProfileService userProfileService, ILogger Get() { diff --git a/src/BlazorBoilerplate.Server/Data/IdentityServerConfig.cs b/src/BlazorBoilerplate.Server/Data/IdentityServerConfig.cs index c47b1af8d..670af2bae 100644 --- a/src/BlazorBoilerplate.Server/Data/IdentityServerConfig.cs +++ b/src/BlazorBoilerplate.Server/Data/IdentityServerConfig.cs @@ -39,9 +39,9 @@ public static IEnumerable GetApiResources() JwtClaimTypes.Email, JwtClaimTypes.PhoneNumber, JwtClaimTypes.Role, + ClaimConstants.Permission, Policies.IsUser, - Policies.IsAdmin, - ClaimConstants.Permission + Policies.IsAdmin } } }; @@ -56,11 +56,12 @@ public static IEnumerable GetApiResources() // http://docs.identityserver.io/en/release/reference/client.html. new IdentityServer4.Models.Client { - ClientId = IdentityServerConfig.AppClientID, + //AbsoluteRefreshTokenLifetime = 7200, + //AccessTokenLifetime = 900, // Lifetime of access token in seconds. + AccessTokenType = AccessTokenType.Jwt, + //AllowedCorsOrigin = "https://localhost:5003", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // Resource Owner Password Credential grant. AllowAccessTokensViaBrowser = true, - RequireClientSecret = false, // This client does not need a secret to request tokens from the token endpoint. - AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, // For UserInfo endpoint. IdentityServerConstants.StandardScopes.Profile, @@ -69,11 +70,18 @@ public static IEnumerable GetApiResources() ScopeConstants.Roles, ApiName }, + AllowRememberConsent = true, AllowOfflineAccess = true, // For refresh token. + ClientId = IdentityServerConfig.AppClientID, + ClientName = IdentityServerConfig.ApiName, + //ClientUri = "https://localhost:5003", + ClientSecrets = new List { new Secret { Value = "BlazorBoilerplate".Sha512() }}, + Enabled = true, + //PostLogoutRedirectUris = new List {"http://localhost:5436"}, + RequireClientSecret = true, // This client does not need a secret to request tokens from the token endpoint. + //RedirectUris = new List {"http://localhost:5436/account/oAuth2"}, RefreshTokenExpiration = TokenExpiration.Sliding, RefreshTokenUsage = TokenUsage.OneTimeOnly, - //AccessTokenLifetime = 900, // Lifetime of access token in seconds. - //AbsoluteRefreshTokenLifetime = 7200, //SlidingRefreshTokenLifetime = 900, }, diff --git a/src/BlazorBoilerplate.Server/Properties/launchSettings.json b/src/BlazorBoilerplate.Server/Properties/launchSettings.json index db8e9e828..6da532f6d 100644 --- a/src/BlazorBoilerplate.Server/Properties/launchSettings.json +++ b/src/BlazorBoilerplate.Server/Properties/launchSettings.json @@ -3,8 +3,8 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "https://localhost:53414/", - "sslPort": 53414 + "applicationUrl": "http://localhost:53414/", + "sslPort": 0 } }, "profiles": { @@ -21,7 +21,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:53416/;https://localhost:5001" + "applicationUrl": "http://localhost:53416/" } } -} \ No newline at end of file +} diff --git a/src/BlazorBoilerplate.Server/Startup.cs b/src/BlazorBoilerplate.Server/Startup.cs index 2c6ad243e..70c42c84a 100644 --- a/src/BlazorBoilerplate.Server/Startup.cs +++ b/src/BlazorBoilerplate.Server/Startup.cs @@ -61,13 +61,12 @@ public void ConfigureServices(IServiceCollection services) }); services.AddIdentity>() - // .AddRoles>() - .AddClaimsPrincipalFactory() + .AddRoles>() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); - //services.AddScoped, - // AdditionalUserClaimsPrincipalFactory>(); + services.AddScoped, + AdditionalUserClaimsPrincipalFactory>(); // Adds IdentityServer var identityServerBuilder = services.AddIdentityServer(options => @@ -77,7 +76,7 @@ public void ConfigureServices(IServiceCollection services) options.Events.RaiseInformationEvents = true; options.Events.RaiseFailureEvents = true; options.Events.RaiseSuccessEvents = true; - }) + }) .AddConfigurationStore(options => { options.ConfigureDbContext = builder => { @@ -106,13 +105,12 @@ public void ConfigureServices(IServiceCollection services) // this enables automatic token cleanup. this is optional. options.EnableTokenCleanup = true; - options.TokenCleanupInterval = 30; + options.TokenCleanupInterval = 3600; //In Seconds 1 hour }) .AddAspNetIdentity(); X509Certificate2 cert = null; - if (_environment.IsDevelopment()) { // The AddDeveloperSigningCredential extension creates temporary key material for signing tokens. @@ -122,7 +120,7 @@ public void ConfigureServices(IServiceCollection services) //.AddDeveloperSigningCredential(true, @"C:\tempkey.rsa") identityServerBuilder.AddDeveloperSigningCredential(); } - else + else { // Works for IIS, finds cert by the thumbprint in appsettings.json // Make sure Certificate is in the Web Hosting folder && installed to LocalMachine or update settings below @@ -162,7 +160,7 @@ public void ConfigureServices(IServiceCollection services) //var vaultConfigSection = Configuration.GetSection("Vault"); //var keyVaultService = new KeyVaultCertificateService(vaultConfigSection["Url"], vaultConfigSection["ClientId"], vaultConfigSection["ClientSecret"]); ////cert = keyVaultService.GetCertificateFromKeyVault(vaultConfigSection["CertificateName"]); - + /// I was informed that this will work as a temp solution in Azure cert = new X509Certificate2("AuthSample.pfx", "Admin123", X509KeyStorageFlags.MachineKeySet | @@ -172,7 +170,6 @@ public void ConfigureServices(IServiceCollection services) identityServerBuilder.AddSigningCredential(cert); } - services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { @@ -181,7 +178,6 @@ public void ConfigureServices(IServiceCollection services) options.RequireHttpsMetadata = _environment.IsProduction() ? true : false; options.ApiName = IdentityServerConfig.ApiName; }); - services.Configure(options => { options.SuppressModelStateInvalidFilter = true; }); @@ -315,11 +311,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseDeveloperExceptionPage(); app.UseBlazorDebugging(); } - //else - //{ + else + { // // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. // app.UseHsts(); //HSTS Middleware (UseHsts) to send HTTP Strict Transport Security Protocol (HSTS) headers to clients. - //} + } //app.UseStaticFiles(); app.UseClientSideBlazorFiles(); @@ -328,7 +324,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseRouting(); //app.UseAuthentication(); app.UseIdentityServer(); - app.UseAuthorization(); // NSwag