My base Request class looks like this:
public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}
public abstract class QueryBase<T> : UserContext, IRequest<T> // IRequest is MediatR interface
{
}
public abstract class UserContext
{
public string ApplicationUserId { get; set; } // and other properties
}
I want to write a middleware to my .NET Core 3.1 WebApi that will grab JWT from request header amd read ApplicationUserId from it. I started to code something:
public class UserInformation
{
private readonly RequestDelegate next;
public UserInformation(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var jwt = context.Request.Headers["Authorization"];
// read jwt here
var userContext = (UserContext)context.Request.Body; // i know it wont work
userContext.ApplicationUserId = //whats next? Any ideas?
await this.next(context);
}
}
But to be honest i have no idea how to start so here are my questions:
As you can see, every request will be packed with my UserContext class and so on. How to cast HttpContext.Request.Body to my request object and attach ApplicationUserId to it? Is it possible? I want to acces to user credentials from my JWT from headers and i want to have that information in every request in my API (pass it to controller, then to command etc).
If getting this information from middleware is not the best practice, what is?
EDIT: Mcontroller that using MediatR:
// base controller:
[ApiController]
[Route("[controller]")]
public abstract class BaseController : ControllerBase
{
private IMediator mediator;
protected IMediator Mediator => this.mediator ?? (this.mediator = HttpContext.RequestServices.GetService<IMediator>());
}
// action in ProjectControlle
[HttpGet]
[Authorize]
public async Task<ActionResult<ProjectsListModel>> GetAllProjects()
{
return Ok(await base.Mediator.Send(new GetAllProjectsQuery()));
}
// query:
public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}
// handler:
public class GetAllProjectsQueryHandler : IRequestHandler<GetAllProjectsQuery, ProjectsListModel>
{
private readonly IProjectRepository projectRepository;
public GetAllProjectsQueryHandler(IProjectRepository projectRepository)
{
this.projectRepository = projectRepository;
}
public async Task<ProjectsListModel> Handle(GetAllProjectsQuery request, CancellationToken cancellationToken)
{
var projects = await this.projectRepository.GetAllProjectsWithTasksAsync();
return new ProjectsListModel
{
List = projects
};
}
}
You might not need a middleware, but need a model binder:
See: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1
Also see: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1
public class UserContextModelBinder : IModelBinder
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IModelBinder _defaultModelBinder;
public UserContextModelBinder(
IHttpContextAccessor httpContextAccessor,
IOptions<MvcOptions> mvcOptions,
IHttpRequestStreamReaderFactory streamReaderFactory)
{
_httpContextAccessor = httpContextAccessor;
_defaultModelBinder = new BodyModelBinder(mvcOptions.Value.InputFormatters, streamReaderFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (!typeof(UserContext).IsAssignableFrom(bindingContext.ModelType))
{
return;
}
await _defaultModelBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet && bindingContext.Result.Model is UserContext)
{
var model = (UserContext)bindingContext.Result.Model;
var httpContext = _httpContextAccessor.HttpContext;
// Read JWT
var jwt = httpContext.Request.Headers["Authorization"];
model.ApplicationUserId = jwt;
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
Then add model binder to UserContext class:
[ModelBinder(typeof(UserContextModelBinder))]
public abstract class UserContext
{
public string ApplicationUserId { get; set; }
}
Also add IHttpContextAccessor to services in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHttpContextAccessor();
}
Related
public class GroupsController : ControllerBase
{
private readonly ILogger<GroupsController> _logger;
public GroupsController(ILogger<GroupsController> logger)
{
_logger = logger;
string auth = Request.Headers["authorization"];
if (auth is null)
throw new Exception("Missing auth token");
}
[HttpGet("/[controller]/allGroups")]
public List<Group> GetGroups()
{
DbContext dbContext = new DbContext();
List<Group> groups = dbContext.Groups.ToList();
return groups;
}
}
I'm looking to require a authorization header only for this controller, but Request is not possible on the constructor and I don't want to add a auth check on every method on the controller.
Is there a way to check this header on all routes on this controller?
You can carate a custom attribute that validates headers and put it on your action or controller that you want to validate headers. like this:
public class RequiredHeaderAttribute : Attribute, IActionFilter
{
private readonly string _requiredHeader;
public RequiredHeaderAttribute(string requiredHeader)
{
_requiredHeader = requiredHeader;
}
public void OnActionExecuted(ActionExecutedContext context)
{
//
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.HttpContext.Request.Headers.
TryGetValue(_requiredHeader, out _))
{
throw new Exception($"Missing Header Exception: {_requiredHeader}");
}
}
}
Usage:
[RequiredHeader("HeaderName")] //<==NOTE THIS
[HttpGet("/[controller]/allGroups")]
public List<Group> GetGroups()
{
DbContext dbContext = new DbContext();
List<Group> groups = dbContext.Groups.ToList();
return groups;
}
Or
[RequiredHeader("HeaderName")]
public class GroupsController : ControllerBase
{
}
Another way is register RequiredHeader as global filters.
I have base class for every request in my app:
public abstract class BaseDto
{
public string Uid { get; set; }
}
public class RequestDto : BaseDto
{
public string SomeData { get; set; }
}
Im using my ReuqestDto class in my controller actions:
[HttpGet]
public IEnumerable<string> Get(RequestDto req)
{
// some logic on request
if (req.Uid != null)
{
// perform action
}
}
The user passing only SomeData property to me. In my JWT Token i have saved some information about Uid for BaseDto. What is the best way to write data to Uid using middleware/filter to have that information in my Get() method? I Tried to serialized HttpContext.Request.Body but not success because i cant find, how to do it properly. Or maybe there are better solutions for this problem? How to write data to my incoming objects in app?
This is probably what you want.
You should to create own interface for models like that
public interface IMyRequestType { }
Your model should implement it for finding model in FilterAttribute
public class MyModel : IMyRequestType
{
public string ID { get; set; }
}
And create your filter attribute with OnActionExecuting implentation
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof(MyFilterImpl)) { }
private class MyFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public MyFilterAttributeImpl(ILoggerFactory loggerFactory)
{
// get something from DI
_logger = loggerFactory.CreateLogger<MyFilterAttributeImpl>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// get your request model
var model = context.ActionArguments.Values.OfType<IMyRequestType>().Single();
// get your key
//context.HttpContext.User or whatever
// do something with model
}
public void OnActionExecuted(ActionExecutedContext context)
{
// perform some logic work
}
}
}
I often created a filter which implements Attribute and IAsyncActionFilter to get the information before go inside the Controller's action.
Here is an example,
using System.IdentityModel.Tokens.Jwt;
public class UserProfileFilter : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string uid = string.Empty;
StringValues authHeaderVal = default(StringValues);
// Get UID from JWT
if (context.HttpContext.Request.Headers.TryGetValue("Authorization", out authHeaderVal))
{
string bearerTokenPrefix = "Bearer";
string accessToken = string.Empty;
string authHeaderStr = authHeaderVal.ToString();
if (!string.IsNullOrEmpty(authHeaderStr) && authHeaderStr.StartsWith(bearerTokenPrefix, StringComparison.OrdinalIgnoreCase))
{
accessToken = authHeaderStr.Replace(bearerTokenPrefix, string.Empty, StringComparison.OrdinalIgnoreCase).Trim();
}
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(accessToken);
uid = token.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Or Get UID from ActionExecutingContext
var user = context.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
uid = user.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Get payload
RequestDto payload = (RequestDto)context.ActionArguments?.Values.FirstOrDefault(v => v is RequestDto);
payload.Uid = uid;
await next();
}
}
And then you can put the filter on any action.
[HttpPost]
[Authorize]
[TypeFilter(typeof(UserProfileFilter))]
public ActionResult<IActionResult> AdminGet(RequestDto request)
{
Debug.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(request));
return this.Ok();
}
The above filter will use the sub claim's value to overwrite the value of the incoming payload.
For example, if I post the payload as following,
{
"uid" : "",
"someData": "Test"
}
The action will finally output {"Uid":"MyID","SomeData":"Test"}.
I create a filter IAuthorizationFilter and I need to use DepencyInjection in this attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AllowAccessAttribute : Attribute, IAuthorizationFilter
{
private readonly IDomainUnitOfWork unitofwork;
private readonly HttpContext httpContext;
public AllowAccessAttribute(IDomainUnitOfWork unitofwork,HttpContext httpContext)
{
this.unitofwork = unitofwork;
this.httpContext = httpContext;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
string controller = context.RouteData.Values["controller"].ToString();
string action = context.RouteData.Values["action"].ToString();
var userId = httpContext.User.Identity.GetUserId<long>();
var access = string.Format("{0}:{1}", controller, action);
if (unitofwork.UserRepository.AccessLevelRepository.ValidateAccess(userId, access))
context.Result = new StatusCodeResult(403);
}
}
When I need to use the this Attribute it show me error
[HttpGet]
[AllowAccess]
public async Task<ApiReturn> ChangeUserActiveStatus(long id)
{
var result = await dispatchers.SendAsync(new UserActiveStateCommand { id = id });
if (result.Success)
{
return Ok();
}
return BadRequest(result.Exception);
}
And show this error:
There is no argument given that corresponds to the required formal parameter 'unitofwork' of 'AllowAccessAttribute.AllowAccessAttribute(IDomainUnitOfWork, HttpContext)
What's the problem? how can I solve this problem?
You need to register the service in IoC container.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IDomainUnitOfWork, DomainUnitOfWork>(); // per request injection
...
}
Try also changing the attribute of the method to TypeFilter:
[HttpGet]
[TypeFilter(typeof(AllowAccessAttribute))]
public async Task<ApiReturn> ChangeUserActiveStatus(long id)
I'm working on an ASP.Net Core 3.1 web API.
The API users are from an Azure AD.
The users can access the API if they have a license, each user can be assigned to multiple licenses and the same license can be assigned to multiple users.
The client want me to structure the API routes with a template like <api_url>/{licenseId}/controller/action.
My controllers are like:
[Authorize]
[Route("{licenseId}/Foo")]
public class FooController : ControllerBase
{
If I think of the License as a Resource, I can use the Resource based authorization as detailed here.
It works but I find myself copying and pasting the auth check for all the actions.
Is there a better way to authorize an user using the route values?
Here what I've got so far:
public class LicenseRequirement : IAuthorizationRequirement
{
public Guid LicenseId { get; private set; }
public LicenseRequirement(Guid licenseId)
{
LicenseId = licenseId;
}
}
public class LicenseAuthorizationHandler : AuthorizationHandler<LicenseRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<LicenseAuthorizationHandler> _logger;
private readonly DBContext _db;
public LicenseAuthorizationHandler(DBContext context, ILogger<LicenseAuthorizationHandler> logger, IHttpContextAccessor httpContextAccessor)
{
_logger = logger;
_db = context;
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, LicenseRequirement requirement)
{
var userId = new Guid(context.User.GetUserId());
var licenseId = _httpContextAccessor.HttpContext.GetRouteData().Values["licenseId"];
if (await _db.ApiUsers.SingleOrDefaultAsync(x => x.LicenseId == new Guid(licenseId as string) && x.UserId == userId) is ApiUser user)
context.Succeed(requirement);
}
}
Now I'm a bit stuck as I don't know how to set it up in Startup.cs and use it as an attribute or area filter creating those requirement at runtime with the licenseId route's value.
I found IAuthorizationFilter pretty straightforward to implement. When I tried it yesterday, I couldn't found the RouteValues but they're there:
public class LicenseAuthorizationFilter : IAuthorizationFilter
{
private readonly ILogger<LicenseAuthorizationFilter> _logger;
private readonly DBContext _db;
public LicenseAuthorizationFilter(DBContext context, ILogger<LicenseAuthorizationFilter> logger)
{
_logger = logger;
_db = context;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var userId = new Guid(context.HttpContext.User.GetUserId());
var licenseId = new Guid(context.HttpContext.Request.RouteValues["licenseId"] as string);
if (!(_db.ApiUsers.SingleOrDefault(x => x.LicenseId == licenseId && x.UserId == userId) is ApiUser user))
{
context.Result = new ForbidResult();
}
}
}
public class LicenseAuthorizationAttribute : TypeFilterAttribute
{
public LicenseAuthorizationAttribute() : base(typeof(LicenseAuthorizationFilter))
{ }
}
And the controller can neatly become:
[Authorize]
[LicenseAuthorization]
[Route("{licenseId}/Items")]
public class ItemsController : ControllerBase
{
[...]
}
According to documents when I configure DbContext like below DI register it in scope (per http request)
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
The problem appears when I am trying to access it in another thread.
public class HomeController : Controller
{
private readonly DBData _context;
public HomeController(DBData context)
{
_context = context;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}
I want to configure DbContext creation per each call (AddTransition). It would give me possibility to write next code
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
//somehow configure it to use AddTransient
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
services.AddTransient<IUnitOfWorkFactoryPerCall, UnitOfWorkFactory>();
services.AddScoped<IUnitOfWorkFactoryPerRequest, UnitOfWorkFactory>();
services.AddMvc();
}
public interface IUnitOfWorkFactoryPerCall : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactoryPerRequest : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactory : IDisposable
{
DBData Context { get; }
}
public class UnitOfWorkFactory : IUnitOfWorkFactoryPerCall, IUnitOfWorkFactoryPerRequest
{
public UnitOfWorkFactory(DBData context)
{
Context = context;
}
public DBData Context
{
get; private set;
}
public void Dispose()
{
Context.Dispose();
}
}
So now if I want to create DBContext per request I will use IUnitOfWorkFactoryPerRequest, and when I want to use DBContext in some background thread I can use IUnitOfWorkFactoryPerCall.
My temporary solution.
I created singleton which can create Context "in transient way"
public class AppDependencyResolver
{
private static AppDependencyResolver _resolver;
public static AppDependencyResolver Current
{
get
{
if (_resolver == null)
throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
return _resolver;
}
}
public static void Init(IServiceProvider services)
{
_resolver = new AppDependencyResolver(services);
}
private readonly IServiceProvider _serviceProvider;
public AppDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IUnitOfWorkFactory CreateUoWinCurrentThread()
{
var scopeResolver = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
return new UnitOfWorkFactory(scopeResolver.ServiceProvider.GetRequiredService<DBData>(), scopeResolver);
}
}
Then I call init method in Startup Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AppDependencyResolver.Init(app.ApplicationServices);
//other configure code
}
And after all I can call AppDependencyResolver.Current.CreateUoWinCurrentThread() in some background thread.
If someone can provide more elegant solution I will be appreciated.
Within your controller, why are you trying to inject into private readonly DBData _context;? If you've registered your IUnitOfWorkFactoryPerCall via DI, you should be injecting that into your controller I believe? You then access your context via the interface.
To expand, this is what I am suggesting you do:
public class HomeController : Controller
{
private readonly IUnitOfWorkFactoryPerCall _contextFactory;
public HomeController(IUnitOfWorkFactoryPerCall contextFactory)
{
_contextFactory = contextFactory;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _contextFactory.Context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}