├── .gitignore ├── result.png ├── AspNetCoreCustomUserManager ├── appsettings.json ├── db.sqlite ├── Views │ ├── _ViewImports.cshtml │ ├── _ViewStart.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ └── Home │ │ └── Index.cshtml ├── wwwroot │ ├── js │ │ └── app.js │ └── css │ │ └── app.css ├── ViewModels │ └── Home │ │ └── IndexViewModel.cs ├── AspNetCoreCustomUserManager.csproj ├── Models │ ├── Role.cs │ ├── Permission.cs │ ├── UserRole.cs │ ├── RolePermission.cs │ ├── User.cs │ ├── CredentialType.cs │ └── Credential.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Pbkdf2Hasher.cs ├── Controllers │ └── HomeController.cs ├── Startup.cs ├── IUserManager.cs ├── Data │ └── Storage.cs └── UserManager.cs ├── LICENSE.txt ├── README.md └── AspNetCoreCustomUserManager.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ -------------------------------------------------------------------------------- /result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitrySikorsky/AspNetCoreCustomUserManager/HEAD/result.png -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Data Source=db.sqlite" 4 | } 5 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/db.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitrySikorsky/AspNetCoreCustomUserManager/HEAD/AspNetCoreCustomUserManager/db.sqlite -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @* Copyright © 2017 Dmitry Sikorsky. All rights reserved. *@ 2 | @* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. *@ 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @* Copyright © 2017 Dmitry Sikorsky. All rights reserved. *@ 2 | @* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. *@ 3 | @{ 4 | this.Layout = "~/Views/Shared/_Layout.cshtml"; 5 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/wwwroot/js/app.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | $(document).ready( 5 | function () { 6 | app.dropDownList(); 7 | } 8 | ); -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/ViewModels/Home/IndexViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace AspNetCoreCustomUserManager 5 | { 6 | public class IndexViewModel 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/AspNetCoreCustomUserManager.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dmitry Sikorsky 5 | Copyright © 2017 Dmitry Sikorsky 6 | net6.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Models/Role.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace AspNetCoreCustomUserManager.Models 5 | { 6 | public class Role 7 | { 8 | public int Id { get; set; } 9 | public string Code { get; set; } 10 | public string Name { get; set; } 11 | public int? Position { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Models/Permission.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace AspNetCoreCustomUserManager.Models 5 | { 6 | public class Permission 7 | { 8 | public int Id { get; set; } 9 | public string Code { get; set; } 10 | public string Name { get; set; } 11 | public int? Position { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Models/UserRole.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace AspNetCoreCustomUserManager.Models 5 | { 6 | public class UserRole 7 | { 8 | public int UserId { get; set; } 9 | public int RoleId { get; set; } 10 | 11 | public virtual User User { get; set; } 12 | public virtual Role Role { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Models/RolePermission.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace AspNetCoreCustomUserManager.Models 5 | { 6 | public class RolePermission 7 | { 8 | public int RoleId { get; set; } 9 | public int PermissionId { get; set; } 10 | 11 | public virtual Role Role { get; set; } 12 | public virtual Permission Permission { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Models/User.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace AspNetCoreCustomUserManager.Models 8 | { 9 | public class User 10 | { 11 | public int Id { get; set; } 12 | public string Name { get; set; } 13 | public DateTime Created { get; set; } 14 | 15 | public virtual ICollection Credentials { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Models/CredentialType.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace AspNetCoreCustomUserManager.Models 7 | { 8 | public class CredentialType 9 | { 10 | public int Id { get; set; } 11 | public string Code { get; set; } 12 | public string Name { get; set; } 13 | public int? Position { get; set; } 14 | 15 | public virtual ICollection Credentials { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | 7 | namespace AspNetCoreCustomUserManager 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | CreateWebHostBuilder(args).Build().Run(); 14 | } 15 | 16 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 17 | WebHost.CreateDefaultBuilder(args) 18 | .UseStartup(); 19 | } 20 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @* Copyright © 2017 Dmitry Sikorsky. All rights reserved. *@ 2 | @* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. *@ 3 | 4 | 5 | 6 | 7 | ASP.NET Core Custom User Manager 8 | 9 | 10 | 11 | 12 | 13 | 14 | @RenderBody() 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET Core Custom User Manager 2 | This is a demo web application for the 3 | “[ASP.NET Core Custom User Manager](https://medium.com/@dmitrysikorsky/asp-net-core-custom-user-manager-a7206e718a90)” 4 | post on the [Dmitry Sikorsky’s blog](https://medium.com/@dmitrysikorsky). It demonstrates how to build custom 5 | user manager on ASP.NET Core if you don’t want to use Identity. 6 | 7 | The result looks like this: 8 | ![Custom ASP.NET Core user manager](result.png) 9 | *Custom ASP.NET Core user manager* 10 | 11 | Updated to use .NET Core 6. 12 | 13 | ## Using the Application 14 | 15 | 1. Run the application. 16 | 2. Click Login user button. 17 | 3. Click Logout user button. 18 | -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Models/Credential.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace AspNetCoreCustomUserManager.Models 5 | { 6 | public class Credential 7 | { 8 | public int Id { get; set; } 9 | public int UserId { get; set; } 10 | public int CredentialTypeId { get; set; } 11 | public string Identifier { get; set; } 12 | public string Secret { get; set; } 13 | public string Extra { get; set; } 14 | 15 | public virtual User User { get; set; } 16 | public virtual CredentialType CredentialType { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:60233/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AspNetCoreCustomUserManager": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:60234" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Pbkdf2Hasher.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Security.Cryptography; 6 | using Microsoft.AspNetCore.Cryptography.KeyDerivation; 7 | 8 | namespace AspNetCoreCustomUserManager 9 | { 10 | public static class Pbkdf2Hasher 11 | { 12 | public static string ComputeHash(string password, byte[] salt) 13 | { 14 | return Convert.ToBase64String( 15 | KeyDerivation.Pbkdf2( 16 | password: password, 17 | salt: salt, 18 | prf: KeyDerivationPrf.HMACSHA1, 19 | iterationCount: 10000, 20 | numBytesRequested: 256 / 8 21 | ) 22 | ); 23 | } 24 | 25 | public static byte[] GenerateRandomSalt() 26 | { 27 | byte[] salt = new byte[128 / 8]; 28 | 29 | using (var rng = RandomNumberGenerator.Create()) 30 | rng.GetBytes(salt); 31 | 32 | return salt; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreCustomUserManager", "AspNetCoreCustomUserManager\AspNetCoreCustomUserManager.csproj", "{6170B3CB-6E0E-44B6-AC59-951CEA7F6869}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6170B3CB-6E0E-44B6-AC59-951CEA7F6869}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6170B3CB-6E0E-44B6-AC59-951CEA7F6869}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6170B3CB-6E0E-44B6-AC59-951CEA7F6869}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6170B3CB-6E0E-44B6-AC59-951CEA7F6869}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace AspNetCoreCustomUserManager 7 | { 8 | public class HomeController : Controller 9 | { 10 | private IUserManager userManager; 11 | 12 | public HomeController(IUserManager userManager) 13 | { 14 | this.userManager = userManager; 15 | } 16 | 17 | [HttpGet] 18 | public IActionResult Index() 19 | { 20 | return this.View(); 21 | } 22 | 23 | [HttpPost] 24 | public IActionResult Login() 25 | { 26 | ValidateResult validateResult = this.userManager.Validate("Email", "admin@example.com", "admin"); 27 | 28 | if (validateResult.Success) 29 | this.userManager.SignIn(this.HttpContext, validateResult.User, false); 30 | 31 | return this.RedirectToAction("Index"); 32 | } 33 | 34 | [HttpPost] 35 | public IActionResult Logout() 36 | { 37 | this.userManager.SignOut(this.HttpContext); 38 | return this.RedirectToAction("Index"); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | /* Copyright © 2017 Dmitry Sikorsky. All rights reserved. */ 2 | /* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. */ 3 | 4 | /* Global things */ 5 | html, body { 6 | color: #323232; 7 | font: normal 20px 'Open Sans', sans-serif; 8 | line-height: 1.5; 9 | text-align: center; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | body { 15 | padding: 50px; 16 | } 17 | 18 | h1, h2 { 19 | font-weight: normal; 20 | margin: 0; 21 | padding: 0; 22 | } 23 | 24 | h1 { 25 | font-size: 60px; 26 | } 27 | 28 | h2 { 29 | font-size: 40px; 30 | margin-top: 40px; 31 | } 32 | 33 | select, button, label { 34 | font: normal 20px 'Open Sans', sans-serif; 35 | } 36 | 37 | /* Field */ 38 | .field { 39 | } 40 | 41 | .field__validation-message { 42 | color: red; 43 | font-style: italic; 44 | display: block; 45 | margin-top: 10px; 46 | } 47 | 48 | /* Buttons */ 49 | .buttons { 50 | } 51 | 52 | /* Form */ 53 | .form { 54 | } 55 | 56 | .form__field { 57 | margin-top: 20px; 58 | } 59 | 60 | .form__buttons { 61 | margin-top: 40px; 62 | } 63 | 64 | /* Marker */ 65 | .marker { 66 | } 67 | 68 | .marker--secondary { 69 | color: #999; 70 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @* Copyright © 2017 Dmitry Sikorsky. All rights reserved. *@ 2 | @* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. *@ 3 | @model AspNetCoreCustomUserManager.IndexViewModel 4 | @inject AspNetCoreCustomUserManager.IUserManager UserManager 5 | @using System.Security.Claims 6 |

