How to get authentificated user from database in Blazor - asp.net-core

i want do display the Emailadress of the current logged in user. But i don't know, how to get it. I searched many hours and i found nothing. I found some snippet to get the name, but no snippet to get the other fields like email or phonenumber.
The user in this snippet don't have a Id to get it from database.
#page "/"
#inject AuthenticationStateProvider AuthenticationStateProvider
<button #onclick="#LogUsername">Write user info to console</button>
<br />
<br />
#Message
#code {
string Message = "";
private async Task LogUsername()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
Message = ($"{user.Identity.Name} is authenticated.");
}
else
{
Message = ("The user is NOT authenticated.");
}
}
}

For Asp.Net Core Blazor with Identity, the Claims will not contains email claim.
For getting user, you could try UserManager.GetUserAsync(ClaimsPrincipal principal) like below:
#page "/"
#inject AuthenticationStateProvider AuthenticationStateProvider
#using Microsoft.AspNetCore.Identity;
#inject UserManager<IdentityUser> UserManager;
<button #onclick="#LogUsername">Write user info to console</button>
<br />
<br />
#Message
#code {
string Message = "";
private async Task LogUsername()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
var currentUser = await UserManager.GetUserAsync(user);
Message = ($"{user.Identity.Name} is authenticated. Email is { currentUser.Email }");
}
else
{
Message = ("The user is NOT authenticated.");
}
}
}

It sounds to me like you're not passing the email address as part of the claim back from the authentication mechanism. Not sure which provider you're using (Identity Server, etc), having a look at the following link, specifically the section that talks about Claims and Procedural Logic might be the answer to your question: here
Again, I believe the issue is with the Claim. Once you have the email coming through in the claim, you should, in theory have access to it via the common principal in code.

Related

Blazor WASM Authentication and Authorization on Components and Controllers

