ActionContext.ActionArguments Is Empty In IAuthenticationFilter - c#

Background:
I want to authenticate a POST request to my web API using an implementation of IAuthenticationFilter injected using Ninject. To authenticate a request I need access to request body.
Problem:
ActionContext.ActionArguments, which I usually use to access request payload, is empty when I try to access it inside the filter.
Question:
How to access POST request payload inside an IAuthenticationFilter implementation?
Why ActionContext.ActionArguments is empty inside an IAuthenticationFilter implementation, but has values if my filter implements ActionFilterAttribute?
Code:
Filter implementation:
public class AuthenticateFilter : IAuthenticationFilter
{
private const string AuthenticationHeader = "X-Auth-Token";
private const string UserHeader = "X-Auth-User";
private readonly ILog log;
public AuthenticateFilter(ILog log)
{
this.log = log;
}
public Task AuthenticateAsync(HttpAuthenticationContext context,
CancellationToken cancellationToken)
{
// context.ActionContext.ActionArguments is empty
if (!IsAuthenticated(context))
{
context.ErrorResult =
new StatusCodeResult(HttpStatusCode.Unauthorized,
context.Request);
}
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken)
{
context.Result =
new StatusCodeResult(HttpStatusCode.Unauthorized,
context.Request);
return Task.FromResult(0);
}
private bool IsAuthenticated(HttpAuthenticationContext context)
{
// Authentication code here
// context.ActionContext.ActionArguments is empty
}
}
The filter is injected using Ninject when controller method has a attribute.
kernel.BindHttpFilter<AuthenticateFilter>(FilterScope.Action)
.WhenActionMethodHas<AuthenticateAttribute>();
AuthenticateAttribute is an empty ActionFilterAttribute.
public class AuthenticateAttribute : ActionFilterAttribute
{
}
Thank you!

This is expected behavior. Authentication and Authorization filters run before ModelBinding/Formatter deserialization stage, where as Action filters run after this stage.

I struggled a bit myself with the same situation, in case it helps anyone, you need to use Reflection and System.Web.Helpers's Json.Decode:
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
var content = request.Content.ReadAsAsync(typeof(Object)).Result.ToString();
var methodInfo = ((ReflectedHttpActionDescriptor)request.Properties["MS_HttpActionDescriptor"]).MethodInfo; // get the method descriptor
if (methodInfo.GetParameters().Any()) //this will get the parameter types
{
var parameterType = methodInfo.GetParameters().First().ParameterType; //you iterate can through the parameters if you need
var casted = Json.Decode(content, parameterType); //convert the json content into the previous type (your parameter)
//do something with your populated object :)
}
return Task.FromResult(context.Request);
}

Related

Passing parameter from MapPost to local function

I was refactoring my endpoints in a Minimal WebApi project and faced this situation:
namespace DACRL.API.Endpoints;
public class ApiUserManagementEndpoints : IEndpoints
{
private const string ContentType = "application/json";
private const string Tag = "ApiAccounts";
private const string BaseRoute = "apiAccounts";
public static void DefineEndpoints(IEndpointRouteBuilder app)
{
app.MapPost($"{BaseRoute}/login", LoginAsync)
.WithName("LoginApiUser")
.Accepts<ApiUserModel>(ContentType)
.Produces<string>()
.Produces<ApiAuthResponse>()
.Produces((int)HttpStatusCode.NotFound)
.Produces((int)HttpStatusCode.BadRequest)
.WithTags(Tag);
}
#region Helpers
private static async Task<IResult> LoginAsync(ApiUserModel model, string ipAddress, IApiUserService userService, IOptions<Jwt> jwtConfigRaw, CancellationToken ct)
{
...
return Results.Ok(result);
}
private static string GenerateToken(Jwt jwtConfig, string username)
{
// Token stuff
}
#endregion
public static void AddServices(IServiceCollection services, IConfiguration configuration) =>
services.AddTransient<IApiUserService, ApiUserService>();
}
Now, LoginAsync needs a parameter value for ipAddress. How do I pass HttpContext.Connection.RemoteIpAddress.ToString() from my app.MapPost ?
You can add the HttpContext as handler method parameter and use it:
private static async Task<IResult> LoginAsync(ApiUserModel model,
HttpContext ctx, // here
IApiUserService userService,
IOptions<Jwt> jwtConfigRaw,
CancellationToken ct)
{
var ip = ctx.Connection.RemoteIpAddress;
//...
}
See the special types subsection of Minimal APIs parameter minding docs:
Special types
The following types are bound without explicit attributes:
HttpContext: The context which holds all the information about the current HTTP request or response.
HttpRequest and HttpResponse: The HTTP request and HTTP response.
CancellationToken: The cancellation token associated with the current HTTP request.
ClaimsPrincipal: The user associated with the request, bound from HttpContext.User.
After a bit of head scratching, I realized exposing the ipAddress parameter made swagger think I had to supply that myself. Here is what I did:
private static async Task<IResult> LoginAsync(ApiUserModel model, IHttpContextAccessor httpContextAccessor, IApiUserService userService, IOptions<Jwt> jwtConfigRaw, CancellationToken ct)
{
...
var ipAddress = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString();
...
}
So injecting the IHttpContextAccessor solved this for me.

