Blazor ServerApp. Windows Authentication in Class? - c#

I'm using the latest VS2019Pro with Core 3.1.
It seems like Blazor ServerApp has real-time code running within the #code{} tags in the .razor pages. So instead of using APIs to provide the data, I was thinking it would make a lot of sense to just create classes and methods to return the data.
The only issue I am facing is being able to use User.Identity.Name in a Class. Usually this is provided in the .razor pages and in Controllers without issue, but how can I (if it's even possible) use the same User.Identity.Name property within classes?

You can use the standard .net core auth: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-3.1
Basically you define a:
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
Then you can await it:
private async Task LogUsername()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
_authMessage = $"{user.Identity.Name} is authenticated.";
}
else
{
_authMessage = "The user is NOT authenticated.";
}
}
Edit -----
Something like this??
Base Component:
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
public String Username {get;set;}
protected override async Task OnInitializedAsync(){
Username = (await authenticationStateTask).User.Identity.Name;
}
Then in your other components:
#inherits BaseCompomnent
#Username

IMyRoleManager _myRoleManager;
private AuthenticationStateProvider _authenticationStateProvider;
public MyRepository(IMyRoleManager myRoleManager, AuthenticationStateProvider authentication)
{
_myRoleManager = myRoleManager;
_authenticationStateProvider = authentication;
}
public async Task<bool> AddRoleToUser(string role)
{
var id = await GetUserIdFromAuthenticationStateProviderAsync();
var result = await _myRoleManager.SetRoleForUser(role, id);
return result;
}
And in the startup file the correct entries have to be there for services.xxx (identity services)

Related

How to make a custom Authentication in Blazor

Hello
l
I am stuck with this problem.
I am building a blazor server side page. I am have a custom login form where I call my api to check if the user login attempt was successful.
This is my login form
After a successful login, the user is redirected to another page. On this page, the navMenu is supposed to change.
I already understand that I need to override my AuthenticationStateProvider. But I dont understand how this works.
When the user inputs his informations a new object named user is created
namespace AdminFrontend.Services
{
public class User
{
//"email" & "password" must be lowercase because the json
needs small initial letters
public string email { get; set; }
public string password { get; set; }
public string mobilenumber { get; set; }
public string service { get; set; }
public override string ToString()
{
return $"{email}: {password}: {mobilenumber}: {service}";
}
}
}
How can i authorize the user after he logged in to see everything in the
<Authorized> Tag.
If you need some more informations let me know :D
Thanks for all answers
First all info i will post it's based on Microsoft Docs
AuthenticationStateProvider : Blazor has a built-in service called AuthenticationStateProvider service which obtains authentication state data from ASP.NET Core's and is used by AuthorizeView component and CascadingAuthenticationState component to get the authentication state. So based on that, we need to modify the core one to create or own authentication state, that inherits from main one (to get access to their main functionalities) so we need to do the following:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
public CustomAuthenticationStateProvider()
{
//Anonymous User
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, "0"),
new Claim(ClaimTypes.Name, "Anonymous"),
new Claim(ClaimTypes.Role, "Anonymous")
}, null);
this.CurrentUser = ClaimsPrincipal(identity);
}
private ClaimsPrincipal CurrentUser { get; set; }
//Set Claims
private ClaimsPrincipal GetUserClaim(string userName, string id, string role)
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes. Sid, id),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Role, role)
}, "Authentication type");
return new ClaimsPrincipal(identity);
}
//Principal function to use (to get the user with AuthenticationState.User)
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var task = Task.FromResult(new AuthenticationState(this.CurrentUser));
return task;
}
public Task<AuthenticationState> ChangeUserClaim(string username, string id, string role)
{
this.CurrentUser = this.GetUser(username, id, role);
var task = this.GetAuthenticationStateAsync();
this.NotifyAuthenticationStateChanged(task);
return task;
}
}
Now with our custom Provider we need to add it on our startup.cs//program.cs (net 5 vs net 6)
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
And finally you can use and on your view! just remember to add this on the razor pages
[CascadingParameter] public Task<AuthenticationState> AuthTask { get; set; }
[Inject] private AuthenticationStateProvider AuthState { get; set; }

My AuthenticationService is returning null