I am developing a Blazor WASM with authentication and authorization. The idea is that the user need to login in order to be able to view the Components of the Client Project but also to be able to consume data of Controllers from Server Project which are behind the /api.
Currently I have implemented the restriction on Client components:
<AuthorizeView>
<NotAuthorized>
<div class="row">
<div class="col-md-4">
<p>Please sign in to use the Platform...</p>
</div>
</div>
</NotAuthorized>
<Authorized>
#Body
</Authorized>
</AuthorizeView>
I have also a Login and a Logout Page which are storing a Cookie for later use and perform a custom AuthenticationStateProvider
await LocalStorage.SetItemAsync<int>($"{Parameters.application}_{Parameters.enviroment}_userid", authentication.user_id);
await LocalStorage.SetItemAsync<string>($"{Parameters.application}_{Parameters.enviroment}_username", authentication.user_name);
await AuthStateProvider.GetAuthenticationStateAsync();
The AuthenticationStateProvider code is the following:
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var state = new AuthenticationState(new ClaimsPrincipal());
string authcookie_name = $"{Parameters.application}_{Parameters.enviroment}_username";
string authcookie_value = await _localStorage.GetItemAsStringAsync(authcookie_name);
if (!string.IsNullOrEmpty(authcookie_value))
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Authentication, authcookie_value)
}, "Login");
state = new AuthenticationState(new ClaimsPrincipal(identity));
}
NotifyAuthenticationStateChanged(Task.FromResult(state));
return state;
}
The authentication controller is the following:
[HttpPost, Route("/api/auth/login")]
public IActionResult AuthLogin(Authentication authentication)
{
try
{
int auth = _IAuth.AuthLogin(authentication);
if (auth != -1)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Authentication, authentication.user_name)
};
var claimsIdentity = new ClaimsIdentity(claims, "Login");
var properties = new AuthenticationProperties()
{
IsPersistent = true
};
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), properties);
}
return Ok(auth);
}
catch { throw; }
}
Everything is working as excepted and the user need to login in order to see the content of the pages, but he is able to see the data of each page if he perform an http call http://domain.ext/api/model/view
In order to resolve this problem I added the Authorize attribute on each controller of Server project like this:
[Authorize]
[Route("/api/model")]
[ApiController]
public class Controller_Model : ControllerBase
{
}
And also added this code on the Program.cs of Server project in order to be able to make controller to work
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
options.LoginPath = new PathString("/auth/login");
options.LogoutPath = new PathString("/auth/logout");
options.Cookie = new CookieBuilder();
options.Cookie.MaxAge = options.ExpireTimeSpan;
options.AccessDeniedPath = "/";
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
Now the user is not able to see the content of a page even he is making a request to the /api.
The problem is that after some time, even I see the User is still logged in the Authorize attribute of controllers is consider the user not authorized and it returns an error because controller is not returning the supposed object list.
I have no clue why and when this is happening. Then if user Logout and Login again it works for a while again.
===============UPDATE 1===============
After lot of investigation, seems that the client side is authenticated and then every time it sees the localstorage item it continues to be in authenticated state. On the other side the server state is based on a cookie which expires after 30mins.
So the Client and the Server states are operated differently and that's why the Client seems authenticated while Server is not while denying access on controllers.
I think the solution is to change the CustomAuthenticationStateProvider in order to check if the cookie exists and if it's valid. So the event order be as follow:
User SingIn via Client Page -> Server Controller creates the cookie -> Client Page is authenticated via Authentication State Provider which reads the cookie.
Any ideas?
Seems that is possible to read and write cookies from Client Project only via Javascript. What needs to be done is the following:
A custom javascript file "cookie.js", under wwwroot/js:
export function get() {
return document.cookie;
}
export function set(key, value) {
document.cookie = `${key}=${value}`;
}
A C# class file "CookieStorageAccessor.cs", under /Classes:
public class CookieStorageAccessor
{
private Lazy<IJSObjectReference> _accessorJsRef = new();
private readonly IJSRuntime _jsRuntime;
public CookieStorageAccessor(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
private async Task WaitForReference()
{
if (_accessorJsRef.IsValueCreated is false)
{
_accessorJsRef = new(await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "/js/cookie.js"));
}
}
public async ValueTask DisposeAsync()
{
if (_accessorJsRef.IsValueCreated)
{
await _accessorJsRef.Value.DisposeAsync();
}
}
public async Task<T> GetValueAsync<T>(string key)
{
await WaitForReference();
var result = await _accessorJsRef.Value.InvokeAsync<T>("get", key);
return result;
}
public async Task SetValueAsync<T>(string key, T value)
{
await WaitForReference();
await _accessorJsRef.Value.InvokeVoidAsync("set", key, value);
}
}
The C# class can be used injecting javascript and reading the cookie on
CustomAuthStateProvider:
//CREATE INSTANCE OF COOKIE ACCESSOR
CookieStorageAccessor cookieStorageAccessor = new CookieStorageAccessor(_jSRuntime);
//CHECK IF COOKIE IS EXISTS FROM COOKIE ACCESSOR
string auth_cookie = await cookieStorageAccessor.GetValueAsync<string>("authentication");
if (!string.IsNullOrEmpty(auth_cookie))
{ }
else
{ }

signInManager.GetExternalLoginInfoAsync() returns Null In Blazor

Im trying to add external login functions to my Sever-side blazor.
so far i could login with a google account and it seems to work great so far.
This is how i setup the authentication for google.
services.AddAuthentication(options => { /* Authentication options */ })
.AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "{MyClientID}";
// Provide the Google Client Secret
options.ClientSecret = "{ClientSecret}";
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
})
My Problem is i dont know if the claims and additional info's are registered (cause there is no trace of them in my database and i can't retrieve them).
I know that i have to get the external user info via SignInManager.
So in my Blazor component i inject the SignManager like this:
#inject SignInManager<ApplicationUser> signInManager
then i call the ExternalInfo Like this:
var result= await signInManager.GetExternalLoginInfoAsync();
But the result is always null. What do i do wrong? Why is it always null?
A Quick update:
I tested a Razor Page. this works fine on razor pages. so signInManager.GetExternalLoginInfoAsync();
returns null when im calling it from a blazor component.
According to your description, I suggest you could use IHttpContextAccessor instead of using SignInManager to get the user information.
More details, you could refer to below codes:
Add HttpContextAccessor service in ConfigureServices method:
services.AddHttpContextAccessor();
Then use it in component:
#page "/"
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor accessor
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>
#username
</h1>
#code{
public string username;
protected override async Task OnInitializedAsync()
{
username = accessor.HttpContext.User.Identity.Name;
}
}
Result:

