IHttpContextAccessor contains empty User.Identity, when used outside of the controller - c#

I am writing an app ASP.Net Core (2.2) MVC. I need to filter some the data inside the DbContext by value of certain claims of the Logged in user. I inject IHttpContextAccessor, but when I try to access HttpContext.User.Identity - all properties are null and all claims are empty.
This is how I am trying to achieve that
I wire up IHttpContextAccessor. I use a standard method like that:
public void ConfigureServices(IServiceCollection services){
services.AddHttpContextAccessor();
...
}
Then I build a custom Provider to extract claims from the User:
public class GetClaimsFromUser : IGetClaimsProvider
{
public string UserId {get; private set;}
public GetClaimsFromUser(IHttpContextAccessor accessor)
{
UserId = accessor.HttpContext?.User.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Name)?.Value;
}
}
Then I also inject it inside ConfigureServices method:
public void ConfigureServices(IServiceCollection services){
...
services.AddScoped<IGetClaimsProvider, GetClaimsFromUser>();
...
}
Afterwards I injected it inside the ApplicationDbContext and try to set the private _userId field inside the constructor:
public class ExpenseManagerDbContext: IdentityDbContext<ApplicationUser>
{
private string _userId;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IGetClaimsProvider claimsProvider) : base(options)
{
_userId = claimsProvider.UserId;
...
}
...
}
And exactly here it is empty. When I access the HttpContext inside the controller, the User.Identity is not empty and everything is fine. However, when I need to access it outside the controller, it is empty.
Thanks for any help!!!
The full code can be found here:
https://github.com/dudelis/expense-manager/blob/master/ExpenseManager.DataAccess/Concrete/EntityFramework/ExpenseManagerDbContext.cs?

You are attempting to access the user in ExpenseManagerDbContext which is the application’s IdentityDbContext. As such, it itself is a dependency of the authentication system and will get resolved when the framework performs the authentication.
So the flow is somewhat like this:
Request comes in.
Authentication middleware runs to authenticate the user.
UserManager resolves ExpenseManagerDbContext.
ExpenseManagerDbContext resolves IGetClaimsProvider.
GetClaimsProvider resolves the HttpContext and attempts to access the user’s claims.
Authentication middleware performs the authentication and sets HttpContext.User with the result.
If you look at steps 5 and 6, you will see that the HttpContext is accessed before the authentication middleware is able to actually authenticate the user and update the user object on the context. And since the authentication middleware always runs at the beginning of a request, this will always be the case.
I would recommend you to rethink your ExpenseManagerDbContext since it probably shouldn’t depend on the currently signed-in user. It should be independent of that. If you have logic there that depends on the user id, then it should probably be a separate service.

Solved!
The problem was in the sharing of the same DbContext for IdentityDbContext and ApplicationDataDbContext.
In my controller I had the following code:
[Authorize]
public class AccountController : Controller
{
[HttpGet]
public IActionResult Index()
{
var accounts = _accountService.GetAll();
var models = _mapper.Map<List<AccountDto>>(accounts);
return View(models);
}
}
And when I tried to call the controller from the browser, the app initialized DbContext first time due to [Authorize] attribute. And this was done without any HttpContext. So when the application made a call to the DbContext in '_accountService.GetAll()', the DbContext was already instantiated and the Constructor method was not called, therefore, all my private fields remained empty!
So I created a second DbContext class only for authentication/authorization purposes.
public class ApplicationDbAuthContext : IdentityDbContext
{
public ApplicationDbAuthContext(DbContextOptions<ApplicationDbAuthContext> options) : base(options)
{
}
}
Due to this, during the request inside the controller the correct DbContext was instantiated when I made a call and it contained the HttpContext.
I will update my code in the repo to show the changes.
Meanwhile, thanks for all the answers.

Related

Blazor Application Managing Authorization in a muli-tiered solution

