How to get username of requesting user not app pool under windows auth - asp.net-core

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:

Related

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:

Getting error "ERR_TOO_MANY_REDIRECTS" in asp.net core

I am trying to create custom login logout where whout loged in user can not access home view. and after loged in user can redirect to home view. If user trying to access home view so he has to be redirect to login page.
here is my code...
LoginPartial View
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
#using Microsoft.AspNetCore.Http;
#{
var userId = Context.Session.GetString("username");
if (userId == null)
{
Context.Response.Redirect("/Login");
}
else
{
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link text-light mt-2">Hello #Context.Session.GetString("username")!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="#Url.Action("Index", "Home", new { area = "" })">
<button type="submit" class="nav-link btn btn-link text-white-50">Logout</button>
</form>
</li>
</ul>
}
}
here is my Login controller
public class LoginController : Controller
{
private readonly ApplicationDbContext _db;
public LoginController(ApplicationDbContext context)
{
_db = context;
}
public IActionResult Index()
{
return View("Login");
}
[HttpPost]
public IActionResult LoginProcess(string username, string password)
{
var userId = _db.logins.Where(p=>p.UserName==username && p.Password==password && p.ExpiryDate> DateTime.Now).Select(p=>p.Id).FirstOrDefault();
if (userId>0)
{
HttpContext.Session.SetString("username", username);
return Redirect("~/Reception/Home");
}
else
{
ViewBag.error = "Invalid Login..!";
return View("Login");
}
}
[HttpGet]
public IActionResult Logout()
{
HttpContext.Session.Remove("username");
return RedirectToAction("Index");
}
}
}
User can not open Home view without login.
Without running the code, it looks on first pass like you're setting the user on an infinite loop. Your view checks for a username and redirects to the "/login" endpoint on failure, which subsequently returns the View, which checks again, and so on. Eventually, the browser hits the brakes on you.
From what you're presenting, it looks like you're trying to roll your own login mechanism rather than take advantage of what ASP NET Core can offer to help deal with some of this automatically. Take a look at Simple authorization in ASP.NET Core
I would suggest ..create a base controller. another controllers should inherit from base controller.You could check whether user is logged-in or not in base controller and if you found user is not logged in then you can redirect user to login page.
The way you have checked user login at login view , it is not recommended.
public class BaseController : Controller
{
// Check here user credentials or whether user is logged-in or not
}
public class LoginController : BaseController
{
public IActionResult home()
{
return View("home");
}
}
SO whenever any user wants to access any page, your application will always check user authentication in this way.

Get Current User in a Blazor component

