YGAuthentication 1.2.3
dotnet add package YGAuthentication --version 1.2.3
NuGet\Install-Package YGAuthentication -Version 1.2.3
<PackageReference Include="YGAuthentication" Version="1.2.3" />
paket add YGAuthentication --version 1.2.3
#r "nuget: YGAuthentication, 1.2.3"
// Install YGAuthentication as a Cake Addin #addin nuget:?package=YGAuthentication&version=1.2.3 // Install YGAuthentication as a Cake Tool #tool nuget:?package=YGAuthentication&version=1.2.3
YGAuthentication
A working Authentication / Authorization functionality for Blazor apps using JWT Claims. With WebAPI or Without
Authentication with BCrypt one way encryption to api/loginEncr. Without encryption: api/login
Authentication Direct without WebAPI with encryption: _authEncr.Authenticate. Without encryption: _auth.Authenticate
where _authEncr and _auth are instances of AuthorizationJWTEncrypted and AuthorizationJWT, respectively. See program.cs for
Dependency Injection instatiation of these classes.
Host your own IDS, no need to rely on external services. If you need help please hit me up. cs@yogigrantz.com
Installation
Include this package in the project file
Be sure to follow the working POC example. The source code includes POC Blazor Web app.
Usage
.csproj file:
<PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup>
<ItemGroup> <PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="YGAuthentication" Version="1.2.3" /> </ItemGroup>
AppSettings.json:
{
"AuthName": "AnyNameOfYourChoice",
"LoginPage": "/Login"
}
program.cs:
using BlazorApp3.Components;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using YGAuthentication.ActionFilters;
using YGAuthentication.Auth;
using YGAuthentication.States;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
IConfiguration config = builder.Configuration;
string authName = config.GetValue<string>("AuthName");
string loginPage = config.GetValue<string>("LoginPage");
builder.Services.AddHttpClient(authName);
builder.Services.AddControllers();
builder.Services.AddBlazoredLocalStorage();
//sample one username and password. In reality you would populate the dictionary with username and pwd from IDS
string username = "yogi";
string pwd = "123";
int jwtExpMinutes = 60;
builder.Services.AddScoped<IAuthorizationJWT, AuthorizationJWT>(_ => {
Dictionary<string, string> userCreds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
userCreds.Add(username, pwd); // Populate this with username and password from DB. The password is clear text
return new AuthorizationJWT(userCreds, "username", jwtExpMinutes, "YourCompanyName", "General Audience");
});
builder.Services.AddScoped<IAuthorizationJWTEncrypted, AuthorizationJWTEncrypted>(_ => {
Dictionary<string, string> userCredsHashed = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
string pwdEncrypted = BCrypt.Net.BCrypt.HashPassword(pwd);
userCredsHashed.Add(username, pwdEncrypted); // Populate this with username and password from DB. The password is BCrypt-hashed
return new AuthorizationJWTEncrypted(userCredsHashed, "username", jwtExpMinutes, "YourCompanyName", "General Audience");
});
builder.Services.AddScoped<IAppState, AppState>(_ => new AppState());
builder.Services.AddScoped<BasicAuthentication>();
builder.Services.AddScoped<IPageAuthCheck, PageAuthCheck>(ioc => new PageAuthCheck(ioc.GetService<NavigationManager>(), ioc.GetService<IHttpClientFactory>(), ioc.GetService<ILocalStorageService>(), authName, loginPage));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "mvc",
pattern: "{controller}/{action}/{id?}");
app.UseAntiforgery();
app.MapControllers();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
Login Page - UI:
@page "/Login"
@rendermode InteractiveServer
@using Blazored.LocalStorage
@using YGAuthentication;
@using YGAuthentication.States
@using YGAuthentication.Auth
@inject NavigationManager _navmgr
@inject ILocalStorageService _localStorage
@inject IHttpClientFactory _iHttpClientFactory
@inject IAppState _appstate
@inject IConfiguration _config
@inject IAuthorizationJWT _auth
@inject IAuthorizationJWTEncrypted _authEncr
<div class="clearfix">
<div class="floatLeft">Username</div>
<div class="floatLeft"><input type="text" @bind="Username" /></div>
</div>
<div class="clearfix">
<div class="floatLeft">Password</div>
<div class="floatLeft"><input type="password" @bind="Password" /></div>
</div>
<br />
<div class="clearfix">
<button class="btn btn-primary" @onclick="ButtonLoginAuthDirect">Login Direct</button> |
<button class="btn btn-primary" @onclick="ButtonLoginAuthDirectWithEncryption">Login Direct Encrypted</button> <br /><br />
<button class="btn btn-dark" @onclick="ButtonLoginAuthWithWebAPI">Login With WebAPI</button> |
<button class="btn btn-dark" @onclick="ButtonLoginAuthWithWebAPIEncr">Login With WebAPI Encrypted</button>
</div>
@if (Processing)
{
<div>
<img src="ajax-loader.gif" />
</div>
}
<div class="clearfix top20">
@Message
</div>
Login Page - Code Behind:
using Newtonsoft.Json;
using System.Text;
using YGAuthentication;
namespace BlazorApp3.Components.Pages;
public partial class Login
{
public bool Processing { get; private set; }
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public string Message { get; set; } = "";
protected override void OnInitialized()
{
base.OnInitialized();
Message = "Please log in";
this.StateHasChanged();
}
public async Task ButtonLoginAuthWithWebAPI()
{
try
{
Processing = true;
this.Message = "Please wait ..";
this.StateHasChanged();
string authName = _config.GetValue<string>("AuthName");
using (HttpClient httpclient = _iHttpClientFactory.CreateClient())
{
LoginDTO login = new LoginDTO() { Username = Username, Password = Password };
string jsonPayload = JsonConvert.SerializeObject(login);
HttpContent payload = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
//var result = await httpclient.PostAsync($"{_navmgr.BaseUri}api/login", payload); // When the passwords in IDS is clear text
var result = await httpclient.PostAsync($"{_navmgr.BaseUri}api/login", payload); // When the passwords in IDS is encrypted
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
string jwt = await result.Content.ReadAsStringAsync();
await _localStorage.SetItemAsStringAsync(authName, jwt);
_appstate.LoggedIn = true;
_navmgr.NavigateTo("/");
}
else
{
this.Message = result.StatusCode.ToString();
}
}
}
catch (Exception ex)
{
this.Message = ex.Message;
}
Processing = false;
this.StateHasChanged();
}
public async Task ButtonLoginAuthWithWebAPIEncr()
{
try
{
Processing = true;
this.Message = "Please wait ..";
this.StateHasChanged();
string authName = _config.GetValue<string>("AuthName");
using (HttpClient httpclient = _iHttpClientFactory.CreateClient())
{
LoginDTO login = new LoginDTO() { Username = Username, Password = Password };
string jsonPayload = JsonConvert.SerializeObject(login);
HttpContent payload = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
//var result = await httpclient.PostAsync($"{_navmgr.BaseUri}api/login", payload); // When the passwords in IDS is clear text
var result = await httpclient.PostAsync($"{_navmgr.BaseUri}api/loginEncr", payload); // When the passwords in IDS is encrypted
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
string jwt = await result.Content.ReadAsStringAsync();
await _localStorage.SetItemAsStringAsync(authName, jwt);
_appstate.LoggedIn = true;
_navmgr.NavigateTo("/");
}
else
{
this.Message = result.StatusCode.ToString();
}
}
}
catch (Exception ex)
{
this.Message = ex.Message;
}
Processing = false;
this.StateHasChanged();
}
public async Task ButtonLoginAuthDirect()
{
string authName = _config.GetValue<string>("AuthName");
string? jwt = _auth.Authenticate(new LoginDTO() { Username = Username, Password = Password });
if (jwt != null)
{
await _localStorage.SetItemAsStringAsync(authName, jwt);
_appstate.LoggedIn = true;
_navmgr.NavigateTo("/");
}
}
public async Task ButtonLoginAuthDirectWithEncryption()
{
string authName = _config.GetValue<string>("AuthName");
string? jwt = _authEncr.Authenticate(new LoginDTO() { Username = Username, Password = Password });
if (jwt != null)
{
await _localStorage.SetItemAsStringAsync(authName, jwt);
_appstate.LoggedIn = true;
_navmgr.NavigateTo("/");
}
}
Any razor pages that needs authorization:
@page "/"
@rendermode InteractiveServer
@using YGAuthentication.Auth
@using YGAuthentication.States
@inject IPageAuthCheck _pageAuthCheck
@inject IAppState _appState
@code {
public string Message { get; private set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await _pageAuthCheck.AuthCheckAsync();
string isIsNot = _pageAuthCheck.EditAuthorized ? "is" : "is not";
Message = $"User {_pageAuthCheck.UserName} {isIsNot} authorized. Expiration: {_pageAuthCheck.AuthExp}";
_appState.LoggedIn = true;
StateHasChanged();
}
}
}
NavMenu:
@code {
private bool _isLoggedIn; // show restricted elements based on this flag
protected override void OnInitialized()
{
base.OnInitialized();
_isLoggedIn = _appState.LoggedIn;
_appState.OnChange += CheckUserLogin;
}
private void CheckUserLogin()
{
if (_isLoggedIn != _appState.LoggedIn)
{
_isLoggedIn = _appState.LoggedIn;
this.StateHasChanged();
}
}
private void SetLogout()
{
_isLoggedIn = false;
this.StateHasChanged();
}
public void Dispose()
{
_appState.OnChange -= CheckUserLogin;
}
}
Examples
A full working POC BlazorApp3 is provided in the Repo.
Output:
Blazor Web Application that uses log in
Dependencies
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.5.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.2" />
Contributing
Any new ideas on how to enhance this class without adding much complexity, please adhere to SOLID principle
License
This project is licensed under the MIT License(LICENSE).
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- BCrypt.Net-Next (>= 4.0.3)
- Blazored.LocalStorage (>= 4.5.0)
- Microsoft.AspNetCore.Mvc.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Mvc.Formatters.Json (>= 2.2.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.IdentityModel.Tokens (>= 7.5.2)
- System.IdentityModel.Tokens.Jwt (>= 7.5.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Updated sample program.cs and Readme.MD to populate the usercred dictionary from db in real time