Using AuthenticationStateProvider in C# class instead of .razor page - c#

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? ...);

Related

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.

Blazor ServerApp. Windows Authentication in Class?

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)

Asp .net Core 3.1 transforming ClaimsIdentity with multiple AuthenticationScheme

I've faced a problem while implementing a couple of authorization schemes in ASP .Net Core application. Lets say they are declared like this in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
AuthenticationBuilder builder = services
.AddAuthentication()
.AddBasicAuthentication(o => { o.Realm = "MyRealm"; })
.AddApiKeyAuthentication()
.AddBearerToken(opt => { });
}
Each of these schemes provide its own implementation of AuthenticationHandler returning ClaimsIdentity if succeeded. But in each case the structure of claims are incosistent, i.e. ApiKeyAuthentication may return ClaimsIdentity with business-sensitive data stored in claim "api_service" while BearerTokenScheme will store it in a claim "sub", and I dont have control over this. So if I would like to use this information in a controller to associate some process with a service which have called my api method, I have to implement some complicated logic that would analyze current ClaimsIdentity, its auth scheme and set of claims.
Instead I would like to implement some sort of tranformation of ClaimsIdentity into MyServiceClaimsIdentity which would expose claims in a handy way so I can utilize them easily in my Controllers code:
public class MyServiceClaimsIdentity: IIdentity
{
private readonly ClaimsIdentity innerIdentity;
public Guid? UserId {get; }
public string UserName {get; }
public string ServiceName {get; }
public MyServiceClaimsIdentity(ClaimsIdentity identity)
{
this.innerIdentity = identity;
TransformClaimsIntoProperties();
}
private void TransformClaimsIntoProperties()
{
......
}
}
I've tried to implement some sort of "transformative" AuthenticationHandler which would produce MyServiceClaimsIdentity after all other handlers would produce their ClaimsIdentity.
public class FinalAuthenticationHandler : AuthenticationHandler<FinalAuthenticationOptions>
{
public FinalAuthenticationHandler(
IOptionsMonitor<FinalAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!this.Context.User.Identity.IsAuthenticated)
{
return null;
}
var identity = new MyServiceClaimsIdentity(this.Context.User.Identity);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, this.Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Too bad at this point this.Context.User.Identity doesnt have any information of an user, so I'm confused where to put this tranformation logic or how would I get current ClaimsIdentity provided by other Handler in my FinalAuthenticationHandler. Any help would be appreciated.
Implementing IClaimsTransformation and registering it as a Singleton did the job just fine
internal sealed class ClaimsTransformation : IClaimsTransformation
{
private readonly IDictionary<string, IClaimsHandler> handlersMap;
public ClaimsTransformation(IEnumerable<IClaimsHandler> handlers)
{
if (handlers == null)
{
throw new ArgumentNullException(nameof(handlers));
}
this.handlersMap = handlers.ToDictionary(t => t.SchemeName);
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (!(principal.Identity is ClaimsIdentity claimsIdentity))
{
throw new InvalidOperationException($"Principal.Identity is of type {principal.Identity.GetType()}, expected ClaimsIdentity");
}
if (!this.handlersMap.TryGetValue(principal.Identity.AuthenticationType, out var handler))
{
throw new AuthenticationException($"Scheme of type {principal.Identity.AuthenticationType} is not supported");
}
var result = new ClaimsPrincipal(handler.Handle(claimsIdentity));
return Task.FromResult(result);
}
}

OwinContext Environment doesn't contains Added element

I am trying to implement multi tenancy in my WebAPI project.
In my Startup.Auth.cs , i am adding selected Tenant object into IOwinContext.
app.Use(async (ctx, next) =>
{
Tenant tenant = GetTenantBasedUrl(ctx.Request.Uri.Host);
if (tenant == null)
{
throw new ApplicationException("tenant not found");
}
ctx.Environment.Add("MultiTenant", tenant);
await next();
}
Where GetTenantBaseUrl function is returnnig us the selected Tenant object.
I have made a class implementing ApiController which i would implement to every controller of mine in order to get the Tenant object.
public class MultiTenantWebApiController : ApiController
{
public Tenant Tenant
{
get
{
object multiTenant;
IDictionary<string, object> dic = HttpContext.Current.GetOwinContext().Environment;
if (!HttpContext.Current.GetOwinContext().Environment.TryGetValue("MultiTenant", out multiTenant))
{
throw new ApplicationException("Could Not Find Tenant");
}
return (Tenant)multiTenant;
}
}
}
In my controller i am getting "MultiTenant" key from OwinContext Environment but i try to fetch the same from ApplicationOAuthProvider class it doesn't show "MultiTenant" key in my OwinContext Environment ie : getEnvironment variable below:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
// some code here
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
try
{
**IDictionary getEnvironment = HttpContext.Current.GetOwinContext().Environment;**
// some code
Does anybody know why i am not getting the "MultiTenant" key in OwinContext.Environment of ApplicationOAuthProvider whereas i get it inside my controller ?
Thanks!
I would like to suggest that you can use the context injected to each of your api controllers so that the tenant and its context is visible across the layers. The context provider can be looking something like this
public class ClaimsContextDataProvider : IUserContextDataProvider
{
public Guid UserId
{
get
{
var userId = (Thread.CurrentPrincipal as ClaimsPrincipal)?.FindFirst(ClaimTypes.Sid)?.Value;
return TryGetGuidFromString(userId);
}
}
}
Then registering the context provider in the DI framework [example of using the Autofac is given below]
builder.RegisterType<ClaimsContextDataProvider>().As<IUserContextDataProvider>();
Then have a BaseApiController something like the below snippet
public Guid TenantId { get { return _userContext.TenantId; } }
public BaseApiController(IMapper mapper, IUserContextDataProvider userContext)
{
_mapper = mapper;
_userContext = userContext;
}
Accessing the TenantId property of the BaseApiController inside the derived controllers [CountriesController.cs]
// POST api/countries
public async Task<HttpResponseMessage> PostCountry(CountryRequestModel requestModel)
{
Country country = _mapper.Map<CountryRequestModel, Country>(requestModel);
country.CreatedOn = DateTimeOffset.Now;
country.TenantId = TenantId;
await _countryService.AddAsync(country);
CountryDto countryDto = _mapper.Map<Country, CountryDto>(country);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, countryDto);
response.Headers.Location = GetCountryLink(country.Id);
return response;
}
You can take a look at the sample app and the template given in the below link
Multi-Tenant dev template
Its a little bit deep to explain here, please feel free to read the docs here

Categories

Resources