I'm starting a new site with Blazor and Windows Authentication and need to identify the current user viewing the page/component.
For a Razor Page, the current user name can be accessed with Context.User.Identity.Name, but that doesn't seem to work in a Blazor component. I've tried injecting HttpContext into the component but the Context is null at runtime.
As a bonus, I will eventually want to incorporate this into Startup.cs so I only need to get the username once and can leverage a corporate user class (with EF Core) for my applications. Answers tailored to that use case would also be appreciated.
There are three possibilities for getting the user in a component (a page is a component):
Inject IHttpContextAccessor and from it access HttpContext and then User; need to register IHttpContextAccessor in Startup.ConfigureServices, normally using AddHttpContextAccessor. Edit: according to the Microsoft docs you must not do this for security reasons.
Inject an AuthenticationStateProvider property, call GetAuthenticationStateAsync and get a User from it
Wrap your component in a <CascadingAuthenticationState> component, declare a Task<AuthenticationState> property and call it to get the User (similar to #2)
See more here: https://learn.microsoft.com/en-us/aspnet/core/security/blazor.
For me the solution mentioned in the first answer 2. option worked perfect:
I am using Blazor server side on .Net Core 5.0 .
I injected
#inject AuthenticationStateProvider GetAuthenticationStateAsync
in my Blazor page and added the following in the code section:
protected async override Task OnInitializedAsync()
{
var authstate = await GetAuthenticationStateAsync.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;
}
In my startup.cs, I have the following lines:
services.AddScoped<ApiAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(p =>
p.GetRequiredService<ApiAuthenticationStateProvider>());
For blazor wasm in net 5.0 and above. Here is how I did,
Wrap your <App> component inside <CascadingAuthenticationState> as shown below,
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
...
</Found>
<NotFound>
...
</NotFound>
</Router>
</CascadingAuthenticationState>
Then add Task<AuthenticationState> CascadingParameter inside any component as shown below,
public class AppRootBase : ComponentBase
{
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
}
Now you can access logged in user Identity and Claims inside component as shown below,
protected override async Task OnInitializedAsync()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
Console.WriteLine($"{user.Identity.Name} is authenticated.");
}
}
Here is the reference from Microsoft docs.
I've now been able to get it to work with a general class, as well as a component.
To get access to the HttpContext User; in ConfigureServices, in Startup.cs add
services.AddHttpContextAccessor();
I have a CorporateUserService class for my CorporateUser class. The service class gets a DbContext through constructor injection.
I then created a new CurrentCorporateUserService that inherits from the CorporateUserService. It accepts a DbContext and an IHttpContextAccessor through constructor injection
public class CurrentCorporateUserService : CorporateUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentCorporateUserService(IHttpContextAccessor httpContextAccessor,
MyDbContext context) : base(context)
{
_httpContextAccessor = httpContextAccessor;
}
...
The base service class has a method GetUserByUsername(string username). The Current service class adds an additional method
public CorporateUser GetCurrentUser()
{
return base.GetUserByUsername(_httpContextAccessor.HttpContext.User.Identity.Name.Substring(8));
}
The Current service class is registered in Startup.cs
services.AddScoped<CurrentCorporateUserService>();
Once that is done, I can use the CurrentCorporateUserService in a component with directive injection.
[Inject]
private CurrentCorporateUserService CurrentCorporateUserService { get; set; } =
default!;
or in any class, with constructor injection.
public MyDbContext(DbContextOptions<MyDbContext> options,
CurrentCorporateUserService CurrentCorporateUserService)
: base(options)
{
_currentUser = CurrentCorporateUserService.GetCurrentUser();
}
Making it a project wide service means all my developers do not have to concern themselves with how to get the Current User, they just need to inject the service into their class.
For example, using it in MyDbContext makes the current user available to every save event. In the code below, any class that inherits the BaseReport class will automatically have the report metadata updated when the record is saved.
public override Int32 SaveChanges()
{
var entries = ChangeTracker.Entries().Where(e => e.Entity is BaseReport
&& (e.State == EntityState.Added || e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
((BaseReport)entityEntry.Entity).ModifiedDate = DateTime.Now;
((BaseReport)entityEntry.Entity).ModifiedByUser = _currentUser.Username;
}
return base.SaveChanges();
}
I've had a similar requirement and have been using:
var authstate = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authstate.User;
var name = user.Identity.Name;
I already had an AuthenticationStateProvider in my startup.cs and added it to the constructor of my custom class.
If you create a new project and choose Blazor with Windows Authentication you get a file called Shared\LoginDisplay.razor with the following content:
<AuthorizeView>
Hello, #context.User.Identity.Name!
</AuthorizeView>
Using the <AuthorizeView> we can access #context.User.Identity.Name without any modifications on any page.
In your App.razor, make sure the element encapsulated inside a CascadingAuthenticationState element. This is what is generated by default if you create your Blazor project with authentication support.
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly" PreferExactMatches="#true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
In your component you can use the AuthenticationStateProvider to access the current user like in the following sample:
#page "/"
#layout MainLayout
#inject AuthenticationStateProvider AuthenticationStateProvider
#inject SignInManager<IdentityUser> SignInManager
#code
{
override protected async Task OnInitializedAsync()
{
var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (SignInManager.IsSignedIn(authenticationState.User))
{
//Do something...
}
}
}
The below solution works only if you are running under IIS or IIS Express on Windows. If running under kestrel using 'dotnet run', please follow steps here, https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.0&tabs=visual-studio#kestrel
[startup.cs]
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
[index.razor]
#page "/"
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor httpContextAccessor
<h1>#UserName</h1>
#code {
public string UserName;
protected override async Task OnInitializedAsync()
{
UserName = httpContextAccessor.HttpContext.User.Identity.Name;
}
}
I also had this problem. The best solution I found was to inject both UserManager and AuthenticationStateProvider and then I made these extension functions:
public static async Task<CustomIdentityUser> GetUserFromClaimAsync(this
ClaimsPrincipal claimsPrincipal,
UserManager<CustomIdentityUser> userManager)
{
var id = userManager.GetUserId(claimsPrincipal);
return await userManager.FindByIdAsync(id);
}
public static async Task<CustomIdentityUser> GetCurrentUserAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
{
return await (await provider.GetAuthenticationStateAsync()).User.GetUserFromClaimAsync(UM);
}
public static async Task<string> GetCurrentUserIdAsync(this AuthenticationStateProvider provider, UserManager<CustomIdentityUser> UM)
{
return UM.GetUserId((await provider.GetAuthenticationStateAsync()).User);
}
This was a painful journey for me chasing a moving target. In my case I only needed the user name for my Blazor component used in a Razor page. My solution required the following:
In the Index.cshtml.cs I added two properties and constructor
public IHttpContextAccessor HttpContextAccessor { get; set; }
public string UserName { get; set; }
public TestModel(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
if (HttpContextAccessor != null) UserName = HttpContextAccessor.HttpContext.User.Identity.Name;
}
Then in the Index.cshtml where I add the component I called it as follows:
<component type="typeof(MyApp.Components.FileMain)" param-UserName="Model.UserName" render-mode="ServerPrerendered" />
In my component I use a code behind file (FileMain.razor.cs using public class FileMainBase : ComponentBase) have the code:
[Parameter]
public string UserName { get; set; } = default!;
and then as a proof of concept I added to the FileMain.razor page
<div class="form-group-sm">
<label class="control-label">User: </label>
#if (UserName != null)
{
<span>#UserName</span>
}
</div>
You should add needed claims to the User after login.
For example I show the FullName on top of site (AuthLinks component) instead of email.
[HttpPost]
public async Task<IActionResult> Login(LoginVM model)
{
var user = await _userManager.FindByNameAsync(model.Email);
if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
return Unauthorized("Email or password is wrong.");
var signingCredentials = GetSigningCredentials();
var claims = GetClaims(user);
var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
var token = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
return Ok(new LoginDto { Token = token, FullName = user.FirstName + " " + user.LastName });
}
#code {
private LoginVM loginVM = new();
[Inject]
public AuthenticationStateProvider _authStateProvider { get; set; }
private async Task SubmitForm()
{
var response = await _http.PostAsJsonAsync("api/accounts/login", loginVM);
var content = await response.Content.ReadAsStringAsync();
var loginDto = JsonSerializer.Deserialize<LoginDto>(content);
await _localStorage.SetItemAsync("authToken", loginDto.Token);
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginDto.Token);
(_authStateProvider as AuthStateProvider).NotifyLogin(loginDto.FullName);
_navigationManager.NavigateTo("/");
}
}
public void NotifyLogin(string fullName)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("FullName", fullName) }, "jwtAuthType"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
<AuthorizeView>
<Authorized>
#context.User.FindFirst("FullName")?.Value
<button class="btn btn-outline-danger mx-4" #onclick="LogOut">LogOut</button>
</Authorized>
<NotAuthorized>
Login
Register
</NotAuthorized>
</AuthorizeView>
GitHub project link:
https://github.com/mammadkoma/Attendance
This worked for me:
Replace:
#context.User.Identity.Name
With:
#context.User.Claims.Where(x => x.Type=="name").Select(x => x.Value).FirstOrDefault()
UPDATE - This answer does not work. Rather than deleting it, I've let it here as information. Please consider the other answers for the question instead.
In ConfigureServices, in Startup.cs, add
services.AddHttpContextAccessor();
In your component class [Note: I use code-behind with null types enabled]
[Inject]
private IHttpContextAccessor HttpContextAccessor { get; set; } = default!;
private string username = default!;
In your component code (code behind), in protected override void OnInitialized()
username = HttpContextAccessor.HttpContext.User.Identity.Name;
username can now be used throughout the component just like any other variable.
However, see my other answer in this question to add get the current user name from a service usable in any class.
This is what works for me on a single page
Add to Startup.cs
services.AddHttpContextAccessor();
On the Razor Component page
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor httpContextAccessor
<div>#GetCurrentUser()</div>
#code{
protected string GetCurrentUser()
{
return httpContextAccessor.HttpContext.User.Identity.Name;
}
}