How to get username of requesting user not app pool under windows auth

Using Blazor server app.
I have the app pool running as a domain account, but I need the user name doing the request.
When using this, I get the name of the app pool instead.
CallingUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Split(new char[] { '\\' })[1];
Update
This post helped me
Get Current User in a Blazor component
Also, needed to yet enable Websockets in IIS. Didn't discover that until I published to a test server.
If you want to get the login user in the sever side. You should make the service as scope and then you could inject the AuthenticationStateProvider into service.
Then you could get the current login user.
Details, you could refer to below codes:
public class WeatherForecastService
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
public WeatherForecastService(AuthenticationStateProvider authenticationStateProvider) {
_authenticationStateProvider = authenticationStateProvider;
}
public string GetCurrentUserName() {
var provider= _authenticationStateProvider.GetAuthenticationStateAsync();
return provider.Result.User.Identity.Name;
}
}
As far as I know, if you want to get the current login user, you could try to use AuthenticationStateProvider service.
The built-in AuthenticationStateProvider service obtains authentication state data from ASP.NET Core's HttpContext.User.
You could inject the AuthenticationStateProvider AuthenticationStateProvider and then use AuthenticationStateProvider.GetAuthenticationStateAsync to get the user state, at last ,you could use user.Identity.Name to get the current user name.
More details ,you could refer to below codes:
#page "/counter"
#using Microsoft.AspNetCore.Components.Authorization
#inject AuthenticationStateProvider AuthenticationStateProvider
<h1>Counter</h1>
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
<hr />
<button class="btn btn-primary" #onclick="GetUserName">Click me</button>
<p>Current Login User = #Username</p>
#code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
private string Username = string.Empty;
private async Task GetUserName()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
Username = user.Identity.Name;
}
}
Result:

I need to save the User Name and email address separately. I am am having an issue with logging into the site after the user is registered

I am creating a new ASP.NET Core (2.2) WebSite. By default, the UserName and Email Address is the same thing. I want to have them be different. The issue comes in when I try to log that user back into the web page.
I scaffolded the register Identity page and made some simple changes to the Code behind and also the Razor page itself (see below). I did also scaffold the login page, but I don't think that I need to make any changes there, because I am ok with folks logging in using their email address still.
Here are the edits to the Register.cshtml.cs page.
I added this to the InputModel class:
[Required]
[StringLength(256, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 5)]
[DataType(DataType.Text)]
[Display(Name = "User Name")]
public string UserName { get; set; }
I also changed the user in the OnPostAsync method
var user = new IdentityUser { UserName = Input.UserName, Email = Input.Email };
Everything else is as default.
On the page itself, I simply added it below the email:
<div class="form-group">
<label asp-for="Input.UserName"></label>
<input asp-for="Input.UserName" class="form-control"/>
<span asp-validation-for="Input.UserName" class="text-danger"></span>
</div>
I would expect for the user to be logged in as normal, alas. The error I get is simply "Invalid Log-in attempt". Annoyingly, The user I created before making any changes still is able to be logged in just fine. So my gut tells me that Something is funny in the register page, I am just not sure where.
You have to modify SignInManager.PasswordSignIn method. By default it uses FindByNameAsync to check if user with given name exists , you should change to FindByEmailAsync .
Create new SignInManager :
public class MySignInManager : SignInManager<IdentityUser>
{
public MySignInManager(Microsoft.AspNetCore.Identity.UserManager<Microsoft.AspNetCore.Identity.IdentityUser> userManager, Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Microsoft.AspNetCore.Identity.IdentityUser> claimsFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor, Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Microsoft.AspNetCore.Identity.IdentityUser>> logger, Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
}
public override async Task<SignInResult> PasswordSignInAsync(string userName, string password,
bool isPersistent, bool lockoutOnFailure)
{
var user = await UserManager.FindByEmailAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
}
}
Register the SignInManager:
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddSignInManager<MySignInManager>() //register new SignInManager
.AddEntityFrameworkStores<ApplicationDbContext>();

