On an ASP.NET Core 2.2 controller I have the following:
var principal = this.User as ClaimsPrincipal;
var authenticated = this.User.Identity.IsAuthenticated;
var claims = this.User.Identities.FirstOrDefault().Claims;
var id = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
I am able to check if the user is authenticated and gets the claims including id.
How can I do the same outside of the Controller where I do not have this.User?
Inject IHttpContextAccessor interface into the target class. This will give access to the current User via the HttpContext
This provides an opportunity to abstract this feature by creating a service to provide just the information you want (Which is the current logged in user)
public interface IUserService {
ClaimsPrincipal GetUser();
}
public class UserService : IUserService {
private readonly IHttpContextAccessor accessor;
public UserService(IHttpContextAccessor accessor) {
this.accessor = accessor;
}
public ClaimsPrincipal GetUser() {
return accessor?.HttpContext?.User as ClaimsPrincipal;
}
}
You need to setup IHttpContextAccessor now in Startup.ConfigureServices in order to be able to inject it:
services.AddHttpContextAccessor();
services.AddTransient<IUserService, UserService>();
and inject your service where needed.
It's important to note that HttpContext could be null. Just because
you have IHttpContextAccessor, doesn't mean that you're going to
actually be able to always get the HttpContext. Importantly, the code where
you're using this must be within the request pipeline in some way or
HttpContext will be null.
Credit #ChrisPratt via comment
Related
I use ASP.NET Core Razor pages with its authentication provider (individual account).
I need to run some code when a user logs in.
Where can I put this code, is there any callback that is being called when a login happens?
There are several ways to achieve this. You can create custom login page with custom model (see more here). Or if you want to use the default one - you can create custom SignInManager and override the method used to signin the user by the login default page (Identity/Account/Login) for simple auth:
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
{
// ...
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager<MySignMan<IdentityUser>>(); // use your custom signin manager
class MySignMan<T> : SignInManager<T> where T : class
{
public MySignMan(UserManager<T> userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<T> claimsFactory, IOptions<IdentityOptions> optionsAccessor, ILogger<SignInManager<T>> logger, IAuthenticationSchemeProvider schemes, IUserConfirmation<T> confirmation) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
public override async Task<SignInResult> PasswordSignInAsync(T user, string password, bool isPersistent, bool lockoutOnFailure)
{
var signInResult = await base.PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
if (signInResult.Succeeded)
{
// do something here
}
return signInResult;
}
}
P.S.
Note that this is implementation depended and in future versions you may need to either override another method or use completely other workaround.
I'm creating a dynamic menu for all user dependence on user claims.
I have three types of user claims Admin, Employee, and Manager. Now I need to get user policy in a class. So how can I get it?
private readonly UserManager<ApplicationUser> _userManager;
private readonly IHttpContextAccessor _httpContext;
public MainMenuModel(UserManager<ApplicationUser> userManager, IHttpContextAccessor httpContext)
{
_userManager = userManager;
_httpContext = httpContext;
}
I try to catch claim using this code.
var user = _userManager.FindByNameAsync(_httpContext.HttpContext.User.Identity.Name);
var claims = _userManager.GetClaimsAsync(user.Result);
Show the message "Object reference not set to an instance of an object". Can I access the user's claims in class?
I have a .NET Core 3.1 Web API application that connects to different databases depending on what organization the user is from.
The organization is stored as a string value in their user account in Azure AD B2C, and upon performing a request, I need that user's organization to be identified and the correct connection string chosen based on their organization.
However, I'm having trouble getting the user's 'userId' in my DbContext.
I have tried two different implementations but neither are working.
Within the 'ConfigureServices' method in my Startup.cs file I have specified the following:
...
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
I then declared the Interface with its Service:
public interface IUserRepository
{
public void LogCurrentUser();
}
public class UserRepository : IUserRepository
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserRepository(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void LogCurrentUser()
{
var username = _httpContextAccessor.HttpContext.User.Identity.Name;
}
}
3)And finally, on my DbContext, I attempt to draw the userId from the claim:
public class ApplicationDbContext : DbContext
{
private readonly ApplicationTenantClient _tenant;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
ITenantService tenantProvider,
IUserRepository userRepository,
IHttpContextAccessor httpContextAccessor) : base(options)
{
// Attempt (1)
userRepository.LogCurrentUser();
// Attempt (2)
var userAccessor = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);
if(userAccessor != null)
{
var userId = userAccessor.Value;
}
_tenant = tenantProvider.GetTenant(); // <- ideally send userId here for lookup on AD Graph
}
...
After authenticating the user, then attempting to call an endpoint, the claim is empty:
First Result:
Second Result:
This is.. highly confusing, any help would be appreciated.
Articles that I have attempted to follow
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-5.0
https://www.koskila.net/how-to-get-current-user-in-asp-net-core/
I have introduced SignalR into my ASP.NET Core 2 project, but I'm having some issues using a scoped service that I normally use in my controllers. I feel like the problem may be due to the difference in lifecycles between HTTP requests, websockets and hubs.
On each HTTP request, middleware reads the Authorization token and updates some properties (e.g. id, claims, etc.) on a scoped service (IUser) for the request. I use this service in all of my controllers with no issue. To get this to work with SignalR, I am sending an access_token query parameter and using some other middleware beforehand to add this query parameter as a header which works fine.
The problem arises when I am trying to access the IUser service in my SignalR hub. On construction of the hub, the IUser that gets injected has none of the properties set, despite the middleware for the /hub request just setting them.
If I set the service to be a singleton then it works, but IUser should never persist longer than a single request.
How should I go about setting an IUser for a specific SignalR connection?
// Startup.cs - User has several settable properties
services.AddScoped<IUser, User>();
// User Middleware
public class UserMiddleware
{
private readonly RequestDelegate _next;
public UserMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
// retrieve service for this request
var user = context.RequestServices.GetService<IUser>();
// set some properties on the User from auth token
// e.g. User.Id = 1;
return _next(context);
}
}
[Authorize(Roles = Claim.Names.Read)]
public class MyHub : Hub
{
private readonly IUser _user;
public MyHub(IUser user)
{
// user that gets injected has null properties
_user = user;
}
public async Task Foo()
{
// do work with the _user credentials
}
}
public class DownloadController : Controller
{
private readonly IUser _user;
public DownloadController(IUser user)
{
// user gets injected and has properties set
_user = user;
}
}
Have you tried Context.GetHttpContext().RequestServices in your Hub-method? That should be the IServiceProvider of the ongoing request.
To use scope service in signalr hub, you could inject ServiceProvider and create a scope in it directly:
public class ChatHub : Hub
{
private IServiceProvider _serviceProvider;
public ChatHub(IServiceProvider serviceProvider)
{
_serviceProvider= serviceProvider;
}
public async Task Foo()
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var user = scope.ServiceProvider.GetRequiredService<IUser>();
}
}
}
I'm trying to understand dependency injection in ASP.NET MVC CORE.
All examples are the same, they show to register HttpContextAccessor
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
Then the class that wants to access it:
public class UserService : IUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public bool IsUserLoggedIn()
{
var context = _httpContextAccessor.HttpContext;
return context.User.Identities.Any(x => x.IsAuthenticated);
}
}
But then, when i actually want to create an instance of UserService, it asks for the httpContextAccessor object in constructor, where do i get that from?
When using dependency injection, you are not supposed to actually create any service yourself. To consume your UserService, you should just inject that somewhere as well.
Typically, the flow in ASP.NET Core for your application code starts in a controller. So if you want to use the UserService inside of a controller action, you should inject it into the controller:
public class ExampleController : Controller
{
private readonly IUserService _userService;
public ExampleController(IUserService userService)
{
_userService = userService;
}
public IActionResult Index()
{
var isLoggedIn = _userService.IsUserLoggedIn();
// …
return View();
}
}
So you don’t create an instance yourself using new but instead you rely on the dependency injection system to provide you with an instance.
You just need to make sure to register the service inside of the ConfigureServices:
services.AddTransient<IUserService, UserService>();
This principle holds regardless of where you are within your application. Since the entry point is always being created by the system, you are always inside of a dependency injection context, so you can just depend on things which have dependencies themselves (which again could have more dependencies etc).
I would strongly suggest you to read the chapter on dependency injection of the documentation, as it covers the idea very well. It also explains what the different lifetimes mean.
in DI, you normally dont need to create Instance by yourself. Instead you need to register your implemented services to the DI service container and then call it inside your constructor.
This way you eliminates the need for manual instance creation.
services.AddScoped<IMyService, MyService>();
then
class MyConsumerClass
{
private readonly IMyService _myService;
MyConsumerclass(IMyService myService)
{
_myService = myService;
}
}
In this way, you don't need to care about what services is needed to be initialized (parameterized) into your constructor.