ASP.NET Core ActionFilter Dependency Injection ObjectDisposedException

I want to create a global action filter to audit all requests to my API. I want to use a globally registered ActionFilter to ensure all API Controller Actions are audited. I want to inject a scoped instance of AuditService, however, I get a System.ObjectDisposedException when calling _auditService.Create. What is the correct way to inject a scoped service into an ActionFilter so that it doesn't get disposed of before OnActionExecuted is called? The service is also disposed of before the OnActionExecuting event.
Startup code:
public void ConfigureServices(IServiceCollection services)
{
// ..
services.AddControllers(c => c.Filters.Add<AuditFilter>());
services.AddDbContext<MyDbContext>(c => c.UseSqlServer("ConnectionString"));
services.AddScoped<IAuditService, AuditService>();
// ..
}
Action Filter:
public class AuditFilter : IActionFilter
{
private readonly IAuditService _auditService;
public AuditFilter(IAuditService auditService)
{
_auditService = auditService;
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
public async void OnActionExecuted(ActionExecutedContext context)
{
string username = ClaimsPrincipal.Current?.Identity?.Name;
string remoteAddr = $"{context.HttpContext.Connection.RemoteIpAddress}:{context.HttpContext.Connection.RemotePort}";
string queryString = context.HttpContext.Request.QueryString.HasValue ? context.HttpContext.Request.QueryString.Value : null;
using StreamReader reader = new StreamReader(context.HttpContext.Request.Body);
string body = await reader.ReadToEndAsync();
body = body.Length > 0 ? body : null;
// System.ObjectDisposedException: 'Cannot access a disposed context instance
await _auditService.Create(username, remoteAddr, context.HttpContext.Request.Method,
context.HttpContext.Request.Path, queryString, body, DateTime.Now);
}
}
Audit Service:
public class AuditService : IAuditService
{
private DRSContext Context { get; }
public AuditService(DRSContext context)
{
Context = context;
}
public async Task<bool> Create(string username, string remoteAddr, string httpMethod, string path, string query,
string body, DateTime timestamp)
{
await Context.AuditLogs.AddAsync(new AuditLog
{
Username = username,
RemoteAddress = remoteAddr,
HttpMethod = httpMethod,
Path = path,
Query = query,
Body = body,
Timestamp = timestamp
});
return await Context.SaveChangesAsync() > 0;
}
// ..
}
There are some things wrong in your code here:
You use async void on the OnActionExecuted. This should be avoided because of unpredictable results you may have. If you want to use async code, try implementing IAsyncActionFilter instead.
You implement Dispose for your implementation class of IAuditService in which you explicitly dispose the DbContext. You don't need to do that manually which can go out-of-sync with how the DI manages the DbContext (as scoped service) for you. Usually code inside Dispose is used to dispose unmanaged resources.
Finally I would suggest you to use IAsyncResourceFilter instead. It will be invoked by both controller actions & page handlers whereas the IAsyncActionFilter will be executed only by controller actions. You can examine the ResourceExecutingContext.ActionDescriptor to know about the action. It can be a ControllerActionDescriptor or a PageActionDescriptor.

Why is HttpContext.Session status changing to disposed?

I'm trying to store token I get from external api on session.
code snippet concerning this;
[HttpPost]
public async void Post()
{
if (HttpContext.Session.GetValue<User>("Token") == null)
{
HttpContext.Session.SetValue("Token", "test");
var res = await _loginBusiness.GetToken();
HttpContext.Session.SetValue("Token", res);
}
}
HttpContext.Session.SetValue("Token", "test");
in this part, it doesn't occur any error but second the same code line give an error after GetToken().
related error
System.ObjectDisposedException: 'IFeatureCollection has been disposed.
Object name: 'Collection'.'
Also GetToken():
public async Task<User> GetToken()
{
String url = "login/login";
var client = httpClientFactory.CreateClient("VoiceScope");
var postRes = await client.PostAsync<User>(new UserLogin(), url);
return postRes;
}
The problem is that you are using async void. These promises can't be observed and their semantics end up a lot different from a normal Task. Your disposal is happening early because the infrastructure just assumes your Post method has completed (it has no way to tell otherwise).
Change the signature of Post to be:
public async Task Post()
Please note that async void should be limited to event handlers.
I am not sure about using HttpContext. You have IHttpContextAccessor in asp.net core.
I think for store token you can use this
public class UserContext
{
public UserContext(IHttpContextAccessor context)
{
Token = GetAccessToken(context);
}
private static string GetAccessToken(IHttpContextAccessor contextAccessor)
{
var identity = (ClaimsIdentity)contextAccessor?.HttpContext?.User?.Identity;
return identity?.Claims.FirstOrDefault(x => x.Type == "token")?.Value;
}
public string Token { get; }
}
And then, add this staff in your DI like scope object and use it in controllers via ServiceProvider.

Order of filters in .NET Core

How do I get a filter added as decorator in a controller to trigger AFTER a filter added in startup please?
I.e.
My Startup.cs looks like this:
services.AddControllers(options =>
{
options.Filters.Add<MyErrorHandlingFilter>();
});
My controller:
[HttpPost()]
[SignResponseFilter]
public async Task<ActionResult> DoSomething([FromBody] request)
{
// does stuff and causes an exception(the MyErrorHandlingFilter.OnExceptionAsync() to be called)
}
My SignResponseFilter:
public class SignResponseFilter : TypeFilterAttribute
{
public SignResponseFilter() : base(typeof(SignResponseFilterImplementation))
{
}
private class SignResponseFilter: IAsyncResultFilter
{
private readonly ISign _signer;
public SignResponseImplementation(ISign signer)
{
_signer= signer;
}
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await next();
var response = await ResponseBodyReader.ReadResponseBody(context.HttpContext.Response);
var signature = await _signer.signIt(response);
context.HttpContext.Response.Headers.Add("myheader", signature);
}
}
}
MyErrorHandlingerfilter:
public class MyErrorHandlingerfilter: ExceptionFilterAttribute
{
private readonly IFormatter _formatter;
public CustomErrorHandlerFilterAttribute(IFormatter fortmatter)
{
_formatter = fortmatter;
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
_formatter.DoFormatting(); // does some formatting
await base.OnExceptionAsync(context);
}
My problem is that SignResponseFilter is skipped when an exception occurs. MyErrorHandlingFilter does its formatting though. I would like it that SignResponse occurs on success and also, even when an exception occurs.
Following diagram shows how these filters interact in filter pipeline during request and response life cycle.
According your code, the SignResponseFilter is a Result filter, so, from above diagram, we can know that when the exception executed, it will trigger the Exception Filter first, not trigger the Result Filter. So, the SignResponse not occurs.
If you want to use other Filters with the Exception Filter, you could consider using the Action Filter.
More detail information, check Filters in ASP.NET Core

How to get params from AuthorizationHandler .NET Core

I am using an authorization handler to put custom authorization in my controller in .net core. How can I get the parameters from the controller and use it to the authorization handler?
In the old .NET I can get the parameters from HttpContext request param like this:
var eventId = filterContext.RequestContext.HttpContext.Request.Params["id"];
I am not sure how can I achieved it in .net core
public class HasAdminRoleFromAnySiteRequirement : AuthorizationHandler<HasAdminRoleFromAnySiteRequirement>, IAuthorizationRequirement
{
public HasAdminRoleFromAnySiteRequirement()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
HasAdminRoleFromAnySiteRequirement requirement)
{
//need to call get param from controller to used in the validation
// something like this
//var eventId = filterContext.RequestContext.HttpContext.Request.Params["id"];
// I tried the suggestion below but I can't get the parameter from routedata
// var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
return Task.FromResult(0);
}
}
In ASP.NET Core 3.0 with endpoint routing enabled, you can get a route parameter value like this:
public class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var routeData = _httpContextAccessor.HttpContext.GetRouteData();
var areaName = routeData?.Values["area"]?.ToString();
var area = string.IsNullOrWhiteSpace(areaName) ? string.Empty : areaName;
var controllerName = routeData?.Values["controller"]?.ToString();
var controller = string.IsNullOrWhiteSpace(controllerName) ? string.Empty : controllerName;
var actionName = routeData?.Values["action"]?.ToString();
var action = string.IsNullOrWhiteSpace(actionName) ? string.Empty : actionName;
//...
}
}
In your handler you can do the following
var mvcContext = context.Resource as
Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext != null)
{
// Examine MVC specific things like routing data.
}
If you want parameter values then the authorize attribute pieces run before binding has taking place. Instead you would move to an imperative call, inside your controller. This is basically resource based authorization, your parameter is a resource.
You would inject the authorization service into your controller;
public class DocumentController : Controller
{
IAuthorizationService _authorizationService;
public DocumentController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
}
Then write your handler slightly differently;
public class DocumentAuthorizationHandler : AuthorizationHandler<MyRequirement, Document>
{
public override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MyRequirement requirement,
Document resource)
{
// Validate the requirement against the resource and identity.
return Task.CompletedTask;
}
}
You can see this handler takes a document, this can be whatever you like, be it an integer for an ID, or some type of view model.
Then you have access to it inside your HandleRequirementAsync() method.
Finally, you'd call it from within your controller, once binding has taken place;
if (await authorizationService.AuthorizeAsync(
User,
document,
yourRequirement))
{
}
In ASP.NET Core 2.2, you can get a route parameter value like this:
public class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var authContext = (AuthorizationFilterContext)context.Resource;
var routeValueOfX = authContext.HttpContext.GetRouteValue("X");
.
.
.
}
}
For future reference, starting .NET Core 5.0, the HttpContext is now passed instead, so you can do:
if (context.Resource is HttpContext httpContext)
{
var value = httpContext.GetRouteValue("key");
}
For .Net 5.0 (If you are using .NET 3.0, 3.1 then it will be better) use following:
public class MyAuthorizationPolicyHandler : AuthorizationHandler<OperationAuthorizationRequirement>
{
public MyAuthorizationPolicyHandler()
{
}
protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
{
var result = false;
if (context.Resource is Microsoft.AspNetCore.Http.DefaultHttpContext httpContext)
{
var endPoint = httpContext.GetEndpoint();
if (endPoint != null)
{
var attributeClaims = endPoint.Metadata.OfType<MyAuthorizeAttribute>()
//TODO: Add your logic here
}
if (result)
{
context.Succeed(requirement);
}
}
}
Please refer to following related discussion: "context.Resource as AuthorizationFilterContext" returning null in ASP.NET Core 3.0
You can access parameters directly from your handler pretty easily. Now, I'm sure if think works for earlier versions of core (you should update core anyway if you can), but in core 2.0 and beyond, you can cast the context.Resource to an AuthorizationFilterContext in the HandleRequirementAsync method like so
if(context.Resource is AuthorizationFilterContext mvcContext)
{
//do stuff to the mvcContext
}
Then, you can access the parameters like this
var value = mvcContext.HttpContext.Request.Query[key].FirstOrDefault();
where key is the parameter name you are looking for.
Or you could parse the query string like this
var queryString = mvcContext.HttpContext.Request.QueryString.ToString()
var foo = HttpUtility.ParseQueryString(queryString);
var value = foo[key]
again, where key is the parameter name you are looking for.

Categories