I have a web client app (Blazor) that is successfully fetching a JWT token after authentication from another project I have created (an API), once the authentication is successfull I am deserialising the response into a User object and storing that in my authentication service so I can use that for further requests and to see if the user is logged in, there seems to be no rhyme or reason as to why this works or does not work, but it seems like the AuthenticationService is either losing my User model.
So I have the following setup (program.cs):
builder.Services
.AddScoped<IAuthenticationService, AuthenticationService>()
.AddScoped<IUserService, UserService>()
.AddScoped<IHttpService, HttpService>()
.AddScoped<ILocalStorageService, LocalStorageService>();
var host = builder.Build();
var authenticationService = host.Services.GetRequiredService<IAuthenticationService>();
await host.RunAsync();
So as far as I'm aware, this should initialise the AuthenticationService once and it should keep that around.
My Authentication service looks like this (AuthenticationService.cs):
using FarmManagerWebClient.Models;
using Flurl;
using Flurl.Http;
using Microsoft.AspNetCore.Components;
namespace FarmManagerWebClient.Services
{
public interface IAuthenticationService
{
User User { get; }
Task Initialize();
Task Login(string username, string password);
Task Logout();
}
public class AuthenticationService : IAuthenticationService
{
private IHttpService _httpService;
private NavigationManager _navigationManager;
private ILocalStorageService _localStorageService;
private IConfiguration _configuration;
private string _apiRootUrl;
public User User { get; private set; }
public AuthenticationService(
IHttpService httpService,
NavigationManager navigationManager,
ILocalStorageService localStorageService,
IConfiguration configuration
)
{
_httpService = httpService;
_navigationManager = navigationManager;
_localStorageService = localStorageService;
_configuration = configuration;
_apiRootUrl = _configuration.GetSection("Settings")["ApiRootUrl"];
}
public async Task Initialize()
{
User = await _localStorageService.GetItem<User>("user");
}
public async Task Login(string username, string password)
{
User = await _apiRootUrl
.AppendPathSegment("/api/v1/users/authenticate")
.PostJsonAsync(new
{
username = username,
password = password
})
.ReceiveJson<User>();
await _localStorageService.SetItem("user", User);
}
public async Task Logout()
{
User = null;
await _localStorageService.RemoveItem("user");
_navigationManager.NavigateTo("/user/login");
}
}
}
Once the login has completed, I (as far as I know) should be able to use this following piece of code to check if the User is authenticated:
#if (AuthenticationService.User != null) { Show/Hide various things }
This seems to work maybe once, but then after a few times of clicking around the site, Authentication.Service.User starts returning null - despite the data still existing in LocalStorage in the browser.
So what am I doing wrong?

How do I access browser local storage from .cs files in blazor?

