3

I have a web api with some actions that should receive both data and files. To do so, I am accepting multipart/form-data instead of JSON and binding to the model using [FromForm]:

[Authorize(Policy = "MyCustomPolicy")]
public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
{
    // Do some stuff here
}

public class MyCustomDto
{
    public int Id { get; set; }
    public string Title { get; set; }

    public IEnumerable<IFormFile> Attachments { get; set; }
}

This works fine and is correctly bound to the DTO.
The Policy is checked and enforced using a AuthorizationHandler, which also works fine.
In said AuthorizationHandler, I need to access some stuff passed to the controller, such as IDs in the route or DTOs. Accessing route data is quite easy with authContext.RouteData.Values["nameOfIdField"].
For the body, however, I have created a helper extension method that reads the body stream and deserializes it:

public static async Task<T> DeserializeBody<T>(this AuthorizationFilterContext context, string name)
{
    var content = string.Empty;

    using(var reader = new StreamReader(context.HttpContext.Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
    {
        content = await reader.ReadToEndAsync();
        reader.BaseStream.Seek(0, SeekOrigin.Begin);
    }

    if (string.IsNullOrWhiteSpace(content))
    {
        return default(T);
    }

    return JsonConvert.DeserializeObject<T>(content);
}

Again, this also works quite fine. However, now I am having trouble with DTOs that are not passed as JSON but - as said in the beginning - as form data.
The body's content is not a JSON that can be serialized easily:

-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="id"

232  
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="title"

test  
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="attachments"; filename="C:\Temp\Test.jpg"
Content-Type: image/jpeg

Is there any way to bind this to my DTO easily, without having to manually parse it?

5
  • Maybe it would be better to do authorization based on message data, after the data is parsed and created for you? Commented Aug 15, 2018 at 13:41
  • @nvoigt do you have any link or resource on that? Because when I looked up how to do authorization, the solution on top (kind of) was what I found, and no other way. Commented Aug 15, 2018 at 13:50
  • I don't have any references, but it seems to be common sense... parsing all your different messages in your middleware and knowing their semantic meaning would be a huge ball of mud. Only the actual controller can know whether the ItemId you got passed needs to be checked for read access or write access, because it depends on what it will do with it. Commented Aug 15, 2018 at 13:58
  • For example: learn.microsoft.com/de-de/aspnet/core/security/authorization/… Commented Aug 15, 2018 at 14:05
  • In addition, replicating all your logic for data transfer in your authorization method means you have all of that as a big redundancy. You cannot just swap a [FromQuery] and a [FromRoute] because while it seems easy enough, you have replicated that logic somewhere hidden from sight and it will fail. Commented Aug 15, 2018 at 14:13

2 Answers 2

2

In said AuthorizationHandler, I need to access some stuff passed to the controller, such as IDs in the route or DTOs

Please don't do that. You are basically replicating the logic already in place on how to parse your parameters from the request.

The basic handlers are for basic cases: For example, only authenticated members of the "BookClub" role should be able to access the BooksController methods. That's great.

As soon as you find yourself needing information from the message itself, please don't go and do all of that parsing by hand. Let ASP do it's thing and parse the message as per your given constraints and then when the message is complete, invoke your authorization logic on the objects you got.

Microsoft Example

Sign up to request clarification or add additional context in comments.

Comments

1

You're better off handling this situation in your action method:

public class SomeController : Controller
{
    private readonly IAuthorizationService _authorizationService;

    public SomeController(IAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
    }

    public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
    {
        var authorizationResult = await _authorizationService.AuthorizeAsync(User, myDto, "MyCustomPolicy");
        if (!authorizationResult.Succeeded)
            return User.Identity.IsAuthenticated ? Forbid() : (IActionResult)Challenge();

        // Do some stuff here
    }

And you define your authorization handler like this:

public class MyCustomDtoAuthorizationHandler : AuthorizationHandler<MyCustomDtoRequirement, MyCustomDto>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyCustomDtoRequirement requirement, MyCustomDto resource)
    {
        // your authorization logic based on the resource argument...
    }
}

The big problem with choosing the AuthorizeFilter way is that authorization filters are executed before model binding takes place. (Just take a look at the source of the ResourceInvoker class.) You would need to bind your model manually to access the required information for authorization in it. Then the framework would do its job resulting in model binding being done two times and thus, in performance degradation. And this should and could be avoided as it was described earlier.

Update

I've just noticed I accidentally left an important piece of code out of the action method. Corrected.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.