I have a solution made up of a Blazor application (Server Side app with .Net 5.0) and a second project that is a Class Library (.Net Standard 2.0). The Blazor app handles all aspects of the website, including authentication. I moved all of my database CRUD procedures into the Class Library. The Blazor app authenticates and uses Roles/Claims to authorize different pages. But, how do I extend that authorization to my Class Library, so that I can use the same Class/Function decorations to designate authorized roles/claims? Here is an example of how my application is setup:
Blazor App - Startup.cs
The Blazor app has a reference to the Class Library and corresponding "Using statements". I then inject those classes into the ConfigureServices method, for use in pages.
public void ConfigureServices(IServiceCollection services)
{
//...adding other required services
// Inject class from my Class Library
services.AddTransient<ISqlDataAccess, SqlDataAccess >();
services.AddTransient<IPeopleData, PeopleData>();
}
Here is an example of how I use the injected classes, to pull data from my database. I can implement roles access here, but I also want to implement it in the Class library.
Somepage.Razor
#page "/mysite/people"
#attribute [Authorize(Roles = "admin")]
#using DataAccessLibrary
#using DataAccessLibrary.Models
#inject IPeopleData_db
//html to display data
#code{
//list of the returned data set, using the model, People
List<PeopleModel> people = new List<PeopleModel>();
protected override async Task OnInitializedAsync()
{
people = await _db.GetAllPeople();
}
}
DataAccessLibarary
IPeopleData.cs
public interface IPeopleData
{
Task<PeopleModel> GetAllPeople();
}
PeopleData.cs
public class PeopleData : IPeopleData
{
// ISqlDataAccess is a class that handles basic CRUD calls to the database. Uses Dapper easy object mapping
private readonly ISqlDataAccess _db;
public PeopleData(ISqlDataAccess db)
{
_db = db;
}
public Task<List<PersonModel>> GetAllPeople()
{
string sql = "select * from dbo.People";
return _db.LoadData<PersonModel, dynamic>(sql, new { });
}
}
So, I can add authorization attributes to the Blazor pages, using [Authorize(Roles = "admin")]. But, this does not protect the Class Library that manages all the database connection stuff. How can I extent the role based access control to that library, so that I can use similar decorations to my classes/functions, like [Authorize(Roles = "admin")]?
To answer your question directly: There is no mechanism for adding authorization to standard DotNetCore classes, which is what your library code is. Authorize is applied to components.
You need to structure your application applying "Separation of Concerns" principles to solve your problems.
You need to reconsider:
services.AddTransient<ISqlDataAccess, SqlDataAccess >();
It will almost certainly leak memory like a sieve. A SQL access service should be either Scoped or normally Singleton.

Using static variables in C# Web Application - Accessing Session variables in class properties

Forgive me for my lack of coding knowledge as well as ability to ask the right question.
I'm rather new to this ASP.Net Web Application thing (Core), yet I still wondered..
In my current application, I have a class that has a property in which it gets it from a static variable, set when a user requests a controller. So the flow is: User sends a request with a variable in body, if not specified in body, the StaticClass.StaticProperty (example) is then set to the variable the user specified in the body (or default = 0), data is returned based upon the variable.
Yet I wondered, since there is no thread guarantee on this variable, whether or not this could be changed or messed up when the web application gets 50,000 requests at once?
I looked into sessions and tried the following:
service.AddSession(); //Not sure this even does anything?
HttpContext.Session.SetString //Setting this works in the controller, but I cant access it elsewhere by GetString
System.Web.HttpContext.Current.Session["test"] // Cant even access System.Web.Httpcontext, doesn't seem to exist.
HttpContext.Current //doesn't exist either
Session["test"] //doesn't exist either
Can I send a session over somewhere? I'm pretty lost.
Not sure if any of this made sense, I'll try to elaborate if needed.
Thank you in advance.
EDIT: Updated info.
I have added this to my startup.cs:
services.AddSingleton();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
and
app.UseSession();
Setting the Session variable:
https://i.imgur.com/CY8rcdk.png
Using the Session variable:
https://i.imgur.com/SuLJKzV.png
Variable is always null.
Thank you for trying to help.
HttpContext is accessible only from things that are request specific, since it's a context of one and only request. And new controller instances are created by the framework for each request, with injected HttpContext. It's the developers job to pass it further if the need arises.
I recommend reading this article about it: https://dotnetcoretutorials.com/2017/01/05/accessing-httpcontext-asp-net-core/
First in your startup.cs, you need to register IHttpContextAccessor as a service like so :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
When you create a helper/service class, you can then inject in the IHttpContextAccessor and use it. It would look like something not too dissimilar to this :
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);
}
}