First of all, I can access localstorage data in .razor pages. I mean I cannot access localstorage data in .cs files. How can I access?
_Imports.razor:
#using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
#inject ProtectedLocalStorage protectedLocalStorage
anyone .razor file:
await protectedLocalStorage.SetAsync(key, JsonSerializer.Serialize(instance));
Above code works for me but I want to call protectedLocalStorage from .cs files additionally.
P.S sorry for grammar mistakes
Edit:
I am using IHttpClientFactory in startup.cs and I want to add token as a header before api request.
startup.cs
services.AddHttpClient("api", hc =>
{
hc.BaseAddress = new Uri("http://localhost:5000/");
string tokenVal = tokenService.GetToken();
if (!String.IsNullOrEmpty(tokenVal))
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenVal);
});
I want to take token value from local storage from this .cs file
public class TokenService : ITokenService
{
private IHttpContextAccessor httpContextAccessor;
public TokenService(IHttpContextAccessor HttpContextAccessor, IProtected) => httpContextAccessor = HttpContextAccessor;
public string GetToken()
{
return "";
}
}
How do I access browser local storage from .cs files in blazor?
ASP.NET supports injection in most constructors. Expanding OP's example:
// Startup.cs -> ConfigureServices(IServiceCollection services)
// Probably not necessary in your case but, to be thorough:
services.AddScoped<ProtectedLocalStorage>();
// SomeFile.cs
public class TokenService : ITokenService
{
// Ignore for the moment that these are being used in the same context
private IHttpContextAccessor httpContextAccessor;
private readonly ProtectedBrowserStorage _storage;
// Injection can happen here in ASP.NET
public TokenService(
IHttpContextAccessor HttpContextAccessor,
ProtectedBrowserStorage storage)
{
httpContextAccessor = HttpContextAccessor;
// injection works but the PBS service might not: see below
_storage = storage;
}
//..
}
However, I don't recommend this for ProtectedBrowserStorage, since it uses IJSRuntime under the hood. If you try to use this in a non-javascript aware context (e.g. during Startup.Configure where the client is still awaiting a response and there is no way to execute javascript), you will run into errors. In Blazor, ProtectedBrowserStorage should only be called - directly or indirectly - from a Blazor component; to keep it simple, wrap it in a class you only use with components, or keep it in the component itself.
Thus, if you are trying to do this:
I am using IHttpClientFactory in startup.cs and I want to add token as a header before api request.
ProtectedBrowserStorage is not the tool for you. Use cookies or another web server technology.
How I solved in the end:
I have created custom authentication class inherited AuthenticationStateProvider. Then I designed all of check processes to be solved on ProtectedLocalStorage.
AuthenticationService
public class AuthenticationService : AuthenticationStateProvider
{
private const string USER_SESSION_OBJECT_KEY = "user_session_obj";
private const string ACCESS_TOKEN = "accesstoken";
private const string USER_PERMISSIONS = "userpermissions";
private readonly ProtectedLocalStorage _protectedLocalStorage;
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthenticationService(ProtectedLocalStorage protectedSessionStore, IHttpContextAccessor httpContextAccessor)
{
_protectedLocalStorage = protectedSessionStore;
_httpContextAccessor = httpContextAccessor;
}
public string IpAddress => _httpContextAccessor?.HttpContext?.Connection?.RemoteIpAddress?.ToString() ?? string.Empty;
private User User { get; set; }
private List<UserPermission> UserPermissionList { get; set; }
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
User userSession = await GetUserSession();
List<UserPermission> userPermissions = await GetUserPermission();
if (userSession != null)
return await GenerateAuthenticationState(userSession, userPermissions);
return await GenerateEmptyAuthenticationState();
}
catch
{
await LogoutAsync();
return null;
}
}
public async Task LoginAsync(User user,List<UserPermission> userPermissions)
{
await SetUserSession(user);
await SetUserPermissionSession(userPermissions);
NotifyAuthenticationStateChanged(GenerateAuthenticationState(user, userPermissions));
}
public async Task LogoutAsync()
{
//await SetUserSession(null);
RefreshUserSession(null);
await _protectedLocalStorage.DeleteAsync(USER_SESSION_OBJECT_KEY);
await _protectedLocalStorage.DeleteAsync(ACCESS_TOKEN);
await _protectedLocalStorage.DeleteAsync(USER_PERMISSIONS);
NotifyAuthenticationStateChanged(GenerateEmptyAuthenticationState());
}
public async Task<User> GetUserSession()
{
if (User != null)
return User;
//TODO burda localUserJson get yaparken hata alıyor. try catch işi çözmezse buraya tekrardan bakılacak.
try
{
var localUserJson = await _protectedLocalStorage.GetAsync<string>(USER_SESSION_OBJECT_KEY);
if (string.IsNullOrEmpty(localUserJson.Value))
return null;
return RefreshUserSession(JsonConvert.DeserializeObject<User>(localUserJson.Value));
}
catch
{
await LogoutAsync();
return null;
}
}
public async Task<List<UserPermission>> GetUserPermission()
{
if (UserPermissionList != null)
return UserPermissionList;
try
{
var localUserPermissionJson = await _protectedLocalStorage.GetAsync<string>(USER_PERMISSIONS);
if (string.IsNullOrEmpty(localUserPermissionJson.Value))
return null;
return RefreshUserPermissionSession(JsonConvert.DeserializeObject<List<UserPermission>>(localUserPermissionJson.Value));
}
catch
{
await LogoutAsync();
return null;
}
}
private async Task SetUserSession(User user)
{
RefreshUserSession(user);
await _protectedLocalStorage.SetAsync(USER_SESSION_OBJECT_KEY, JsonConvert.SerializeObject(user));
}
private async Task SetUserPermissionSession(List<UserPermission> userPermissions)
{
RefreshUserPermissionSession(userPermissions);
await _protectedLocalStorage.SetAsync(USER_PERMISSIONS, JsonConvert.SerializeObject(userPermissions));
}
private User RefreshUserSession(User user) => User = user;
private List<UserPermission> RefreshUserPermissionSession(List<UserPermission> userPermission) => UserPermissionList = userPermission;
private Task<AuthenticationState> GenerateAuthenticationState(User user, List<UserPermission> userPermission)
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.Id.ToString()),
new Claim(ClaimTypes.Role, userPermission.ToString()),
}, "auth");
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
return Task.FromResult(new AuthenticationState(claimsPrincipal));
}
private Task<AuthenticationState> GenerateEmptyAuthenticationState() => Task.FromResult(new AuthenticationState(new ClaimsPrincipal()));
}
Then I registered this class in startup.cs
Startup
services.AddScoped<AuthenticationStateProvider, AuthenticationService>();
During changing page, authentication system interrupt showing page to check if it is authenticate or not thanks to below code.
_Imports
#attribute [Authorize]
*You can set localstorage at login page. You can create your way to check thanks to this way.

