Auth System

This commit is contained in:
Andre Beging
2022-04-04 10:04:19 +02:00
parent 77382944eb
commit 427b924759
14 changed files with 552 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>

View File

@@ -0,0 +1,133 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
namespace FoodsharingSiegen.Server.Auth
{
/// <summary>
/// The auth helper class (a. beging, 04.04.2022)
/// </summary>
public static class AuthHelper
{
#region Public Method Decrypt
/// <summary>
/// Decrypts the crypted text (a. beging, 04.04.2022)
/// </summary>
/// <param name="cryptedText">The crypted text</param>
/// <returns>The string</returns>
public static string Decrypt(string cryptedText)
{
CreateAlgorithm(out var tripleDes);
var toEncryptArray = Convert.FromBase64String(cryptedText);
var cTransform = tripleDes.CreateDecryptor();
var resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
tripleDes.Clear();
return Encoding.UTF8.GetString(resultArray);
}
#endregion
#region Public Method Encrypt
/// <summary>
/// Encrypts the plain text (a. beging, 04.04.2022)
/// </summary>
/// <param name="plainText">The plain text</param>
/// <returns>The string</returns>
public static string Encrypt(string plainText)
{
CreateAlgorithm(out var tripleDes);
var toEncryptArray = Encoding.UTF8.GetBytes(plainText );
var cTransform = tripleDes.CreateEncryptor();
var resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
tripleDes.Clear();
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
#endregion
#region Public Method GetSigningKey
/// <summary>
/// Gets the signing key (a. beging, 04.04.2022)
/// </summary>
/// <returns>The security key</returns>
public static SecurityKey GetSigningKey() => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SigningKey));
#endregion
#region Public Method ValidateToken
/// <summary>
/// Validates the token using the specified token (a. beging, 04.04.2022)
/// </summary>
/// <param name="token">The token</param>
/// <returns>A task containing the bool</returns>
public static async Task<bool> ValidateToken(string? token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var result = await tokenHandler.ValidateTokenAsync(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = GetSigningKey(),
ValidateAudience = true,
ValidAudience = "FS-Siegen",
ValidateIssuer = true,
ValidIssuer = "FS-Siegen"
});
return result.IsValid;
}
catch (Exception e)
{
return false;
}
}
#endregion
#region Private Method CreateAlgorithm
/// <summary>
/// Creates the algorithm using the specified triple des (a. beging, 04.04.2022)
/// </summary>
/// <param name="tripleDes">The triple des</param>
private static void CreateAlgorithm(out TripleDES tripleDes)
{
var md5 = MD5.Create();
var keyArray = md5.ComputeHash(Encoding.UTF8.GetBytes(SigningKey));
md5.Clear();
tripleDes = TripleDES.Create();
tripleDes.Key = keyArray;
tripleDes.Mode = CipherMode.ECB;
tripleDes.Padding = PaddingMode.PKCS7;
}
#endregion
/// <summary>
/// The signing key
/// </summary>
public const string SigningKey = "2uasw2§$%1nd47n9s43&%Zs3529s23&/%AW";
}
}

View File