How to get authentificated user from database in Blazor

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.

Redirect Login to Controller Action

Starting with the ASP.NET 5 Web App Template using Individual User Accounts I have managed to get external authentication working with Microsoft accounts. When users click Login they are redirected to ExternalLogin in AccountController like this
<form asp-controller="Account" asp-action="ExternalLogin" method="post" asp-route-returnurl="#ViewData["ReturnUrl"]" class="nav navbar-right">
<button type="submit" class="btn btn-null nav navbar-nav navbar-right" name="provider" value="Microsoft" title="Log in"><span class="fa fa-sign-in"/> Log In</button>
</form>
That gets them logged in using thier Microsoft account and all seems to work nicely. But how do I intercept direct attempts to access privileged actions [Authorize]
so that the user is redirected to ExternalLogin? Can a default action be set in Startup.cs?
EDIT 1 Attempting to follow the advice of #Yves I have created CustomAutorizationFilter in a Filters folder. It doesn't check for any conditions
public class CustomAutorizationFilter : IAuthorizationFilter
{
public void OnAuthorization(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
//if (...) // Check you conditions here
//{
context.Result = new RedirectToActionResult("ExternalLogin", "Account", null);
//}
}
}
and have edited ConfigureServices as below
services.AddMvc(config =>
{
config.Filters.Add(typeof(Filters.CustomAutorizationFilter));
});
When I run the app locally it no longer goes to the Home page. It returns a blank http://localhost:52711/Account/ExternalLogin
Obviously there is much I do not understand.
Edit 2: Here is the signature of ExternalLogin
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
This is how ExternalLogin comes out of the box in the ASP.Net 5 Web App Template.
You can register an IAuthorizationFilter or an IActionFilter implementations to accomplish this. In these filters you can check if the request is trying to access a privileged action, if the user is logged in or have enough permission to do it.
If you are using AutorizeAttribute, I suggest you to use AutorizationFilter.
If you go with your own custom attributes, then use ActionFilter.
Here is an example:
MVC calls IAuthorizationFilter.OnAuthorization method before every action execution.
public class CustomAuthorizationFilter : IAuthorizationFilter
{
public void OnAuthorization(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
if (...) // Check you conditions here
{
context.Result = new RedirectToActionResult("ExternalLogin", "Account", null);
}
}
}
To register this filter, in Startup.cs edit your ConfigureServices method:
services.AddMvc(config =>
{
config.Filters.Add(typeof(CustomAuthorizationFilter ));
});
Or if you wan to use your own attributes you can use ActionFilter's OnActionExecuting method to check if everything is happening as you wish...
As I was unable to get CustomAuthorizationFilter working as suggested by #Yves I have resorted to a nasty hack. I have modified AccountController Login as below
// GET: /Account/Login
[HttpGet]
[AllowAnonymous]
public IActionResult Login(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return RedirectToAction(nameof(ExternalLogin), new { provider = "Microsoft", returnUrl = returnUrl });
//return View();
}
This seems to work but I'd appreciate any feedback or advice if there is a better way.