├── Pages ├── Index.razor ├── LoginDisplay.razor ├── Logout.razor ├── Counter.razor ├── FetchData.razor ├── Login.razor └── Register.razor ├── Models ├── RegisterResult.cs ├── LoginResult.cs ├── LoginModel.cs └── RegisterModel.cs ├── Service ├── IAuthService.cs └── AuthService.cs ├── Data ├── ApplicationDbContext.cs └── Migrations │ ├── 20230217193849_CreateIdentitySchema.cs │ ├── ApplicationDbContextModelSnapshot.cs │ └── 20230217193849_CreateIdentitySchema.Designer.cs ├── Shared ├── MainLayout.razor ├── SurveyPrompt.razor ├── NavMenu.razor ├── NavMenu.razor.css └── MainLayout.razor.css ├── appsettings.json ├── README.md ├── _Imports.razor ├── App.razor ├── Controllers ├── WeatherForecastController.cs ├── AccountsController.cs └── LoginController.cs ├── Program.cs └── Helpers └── ApiAuthenticationStateProvider.cs /Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Index 4 | 5 |

Hello, world!

6 | 7 | Welcome to your new app. 8 | 9 | 10 | -------------------------------------------------------------------------------- /Models/RegisterResult.cs: -------------------------------------------------------------------------------- 1 | namespace JwtLoginOnline.Server.Models 2 | { 3 | public class RegisterResult 4 | { 5 | public bool Successful { get; set; } 6 | public IEnumerable? Errors { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Models/LoginResult.cs: -------------------------------------------------------------------------------- 1 | namespace JwtLoginOnline.Server.Models 2 | { 3 | public class LoginResult 4 | { 5 | public bool Successful { get; set; } 6 | public string? Error { get; set; } 7 | public string? Token { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Pages/LoginDisplay.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello, @context.User.Identity!.Name! 5 | Log out 6 | 7 | 8 | Register 9 | Log in 10 | 11 | -------------------------------------------------------------------------------- /Pages/Logout.razor: -------------------------------------------------------------------------------- 1 | @page "/logout" 2 | @inject IAuthService AuthService 3 | @inject NavigationManager NavigationManager 4 | 5 | @code { 6 | 7 | protected override async Task OnInitializedAsync() 8 | { 9 | await AuthService.Logout(); 10 | NavigationManager.NavigateTo("/"); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Service/IAuthService.cs: -------------------------------------------------------------------------------- 1 | using JwtLoginOnline.Server.Models; 2 | 3 | namespace JwtLoginOnline.Client.Service 4 | { 5 | public interface IAuthService 6 | { 7 | Task Register(RegisterModel registerModel); 8 | Task Login(LoginModel loginModel); 9 | Task Logout(); 10 | } 11 | } -------------------------------------------------------------------------------- /Models/LoginModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace JwtLoginOnline.Server.Models 4 | { 5 | public class LoginModel 6 | { 7 | [Required] 8 | public string? Email { get; set; } 9 | 10 | [Required] 11 | public string? Password { get; set; } 12 | 13 | public bool RememberMe { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace JwtLoginOnline.Server.Data 5 | { 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) : base(options) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 | Counter 4 | 5 |

Counter

6 | 7 |

Current count: @currentCount

8 | 9 | 10 | 11 | @code { 12 | private int currentCount = 0; 13 | 14 | private void IncrementCount() 15 | { 16 | currentCount++; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | 7 | 8 |
9 |
10 | 11 | About 12 |
13 | 14 |
15 | @Body 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=AuthenticationWithClientSideBlazor;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "JwtSecurityKey": "RANDOM_KEY_MUST_NOT_BE_SHARED", 6 | "JwtIssuer": "https://localhost", 7 | "JwtAudience": "https://localhost", 8 | "JwtExpiryInDays": 1, 9 | "Logging": { 10 | "LogLevel": { 11 | "Default": "Information", 12 | "Microsoft.AspNetCore": "Warning" 13 | } 14 | }, 15 | "AllowedHosts": "*" 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Authentication with client-side Blazor using Web API and ASP.NET Core Identity
2 | 3 | /*Follow Netcode-Hub*/
4 | https://netcodehub.blogspot.com
5 | https://github.com/Netcode-Hub
6 | https://twitter.com/NetcodeHub | Twitter
7 | https://web.facebook.com/NetcodeHub | Facebook
8 | https://www.linkedin.com/in/netcode-hub-90b188258/ | LinkedIn
9 | 10 | /*You can buy a coffee for me to support the channel*/ ☕️
11 | https://www.buymeacoffee.com/NetcodeHub
12 | 13 | PLEASE DO NOT FOGET TO STAR THIS REPOSITORY
14 | -------------------------------------------------------------------------------- /_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using JwtLoginOnline.Client 10 | @using JwtLoginOnline.Client.Shared 11 | @using JwtLoginOnline.Client.Service 12 | @using JwtLoginOnline.Server.Models 13 | @using Microsoft.AspNetCore.Components.Authorization 14 | -------------------------------------------------------------------------------- /Shared/SurveyPrompt.razor: -------------------------------------------------------------------------------- 1 |
2 | 3 | @Title 4 | 5 | 6 | Please take our 7 | brief survey 8 | 9 | and tell us what you think. 10 |
11 | 12 | @code { 13 | // Demonstrates how a parent component can supply parameters 14 | [Parameter] 15 | public string? Title { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /Models/RegisterModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace JwtLoginOnline.Server.Models 4 | { 5 | public class RegisterModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | [Display(Name = "Email")] 10 | public string? Email { get; set; } 11 | 12 | [Required] 13 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Password)] 15 | [Display(Name = "Password")] 16 | public string? Password { get; set; } 17 | 18 | [DataType(DataType.Password)] 19 | [Display(Name = "Confirm password")] 20 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 21 | public string? ConfirmPassword { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /App.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Authorization; 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 |
14 | 15 | 16 | @code { 17 | [CascadingParameter] private Task authenticationStateTask { get; set; } 18 | 19 | private async Task LogUserAuthenticationState() 20 | { 21 | var authState = await authenticationStateTask; 22 | var user = authState.User; 23 | 24 | if (user.Identity!.IsAuthenticated) 25 | { 26 | Console.WriteLine($"User {user.Identity.Name} is authenticated."); 27 | } 28 | else 29 | { 30 | Console.WriteLine("User is NOT authenticated."); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using JwtLoginOnline.Shared; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace JwtLoginOnline.Server.Controllers 5 | { 6 | [ApiController] 7 | [Route("[controller]")] 8 | public class WeatherForecastController : ControllerBase 9 | { 10 | private static readonly string[] Summaries = new[] 11 | { 12 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 13 | }; 14 | 15 | private readonly ILogger _logger; 16 | 17 | public WeatherForecastController(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | [HttpGet] 23 | public IEnumerable Get() 24 | { 25 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 26 | { 27 | Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), 28 | TemperatureC = Random.Shared.Next(-20, 55), 29 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 30 | }) 31 | .ToArray(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Pages/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | @using JwtLoginOnline.Shared 3 | @inject HttpClient Http 4 | 5 | Weather forecast 6 | 7 |

Weather forecast

8 | 9 |

This component demonstrates fetching data from the server.

10 | 11 | @if (forecasts == null) 12 | { 13 |

Loading...

14 | } 15 | else 16 | { 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | @foreach (var forecast in forecasts) 28 | { 29 | 30 | 31 | 32 | 33 | 34 | 35 | } 36 | 37 |
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
38 | } 39 | 40 | @code { 41 | private WeatherForecast[]? forecasts; 42 | 43 | protected override async Task OnInitializedAsync() 44 | { 45 | forecasts = await Http.GetFromJsonAsync("WeatherForecast"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Controllers/AccountsController.cs: -------------------------------------------------------------------------------- 1 | using JwtLoginOnline.Server.Models; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace JwtLoginOnline.Server.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | [ApiController] 10 | 11 | public class AccountsController : ControllerBase 12 | { 13 | //private static UserModel LoggedOutUser = new UserModel { IsAuthenticated = false }; 14 | 15 | private readonly UserManager _userManager; 16 | 17 | public AccountsController(UserManager userManager) 18 | { 19 | _userManager = userManager; 20 | } 21 | 22 | [HttpPost] 23 | public async Task Post([FromBody] RegisterModel model) 24 | { 25 | var newUser = new IdentityUser { UserName = model.Email, Email = model.Email }; 26 | 27 | var result = await _userManager.CreateAsync(newUser, model.Password!); 28 | 29 | if (!result.Succeeded) 30 | { 31 | var errors = result.Errors.Select(x => x.Description); 32 | 33 | return Ok(new RegisterResult { Successful = false, Errors = errors }); 34 | 35 | } 36 | 37 | return Ok(new RegisterResult { Successful = true }); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | 30 | @code { 31 | private bool collapseNavMenu = true; 32 | 33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 34 | 35 | private void ToggleNavMenu() 36 | { 37 | collapseNavMenu = !collapseNavMenu; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | 63 | .nav-scrollable { 64 | /* Allow sidebar to scroll for tall menus */ 65 | height: calc(100vh - 3.5rem); 66 | overflow-y: auto; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row:not(.auth) { 41 | display: none; 42 | } 43 | 44 | .top-row.auth { 45 | justify-content: space-between; 46 | } 47 | 48 | .top-row ::deep a, .top-row ::deep .btn-link { 49 | margin-left: 0; 50 | } 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .page { 55 | flex-direction: row; 56 | } 57 | 58 | .sidebar { 59 | width: 250px; 60 | height: 100vh; 61 | position: sticky; 62 | top: 0; 63 | } 64 | 65 | .top-row { 66 | position: sticky; 67 | top: 0; 68 | z-index: 1; 69 | } 70 | 71 | .top-row.auth ::deep a:first-child { 72 | flex: 1; 73 | text-align: right; 74 | width: 0; 75 | } 76 | 77 | .top-row, article { 78 | padding-left: 2rem !important; 79 | padding-right: 1.5rem !important; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Pages/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/login" 2 | @inject IAuthService AuthService 3 | @inject NavigationManager NavigationManager 4 | 5 |

Login

6 | 7 | @if (ShowErrors) 8 | { 9 | 12 | } 13 | 14 |
15 |
16 |
Please enter your details
17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | @code { 37 | 38 | private LoginModel loginModel = new LoginModel(); 39 | private bool ShowErrors; 40 | private string Error = ""; 41 | 42 | private async Task HandleLogin() 43 | { 44 | ShowErrors = false; 45 | 46 | var result = await AuthService.Login(loginModel); 47 | 48 | if (result.Successful) 49 | { 50 | NavigationManager.NavigateTo("/"); 51 | } 52 | else 53 | { 54 | Error = result.Error!; 55 | ShowErrors = true; 56 | } 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /Controllers/LoginController.cs: -------------------------------------------------------------------------------- 1 | using JwtLoginOnline.Server.Models; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.IdentityModel.Tokens; 6 | using System.IdentityModel.Tokens.Jwt; 7 | using System.Security.Claims; 8 | using System.Text; 9 | 10 | namespace JwtLoginOnline.Server.Controllers 11 | { 12 | 13 | [Route("api/[controller]")] 14 | [ApiController] 15 | public class LoginController : ControllerBase 16 | { 17 | private readonly IConfiguration _configuration; 18 | private readonly SignInManager _signInManager; 19 | 20 | public LoginController(IConfiguration configuration, 21 | SignInManager signInManager) 22 | { 23 | _configuration = configuration; 24 | _signInManager = signInManager; 25 | } 26 | 27 | [HttpPost] 28 | public async Task Login([FromBody] LoginModel login) 29 | { 30 | var result = await _signInManager.PasswordSignInAsync(login.Email!, login.Password!, false, false); 31 | 32 | if (!result.Succeeded) return BadRequest(new LoginResult { Successful = false, Error = "Username and password are invalid." }); 33 | 34 | var claims = new[] 35 | { 36 | new Claim(ClaimTypes.Name, login.Email!) 37 | }; 38 | 39 | var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"]!)); 40 | var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); 41 | var expiry = DateTime.Now.AddDays(Convert.ToInt32(_configuration["JwtExpiryInDays"])); 42 | 43 | var token = new JwtSecurityToken( 44 | _configuration["JwtIssuer"], 45 | _configuration["JwtAudience"], 46 | claims, 47 | expires: expiry, 48 | signingCredentials: creds 49 | ); 50 | 51 | return Ok(new LoginResult { Successful = true, Token = new JwtSecurityTokenHandler().WriteToken(token) }); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Pages/Register.razor: -------------------------------------------------------------------------------- 1 | @page "/register" 2 | @inject IAuthService AuthService 3 | @inject NavigationManager NavigationManager 4 | 5 |

Register

6 | 7 | @if (ShowErrors) 8 | { 9 | 15 | } 16 | 17 |
18 |
19 |
Please enter your details
20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 | @code { 45 | 46 | private RegisterModel RegisterModel = new RegisterModel(); 47 | private bool ShowErrors; 48 | private IEnumerable? Errors; 49 | 50 | private async Task HandleRegistration() 51 | { 52 | ShowErrors = false; 53 | 54 | var result = await AuthService.Register(RegisterModel); 55 | 56 | if (result.Successful) 57 | { 58 | NavigationManager.NavigateTo("/login"); 59 | } 60 | else 61 | { 62 | Errors = result.Errors; 63 | ShowErrors = true; 64 | } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using JwtLoginOnline.Server.Data; 3 | using Microsoft.AspNetCore.Authentication.JwtBearer; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.ResponseCompression; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.IdentityModel.Tokens; 9 | using System.Configuration; 10 | using System.Text; 11 | 12 | var builder = WebApplication.CreateBuilder(args); 13 | 14 | // Add services to the container. 15 | 16 | builder.Services.AddControllersWithViews(); 17 | builder.Services.AddRazorPages(); 18 | builder.Services.AddSwaggerGen(); 19 | builder.Services.AddBlazoredLocalStorage(); 20 | builder.Services.AddDbContext(options => 21 | options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); 22 | 23 | builder.Services.AddDefaultIdentity() 24 | .AddEntityFrameworkStores(); 25 | 26 | builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 27 | .AddJwtBearer(options => 28 | { 29 | options.TokenValidationParameters = new TokenValidationParameters 30 | { 31 | ValidateIssuer = true, 32 | ValidateAudience = true, 33 | ValidateLifetime = true, 34 | ValidateIssuerSigningKey = true, 35 | ValidIssuer = builder.Configuration["JwtIssuer"], 36 | ValidAudience = builder.Configuration["JwtAudience"], 37 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSecurityKey"]!)) 38 | }; 39 | }); 40 | var app = builder.Build(); 41 | 42 | // Configure the HTTP request pipeline. 43 | if (app.Environment.IsDevelopment()) 44 | { 45 | app.UseWebAssemblyDebugging(); 46 | } 47 | else 48 | { 49 | app.UseExceptionHandler("/Error"); 50 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 51 | app.UseHsts(); 52 | } 53 | 54 | app.UseHttpsRedirection(); 55 | 56 | app.UseBlazorFrameworkFiles(); 57 | app.UseStaticFiles(); 58 | app.UseSwaggerUI(); 59 | app.UseSwagger(); 60 | 61 | app.UseRouting(); 62 | 63 | app.UseAuthentication(); 64 | app.UseAuthorization(); 65 | app.MapRazorPages(); 66 | app.MapControllers(); 67 | app.MapFallbackToFile("index.html"); 68 | 69 | app.Run(); 70 | -------------------------------------------------------------------------------- /Service/AuthService.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using JwtLoginOnline.Client.Helpers; 3 | using Microsoft.AspNetCore.Components.Authorization; 4 | using System.Net.Http.Headers; 5 | using System.Net.Http.Json; 6 | using System.Text.Json; 7 | using System.Text; 8 | using JwtLoginOnline.Server.Models; 9 | using System.Net.Http; 10 | using System.Reflection; 11 | 12 | namespace JwtLoginOnline.Client.Service 13 | { 14 | public class AuthService : IAuthService 15 | { 16 | private readonly HttpClient _httpClient; 17 | private readonly AuthenticationStateProvider _authenticationStateProvider; 18 | private readonly ILocalStorageService _localStorage; 19 | 20 | public AuthService(HttpClient httpClient, 21 | AuthenticationStateProvider authenticationStateProvider, 22 | ILocalStorageService localStorage) 23 | { 24 | _httpClient = httpClient; 25 | _authenticationStateProvider = authenticationStateProvider; 26 | _localStorage = localStorage; 27 | } 28 | 29 | public async Task Register(RegisterModel registerModel) 30 | { 31 | var result = await _httpClient.PostAsJsonAsync("api/accounts", registerModel); 32 | if (!result.IsSuccessStatusCode) 33 | return new RegisterResult { Successful = true, Errors = null }; 34 | return new RegisterResult { Successful = false, Errors = new List { "Error occured"} }; 35 | } 36 | 37 | public async Task Login(LoginModel loginModel) 38 | { 39 | var loginAsJson = JsonSerializer.Serialize(loginModel); 40 | var response = await _httpClient.PostAsync("api/Login", new StringContent(loginAsJson, Encoding.UTF8, "application/json")); 41 | var loginResult = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); 42 | 43 | if (!response.IsSuccessStatusCode) 44 | { 45 | return loginResult!; 46 | } 47 | 48 | await _localStorage.SetItemAsync("authToken", loginResult!.Token); 49 | ((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email!); 50 | _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token); 51 | 52 | return loginResult; 53 | } 54 | 55 | public async Task Logout() 56 | { 57 | await _localStorage.RemoveItemAsync("authToken"); 58 | ((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsLoggedOut(); 59 | _httpClient.DefaultRequestHeaders.Authorization = null; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Helpers/ApiAuthenticationStateProvider.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using Microsoft.AspNetCore.Components.Authorization; 3 | using System.Net.Http.Headers; 4 | using System.Security.Claims; 5 | using System.Text.Json; 6 | 7 | namespace JwtLoginOnline.Client.Helpers 8 | { 9 | public class ApiAuthenticationStateProvider : AuthenticationStateProvider 10 | { 11 | private readonly HttpClient _httpClient; 12 | private readonly ILocalStorageService _localStorage; 13 | 14 | public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage) 15 | { 16 | _httpClient = httpClient; 17 | _localStorage = localStorage; 18 | } 19 | public override async Task GetAuthenticationStateAsync() 20 | { 21 | var savedToken = await _localStorage.GetItemAsync("authToken"); 22 | 23 | if (string.IsNullOrWhiteSpace(savedToken)) 24 | { 25 | return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); 26 | } 27 | 28 | _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken); 29 | 30 | return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt"))); 31 | } 32 | 33 | public void MarkUserAsAuthenticated(string email) 34 | { 35 | var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth")); 36 | var authState = Task.FromResult(new AuthenticationState(authenticatedUser)); 37 | NotifyAuthenticationStateChanged(authState); 38 | } 39 | 40 | public void MarkUserAsLoggedOut() 41 | { 42 | var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity()); 43 | var authState = Task.FromResult(new AuthenticationState(anonymousUser)); 44 | NotifyAuthenticationStateChanged(authState); 45 | } 46 | 47 | private IEnumerable ParseClaimsFromJwt(string jwt) 48 | { 49 | var claims = new List(); 50 | var payload = jwt.Split('.')[1]; 51 | var jsonBytes = ParseBase64WithoutPadding(payload); 52 | var keyValuePairs = JsonSerializer.Deserialize>(jsonBytes); 53 | 54 | keyValuePairs!.TryGetValue(ClaimTypes.Role, out object roles); 55 | 56 | if (roles != null) 57 | { 58 | if (roles.ToString()!.Trim().StartsWith("[")) 59 | { 60 | var parsedRoles = JsonSerializer.Deserialize(roles.ToString()!); 61 | 62 | foreach (var parsedRole in parsedRoles!) 63 | { 64 | claims.Add(new Claim(ClaimTypes.Role, parsedRole)); 65 | } 66 | } 67 | else 68 | { 69 | claims.Add(new Claim(ClaimTypes.Role, roles.ToString()!)); 70 | } 71 | 72 | keyValuePairs.Remove(ClaimTypes.Role); 73 | } 74 | 75 | claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()!))); 76 | 77 | return claims; 78 | } 79 | 80 | private byte[] ParseBase64WithoutPadding(string base64) 81 | { 82 | switch (base64.Length % 4) 83 | { 84 | case 2: base64 += "=="; break; 85 | case 3: base64 += "="; break; 86 | } 87 | return Convert.FromBase64String(base64); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Data/Migrations/20230217193849_CreateIdentitySchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace JwtLoginOnline.Server.Data.Migrations 7 | { 8 | /// 9 | public partial class CreateIdentitySchema : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.CreateTable( 15 | name: "AspNetRoles", 16 | columns: table => new 17 | { 18 | Id = table.Column(type: "nvarchar(450)", nullable: false), 19 | Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 20 | NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 21 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) 22 | }, 23 | constraints: table => 24 | { 25 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 26 | }); 27 | 28 | migrationBuilder.CreateTable( 29 | name: "AspNetUsers", 30 | columns: table => new 31 | { 32 | Id = table.Column(type: "nvarchar(450)", nullable: false), 33 | UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 34 | NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 35 | Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 36 | NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 37 | EmailConfirmed = table.Column(type: "bit", nullable: false), 38 | PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), 39 | SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), 40 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), 41 | PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), 42 | PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), 43 | TwoFactorEnabled = table.Column(type: "bit", nullable: false), 44 | LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), 45 | LockoutEnabled = table.Column(type: "bit", nullable: false), 46 | AccessFailedCount = table.Column(type: "int", nullable: false) 47 | }, 48 | constraints: table => 49 | { 50 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 51 | }); 52 | 53 | migrationBuilder.CreateTable( 54 | name: "AspNetRoleClaims", 55 | columns: table => new 56 | { 57 | Id = table.Column(type: "int", nullable: false) 58 | .Annotation("SqlServer:Identity", "1, 1"), 59 | RoleId = table.Column(type: "nvarchar(450)", nullable: false), 60 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 61 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) 62 | }, 63 | constraints: table => 64 | { 65 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 66 | table.ForeignKey( 67 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 68 | column: x => x.RoleId, 69 | principalTable: "AspNetRoles", 70 | principalColumn: "Id", 71 | onDelete: ReferentialAction.Cascade); 72 | }); 73 | 74 | migrationBuilder.CreateTable( 75 | name: "AspNetUserClaims", 76 | columns: table => new 77 | { 78 | Id = table.Column(type: "int", nullable: false) 79 | .Annotation("SqlServer:Identity", "1, 1"), 80 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 81 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 82 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) 83 | }, 84 | constraints: table => 85 | { 86 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 87 | table.ForeignKey( 88 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 89 | column: x => x.UserId, 90 | principalTable: "AspNetUsers", 91 | principalColumn: "Id", 92 | onDelete: ReferentialAction.Cascade); 93 | }); 94 | 95 | migrationBuilder.CreateTable( 96 | name: "AspNetUserLogins", 97 | columns: table => new 98 | { 99 | LoginProvider = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), 100 | ProviderKey = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), 101 | ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), 102 | UserId = table.Column(type: "nvarchar(450)", nullable: false) 103 | }, 104 | constraints: table => 105 | { 106 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 107 | table.ForeignKey( 108 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 109 | column: x => x.UserId, 110 | principalTable: "AspNetUsers", 111 | principalColumn: "Id", 112 | onDelete: ReferentialAction.Cascade); 113 | }); 114 | 115 | migrationBuilder.CreateTable( 116 | name: "AspNetUserRoles", 117 | columns: table => new 118 | { 119 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 120 | RoleId = table.Column(type: "nvarchar(450)", nullable: false) 121 | }, 122 | constraints: table => 123 | { 124 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 125 | table.ForeignKey( 126 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 127 | column: x => x.RoleId, 128 | principalTable: "AspNetRoles", 129 | principalColumn: "Id", 130 | onDelete: ReferentialAction.Cascade); 131 | table.ForeignKey( 132 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 133 | column: x => x.UserId, 134 | principalTable: "AspNetUsers", 135 | principalColumn: "Id", 136 | onDelete: ReferentialAction.Cascade); 137 | }); 138 | 139 | migrationBuilder.CreateTable( 140 | name: "AspNetUserTokens", 141 | columns: table => new 142 | { 143 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 144 | LoginProvider = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), 145 | Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), 146 | Value = table.Column(type: "nvarchar(max)", nullable: true) 147 | }, 148 | constraints: table => 149 | { 150 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 151 | table.ForeignKey( 152 | name: "FK_AspNetUserTokens_AspNetUsers_UserId", 153 | column: x => x.UserId, 154 | principalTable: "AspNetUsers", 155 | principalColumn: "Id", 156 | onDelete: ReferentialAction.Cascade); 157 | }); 158 | 159 | migrationBuilder.CreateIndex( 160 | name: "IX_AspNetRoleClaims_RoleId", 161 | table: "AspNetRoleClaims", 162 | column: "RoleId"); 163 | 164 | migrationBuilder.CreateIndex( 165 | name: "RoleNameIndex", 166 | table: "AspNetRoles", 167 | column: "NormalizedName", 168 | unique: true, 169 | filter: "[NormalizedName] IS NOT NULL"); 170 | 171 | migrationBuilder.CreateIndex( 172 | name: "IX_AspNetUserClaims_UserId", 173 | table: "AspNetUserClaims", 174 | column: "UserId"); 175 | 176 | migrationBuilder.CreateIndex( 177 | name: "IX_AspNetUserLogins_UserId", 178 | table: "AspNetUserLogins", 179 | column: "UserId"); 180 | 181 | migrationBuilder.CreateIndex( 182 | name: "IX_AspNetUserRoles_RoleId", 183 | table: "AspNetUserRoles", 184 | column: "RoleId"); 185 | 186 | migrationBuilder.CreateIndex( 187 | name: "EmailIndex", 188 | table: "AspNetUsers", 189 | column: "NormalizedEmail"); 190 | 191 | migrationBuilder.CreateIndex( 192 | name: "UserNameIndex", 193 | table: "AspNetUsers", 194 | column: "NormalizedUserName", 195 | unique: true, 196 | filter: "[NormalizedUserName] IS NOT NULL"); 197 | } 198 | 199 | /// 200 | protected override void Down(MigrationBuilder migrationBuilder) 201 | { 202 | migrationBuilder.DropTable( 203 | name: "AspNetRoleClaims"); 204 | 205 | migrationBuilder.DropTable( 206 | name: "AspNetUserClaims"); 207 | 208 | migrationBuilder.DropTable( 209 | name: "AspNetUserLogins"); 210 | 211 | migrationBuilder.DropTable( 212 | name: "AspNetUserRoles"); 213 | 214 | migrationBuilder.DropTable( 215 | name: "AspNetUserTokens"); 216 | 217 | migrationBuilder.DropTable( 218 | name: "AspNetRoles"); 219 | 220 | migrationBuilder.DropTable( 221 | name: "AspNetUsers"); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /Data/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using JwtLoginOnline.Server.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | #nullable disable 10 | 11 | namespace JwtLoginOnline.Server.Data.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "7.0.3") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 24 | 25 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 26 | { 27 | b.Property("Id") 28 | .HasColumnType("nvarchar(450)"); 29 | 30 | b.Property("ConcurrencyStamp") 31 | .IsConcurrencyToken() 32 | .HasColumnType("nvarchar(max)"); 33 | 34 | b.Property("Name") 35 | .HasMaxLength(256) 36 | .HasColumnType("nvarchar(256)"); 37 | 38 | b.Property("NormalizedName") 39 | .HasMaxLength(256) 40 | .HasColumnType("nvarchar(256)"); 41 | 42 | b.HasKey("Id"); 43 | 44 | b.HasIndex("NormalizedName") 45 | .IsUnique() 46 | .HasDatabaseName("RoleNameIndex") 47 | .HasFilter("[NormalizedName] IS NOT NULL"); 48 | 49 | b.ToTable("AspNetRoles", (string)null); 50 | }); 51 | 52 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 53 | { 54 | b.Property("Id") 55 | .ValueGeneratedOnAdd() 56 | .HasColumnType("int"); 57 | 58 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 59 | 60 | b.Property("ClaimType") 61 | .HasColumnType("nvarchar(max)"); 62 | 63 | b.Property("ClaimValue") 64 | .HasColumnType("nvarchar(max)"); 65 | 66 | b.Property("RoleId") 67 | .IsRequired() 68 | .HasColumnType("nvarchar(450)"); 69 | 70 | b.HasKey("Id"); 71 | 72 | b.HasIndex("RoleId"); 73 | 74 | b.ToTable("AspNetRoleClaims", (string)null); 75 | }); 76 | 77 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => 78 | { 79 | b.Property("Id") 80 | .HasColumnType("nvarchar(450)"); 81 | 82 | b.Property("AccessFailedCount") 83 | .HasColumnType("int"); 84 | 85 | b.Property("ConcurrencyStamp") 86 | .IsConcurrencyToken() 87 | .HasColumnType("nvarchar(max)"); 88 | 89 | b.Property("Email") 90 | .HasMaxLength(256) 91 | .HasColumnType("nvarchar(256)"); 92 | 93 | b.Property("EmailConfirmed") 94 | .HasColumnType("bit"); 95 | 96 | b.Property("LockoutEnabled") 97 | .HasColumnType("bit"); 98 | 99 | b.Property("LockoutEnd") 100 | .HasColumnType("datetimeoffset"); 101 | 102 | b.Property("NormalizedEmail") 103 | .HasMaxLength(256) 104 | .HasColumnType("nvarchar(256)"); 105 | 106 | b.Property("NormalizedUserName") 107 | .HasMaxLength(256) 108 | .HasColumnType("nvarchar(256)"); 109 | 110 | b.Property("PasswordHash") 111 | .HasColumnType("nvarchar(max)"); 112 | 113 | b.Property("PhoneNumber") 114 | .HasColumnType("nvarchar(max)"); 115 | 116 | b.Property("PhoneNumberConfirmed") 117 | .HasColumnType("bit"); 118 | 119 | b.Property("SecurityStamp") 120 | .HasColumnType("nvarchar(max)"); 121 | 122 | b.Property("TwoFactorEnabled") 123 | .HasColumnType("bit"); 124 | 125 | b.Property("UserName") 126 | .HasMaxLength(256) 127 | .HasColumnType("nvarchar(256)"); 128 | 129 | b.HasKey("Id"); 130 | 131 | b.HasIndex("NormalizedEmail") 132 | .HasDatabaseName("EmailIndex"); 133 | 134 | b.HasIndex("NormalizedUserName") 135 | .IsUnique() 136 | .HasDatabaseName("UserNameIndex") 137 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 138 | 139 | b.ToTable("AspNetUsers", (string)null); 140 | }); 141 | 142 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 143 | { 144 | b.Property("Id") 145 | .ValueGeneratedOnAdd() 146 | .HasColumnType("int"); 147 | 148 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 149 | 150 | b.Property("ClaimType") 151 | .HasColumnType("nvarchar(max)"); 152 | 153 | b.Property("ClaimValue") 154 | .HasColumnType("nvarchar(max)"); 155 | 156 | b.Property("UserId") 157 | .IsRequired() 158 | .HasColumnType("nvarchar(450)"); 159 | 160 | b.HasKey("Id"); 161 | 162 | b.HasIndex("UserId"); 163 | 164 | b.ToTable("AspNetUserClaims", (string)null); 165 | }); 166 | 167 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 168 | { 169 | b.Property("LoginProvider") 170 | .HasMaxLength(128) 171 | .HasColumnType("nvarchar(128)"); 172 | 173 | b.Property("ProviderKey") 174 | .HasMaxLength(128) 175 | .HasColumnType("nvarchar(128)"); 176 | 177 | b.Property("ProviderDisplayName") 178 | .HasColumnType("nvarchar(max)"); 179 | 180 | b.Property("UserId") 181 | .IsRequired() 182 | .HasColumnType("nvarchar(450)"); 183 | 184 | b.HasKey("LoginProvider", "ProviderKey"); 185 | 186 | b.HasIndex("UserId"); 187 | 188 | b.ToTable("AspNetUserLogins", (string)null); 189 | }); 190 | 191 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 192 | { 193 | b.Property("UserId") 194 | .HasColumnType("nvarchar(450)"); 195 | 196 | b.Property("RoleId") 197 | .HasColumnType("nvarchar(450)"); 198 | 199 | b.HasKey("UserId", "RoleId"); 200 | 201 | b.HasIndex("RoleId"); 202 | 203 | b.ToTable("AspNetUserRoles", (string)null); 204 | }); 205 | 206 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 207 | { 208 | b.Property("UserId") 209 | .HasColumnType("nvarchar(450)"); 210 | 211 | b.Property("LoginProvider") 212 | .HasMaxLength(128) 213 | .HasColumnType("nvarchar(128)"); 214 | 215 | b.Property("Name") 216 | .HasMaxLength(128) 217 | .HasColumnType("nvarchar(128)"); 218 | 219 | b.Property("Value") 220 | .HasColumnType("nvarchar(max)"); 221 | 222 | b.HasKey("UserId", "LoginProvider", "Name"); 223 | 224 | b.ToTable("AspNetUserTokens", (string)null); 225 | }); 226 | 227 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 228 | { 229 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 230 | .WithMany() 231 | .HasForeignKey("RoleId") 232 | .OnDelete(DeleteBehavior.Cascade) 233 | .IsRequired(); 234 | }); 235 | 236 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 237 | { 238 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 239 | .WithMany() 240 | .HasForeignKey("UserId") 241 | .OnDelete(DeleteBehavior.Cascade) 242 | .IsRequired(); 243 | }); 244 | 245 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 246 | { 247 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 248 | .WithMany() 249 | .HasForeignKey("UserId") 250 | .OnDelete(DeleteBehavior.Cascade) 251 | .IsRequired(); 252 | }); 253 | 254 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 255 | { 256 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 257 | .WithMany() 258 | .HasForeignKey("RoleId") 259 | .OnDelete(DeleteBehavior.Cascade) 260 | .IsRequired(); 261 | 262 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 263 | .WithMany() 264 | .HasForeignKey("UserId") 265 | .OnDelete(DeleteBehavior.Cascade) 266 | .IsRequired(); 267 | }); 268 | 269 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 270 | { 271 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 272 | .WithMany() 273 | .HasForeignKey("UserId") 274 | .OnDelete(DeleteBehavior.Cascade) 275 | .IsRequired(); 276 | }); 277 | #pragma warning restore 612, 618 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /Data/Migrations/20230217193849_CreateIdentitySchema.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using JwtLoginOnline.Server.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | #nullable disable 11 | 12 | namespace JwtLoginOnline.Server.Data.Migrations 13 | { 14 | [DbContext(typeof(ApplicationDbContext))] 15 | [Migration("20230217193849_CreateIdentitySchema")] 16 | partial class CreateIdentitySchema 17 | { 18 | /// 19 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 | { 21 | #pragma warning disable 612, 618 22 | modelBuilder 23 | .HasAnnotation("ProductVersion", "7.0.3") 24 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 25 | 26 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 27 | 28 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 29 | { 30 | b.Property("Id") 31 | .HasColumnType("nvarchar(450)"); 32 | 33 | b.Property("ConcurrencyStamp") 34 | .IsConcurrencyToken() 35 | .HasColumnType("nvarchar(max)"); 36 | 37 | b.Property("Name") 38 | .HasMaxLength(256) 39 | .HasColumnType("nvarchar(256)"); 40 | 41 | b.Property("NormalizedName") 42 | .HasMaxLength(256) 43 | .HasColumnType("nvarchar(256)"); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.HasIndex("NormalizedName") 48 | .IsUnique() 49 | .HasDatabaseName("RoleNameIndex") 50 | .HasFilter("[NormalizedName] IS NOT NULL"); 51 | 52 | b.ToTable("AspNetRoles", (string)null); 53 | }); 54 | 55 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 56 | { 57 | b.Property("Id") 58 | .ValueGeneratedOnAdd() 59 | .HasColumnType("int"); 60 | 61 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 62 | 63 | b.Property("ClaimType") 64 | .HasColumnType("nvarchar(max)"); 65 | 66 | b.Property("ClaimValue") 67 | .HasColumnType("nvarchar(max)"); 68 | 69 | b.Property("RoleId") 70 | .IsRequired() 71 | .HasColumnType("nvarchar(450)"); 72 | 73 | b.HasKey("Id"); 74 | 75 | b.HasIndex("RoleId"); 76 | 77 | b.ToTable("AspNetRoleClaims", (string)null); 78 | }); 79 | 80 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => 81 | { 82 | b.Property("Id") 83 | .HasColumnType("nvarchar(450)"); 84 | 85 | b.Property("AccessFailedCount") 86 | .HasColumnType("int"); 87 | 88 | b.Property("ConcurrencyStamp") 89 | .IsConcurrencyToken() 90 | .HasColumnType("nvarchar(max)"); 91 | 92 | b.Property("Email") 93 | .HasMaxLength(256) 94 | .HasColumnType("nvarchar(256)"); 95 | 96 | b.Property("EmailConfirmed") 97 | .HasColumnType("bit"); 98 | 99 | b.Property("LockoutEnabled") 100 | .HasColumnType("bit"); 101 | 102 | b.Property("LockoutEnd") 103 | .HasColumnType("datetimeoffset"); 104 | 105 | b.Property("NormalizedEmail") 106 | .HasMaxLength(256) 107 | .HasColumnType("nvarchar(256)"); 108 | 109 | b.Property("NormalizedUserName") 110 | .HasMaxLength(256) 111 | .HasColumnType("nvarchar(256)"); 112 | 113 | b.Property("PasswordHash") 114 | .HasColumnType("nvarchar(max)"); 115 | 116 | b.Property("PhoneNumber") 117 | .HasColumnType("nvarchar(max)"); 118 | 119 | b.Property("PhoneNumberConfirmed") 120 | .HasColumnType("bit"); 121 | 122 | b.Property("SecurityStamp") 123 | .HasColumnType("nvarchar(max)"); 124 | 125 | b.Property("TwoFactorEnabled") 126 | .HasColumnType("bit"); 127 | 128 | b.Property("UserName") 129 | .HasMaxLength(256) 130 | .HasColumnType("nvarchar(256)"); 131 | 132 | b.HasKey("Id"); 133 | 134 | b.HasIndex("NormalizedEmail") 135 | .HasDatabaseName("EmailIndex"); 136 | 137 | b.HasIndex("NormalizedUserName") 138 | .IsUnique() 139 | .HasDatabaseName("UserNameIndex") 140 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 141 | 142 | b.ToTable("AspNetUsers", (string)null); 143 | }); 144 | 145 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 146 | { 147 | b.Property("Id") 148 | .ValueGeneratedOnAdd() 149 | .HasColumnType("int"); 150 | 151 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 152 | 153 | b.Property("ClaimType") 154 | .HasColumnType("nvarchar(max)"); 155 | 156 | b.Property("ClaimValue") 157 | .HasColumnType("nvarchar(max)"); 158 | 159 | b.Property("UserId") 160 | .IsRequired() 161 | .HasColumnType("nvarchar(450)"); 162 | 163 | b.HasKey("Id"); 164 | 165 | b.HasIndex("UserId"); 166 | 167 | b.ToTable("AspNetUserClaims", (string)null); 168 | }); 169 | 170 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 171 | { 172 | b.Property("LoginProvider") 173 | .HasMaxLength(128) 174 | .HasColumnType("nvarchar(128)"); 175 | 176 | b.Property("ProviderKey") 177 | .HasMaxLength(128) 178 | .HasColumnType("nvarchar(128)"); 179 | 180 | b.Property("ProviderDisplayName") 181 | .HasColumnType("nvarchar(max)"); 182 | 183 | b.Property("UserId") 184 | .IsRequired() 185 | .HasColumnType("nvarchar(450)"); 186 | 187 | b.HasKey("LoginProvider", "ProviderKey"); 188 | 189 | b.HasIndex("UserId"); 190 | 191 | b.ToTable("AspNetUserLogins", (string)null); 192 | }); 193 | 194 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 195 | { 196 | b.Property("UserId") 197 | .HasColumnType("nvarchar(450)"); 198 | 199 | b.Property("RoleId") 200 | .HasColumnType("nvarchar(450)"); 201 | 202 | b.HasKey("UserId", "RoleId"); 203 | 204 | b.HasIndex("RoleId"); 205 | 206 | b.ToTable("AspNetUserRoles", (string)null); 207 | }); 208 | 209 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 210 | { 211 | b.Property("UserId") 212 | .HasColumnType("nvarchar(450)"); 213 | 214 | b.Property("LoginProvider") 215 | .HasMaxLength(128) 216 | .HasColumnType("nvarchar(128)"); 217 | 218 | b.Property("Name") 219 | .HasMaxLength(128) 220 | .HasColumnType("nvarchar(128)"); 221 | 222 | b.Property("Value") 223 | .HasColumnType("nvarchar(max)"); 224 | 225 | b.HasKey("UserId", "LoginProvider", "Name"); 226 | 227 | b.ToTable("AspNetUserTokens", (string)null); 228 | }); 229 | 230 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 231 | { 232 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 233 | .WithMany() 234 | .HasForeignKey("RoleId") 235 | .OnDelete(DeleteBehavior.Cascade) 236 | .IsRequired(); 237 | }); 238 | 239 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 240 | { 241 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 242 | .WithMany() 243 | .HasForeignKey("UserId") 244 | .OnDelete(DeleteBehavior.Cascade) 245 | .IsRequired(); 246 | }); 247 | 248 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 249 | { 250 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 251 | .WithMany() 252 | .HasForeignKey("UserId") 253 | .OnDelete(DeleteBehavior.Cascade) 254 | .IsRequired(); 255 | }); 256 | 257 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 258 | { 259 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 260 | .WithMany() 261 | .HasForeignKey("RoleId") 262 | .OnDelete(DeleteBehavior.Cascade) 263 | .IsRequired(); 264 | 265 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 266 | .WithMany() 267 | .HasForeignKey("UserId") 268 | .OnDelete(DeleteBehavior.Cascade) 269 | .IsRequired(); 270 | }); 271 | 272 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 273 | { 274 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 275 | .WithMany() 276 | .HasForeignKey("UserId") 277 | .OnDelete(DeleteBehavior.Cascade) 278 | .IsRequired(); 279 | }); 280 | #pragma warning restore 612, 618 281 | } 282 | } 283 | } 284 | --------------------------------------------------------------------------------