@@ -0,0 +1,147 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using FoodsharingSiegen.Contracts;
using FoodsharingSiegen.Contracts.Entity;
using FoodsharingSiegen.Server.Data;
using FoodsharingSiegen.Server.Data.Service;
using FoodsharingSiegen.Server.Service;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
namespace FoodsharingSiegen.Server.Auth
{
/// <summary>
/// The auth service class (a. beging, 04.04.2022)
/// </summary>
/// <seealso cref="ServiceBase"/>
public class AuthService : ServiceBase
{
#region Public Properties
/// <summary>
/// Gets or sets the value of the user (ab)
/// </summary>
public User? User { get; set; }
#endregion
#region Private Fields
/// <summary>
/// The authentication state provider
/// </summary>
private readonly AuthenticationStateProvider _authenticationStateProvider;
/// <summary>
/// The local storage service
/// </summary>
private readonly LocalStorageService _localStorageService;
#endregion
#region Setup/Teardown
/// <summary>
/// Initializes a new instance of the <see cref="AuthService"/> class
/// </summary>
/// <param name="context">The context</param>
/// <param name="localStorageService">The local storage service</param>
/// <param name="authenticationStateProvider">The authentication state provider</param>
public AuthService(FsContext context, LocalStorageService localStorageService, AuthenticationStateProvider authenticationStateProvider) : base(context)
{
_localStorageService = localStorageService;
_authenticationStateProvider = authenticationStateProvider;
}
#endregion
#region Public Method Login
/// <summary>
/// Logins the mail address (a. beging, 04.04.2022)
/// </summary>
/// <param name="mailAddress">The mail address</param>
/// <param name="password">The password</param>
/// <returns>A task containing the operation result</returns>
public async Task<OperationResult> Login(string mailAddress, string password)
{
#region Ensure Admin
var existingTroogS = await Context.Users.AnyAsync(x => x.Mail == "fs@beging.de");
if (!existingTroogS)
{
var troogs = new User
{
Name = "Andre",
Mail = "fs@beging.de",
Type = UserType.Admin,
Created = DateTime.UtcNow,
EncryptedPassword = "qSIxTZo7J8M="
};
await Context.Users.AddAsync(troogs);
await Context.SaveChangesAsync();
}
#endregion Ensure Admin
var encryptedPassword = AuthHelper.Encrypt(password);
User = await Context.Users.FirstOrDefaultAsync(x => x.Mail.ToLower() == mailAddress.ToLower() && x.EncryptedPassword == encryptedPassword);
if (User != null)
{
// Daten korrekt
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, User.Id.ToString()),
}),
Expires = DateTime.UtcNow.AddDays(30),
Issuer = "FS-Siegen",
Audience = "FS-Siegen",
SigningCredentials = new SigningCredentials(AuthHelper.GetSigningKey(), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var serializedToken = tokenHandler.WriteToken(token);
await _localStorageService.SetItem(StorageKeys.TokenKey, serializedToken);
return new OperationResult();
}
return new OperationResult(new Exception("Invalid"));
}
#endregion
#region Public Method Logout
/// <summary>
/// Logouts this instance (a. beging, 04.04.2022)
/// </summary>
/// <returns>A task containing the operation result</returns>
public async Task<OperationResult> Logout()
{
try
{
await _localStorageService.RemoveItem(StorageKeys.TokenKey);
User = null;
((TokenAuthStateProvider) _authenticationStateProvider).MarkUserAsLoggedOut();
return new OperationResult();
}
catch (Exception e)
{
return new OperationResult(e);
}
}
#endregion
}
}

View File

@@ -0,0 +1,78 @@
using System.Text.Json;
using Microsoft.JSInterop;
namespace FoodsharingSiegen.Server.Service
{
/// <summary>
/// The local storage service class (a. beging, 02.04.2022)
/// </summary>
public class LocalStorageService
{
#region Private Fields
/// <summary>
/// The js runtime
/// </summary>
private readonly IJSRuntime _jsRuntime;
#endregion
#region Setup/Teardown
/// <summary>
/// Constructor
/// </summary>
/// <param name="jsRuntime"></param>
public LocalStorageService(IJSRuntime jsRuntime) => _jsRuntime = jsRuntime;
#endregion
#region Public Method GetItem
/// <summary>
/// Ein Item aus dem LocalStorage laden
/// </summary>
/// <param name="key">Der Key des Items</param>
/// <typeparam name="T">Typ des Item</typeparam>
/// <returns></returns>
public async Task<T?> GetItem<T>(string key)
{
var json = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", key);
if (json == null)
return default;
return JsonSerializer.Deserialize<T>(json);
}
#endregion
#region Public Method RemoveItem
/// <summary>
/// Ein Item aus dem LocalStorage löschen
/// </summary>
/// <param name="key">Der Key des Items</param>
public async Task RemoveItem(string key)
{
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", key);
}
#endregion
#region Public Method SetItem
/// <summary>
/// Ein Item in den LocalStorage schreiben
/// </summary>
/// <param name="key">Der Key des Items</param>
/// <param name="value">Das Item</param>
/// <typeparam name="T">Typ des Item</typeparam>
public async Task SetItem<T>(string key, T value)
{
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", key, JsonSerializer.Serialize(value));
}
#endregion
}
}

View File

