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>
<body>
<Routes />
<Routes />
<script src="_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js" type="text/javascript"></script>
<loading-theme storage-name="theme"></loading-theme>
<script src="_framework/blazor.web.js"></script>

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

@ -6,18 +6,18 @@
<nav class="sitenav" aria-labelledby="main-menu">
<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>
</FluentNavMenu>
<AuthorizeView Roles="Administrator,User">
<Authorized>
<FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="Administrator,User">
<Authorized>
<FluentNavLink Href="counter" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Counter</FluentNavLink>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="Administrator">
<Authorized>
<FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="Administrator">
<Authorized>
<FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink>
</Authorized>
</AuthorizeView>
</FluentNavMenu>
</nav>
</div>

@ -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
<FluentGrid Class="mt-3" Justify="JustifyContent.Center">
<FluentGridItem xs="9" md="6" lg="3">
@ -40,9 +44,6 @@
</FluentGrid>
@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<Claim>
{
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("/");
}
}

@ -1,6 +1,7 @@
@page "/logout"
@using FluentBlazorAuth.States
@using Microsoft.AspNetCore.Authentication
@inject NavigationManager NavManager
@inject AuthenticationStateProvider AuthenticationStateProvider
@rendermode InteractiveServer
<FluentStack Class="mt-3" Orientation="Orientation.Vertical" VerticalGap="10" HorizontalAlignment="HorizontalAlignment.Center">
@ -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();
}
}

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

@ -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<IUserAccountService, UserAccountService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddHttpContextAccessor();
#endregion
#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.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<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");
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<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