Why is my Unauthorzied request not getting redirected to AccessDenied URL in ASP.Net MVC Core?

I have my AccountAdmin Controller where I manage Users in Identity.
So I have the Authorize Attribute like this at the top of the controller:
[Authorize(Roles = "Admin")]
public class AccountAdminController : Controller
The whole system is working great. If I log in as a user with the Admin role I can get to the page. And if I log in as a user without the Admin role I cannot get to the page. But my problem is that instead of being redirected to the "Account/AccessDenied" Page, I just get the "/AccountAdmin/Index" URL where I am denied the content and it just gives me the "Status Code: 403; Forbidden" message from:
app.UseStatusCodePages();
in my startup.
In StartUp.ConfigureServices I have:
services.AddIdentity<AppUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 4;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireDigit = false;
options.User.AllowedUserNameCharacters = null;
}).AddEntityFrameworkStores<ApplicationDbContext>();
In StartupConfigure I have:
app.UseIdentity();
I know I haven't posted a lot of code here but it's all pretty straight forward stuff.
The caveaot is I am using Windows Authentication. I show the user login like DomainName\UserName in the UpperRight corner.
And then I made kind of an Impersonation Sign in Page where we can Sign in with TestRole1, TestRole2, etc.
The AccountController looks like this:
public class AccountController : Controller
{
private SignInManager<AppUser> _signInManager;
private UserManager<AppUser> _userManager;
public AccountController(SignInManager<AppUser> signInManager,
UserManager<AppUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
public IActionResult Login()
{
return View(_userManager.Users.OrderBy(u => u.UserName));
}
[HttpPost]
public async Task<IActionResult> Login(string userName, bool persistant)
{
await _signInManager.SignInAsync(await _userManager.FindByNameAsync(userName), persistant);
return RedirectToAction("Index", "Home");
}
public async Task<IActionResult> LogOff()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Login", "Account");
}
It's all working pretty well as far as Authentication and Authorization goes.
Accept I found that until I click my SignIn my real windows account won't match up with the roles I assigned to myself. It has to go through the SignInManager at:
[HttpPost]
public async Task<IActionResult> Login(string userName, bool persistant)
{
await _signInManager.SignInAsync(await _userManager.FindByNameAsync(userName), persistant);
return RedirectToAction("Index", "Home");
}
How can I get this AccessDenied redirect working?
Update 1:
I tried to make a filter like this:
public class MyAuthorizationFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Response.StatusCode == 403)
{
context.HttpContext.Response.Redirect("/Access/Denied");
}
}
}
But now when I try to replace the standard Authorize Attribute with this:
[MyAuthorizationFilter(Roles = "Admin")]
it doesn't know what "Roles" is.
It says: "The type or namespace Roles could not be found. Are you missing an assembly or namespace?"
Well it took me a while to work out how I want to do this. But here it is.
I didn't want this to go through error handling because it is not an unhandled error.
It is handled with app.UseStatusCodePages(); in StartUp.Configure.
So on a line below that in StartUp.Configure I add:
// 400 - 599
app.UseStatusCodePagesWithRedirects("~/Account/Status/{0}");
This is a place I can redirect to handle what I want to do with specific statuses but leave a default for what I don't specify.
The "Account/Status" action in the Account controller looks like this.
public IActionResult Status()
{
string statusCode = HttpContext.Request.Path;
statusCode = statusCode.Substring(statusCode.LastIndexOf('/') + 1);
ViewBag.StatusCode = statusCode;
return View(ViewBag);
}
and the view looks like this:
<div class="container-fluid">
<div class="panel panel-danger">
<div class="panel-header">
<h3>StatusCode:
#switch ((string)ViewBag.StatusCode)
{
case "403":
#:Access Denied
<div style="margin-top:5px"><a asp-controller="Account" asp-action="Login" class="btn btn-primary">Login as a different user</a></div>
break;
default:
#ViewBag.StatusCode
break;
}
</h3>
</div>
</div>
</div>
I just wanted this to stay out of the way of my error handling when I get to plugging that in. That is something different.
There must be a simple setting somewhere. But none of these solutions I see like AuthenticatonChallenge = true and so forth are working for me.
I think this is pretty tidy for now.