ASP.NET Core
Custom User Manager

7 |
8 | User.Identity.IsAuthenticated: @User.Identity.IsAuthenticated 9 |
10 | @if (this.User.Identity.IsAuthenticated) 11 | { 12 |
13 | UserManager.GetCurrentUser(this.Context).Name: @UserManager.GetCurrentUser(this.Context).Name 14 |
15 |
16 | User.HasClaim(ClaimTypes.Role, "Administrator"): @User.HasClaim(ClaimTypes.Role, "Administrator") 17 |
18 |
19 | User.HasClaim("Permission", "DoEverything"): @User.HasClaim("Permission", "DoEverything") 20 |
21 | } 22 | @if (this.User.Identity.IsAuthenticated) 23 | { 24 |
25 |
26 | 27 |
28 |
29 | } 30 | else 31 | { 32 |
33 |
34 | 35 |
36 |
37 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using AspNetCoreCustomUserManager.Data; 6 | using Microsoft.AspNetCore.Authentication.Cookies; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Hosting; 13 | 14 | namespace AspNetCoreCustomUserManager 15 | { 16 | public class Startup 17 | { 18 | public IConfiguration Configuration { get; } 19 | 20 | public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment) 21 | { 22 | this.Configuration = configuration; 23 | } 24 | 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddDbContext( 28 | options => options.UseSqlite(this.Configuration.GetConnectionString("DefaultConnection")) 29 | ); 30 | 31 | services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 32 | .AddCookie(options => 33 | { 34 | options.ExpireTimeSpan = TimeSpan.FromDays(7); 35 | } 36 | ); 37 | 38 | services.AddScoped(); 39 | services.AddMvc(); 40 | } 41 | 42 | public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironment webHostEnvironment) 43 | { 44 | if (webHostEnvironment.IsDevelopment()) 45 | applicationBuilder.UseDeveloperExceptionPage(); 46 | 47 | applicationBuilder.UseAuthentication(); 48 | applicationBuilder.UseStaticFiles(); 49 | applicationBuilder.UseRouting(); 50 | applicationBuilder.UseEndpoints(builder => builder.MapDefaultControllerRoute()); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/IUserManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Threading.Tasks; 5 | using AspNetCoreCustomUserManager.Models; 6 | using Microsoft.AspNetCore.Http; 7 | 8 | namespace AspNetCoreCustomUserManager 9 | { 10 | public enum SignUpResultError 11 | { 12 | CredentialTypeNotFound 13 | } 14 | 15 | public class SignUpResult 16 | { 17 | public User User { get; set; } 18 | public bool Success { get; set; } 19 | public SignUpResultError? Error { get; set; } 20 | 21 | public SignUpResult(User user = null, bool success = false, SignUpResultError? error = null) 22 | { 23 | this.User = user; 24 | this.Success = success; 25 | this.Error = error; 26 | } 27 | } 28 | 29 | public enum ValidateResultError 30 | { 31 | CredentialTypeNotFound, 32 | CredentialNotFound, 33 | SecretNotValid 34 | } 35 | 36 | public class ValidateResult 37 | { 38 | public User User { get; set; } 39 | public bool Success { get; set; } 40 | public ValidateResultError? Error { get; set; } 41 | 42 | public ValidateResult(User user = null, bool success = false, ValidateResultError? error = null) 43 | { 44 | this.User = user; 45 | this.Success = success; 46 | this.Error = error; 47 | } 48 | } 49 | 50 | public enum ChangeSecretResultError 51 | { 52 | CredentialTypeNotFound, 53 | CredentialNotFound 54 | } 55 | 56 | public class ChangeSecretResult 57 | { 58 | public bool Success { get; set; } 59 | public ChangeSecretResultError? Error { get; set; } 60 | 61 | public ChangeSecretResult(bool success = false, ChangeSecretResultError? error = null) 62 | { 63 | this.Success = success; 64 | this.Error = error; 65 | } 66 | } 67 | 68 | public interface IUserManager 69 | { 70 | SignUpResult SignUp(string name, string credentialTypeCode, string identifier); 71 | SignUpResult SignUp(string name, string credentialTypeCode, string identifier, string secret); 72 | void AddToRole(User user, string roleCode); 73 | void AddToRole(User user, Role role); 74 | void RemoveFromRole(User user, string roleCode); 75 | void RemoveFromRole(User user, Role role); 76 | ChangeSecretResult ChangeSecret(string credentialTypeCode, string identifier, string secret); 77 | ValidateResult Validate(string credentialTypeCode, string identifier); 78 | ValidateResult Validate(string credentialTypeCode, string identifier, string secret); 79 | Task SignIn(HttpContext httpContext, User user, bool isPersistent = false); 80 | Task SignOut(HttpContext httpContext); 81 | int GetCurrentUserId(HttpContext httpContext); 82 | User GetCurrentUser(HttpContext httpContext); 83 | } 84 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/Data/Storage.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using AspNetCoreCustomUserManager.Models; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace AspNetCoreCustomUserManager.Data 8 | { 9 | public class Storage : DbContext 10 | { 11 | public DbSet Users { get; set; } 12 | public DbSet CredentialTypes { get; set; } 13 | public DbSet Credentials { get; set; } 14 | public DbSet Roles { get; set; } 15 | public DbSet UserRoles { get; set; } 16 | public DbSet Permissions { get; set; } 17 | public DbSet RolePermissions { get; set; } 18 | 19 | public Storage(DbContextOptions options) : base(options) { } 20 | 21 | protected override void OnModelCreating(ModelBuilder modelBuilder) 22 | { 23 | base.OnModelCreating(modelBuilder); 24 | modelBuilder.Entity(etb => 25 | { 26 | etb.HasKey(e => e.Id); 27 | etb.Property(e => e.Id).ValueGeneratedOnAdd(); 28 | etb.Property(e => e.Name).IsRequired().HasMaxLength(64); 29 | etb.ToTable("Users"); 30 | } 31 | ); 32 | 33 | modelBuilder.Entity(etb => 34 | { 35 | etb.HasKey(e => e.Id); 36 | etb.Property(e => e.Id).ValueGeneratedOnAdd(); 37 | etb.Property(e => e.Code).IsRequired().HasMaxLength(32); 38 | etb.Property(e => e.Name).IsRequired().HasMaxLength(64); 39 | etb.ToTable("CredentialTypes"); 40 | } 41 | ); 42 | 43 | modelBuilder.Entity(etb => 44 | { 45 | etb.HasKey(e => e.Id); 46 | etb.Property(e => e.Id).ValueGeneratedOnAdd(); 47 | etb.Property(e => e.Identifier).IsRequired().HasMaxLength(64); 48 | etb.Property(e => e.Secret).HasMaxLength(1024); 49 | etb.ToTable("Credentials"); 50 | } 51 | ); 52 | 53 | modelBuilder.Entity(etb => 54 | { 55 | etb.HasKey(e => e.Id); 56 | etb.Property(e => e.Id).ValueGeneratedOnAdd(); 57 | etb.Property(e => e.Code).IsRequired().HasMaxLength(32); 58 | etb.Property(e => e.Name).IsRequired().HasMaxLength(64); 59 | etb.ToTable("Roles"); 60 | } 61 | ); 62 | 63 | modelBuilder.Entity(etb => 64 | { 65 | etb.HasKey(e => new { e.UserId, e.RoleId }); 66 | etb.ToTable("UserRoles"); 67 | } 68 | ); 69 | 70 | modelBuilder.Entity(etb => 71 | { 72 | etb.HasKey(e => e.Id); 73 | etb.Property(e => e.Id).ValueGeneratedOnAdd(); 74 | etb.Property(e => e.Code).IsRequired().HasMaxLength(32); 75 | etb.Property(e => e.Name).IsRequired().HasMaxLength(64); 76 | etb.ToTable("Permissions"); 77 | } 78 | ); 79 | 80 | modelBuilder.Entity(etb => 81 | { 82 | etb.HasKey(e => new { e.RoleId, e.PermissionId }); 83 | etb.ToTable("RolePermissions"); 84 | } 85 | ); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /AspNetCoreCustomUserManager/UserManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dmitry Sikorsky. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Claims; 8 | using System.Threading.Tasks; 9 | using AspNetCoreCustomUserManager.Data; 10 | using AspNetCoreCustomUserManager.Models; 11 | using Microsoft.AspNetCore.Authentication; 12 | using Microsoft.AspNetCore.Authentication.Cookies; 13 | using Microsoft.AspNetCore.Http; 14 | 15 | namespace AspNetCoreCustomUserManager 16 | { 17 | public class UserManager : IUserManager 18 | { 19 | private Storage storage; 20 | 21 | public UserManager(Storage storage) 22 | { 23 | this.storage = storage; 24 | } 25 | 26 | public SignUpResult SignUp(string name, string credentialTypeCode, string identifier) 27 | { 28 | return this.SignUp(name, credentialTypeCode, identifier, null); 29 | } 30 | 31 | public SignUpResult SignUp(string name, string credentialTypeCode, string identifier, string secret) 32 | { 33 | User user = new User(); 34 | 35 | user.Name = name; 36 | user.Created = DateTime.Now; 37 | this.storage.Users.Add(user); 38 | this.storage.SaveChanges(); 39 | 40 | CredentialType credentialType = this.storage.CredentialTypes.FirstOrDefault(ct => ct.Code.ToLower() == credentialTypeCode.ToLower()); 41 | 42 | if (credentialType == null) 43 | return new SignUpResult(success: false, error: SignUpResultError.CredentialTypeNotFound); 44 | 45 | Credential credential = new Credential(); 46 | 47 | credential.UserId = user.Id; 48 | credential.CredentialTypeId = credentialType.Id; 49 | credential.Identifier = identifier; 50 | 51 | if (!string.IsNullOrEmpty(secret)) 52 | { 53 | byte[] salt = Pbkdf2Hasher.GenerateRandomSalt(); 54 | string hash = Pbkdf2Hasher.ComputeHash(secret, salt); 55 | 56 | credential.Secret = hash; 57 | credential.Extra = Convert.ToBase64String(salt); 58 | } 59 | 60 | this.storage.Credentials.Add(credential); 61 | this.storage.SaveChanges(); 62 | return new SignUpResult(user: user, success: true); 63 | } 64 | 65 | public void AddToRole(User user, string roleCode) 66 | { 67 | Role role = this.storage.Roles.FirstOrDefault(r => r.Code.ToLower() == roleCode.ToLower()); 68 | 69 | if (role == null) 70 | return; 71 | 72 | this.AddToRole(user, role); 73 | } 74 | 75 | public void AddToRole(User user, Role role) 76 | { 77 | UserRole userRole = this.storage.UserRoles.Find(user.Id, role.Id); 78 | 79 | if (userRole != null) 80 | return; 81 | 82 | userRole = new UserRole(); 83 | userRole.UserId = user.Id; 84 | userRole.RoleId = role.Id; 85 | this.storage.UserRoles.Add(userRole); 86 | this.storage.SaveChanges(); 87 | } 88 | 89 | public void RemoveFromRole(User user, string roleCode) 90 | { 91 | Role role = this.storage.Roles.FirstOrDefault(r => r.Code.ToLower() == roleCode.ToLower()); 92 | 93 | if (role == null) 94 | return; 95 | 96 | this.RemoveFromRole(user, role); 97 | } 98 | 99 | public void RemoveFromRole(User user, Role role) 100 | { 101 | UserRole userRole = this.storage.UserRoles.Find(user.Id, role.Id); 102 | 103 | if (userRole == null) 104 | return; 105 | 106 | this.storage.UserRoles.Remove(userRole); 107 | this.storage.SaveChanges(); 108 | } 109 | 110 | public ChangeSecretResult ChangeSecret(string credentialTypeCode, string identifier, string secret) 111 | { 112 | CredentialType credentialType = this.storage.CredentialTypes.FirstOrDefault(ct => ct.Code.ToLower() == credentialTypeCode.ToLower()); 113 | 114 | if (credentialType == null) 115 | return new ChangeSecretResult(success: false, error: ChangeSecretResultError.CredentialTypeNotFound); 116 | 117 | Credential credential = this.storage.Credentials.FirstOrDefault(c => c.CredentialTypeId == credentialType.Id && c.Identifier == identifier); 118 | 119 | if (credential == null) 120 | return new ChangeSecretResult(success: false, error: ChangeSecretResultError.CredentialNotFound); 121 | 122 | byte[] salt = Pbkdf2Hasher.GenerateRandomSalt(); 123 | string hash = Pbkdf2Hasher.ComputeHash(secret, salt); 124 | 125 | credential.Secret = hash; 126 | credential.Extra = Convert.ToBase64String(salt); 127 | this.storage.Credentials.Update(credential); 128 | this.storage.SaveChanges(); 129 | return new ChangeSecretResult(success: true); 130 | } 131 | 132 | public ValidateResult Validate(string credentialTypeCode, string identifier) 133 | { 134 | return this.Validate(credentialTypeCode, identifier, null); 135 | } 136 | 137 | public ValidateResult Validate(string credentialTypeCode, string identifier, string secret) 138 | { 139 | CredentialType credentialType = this.storage.CredentialTypes.FirstOrDefault(ct => ct.Code.ToLower() == credentialTypeCode.ToLower()); 140 | 141 | if (credentialType == null) 142 | return new ValidateResult(success: false, error: ValidateResultError.CredentialTypeNotFound); 143 | 144 | Credential credential = this.storage.Credentials.FirstOrDefault(c => c.CredentialTypeId == credentialType.Id && c.Identifier == identifier); 145 | 146 | if (credential == null) 147 | return new ValidateResult(success: false, error: ValidateResultError.CredentialNotFound); 148 | 149 | if (!string.IsNullOrEmpty(secret)) 150 | { 151 | byte[] salt = Convert.FromBase64String(credential.Extra); 152 | string hash = Pbkdf2Hasher.ComputeHash(secret, salt); 153 | 154 | if (credential.Secret != hash) 155 | return new ValidateResult(success: false, error: ValidateResultError.SecretNotValid); 156 | } 157 | 158 | return new ValidateResult(user: this.storage.Users.Find(credential.UserId), success: true); 159 | } 160 | 161 | public async Task SignIn(HttpContext httpContext, User user, bool isPersistent = false) 162 | { 163 | ClaimsIdentity identity = new ClaimsIdentity(this.GetUserClaims(user), CookieAuthenticationDefaults.AuthenticationScheme); 164 | ClaimsPrincipal principal = new ClaimsPrincipal(identity); 165 | 166 | await httpContext.SignInAsync( 167 | CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties() { IsPersistent = isPersistent } 168 | ); 169 | } 170 | 171 | public async Task SignOut(HttpContext httpContext) 172 | { 173 | await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); 174 | } 175 | 176 | public int GetCurrentUserId(HttpContext httpContext) 177 | { 178 | if (!httpContext.User.Identity.IsAuthenticated) 179 | return -1; 180 | 181 | Claim claim = httpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier); 182 | 183 | if (claim == null) 184 | return -1; 185 | 186 | int currentUserId; 187 | 188 | if (!int.TryParse(claim.Value, out currentUserId)) 189 | return -1; 190 | 191 | return currentUserId; 192 | } 193 | 194 | public User GetCurrentUser(HttpContext httpContext) 195 | { 196 | int currentUserId = this.GetCurrentUserId(httpContext); 197 | 198 | if (currentUserId == -1) 199 | return null; 200 | 201 | return this.storage.Users.Find(currentUserId); 202 | } 203 | 204 | private IEnumerable GetUserClaims(User user) 205 | { 206 | List claims = new List(); 207 | 208 | claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())); 209 | claims.Add(new Claim(ClaimTypes.Name, user.Name)); 210 | claims.AddRange(this.GetUserRoleClaims(user)); 211 | return claims; 212 | } 213 | 214 | private IEnumerable GetUserRoleClaims(User user) 215 | { 216 | List claims = new List(); 217 | IEnumerable roleIds = this.storage.UserRoles.Where(ur => ur.UserId == user.Id).Select(ur => ur.RoleId).ToList(); 218 | 219 | if (roleIds != null) 220 | { 221 | foreach (int roleId in roleIds) 222 | { 223 | Role role = this.storage.Roles.Find(roleId); 224 | 225 | claims.Add(new Claim(ClaimTypes.Role, role.Code)); 226 | claims.AddRange(this.GetUserPermissionClaims(role)); 227 | } 228 | } 229 | 230 | return claims; 231 | } 232 | 233 | private IEnumerable GetUserPermissionClaims(Role role) 234 | { 235 | List claims = new List(); 236 | IEnumerable permissionIds = this.storage.RolePermissions.Where(rp => rp.RoleId == role.Id).Select(rp => rp.PermissionId).ToList(); 237 | 238 | if (permissionIds != null) 239 | { 240 | foreach (int permissionId in permissionIds) 241 | { 242 | Permission permission = this.storage.Permissions.Find(permissionId); 243 | 244 | claims.Add(new Claim("Permission", permission.Code)); 245 | } 246 | } 247 | 248 | return claims; 249 | } 250 | } 251 | } --------------------------------------------------------------------------------