diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/App.razor b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/App.razor index f1f9f4c..3537306 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/App.razor +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/App.razor @@ -15,7 +15,7 @@ - + diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/MainLayout.razor b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/MainLayout.razor index 5939f45..f126709 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/MainLayout.razor +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/MainLayout.razor @@ -28,7 +28,7 @@ - + diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/NavMenu.razor b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/NavMenu.razor index 1dde764..0815038 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/NavMenu.razor +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/NavMenu.razor @@ -6,18 +6,18 @@ diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Login.razor b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Login.razor index 937be53..e9283fd 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Login.razor +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Login.razor @@ -2,10 +2,14 @@ @using FluentBlazorAuth.Data @using FluentBlazorAuth.Models.ViewModels @using System.Security.Claims +@using FluentBlazorAuth.Services +@using FluentBlazorAuth.States @using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Authentication.Cookies -@inject AppDbContext AppDbContext +@inject AuthenticationStateProvider AuthenticationStateProvider +@inject IUserAccountService UserAccountService @inject NavigationManager NavManager +@rendermode RenderMode.InteractiveServer @@ -40,9 +44,6 @@ @code { - [CascadingParameter] - public HttpContext? HttpContext { get; set; } - [SupplyParameterFromForm] public LoginViewModel LoginViewModel { get; set; } @@ -56,24 +57,15 @@ async Task OnLoginSubmit(EditContext context) { - var userAccount = AppDbContext.UserAccounts.Where(u => u.UserName == LoginViewModel.UserName).FirstOrDefault(); - if (userAccount is null || userAccount.Password != LoginViewModel.Password) + var authProvider = (CustomAuthenticationStateProvider)AuthenticationStateProvider; + var userAccount = UserAccountService.Authenticate(LoginViewModel.UserName, LoginViewModel.Password); + if (userAccount == null) { - errorMessage = "Please login again."; - await InvokeAsync(StateHasChanged); + errorMessage = "Invalid User account."; return; } - var claims = new List - { - new Claim(ClaimTypes.Name, LoginViewModel.UserName), - new Claim(ClaimTypes.Role, userAccount.Role) - }; - - var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - var principal = new ClaimsPrincipal(identity); - await HttpContext.SignInAsync(principal); - + authProvider.MarkUserAsAuthenticated(userAccount); NavManager.NavigateTo("/"); } } \ No newline at end of file diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Logout.razor b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Logout.razor index be72528..996982e 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Logout.razor +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Logout.razor @@ -1,6 +1,7 @@ @page "/logout" +@using FluentBlazorAuth.States @using Microsoft.AspNetCore.Authentication -@inject NavigationManager NavManager +@inject AuthenticationStateProvider AuthenticationStateProvider @rendermode InteractiveServer @@ -17,10 +18,9 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - if (HttpContext.User.Identity.IsAuthenticated) - await HttpContext.SignOutAsync(); - NavManager.NavigateTo("/logout", true); + var authProvider = (CustomAuthenticationStateProvider)AuthenticationStateProvider; + authProvider.MarkUserAsLoggedOut(); } } diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth.csproj b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth.csproj index 8d8c85f..b015a6c 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth.csproj +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth.csproj @@ -7,7 +7,6 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Program.cs b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Program.cs index 60ff998..60a9753 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Program.cs +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Program.cs @@ -3,7 +3,9 @@ using FluentBlazorAuth.Components; using Microsoft.AspNetCore.Authentication.Cookies; using FluentBlazorAuth.Data; using Microsoft.EntityFrameworkCore; -using Blazored.LocalStorage; +using FluentBlazorAuth.Services; +using FluentBlazorAuth.States; +using Microsoft.AspNetCore.Components.Authorization; namespace FluentBlazorAuth; @@ -31,8 +33,10 @@ public class Program builder.Services.AddCascadingAuthenticationState(); #endregion - #region LocalStorage - builder.Services.AddBlazoredLocalStorage(); + #region AuthTest + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddHttpContextAccessor(); #endregion #region Database diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/IUserAccountService.cs b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/IUserAccountService.cs new file mode 100644 index 0000000..700051d --- /dev/null +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/IUserAccountService.cs @@ -0,0 +1,9 @@ +using FluentBlazorAuth.Models; + +namespace FluentBlazorAuth.Services +{ + public interface IUserAccountService + { + UserAccount? Authenticate(string username, string password); + } +} diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/UserAccountService.cs b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/UserAccountService.cs new file mode 100644 index 0000000..2cf2400 --- /dev/null +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/UserAccountService.cs @@ -0,0 +1,20 @@ +using FluentBlazorAuth.Data; +using FluentBlazorAuth.Models; + +namespace FluentBlazorAuth.Services +{ + public class UserAccountService : IUserAccountService + { + private AppDbContext _appDbContext; + + public UserAccountService(AppDbContext appDbContext) + { + _appDbContext = appDbContext; + } + + public UserAccount? Authenticate(string username, string password) + { + return _appDbContext.UserAccounts.SingleOrDefault(u => u.UserName == username && u.Password == password); + } + } +} diff --git a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/States/CustomAuthenticationStateProvider.cs b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/States/CustomAuthenticationStateProvider.cs index d23a610..9f036c9 100644 --- a/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/States/CustomAuthenticationStateProvider.cs +++ b/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/States/CustomAuthenticationStateProvider.cs @@ -1,25 +1,83 @@ -using Blazored.LocalStorage; +using FluentBlazorAuth.Models; +using FluentBlazorAuth.Services; using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Http; using System.Security.Claims; +using System.Text.Json; namespace FluentBlazorAuth.States { public class CustomAuthenticationStateProvider : AuthenticationStateProvider { - private readonly ILocalStorageService _localStorage; + private readonly IUserAccountService _userAccountService; + private readonly IHttpContextAccessor _httpContextAccessor; - public CustomAuthenticationStateProvider(ILocalStorageService localStorage) + private const string UserCookieKey = "UserCookie"; + private UserAccount? _currentUser; + + + public CustomAuthenticationStateProvider(IUserAccountService userAccountService, IHttpContextAccessor httpContextAccessor) + { + _userAccountService = userAccountService; + _httpContextAccessor = httpContextAccessor; + + LoadUserFromCookie(); + } + + public override Task GetAuthenticationStateAsync() { - _localStorage = localStorage; + var identity = _currentUser != null + ? new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.Name, _currentUser.UserName), + new Claim(ClaimTypes.Role, _currentUser.Role) + }, "apiauth_type") + : new ClaimsIdentity(); + + var claimPrincipal = new ClaimsPrincipal(identity); + + return Task.FromResult(new AuthenticationState(claimPrincipal)); } - public override async Task GetAuthenticationStateAsync() + public void MarkUserAsAuthenticated(UserAccount userAccount) { - var token = await _localStorage.GetItemAsync("authToken"); - if (string.IsNullOrWhiteSpace(token)) - return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); + _currentUser = userAccount; + if (_currentUser != null) + { + SaveUserCookie(); + NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); + } + } - return null; + public void MarkUserAsLoggedOut() + { + _currentUser = null; + RemoveUserCookie(); + NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); + } + + private void LoadUserFromCookie() + { + if (_httpContextAccessor.HttpContext?.Request.Cookies.TryGetValue(UserCookieKey, out var jsonUser) == true) + { + _currentUser = JsonSerializer.Deserialize(jsonUser); + } + } + + private void SaveUserCookie() + { + _currentUser.Password = null; + var jsonUser = JsonSerializer.Serialize(_currentUser); + _httpContextAccessor.HttpContext?.Response.Cookies.Append(UserCookieKey, jsonUser, new CookieOptions + { + HttpOnly = true, + Expires = DateTimeOffset.Now.AddMinutes(5) + }); + } + + private void RemoveUserCookie() + { + _httpContextAccessor.HttpContext?.Response.Cookies.Delete(UserCookieKey); } } }