Using AuthenticationStateProvider in C# class instead of .razor page

I have a web application that uses Oauth 2.0 authorization to login.
Getting the claims from AuthenticationStateProvider in a Blazor page is pretty simple:
#inject AuthenticationStateProvider AuthState
#code
{
protected override async Task OnInitializedAsync()
{
authenticationState = await AuthState.GetAuthenticationStateAsync();
username = authenticationState.User.FindFirst("claim type name here");
}
}
Then I can display the username easily in the .razor document HTML part.
However, I want to get this username and use it when calling an API on behalf of that username. I just can't understand how to use the AuthenticationStateProvider and inject the stuff I need in a normal C# class that's not a blazor page.
I found a solution for getting the claims from HttpContext and it's like this:
public class UserIdentity
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserIdentity(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
}
public string GetUsername()
{
var username = _httpContextAccessor.HttpContext.User.Identity.Name;
return username;
}
}
But I don't understand how to use that class with the constructor elsewhere. I would have to pass a IHttpContextAccessor when creating an instance of the class and I can't make sense of it.
EDIT: Changed some of this and tried a new approach:
public class UserIdentity
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
public UserIdentity(AuthenticationStateProvider _authenticationStateProvider)
{
this._authenticationStateProvider = _authenticationStateProvider;
}
public async Task<string> GetUsername()
{
var authstate = await _authenticationStateProvider.GetAuthenticationStateAsync();
System.Security.Claims.Claim username = authstate.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
return username.Value;
}
}
Returns null reference exception. But the real problem is I don't know how to use the UserIdentity. I don't see the point of constructor injection when I have to pass a value.
UserIdentity identity = new UserIdentity(... AuthenticationStateProvider? ...);

Session Like Singleton in Blazor Server Side

One of the problems of Blazor Server side is its Singleton and Scoped services, Its singleton is shared between all users making it act like a cache, And scoped service is lost on refresh, I solved this problem to have a Singleton service that is not shared like the following:
First we require two services:
namespace BlazorStore9.Services
{
public class MZSingleton
{
public Dictionary<Guid, Cart> Carts { get; set; } = new Dictionary<Guid, Cart>();
}
public class MZScopedSessionID
{
public Guid SessionID { get; set; }
}
}
Then register them as Singleton and Scoped respectively, And inject them in the Layout pages and other pages or components that need it, The following is a Layout page, That stores and retrives sessionID using Local Storage:
#inject IJsInterop jsInterop
#inject MZSingleton mzSingleton
#inject MZScopedSessionID sessionID
#code
{
private BlazorStore9.Services.Cart cart = null;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
#region some imp codes
if (firstRender)
{
string SessionIDString = await jsInterop.GetAccessToken("SessionID");
if (string.IsNullOrEmpty(SessionIDString) || !Guid.TryParse(SessionIDString, out Guid theGuid))
{
Guid id = Guid.NewGuid();
await jsInterop.SaveAccessToken("SessionID", id.ToString());
sessionID.SessionID = id;
mzSingleton.Carts[sessionID.SessionID] = new Cart();
}
else
{
sessionID.SessionID = Guid.Parse(SessionIDString);
if (!mzSingleton.Carts.ContainsKey(sessionID.SessionID))
mzSingleton.Carts[sessionID.SessionID] = new Cart();
}
cart = mzSingleton.Carts[sessionID.SessionID];
}
}
Then in each page or component we have:
#inject MZSingleton mzSingleton
#inject MZScopedSessionID sessionID
#code
{
private Cart cart = null;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
cart = mzSingleton.Carts[sessionID.SessionID];
}
}
My question is, If this aproach is secure for using with Authentication purposes? Here I showed only a shopping cart that could not be secure, but what for Authentication purposes?

Categories

Resources