Peace 9 months ago
parent 1cef5c0063
commit 8f76544e4c
  1. 2
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/App.razor
  2. 2
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/MainLayout.razor
  3. 22
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Layout/NavMenu.razor
  4. 28
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Login.razor
  5. 8
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Components/Pages/Account/Logout.razor
  6. 1
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth.csproj
  7. 10
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Program.cs
  8. 9
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/IUserAccountService.cs
  9. 20
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/Services/UserAccountService.cs
  10. 76
      FluentBlazorAuth/FluentBlazorAuth/FluentBlazorAuth/States/CustomAuthenticationStateProvider.cs

@ -15,7 +15,7 @@
</head> </head>
<body> <body>
<Routes /> <Routes />
<script src="_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js" type="text/javascript"></script> <script src="_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js" type="text/javascript"></script>
<loading-theme storage-name="theme"></loading-theme> <loading-theme storage-name="theme"></loading-theme>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>

@ -28,7 +28,7 @@
</FluentFooter> </FluentFooter>
</FluentLayout> </FluentLayout>
<FluentToastProvider MaxToastCount="5" /> <FluentToastProvider MaxToastCount="5" @rendermode="RenderMode.InteractiveServer" />
<FluentDialogProvider /> <FluentDialogProvider />
<FluentTooltipProvider /> <FluentTooltipProvider />

@ -6,18 +6,18 @@
<nav class="sitenav" aria-labelledby="main-menu"> <nav class="sitenav" aria-labelledby="main-menu">
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="expanded" CustomToggle="true"> <FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="expanded" CustomToggle="true">
<FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink> <FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink>
</FluentNavMenu> <AuthorizeView Roles="Administrator,User">
<AuthorizeView Roles="Administrator,User"> <Authorized>
<Authorized> <FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink>
<FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink> </Authorized>
</Authorized> </AuthorizeView>
</AuthorizeView>
<AuthorizeView Roles="Administrator"> <AuthorizeView Roles="Administrator">
<Authorized> <Authorized>
<FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink> <FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink>
</Authorized> </Authorized>
</AuthorizeView> </AuthorizeView>
</FluentNavMenu>
</nav> </nav>
</div> </div>

@ -2,10 +2,14 @@
@using FluentBlazorAuth.Data @using FluentBlazorAuth.Data
@using FluentBlazorAuth.Models.ViewModels @using FluentBlazorAuth.Models.ViewModels
@using System.Security.Claims @using System.Security.Claims
@using FluentBlazorAuth.Services
@using FluentBlazorAuth.States
@using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Authentication.Cookies @using Microsoft.AspNetCore.Authentication.Cookies
@inject AppDbContext AppDbContext @inject AuthenticationStateProvider AuthenticationStateProvider
@inject IUserAccountService UserAccountService
@inject NavigationManager NavManager @inject NavigationManager NavManager
@rendermode RenderMode.InteractiveServer
<FluentGrid Class="mt-3" Justify="JustifyContent.Center"> <FluentGrid Class="mt-3" Justify="JustifyContent.Center">
<FluentGridItem xs="9" md="6" lg="3"> <FluentGridItem xs="9" md="6" lg="3">
@ -40,9 +44,6 @@
</FluentGrid> </FluentGrid>
@code { @code {
[CascadingParameter]
public HttpContext? HttpContext { get; set; }
[SupplyParameterFromForm] [SupplyParameterFromForm]
public LoginViewModel LoginViewModel { get; set; } public LoginViewModel LoginViewModel { get; set; }
@ -56,24 +57,15 @@
async Task OnLoginSubmit(EditContext context) async Task OnLoginSubmit(EditContext context)
{ {
var userAccount = AppDbContext.UserAccounts.Where(u => u.UserName == LoginViewModel.UserName).FirstOrDefault(); var authProvider = (CustomAuthenticationStateProvider)AuthenticationStateProvider;
if (userAccount is null || userAccount.Password != LoginViewModel.Password) var userAccount = UserAccountService.Authenticate(LoginViewModel.UserName, LoginViewModel.Password);
if (userAccount == null)
{ {
errorMessage = "Please login again."; errorMessage = "Invalid User account.";
await InvokeAsync(StateHasChanged);
return; return;
} }
var claims = new List<Claim> authProvider.MarkUserAsAuthenticated(userAccount);
{
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);
NavManager.NavigateTo("/"); NavManager.NavigateTo("/");
} }
} }

@ -1,6 +1,7 @@
@page "/logout" @page "/logout"
@using FluentBlazorAuth.States
@using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Authentication
@inject NavigationManager NavManager @inject AuthenticationStateProvider AuthenticationStateProvider
@rendermode InteractiveServer @rendermode InteractiveServer
<FluentStack Class="mt-3" Orientation="Orientation.Vertical" VerticalGap="10" HorizontalAlignment="HorizontalAlignment.Center"> <FluentStack Class="mt-3" Orientation="Orientation.Vertical" VerticalGap="10" HorizontalAlignment="HorizontalAlignment.Center">
@ -17,10 +18,9 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
if (HttpContext.User.Identity.IsAuthenticated)
await HttpContext.SignOutAsync();
NavManager.NavigateTo("/logout", true); var authProvider = (CustomAuthenticationStateProvider)AuthenticationStateProvider;
authProvider.MarkUserAsLoggedOut();
} }
} }

@ -7,7 +7,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

@ -3,7 +3,9 @@ using FluentBlazorAuth.Components;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using FluentBlazorAuth.Data; using FluentBlazorAuth.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Blazored.LocalStorage; using FluentBlazorAuth.Services;
using FluentBlazorAuth.States;
using Microsoft.AspNetCore.Components.Authorization;
namespace FluentBlazorAuth; namespace FluentBlazorAuth;
@ -31,8 +33,10 @@ public class Program
builder.Services.AddCascadingAuthenticationState(); builder.Services.AddCascadingAuthenticationState();
#endregion #endregion
#region LocalStorage #region AuthTest
builder.Services.AddBlazoredLocalStorage(); builder.Services.AddScoped<IUserAccountService, UserAccountService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddHttpContextAccessor();
#endregion #endregion
#region Database #region Database

@ -0,0 +1,9 @@
using FluentBlazorAuth.Models;
namespace FluentBlazorAuth.Services
{
public interface IUserAccountService
{
UserAccount? Authenticate(string username, string password);
}
}

@ -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);
}
}
}

@ -1,25 +1,83 @@
using Blazored.LocalStorage; using FluentBlazorAuth.Models;
using FluentBlazorAuth.Services;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Http;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Json;
namespace FluentBlazorAuth.States namespace FluentBlazorAuth.States
{ {
public class CustomAuthenticationStateProvider : AuthenticationStateProvider 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<AuthenticationState> 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<AuthenticationState> GetAuthenticationStateAsync() public void MarkUserAsAuthenticated(UserAccount userAccount)
{ {
var token = await _localStorage.GetItemAsync<string>("authToken"); _currentUser = userAccount;
if (string.IsNullOrWhiteSpace(token)) if (_currentUser != null)
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); {
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<UserAccount>(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);
} }
} }
} }

Loading…
Cancel
Save