@@ -0,0 +1,79 @@
using System.Security.Claims;
using FoodsharingSiegen.Contracts;
using FoodsharingSiegen.Server.Auth;
using Microsoft.AspNetCore.Components.Authorization;
namespace FoodsharingSiegen.Server.Service
{
/// <summary>
/// The token auth state provider class (a. beging, 02.04.2022)
/// </summary>
/// <seealso cref="AuthenticationStateProvider"/>
public class TokenAuthStateProvider : AuthenticationStateProvider
{
#region Private Fields
/// <summary> LocalStorageService </summary>
private readonly LocalStorageService _localStorageService;
#endregion
#region Setup/Teardown
/// <summary>
/// Constructor
/// </summary>
/// <param name="localStorageService"></param>
public TokenAuthStateProvider(LocalStorageService localStorageService) => _localStorageService = localStorageService;
#endregion
#region Override GetAuthenticationStateAsync
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Get the current authenticationstate </summary>
/// <remarks> A. Beging, 02.02.2022. </remarks>
////////////////////////////////////////////////////////////////////////////////////////////////////
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _localStorageService.GetItem<string>(StorageKeys.TokenKey);
var tokenValid = await AuthHelper.ValidateToken(token);
var identity = new ClaimsIdentity();
if (tokenValid)
identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "user")
}, "TODO");
var claimsPrincipal = new ClaimsPrincipal(identity);
return new AuthenticationState(claimsPrincipal);
}
#endregion
#region Public Method MarkUserAsAuthenticated
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Mark user as authenticated. </summary>
/// <remarks> A. Beging, 02.02.2022. </remarks>
////////////////////////////////////////////////////////////////////////////////////////////////////
public void MarkUserAsAuthenticated() => NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
#endregion
#region Public Method MarkUserAsLoggedOut
/// <summary>
/// Marks the user as logged out (a. beging, 02.04.2022)
/// </summary>
public void MarkUserAsLoggedOut()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
#endregion
}
}

View File

@@ -0,0 +1,29 @@
@page "/login"
@using FoodsharingSiegen.Server.Service
@using FoodsharingSiegen.Server.Auth
@layout LoginLayout
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@code
{
private string? Mailaddress { get; set; }
private string? Password { get; set; }
private async Task PerformLogin()
{
//Todo Eingaben Validieren [04.04.22 - Andre Beging]
var loginR = await AuthService.Login(Mailaddress, Password);
if (loginR.Success)
{
NavigationManager.NavigateTo("/", true);
}
}
}
<TextEdit @bind-Text="Mailaddress" Placeholder="Mail"></TextEdit>
<TextEdit @bind-Text="Password" Placeholder="Password"></TextEdit>
<Button Clicked="PerformLogin">Go</Button>

View File

@@ -0,0 +1,17 @@
@layout LoginLayout
@page "/logout"
@using FoodsharingSiegen.Server.Service
@using FoodsharingSiegen.Server.Auth
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@code {
protected override async Task OnInitializedAsync()
{
var logoutR = await AuthService.Logout();
if(logoutR.Success) NavigationManager.NavigateTo("/");
}
}

View File

@@ -5,4 +5,4 @@
Layout = "_Layout";
}
<component type="typeof(App)" render-mode="ServerPrerendered"/>
<component type="typeof(App)" render-mode="Server"/>

View File

@@ -1,8 +1,11 @@
using Blazorise;
using Blazorise.Icons.Material;
using Blazorise.Material;
using FoodsharingSiegen.Server.Auth;
using FoodsharingSiegen.Server.Data;
using FoodsharingSiegen.Server.Data.Service;
using FoodsharingSiegen.Server.Service;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
@@ -15,7 +18,11 @@ builder.Services.AddDbContextFactory<FsContext>(opt =>
opt.UseSqlite($"Data Source=app.db"));
// DI
builder.Services.AddScoped<LocalStorageService>();
builder.Services.AddScoped<AuthenticationStateProvider, TokenAuthStateProvider>();
builder.Services.AddScoped<FsContext>();
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<ProspectService>();

View File

@@ -0,0 +1,11 @@
@inherits LayoutComponentBase
<PageTitle>Login</PageTitle>
<div class="page">
<main>
<article class="content px-4">
@Body
</article>
</main>
</div>

View File

@@ -2,18 +2,25 @@
<PageTitle>FoodsharingSiegen.Server</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu/>
</div>
<AuthorizeView>
<Authorized>
<div class="page">
<div class="sidebar">
<NavMenu/>
</div>
<main>
<div class="top-row px-4">
<main>
<div class="top-row px-4">
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
</Authorized>
<NotAuthorized>
<RedirectToLogin/>
</NotAuthorized>
</AuthorizeView>

View File

@@ -19,9 +19,13 @@
<span class="fas fa-users mr-1" aria-hidden="true" style="font-size: 1.4em;"></span> Benutzer
</NavLink>
</div>
<div class="nav-item px-3 pt-5">
<NavLink class="nav-link" href="logout" Match="NavLinkMatch.All">
<span class="fa-solid fa-door-open mr-1" aria-hidden="true" style="font-size: 1.4em;"></span> Ausloggen
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true;

View File

@@ -0,0 +1,10 @@
@inject NavigationManager NavigationManager
@code
{
protected override async Task OnInitializedAsync()
{
NavigationManager.NavigateTo("/login");
await base.OnInitializedAsync();
}
}