model-view-controllerjwtasp.net-core-webapiactionfilterattribute

How to best implement simple validation of JWT credential to data being passed to my controller


My ASPNetCore web API is using JWT (Json Web Tokens) for authentication. The JWT token has an external and internal user ID inside it. Having these ID's in the JWT does not concern me, as JWT's can't be tampered with or they become invalid, and the internal ID is not useful anywhere outside the system. Of course, the password is not in the JWT content.

Within the JWT, the external user ID becomes the user's System.Security.Claims.ClaimType.Name. The internal ID is set as a JwtRegisteredClaimName.UniqueName value.

When calls are made to the web API, it is good that the [Authorize] attribute attribute makes sure that the user has authenticated and has a currently valid JWT. The concern I have is that once the user is logged in, there is an opportunity for hacking by using the Web API, sending external or internal user id's as criteria that do not match the currently authenticated user. Some web methods in the controllers accept the internal user ID as part of the request being posted, for example, a call to save user information has the internal user ID inside, used as the key for saving the data. I need to be sure that the authenticated user matches/is the same as the user whose data is being saved via the Web API.

My Question is how and where to best implement this data-level security in my web api? Policies don't seem like they can be applied against the data being passed. Authorization filters don't seem to have access to the message body nor any data bindings. Action filters (Microsoft.ASPNetCore.MVC.Filters) run later, but seem like they may not really be intended for this. Also, how do you access the body of the message that was posted inside an action filter? Or should I always make sure that the user ID is passed to methods as a consistently named parameter that I can access via ActionExecutingContext.ActionArguments?

I've searched many posts and not found any scenarios that match what I'm trying to do.


Solution

  • You can always use Middleware to intercept the call when the Response object has been populated , see code sample form here and here .

    Authorization filters could also read the request body with EnableRewind :

    public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var request = context.HttpContext.Request;
            context.HttpContext.Request.EnableRewind();
            using (var stream = new StreamReader(request.Body))
            {
                stream.BaseStream.Position = 0;
              var  requestBody = stream.ReadToEnd();
            }
    
        }
    }
    

    Also works in action filters :

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class ReadableBodyStreamAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext actionContext)
        {
            var request = actionContext.HttpContext.Request;
            var route = request.Path.HasValue ? request.Path.Value : "";
            var requestHeader = request.Headers.Aggregate("", (current, header) => current + $"{header.Key}: {header.Value}{Environment.NewLine}");
            var requestBody = "";
            request.EnableRewind();
            using (var stream = new StreamReader(request.Body))
            {
                stream.BaseStream.Position = 0;
                requestBody = stream.ReadToEnd();
            }
            if (...)
            {
                var wrongResult = new { error = "Wrong parameters" };
                actionContext.Result = new JsonResult(wrongResult);
            }
    
        }
    }