Capture User last request timestamp in ASP NET Web API

I'm looking for a good place in the ASP.NET Web API lifecycle To update a property in my User entity that is purposed to store the date and time the User last made a request. Obviously, I could just add the code to each of my Controller methods but I would prefer doing this in one place outside of my controllers.
Ideally I would have access to the User principal and could use its Identity property to get the user's ID so that I could retrieve and update my User entity using Entity Framework.
I am currently looking at using a DelegatingHandler implementation.
Can anyone suggest the place in the lifecycle where I should carry this out? A code example would be appreciated.
Create an ActionFilter:
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// Do your work
}
}
Yes, but wouldn't I have to add the ActionFilter to each and every controller method?
No, you can apply it to the controller or to actions.
Alternatively, you can do the following and you will not have to apply it to every controller (sort of like a global filter):
[LogActionFilter ]
public class LogableApiController : ApiController
{
...
}
Then inherit that wherever you want.
And lastly, another option is to add to global filters by finding the App_Start/FilterConfig.cs and add:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LogActionFilter());
}
So I have shown you how to apply it to action level, controller level, one or more controllers but not all controllers and then how to apply it to all controllers (global).
I would create an Attribute for your Controller to execute the update on your User Entity with an ActionFilter.
This example explain how to create an attribute for a controller method, it is the same way to do it: Custom Attribute above a controller function
b.e, your controller would be like this:
[SaveUserRequest]
public class HomeController : ApiController

Pass identity user to services

We are developing a web application in which the user can register orders, customers, etc. and later review them. We have services that are used by MVC controllers in order to interface with the web UI.
Now we face the problem of multiple users: each service should be provided the currently authorised user Id, so all operations (CRUD and bussiness logic) will only be allowed for that user id. How is it supposed to be passed?
I am thinking about having a parameter passed to my IDataService (base class for services), which is instantiated by the WhateverController, which in turn has access to the User.Identity.GetUserId() method, BUT as I am using an IoC container (Ninject) I don't know how to do that. I guess that IDataService needs a reference to a IUserInfoProvider, so it can call IUserInfoProvider.GetUserId(). Then I can inject somehow an implementation based on Identity and having the current web context information, pretty much in the same way that the Controller must be instantiated.
Question is: how to get that data?
A simpler solution, of course, would be to do it by hand in each Controller constructor, but there should be a more automatic and elegant way to solve this.
EDIT: After some more reasearch, thanks to the answer of Cuong Le, the question I had to ask was, in fact, "how to inject the UserManager from the current context?".
However, in order to decouple my services layer from MVC, I created an IUserInfoProvider, which provides access to the authenticated user data. The implementation based in Identity and the UserManager lies in the Web UI (MVC) project, so it has a IPrincipal as suggested by Cuong Le, and an ApplicationUserManager, all injected using Ninject.
The following interface abstract the user information from Identity and the UserManager.
public interface IUserInfoProvider<T>
{
string GetUserId();
T GetUserData();
}
Here is the implementation in the MVC project using Identity and UserManager.
public class IdentityUserInfoProvider : IUserInfoProvider<DatosEmpresa>
{
private readonly ApplicationUserManager _userManager;
private readonly IPrincipal _user;
public IdentityUserInfoProvider(ApplicationUserManager userManager, IPrincipal user)
{
_userManager = userManager;
_user = user;
}
public string GetUserId()
{
return _user.Identity.GetUserId();
}
public DatosEmpresa GetUserData()
{
return _userManager.FindById(_user.Identity.GetUserId()).DatosEmpresa;
}
}
And the Ninject configuration bit
kernel.Bind<IUserInfoProvider<DatosEmpresa>>().To<IdentityUserInfoProvider>();
kernel.Bind<IPrincipal>()
.ToMethod(ctx => HttpContext.Current.User)
.InRequestScope();
kernel.Bind<ApplicationUserManager>()
.ToMethod(ctx => HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>())
.InRequestScope();
Then I can use an IUserInfoProvider inside any service object and it gets the correct user.
The simple solution is you can put IPrincipal into NInject Container:
kernel.Bind<IPrincipal>().ToMethod(context => HttpContext.Current.User);
So in your ServiceBase you can inject IPrincipal via either property or contructor, like this:
class ServiceBase
{
[Inject]
public IPrincipal User { get; set; }
}
Now you can get information from this property.

