YGAuthentication 1.2.7
dotnet add package YGAuthentication --version 1.2.7
NuGet\Install-Package YGAuthentication -Version 1.2.7
<PackageReference Include="YGAuthentication" Version="1.2.7" />
<PackageVersion Include="YGAuthentication" Version="1.2.7" />
<PackageReference Include="YGAuthentication" />
paket add YGAuthentication --version 1.2.7
#r "nuget: YGAuthentication, 1.2.7"
#:package YGAuthentication@1.2.7
#addin nuget:?package=YGAuthentication&version=1.2.7
#tool nuget:?package=YGAuthentication&version=1.2.7
YGAuthentication
This package is used to authenticate and authorize with local Identity Server, where the user credentials are stored locally. Authentication process is done one time when the user enters their username and password on the login screen. Authorization process is done multiple times, anytime a user visit a page where the contents are available to authorized users only
Features:
- 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 requires @inject in the consuming pages.
- WebAPI endpoints api/login and api/auth
Host your own IDS. If you need help please hit me up. cs@yogigrantz.com or find me (Yogi Grantz) at codementor.com
Installation
Include this package in the project file
!!! Be sure to follow the working POC example [YGEnvAuthenticationTestApp]. The source code includes POC Blazor Web app.
This is an advanced library. It requires special IoC registrations in program.cs, and the login page requires a bunch of usings and injects to include the necessary libraries and retrieve instances of DI objects from IoC Container. The sample project YGEnvAuthenticationTestApp contains 4 sample login pages:
Login 1: Direct authentication without encrypted password Login 2: Direct authentication with encrypted password Login 3: WebAPI authentication without encrypted password Login 4: WebAPI authentication with encrypted password
Usage
.csproj file:
<PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>disable</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:
Please see the source code - YGAuthenticationTestApp:
Login1 - straight authentication without password encryption
Login2 - straight authentication with password encryption
Login3 - WebAPI authentication without password encryption
Login4 - WebAPI authentication with password encryption
App.razor:
@using YGAuthentication.Auth
@inject IPageAuthCheck _pageCheck
@using YGAuthentication.States
@inject IAppState _appstate
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@code {
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await _pageCheck.AuthCheckAsync();
_appstate.ErrorMessage = _pageCheck.ErrorMessage;
if (_pageCheck.IsAuthorized)
_appstate.LoggedIn = true;
else
_appstate.LoggedIn = false;
}
}
}
NavMenu:
@using YGAuthentication.Auth
@inject IPageAuthCheck _pageCheck
@using YGAuthentication.States
@inject IAppState _appstate
protected override void OnInitialized()
{
_appState.OnChange += StateHasChanged;
}
public void Dispose()
{
_appState.OnChange -= StateHasChanged;
}
Any Page:
@using YGAuthentication.States
@inject IAppState _appstate
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (_appstate.LoggedIn)
{
_message = "Authorized";
_msgClass = "alert-success";
}
else
_message = $"You are NOT Authorized {_appstate.ErrorMessage}";
}
Logout Page:
@page "/Logout"
@using Blazored.LocalStorage
@using YGAuthentication.Auth
@inject ILocalStorageService _localStorage
@inject IPageAuthCheck _pageAuth
<h3>Logout</h3>
<div>@_errMsg</div>
@code {
private string _errMsg = "";
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
try
{
var result = await _pageAuth.LogoutAsync(_localStorage);
if (!result.Item1)
_errMsg = result.Item2;
else
_errMsg = $"Logged out: {result.Item2}";
}
catch (Exception ex)
{
_errMsg = ex.Message;
}
}
}
Examples
A full working POC SampleAuthTest (MudBlazor) and YGEnvAuthenticationTestApp (Plain Blazor) are provided in the Repo.
Output:
Blazor Web Application that uses log in
Dependencies
<ItemGroup> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <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="8.0.2" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.2" /> </ItemGroup>
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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.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 (>= 8.0.2)
- System.IdentityModel.Tokens.Jwt (>= 8.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Changed function name to IsAuthorized to clarify. Add Auth check in app.razor and NavMenu.razor, New Sample Project: YGEnvAuthenticationTestApp