I am building a webapi in .Net Core 6.0.
public class UserDetails
{
public int UserId { get; set; }
public string UserName { get; set; }
}
I want to read the user details from the httpcontext.Request and initialize this UserDetails object and store the values in the properties.
And use the same object of UserDetails across the application to read the userId and userName.
All the online articles suggest using ConfigureServices() and add a singleton of the UserDetails class.
But the problem is I wont have access to httpContext in the startup.cs/program.cs.
is there a better way of doing it?
EDIT1:
My Middleware :
public class UserDetailsMiddleWare
{
private readonly RequestDelegate _next;
public UserDetailsMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
UserDetails userDtls = new UserDetails()
{
UserId = httpContext.Request.Headers["userid"],
UserName = httpContext.Request.Headers["username"]
};
await _next(httpContext);
}
}
My Program.cs file:
services.AddScoped<UserDetails>();
app.UseMiddleware<UserDetailsMiddleWare>();
app.Use((context, next) =>
{
// Get the scoped service from middleware
var userDetails = context.RequestServices.GetRequiredService<UserDetailsMiddleWare>();
// Populate the user details from the request so that anyone else that resolves this service will have the correct details.
userDetails.InvokeAsync(context);
return next(context);
});
My Service:
public class MyService: IMyService
{
private readonly IMyGateway _myGateway;
private UserDetails _details;
public MyService(IMyGateway myGateway, UserDetails details)
{
_myGateway = myGateway;
_details = details;
}
public async Task<string> myMethod()
{
string userid =_details.UserId;
return await _myGateway.GenerateAsync();
}
}
It's worthwhile thinking about the lifetime of objects when reasoning through solving this problem. The reason you don't have an http request at startup is because the server isn't even listening for requests yet. The lifetime of the UserDetails object should match the lifetime of the incoming request (because that's where it's getting the details from). If you made UserDetails a singleton, which is a single instance for ALL requests, which user's details would it have? Which request? A singleton is NOT what you want in this case.
So breaking the problem down some more, you need to populate the user details object in a place where you have access to the request. The most obvious place for doing that would be in ASP.NET Core middleware.
Now to have that UserDetails object flow to other services, you'd need to make it a scoped service.
services.AddScoped<UserDetails>();
...
app.Use((context, next) =>
{
// Get the scoped service from middleware
var userDetails = context.RequestServices.GetRequiredService<UserDetails>();
// Populate the user details from the request so that anyone else that resolves this service will have the correct details.
userDetails.UserId = httpContext.Request.Headers["userid"];
userDetails.UserName = httpContext.Request.Headers["username"];
return next(context);
});
Related
I'm testing some custom authorization without the default Entity Framework stuff.
I have created an "ASP.NET Core Web App (Model-View-Controller)" project using "Authentication type" = "Individual Accounts".
In Program.cs I have:
builder.Services.AddTransient<IUserStore<CustomIdentityUser>, CustomUserStore>();
builder.Services
.AddDefaultIdentity<CustomIdentityUser>()
.AddUserStore<CustomUserStore>();
For the moment CustomIdentityUser is just an empty class.
CustomUserStore looks like this:
public class CustomUserStore : IUserStore<CustomIdentityUser>
{
public void Dispose()
{
GC.SuppressFinalize(this);
}
public Task<CustomIdentityUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
// Use dummy user for now
//return new Task<CustomIdentityUser>(() => new CustomIdentityUser());
return new Task<CustomIdentityUser>(() => { throw new Exception("THIS DOES NOT HAPPEN!"); });
}
...
(All other methods from IUserStore currently throws NotImplementedException.)
If I start the application, go the the login page, enter some credentials and click login I can see that FindByNameAsync in CustomUserStore is called. Good, it seems the application actually uses my custom user store to look for the user whose name I just entered.
But that's where my luck ends. The user interface seems to be waiting for the login to complete. The Task returned from FindByNameAsync doesn't seem to be started at all...why? I think that the caller should get CustomIdentityUser instance from it (and then probably call GetPasswordHashAsync in CustomUserStore).
why do you return new task in FindByNameAsync , also CustomUserStore
should be for extending identity properties. in my opinion, creating a service for identity functionality and using dependency injection
create an interface with the name IIdentity.cs
public Task<IdentityResult> UpdateUserAsync(string id , UpdateUserVm updatedUser);
then the implementation class IdentityService
public class IdentityService : IIdentity
{
...
public async Task<IdentityResult> UpdateUserAsync(string id, UpdateUserVm updatedUser)
{
var currentUser = await _userManager.FindByIdAsync(id);
currentUser.PhoneNumber = updatedUser.PhoneNumber;
return currentUser != null ? await _userManager.UpdateAsync(currentUser) : IdentityResult.Failed();
}
}
then register the service
service.AddScoped<IIdentity, IdentityService>();
in controller
public class AccountController : Controller
{
private readonly IIdentity _identityService;
public AccountController(IIdentity identityService) =>
(_identityService) = (identityService);
[HttpPost]
public async Task<IActionResult> UpdateProfile(params)
{
...
await _identityService.UpdateUserAsync(params)
}
}
I have a blazor server web application and a .NET Core worker process, these both use a common class for data access (generic unit of work / generic repository).
In the database I would like to log the user names that are inserting or editing records. To do this I want to inject a ClaimsPrincipal to the shared UoW and Repo classes).
So, I would like to be able to extract the current ClaimsPrincipal in a transient service via dependency injection.
For the worker I can inject a ClaimsPrincipal via the following code;
public static IServiceCollection CreateWorkerClaimsPrincipal(this IServiceCollection services, string workerName)
{
Claim workerNameClaim = new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", workerName);
ClaimsIdentity identity = new ClaimsIdentity(
new System.Security.Claims.Claim[] { workerNameClaim },
"My-Worker-Authentication-Type",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"role");
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
services.AddTransient<ClaimsPrincipal>(s => principal);
return services;
}
This is working and meets my needs.
For the blazor server web application I need to do something similar.
I believe that the correct way to extract the ClaimsPrincipal is via the AuthenticationStateProvider, however this needs a call to an async method GetAuthenticationStateAsync.
NOTE: I cannot user IHttpContextAccessor as this doesn't work with Azure App Service.
I want something like;
public void ConfigureServices(IServiceCollection services)
{
/// ...
services.AddTransient<ClaimsPrincipal>(); // I think I need to do something here?
/// ...
}
So when I request a ClaimsPrincipal via dependency injection I want to return the user from;
var authState = await AUthenticationStateProvider.GetAuthenticationStateAsync();
return authState.User;
Is this possible?
As is often the way, by working this through into a simple example for a SO post I have found a workable (I think) solution from https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0#implement-a-custom-authenticationstateprovider
NOTE: I'm still not 100% sure if the async init pattern will always resolve the AuthenticationState before the Repository property is called, but its hanging together so far... Just beware of this if you choose to use this code.
I have changed the approach, and instead of trying to resolve ClaimsPrincipal via DI (because AuthenticationStateProvider is not available for a worker process), I have created a custom AuthenticationStateProvider in the worker.
public class WorkerAuthStateProvider : AuthenticationStateProvider
{
private readonly string _workerName;
public WorkerAuthStateProvider(string workerName)
{
_workerName = workerName;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, _workerName),
}, "My-Worker-Authentication-Type");
ClaimsPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
}
and then register this in configureServices to resolve for instances of AuthenticationStateProvider in the worker program.cs file (also passing a custom worker process name, so I can use this on all my workers);
services.AddScoped<AuthenticationStateProvider, WorkerAuthStateProvider>(serviceProvider =>
{
return new WorkerAuthStateProvider(Constants.Logging.RoleNames.MYWORKERNAME);
});
The AuthenticationStateProvider already works in the blazor web apps so this allows me to resolve this correctly, in the constructor for my GenericUnitOfWork pattern for data access on both Web and Workers, for example;
private TDbContext _dbContext;
private readonly ILogger<TEntity> _logger;
private GenericRepository<TEntity, TDbContext> _repository;
private ClaimsPrincipal _user;
private readonly AuthenticationStateProvider _authenticationStateProvider;
public GenericUnitOfWork(TDbContext context, ILogger<TEntity> logger, AuthenticationStateProvider authenticationStateProvider)
{
_dbContext = context;
_logger = logger;
_authenticationStateProvider = authenticationStateProvider;
UserInit = InitUserAsync();
}
/// <summary>
/// Async initialisation pattern from https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
/// </summary>
public Task UserInit { get; private set; }
private async Task InitUserAsync()
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
_user = authState.User;
}
public IGenericRepository<TEntity, TDbContext> Repository
{
get
{
if (_repository == null)
{
// when accessing the repository, we are expecting to pass the current application claims principal
// however the ClaimsPrincipal is resolved using an Async method from the AuthenticationStateProvider.
// In the event that the Async method has not yet completed we need to throw an exception so we can determine
// if a further async code fix is required.
if (_user == null)
{
throw new InvalidOperationException("Async ClaimsPrincipal has not been loaded from the AuthenticationStateProvider");
}
_repository = new GenericRepository<TEntity, TDbContext>(_dbContext, _logger, _user);
}
return _repository;
}
}
I am struggling to find a good solution for doing custom authorization checks without having to repeat the authorization check manually over and over again.
To illustrate, suppose I have the following setup for a .net core web api, which has two endpoints, one for GET and one for POST. I would like to check (maybe against db) whether the user has the right to see the resource, or the right to create a resource.
This is what the documentation refers to as resource based authorization
and would look something like this:
[Authorize]
[ApiVersion ("1.0")]
[Route ("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ResourcesController : ControllerBase {
private readonly IAuthorizationService _authorizationService;
//..constructor DI
[HttpGet ("{resourceId}")]
public ActionResult<Resource> Get (Guid resourceId) {
var authorizationCheck = await _authorizationService.AuthorizeAsync (User, resourceId, ServiceOperations.Read);
if (!authorizationCheck.Succeeded) {
return Forbid ();
}
return Ok (ResourceRep.Get (resourceId));
}
[HttpPost]
public ActionResult<Resource> Post ([FromBody] Resource resource) {
var authorizationCheck = await _authorizationService.AuthorizeAsync (User, null, ServiceOperations.Write);
if (!authorizationCheck.Succeeded) {
return Forbid ();
}
return Ok (ResourceRep.Create (resource));
}
}
Now imagine the ServiceOperations enum has a long list of supported operations, or there are 100 different endpoints, I will have to do the same check everywhere, or even worse, might forget to add a check where I should definitely have added a check. And there is not an easy way to pick this up in unit tests.
I thought of using attributes but as the docs state:
Attribute evaluation occurs before data binding and before execution of the page handler or action that loads the document. For these reasons, declarative authorization with an [Authorize] attribute doesn't suffice. Instead, you can invoke a custom authorization method—a style known as imperative authorization.
So it seems I cannot use an authorization policy and decorate the methods with authorization attributes (which are easy to unit test that they are there) when the check itself requires a parameter that is not available (the resourceId).
So for the question itself:
How do you use imperative (resource based) authorization generically without having to repeat yourself (which is error-prone). I would love to have an attribute like the following:
[HttpGet ("{resourceId}")]
[AuthorizeOperation(Operation = ServiceOperations.Read, Resource=resourceId)]
public ActionResult<Resource> Get (Guid resourceId) {..}
[AuthorizeOperation(Operation = ServiceOperations.Write)]
[HttpPost]
public ActionResult<Resource> Post ([FromBody] Resource resource) {..}
You can achieve it using AuthorizationHandler in a policy-based authorization and combine with an injected service specifically created to determine the Operation-Resources pairing.
To do it, first setup the policy in Startup.ConfigureServices :
services.AddAuthorization(options =>
{
options.AddPolicy("OperationResource", policy => policy.Requirements.Add( new OperationResourceRequirement() ));
});
services.AddScoped<IAuthorizationHandler, UserResourceHandler>();
services.AddScoped<IOperationResourceService, OperationResourceService>();
next create the OperationResourceHandler :
public class OperationResourceHandler: AuthorizationHandler<OperationResourceRequirement>
{
readonly IOperationResourceService _operationResourceService;
public OperationResourceHandler(IOperationResourceService o)
{
_operationResourceService = o;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authHandlerContext, OperationResourceRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext filterContext)
{
var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
if (_operationResourceService.IsAuthorize(area, controller, action, id))
{
context.Succeed(requirement);
}
}
}
}
the OperationResourceRequirement can be an empty class:
public class OperationResourceRequirement : IAuthorizationRequirement { }
The trick is, rather than specify action's Operation in attribute, we specify it elsewhere such as in database, in appsettings.json, in some config file, or hardcoded.
Here's an example getting the Operation-Resource pair from config file:
public class OperationResourceService : IOperationResourceService
{
readonly IConfiguration _config;
readonly IHttpContextAccessor _accessor;
readonly UserManager<AppUser> _userManager;
public class OpeartionResourceService(IConfiguration c, IHttpContextAccessor a, UserManager<AppUser> u)
{
_config = c;
_accessor = a;
_userManager = u;
}
public bool IsAuthorize(string area, string controller, string action, string id)
{
var operationConfig = _config.GetValue<string>($"OperationSetting:{area}:{controller}:{action}"); //assuming we have the setting in appsettings.json
var appUser = await _userManager.GetUserAsync(_accessor.HttpContext.User);
//all of needed data are available now, do the logic of authorization
return result;
}
}
Please note that to make IHttpContextAccessor injectable, add services.AddHttpContextAccessor() in Startup.ConfigurationServices method body.
After all is done, use the policy on an action:
[HttpGet ("{resourceId}")]
[Authorize(Policy = "OperationResource")]
public ActionResult<Resource> Get (Guid resourceId) {..}
the authorize policy can be the same for every action.
I need to get the time of the request to version some database records and other records that will be created throughout the request.
I can't use DateTime now because I want the same time to be accessible throughout the request.
I can't seem to find anything in the HTTPContext class to help me.
Using HttpContext.Features and your HTTP request pipeline middleware
public interface IHttpRequestTimeFeature
{
DateTime RequestTime { get; }
}
public class HttpRequestTimeFeature : IHttpRequestTimeFeature
{
public DateTime RequestTime { get; }
public HttpRequestTimeFeature()
{
RequestTime = DateTime.Now;
}
}
// You don't need a separate class for this
public class RequestTimeMiddleware
{
private readonly RequestDelegate _next;
public RequestTimeMiddleware(RequestDelegate next)
{
_next = next;
}
public Task InvokeAsync(HttpContext context)
{
var httpRequestTimeFeature = new HttpRequestTimeFeature();
context.Features.Set<IHttpRequestTimeFeature>(httpRequestTimeFeature);
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
}
You have to add this middleware in your Startup.Configure:
app.UseMiddleware<RequestTimeMiddleware>();
You can access request time like:
var httpRequestTimeFeature = HttpContext.Features.Get<IHttpRequestTimeFeature>();
if (httpRequestTimeFeature != null)
{
var requestTime = httpRequestTimeFeature.RequestTime;
}
Using HttpContext.Items
HttpContext.Items["RequestTime"] = DateTime.Now;
You can also store it in your scoped service(services.AddScoped<YourService>()) if I'm not wrong, that will be valid through the entire request.
I'm not aware if there's such thing as request time built-in into ASP.NET Core though.
You can also set this in MVC filters but I think this is more valid in the lower level (HTTP request pipeline).
My scoped service for some reason seems to be generating different instances of the same class when I try to access it in 2 middlewares within the same request.
Scenario: I am adding a scoped service as such:
public interface ISimplyRecorder
{
void AddInfo(string key, string value);
Dictionary<string, string> GetAllInfo();
}
public class SimplyCoreRecorderService : ISimplyRecorder
{
private Dictionary<string,string> data;
public SimplyCoreRecorderService()
{
data = new Dictionary<string, string>();
}
public void AddInfo(string key,string value)
{
data.Add("",value);
}
public Dictionary<string,string> GetAllInfo()
{
return data;
}
}
and then the following in startup.cs
services.AddScoped<ISimplyRecorder,SimplyRecorderService>();
now I am calling this service in the constructor of a sample Middleware. I am able to access the service with a new instance and add data into it and then I call await _next(context). However, when I am calling the service in my HomeController, MVC which follows the middleware above, I seem to be getting a new instance of the service even though it's the same request.
HomeController:
ISimplyRecorder _simply;
private IHostingEnvironment _env;
public HomeController(IHostingEnvironment env,ISimplyRecorder simply)
{
_simply = simply;
_env = env;
}
public IActionResult Index()
{
_simply.AddInfo("Home:Action","resulted in index action");
return View();
}
complete code available at: https://github.com/muqeet-khan/SimplyCore if someone wants to give it a go.
Middlewares are instantiated only once when it's first involved, then all the following requests are handled by that middleware instance. NOT a new middleware instance for each request.
You get your ISimplyRecorder in the constructor of the middleware and "cache" it as a private readonly variable. This means the middleware will get the ISimplyRecorder instance of the first request, then keep adding data to that instance for all the following requests rather than the new ISimplyRecorder instance for the following requests which you get in HomeController.
To solve it, you need to get ISimplyRecorder instance from the Invoke method of the middleware.
// using Microsoft.Extensions.DependencyInjection;
public async Task Invoke(HttpContext httpContext)
{
ISimplyRecorder recoder = httpContext.RequestServices.GetRequiredService<ISimplyRecorder>();
}
EDIT:
The comment of Juergen is correct, I tried it out. You may also just write like this:
public async Task Invoke(HttpContext httpContext, ISimplyRecorder recorder)
{
// recorder is from DI
}