User (IPrincipal) not available on ApiController's constructor using Web Api 2.1 and Owin

I am Using Web Api 2.1 with Asp.Net Identity 2. I am trying to get the authenticated User on my ApiController's constructor (I am using AutoFac to inject my dependencies), but the User shows as not authenticated when the constructor is called.
I am trying to get the User so I can generate Audit information for any DB write-operations.
A few things I'm doing that can help on the diagnosis:
I am using only app.UseOAuthBearerTokens as authentication with Asp.Net Identity 2. This means that I removed the app.UseCookieAuthentication(new CookieAuthenticationOptions()) that comes enabled by default when you are creating a new Web Api 2.1 project with Asp.Net Identity 2.
Inside WebApiConfig I'm injecting my repository:
builder.RegisterType<ValueRepository>().As<IValueRepository>().InstancePerRequest();
Here's my controller:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
private IValueRepository valueRepository;
public ValuesController(IValueRepository repo)
{
valueRepository = repo;
// I would need the User information here to pass it to my repository
// something like this:
valueRepository.SetUser(User);
}
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
// User is not avaliable here either...
}
}
But if I inspect the User object on the constructor, this is what I get:
The authentication is working, if I don't pass my token, it will respond with Unauthorized. If I pass the token and I try to access the user from any of the methods, it is authenticated and populated correctly. It just doesn't show up on the constructor when it is called.
In my WebApiConfig I am using:
public static void Register(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// ... other unrelated injections using AutoFac
}
I noticed that if I remove this line: config.SuppressDefaultHostAuthentication() the User is populated on the constructor.
Is this expected? How can I get the authenticated user on the constructor?
EDIT:
As Rikard suggested I tried to get the user in the Initialize method, but it is still not available, giving me the same thing described in the image.
The problem lies indeed with config.SuppressDefaultHostAuthentication().
This article by Brock Allen nicely explains why that is. The method sets the principal intentionally to null so that default authentication like cookies do not work. Instead, the Web API Authentication filter then takes care of the authentication part.
Removing this configuration when you do not have cookie authentication could be an option.
A neat solution as mentioned here, is to scope the Web API parts of the application, so that you can separate out this configuration to a specific path only:
public void Configuration(IAppBuilder app)
{
var configuration = WebApiConfiguration.HttpConfiguration;
app.Map("/api", inner =>
{
inner.SuppressDefaultHostAuthentication();
// ...
inner.UseWebApi(configuration);
});
}
Don't know if this is still relevant, but I've had exactly the same problems, as you've described above. I've managed to solve it using custom OWIN middleware component.
Some info about my application structure:
Using MVC WebApp and WebAPI in same project (probably not the best option, but I have no time to change it, since deadline is approaching ;))
Using AutoFac as IoC container
Implemented custom ICurrentContext to hold information about currently logged on user (with CookieAuth in MVC and Bearer Token Auth in WebAPI), which is injected where needed (controllers, BAL objects, etc.)
Using EntityFramework 6 for Db access
Converted ASP.NET Identity to use int keys rather than string (http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity)
So on to the code. This is my ICurrentContext interface:
public interface ICurrentContext
{
User CurrentUser { get; set; } // User is my User class which holds some user properties
int? CurrentUserId { get; }
}
and implementation of it:
public class DefaultCurrentContext : ICurrentContext
{
public User CurrentUser { get; set; }
public int? CurrentUserId { get { return User != null ? CurrentUser.Id : (int?)null; } }
}
I've also created an OWIN middleware component:
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
namespace MyWebApp.Web.AppCode.MiddlewareOwin
{
public class WebApiAuthInfoMiddleware : OwinMiddleware
{
public WebApiAuthInfoMiddleware(OwinMiddleware next)
: base(next)
{
}
public override Task Invoke(IOwinContext context)
{
var userId = context.Request.User.Identity.GetUserId<int>();
context.Environment[MyWebApp.Constants.Constant.WebApiCurrentUserId] = userId;
return Next.Invoke(context);
}
}
}
Some information about this component: MyWebApp.Constants.Constant.WebApiCurrentUserId is some string constant (you can use your own) that I've used to avoid typos since its used in more than one place. Basicly what this middleware does, is that it adds current UserId to the OWIN environment dictionary and then Invokes the next action in pipeline.
Then I've created Use* extension statement to include OMC (OWIN Middleware Component) into OWIN pipeline:
using System;
using Owin;
namespace MyWebApp.Web.AppCode.MiddlewareOwin
{
public static class OwinAppBuilderExtensions
{
public static IAppBuilder UseWebApiAuthInfo(this IAppBuilder #this)
{
if (#this == null)
{
throw new ArgumentNullException("app");
}
#this.Use(typeof(WebApiAuthInfoMiddleware));
return #this;
}
}
}
To use this OMC, I've put the Use* statement right after Use* statement for Bearer token inside my Startup.Auth.cs:
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions); // This was here before
// Register AuthInfo to retrieve UserId before executing of Api controllers
app.UseWebApiAuthInfo(); // Use newly created OMC
Now the actual usage of this principle was inside AutoFac's Register method (called on some bootstrap code at the start of web application; in my case this was inside Startup class (Startup.cs), Configuration method) for my ICurrentContext implementation which is:
private static void RegisterCurrentContext(ContainerBuilder builder)
{
// Register current context
builder.Register(c =>
{
// Try to get User's Id first from Identity of HttpContext.Current
var appUserId = HttpContext.Current.User.Identity.GetUserId<int>();
// If appUserId is still zero, try to get it from Owin.Enviroment where WebApiAuthInfo middleware components puts it.
if (appUserId <= 0)
{
object appUserIdObj;
var env = HttpContext.Current.GetOwinContext().Environment;
if (env.TryGetValue(MyWebApp.Constants.Constant.WebApiCurrentUserId, out appUserIdObj))
{
appUserId = (int)appUserIdObj;
}
}
// WORK: Read user from database based on appUserId and create appUser object.
return new DefaultCurrentContext
{
CurrentUser = appUser,
};
}).As<ICurrentContext>().InstancePerLifetimeScope();
}
This method is called where I build AutoFac's container (hence the input parameter of type ContainerBuilder).
This way I got single implementation of CurrentContext, no matter how user was authenticated (via MVC Web Application or Web API). Web API calls in my case were made from some desktop application, but database and most of codebase were the same for MVC App and Web API.
Don't know if its the right way to go, but it has worked for me. Although I am still a little concerned how would this behave thread-wise, since I don't know exactly how using HttpContext.Current inside API calls would behave. I've read somewhere that OWIN Dictionary is used per-request basis, so I think this is safe approach. And I also think that this isn't so neat code, but rather a little nasty hack to read UserId. ;) If there's anything wrong with using this approcah, I'd appreciate any comment regarding it. I've been strugling with this for two weeks now and this is the closest I got of getting UserId in one place (when resolving ICurrentContext from AutoFac through lambda).
NOTE: Wherever there is usage of GetUserId, it can be replaced with original GetUserId (which returns string) implementation. The reason I'm using GetUserId is because I've rewritten ASP.NET to some extent for using ints instead of strings for TKey. I've done this based on following article: http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity
The User property of the controller is not populated until the Initialize method is called which happens after the constructor is invoked, hence thats why the Identity is not yet populated with the authorzied user data.
I realized that removing config.SuppressDefaultHostAuthentication() allowed me to get the Identity in the constructor much earlier. However, I wouldnt suggest doing this if you are using Token Authentication.
Thread.CurrentPrincipical is available throughout the pipeline, you could skip the User registration below:
valueRepository.SetUser(User);
and access
Thread.CurrentPrincipical
In the repository instead, making the repository context aware. Furthermore, you could add a context layer.
If nothing of the above solutions work try this one:
public ActionResult GetFiles()
{
...
string domainID = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name;
...
}

Categories

Resources