Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to delay reading the body of the trigger HTTP request until later in an Azure Function?

I am using Azure Functions to handle large (>20MB) files encoded in base64 and passed inside a Json.

Before reading and parsing the JSON body, I need to authenticate the client via an API key passed in the header of the HTTP request.

Analyzing the logs, it seems that the entire JSON is read before even executing the function.

Is there a way I can delay the reading of the JSON body until after I authenticated the user?

Edit:

The function is declared like this:

public static class V1Functions
{

    [FunctionName("V1MyFunction")]
    public static async Task<IActionResult> MyFunction(
        [HttpTriggerAttribute(AuthorizationLevel.Anonymous, "post", Route = "v1/my_function")] HttpRequest request,
        ILogger logger)
    {
       //...
    }
}
like image 426
Victor Avatar asked Dec 19 '19 15:12

Victor


People also ask

How do I create a timer trigger in Azure function?

Create a timer triggered function In your function app, select Functions, and then select + Create. Select the Timer trigger template. Configure the new trigger with the settings as specified in the table below the image, and then select Create. Defines the name of your timer triggered function.

What is timer trigger in Azure?

A timer trigger lets you run a function on a schedule. This is reference information for Azure Functions developers. If you're new to Azure Functions, start with the following resources: Azure Functions developer reference.

Can you trigger an Azure function using an HTTP request?

The HTTP trigger lets you invoke a function with an HTTP request. You can use an HTTP trigger to build serverless APIs and respond to webhooks. The default return value for an HTTP-triggered function is: HTTP 204 No Content with an empty body in Functions 2.

Which HTTP response code indicates the orchestration is still in progress?

The HyperText Transfer Protocol (HTTP) 202 Accepted response status code indicates that the request has been accepted for processing, but the processing has not been completed; in fact, processing may not have started yet.


2 Answers

You can use Azure app service built-in authentication and authorization support(easy auth) to authenticate users. Details about Azure app service easy auth see here.

Azure functions are based on Azure app service, so it is enabled for Azure functions too.

This section indicates how it works:

enter image description here

Every incoming HTTP request passes through it before being handled by your application code.

I think this is the auth way that you are looking for: not handle request data in your code before users are authenticated. This blog will be helpful for you to use Azure function and Easy Auth.

Hope it helps.

like image 116
Stanley Gong Avatar answered Oct 21 '22 04:10

Stanley Gong


I had a similar issue, where the incoming request had to be authenticated before executing. However, I did not really care about the size of the content (it should never reach e.g. ~20MB). Thus, I'm not sure if this approach actually fits your needs.

As you stated in your comment OnExecutingAsync can be used for placing code to be executed before the function itself. The problem is, that OnExecutingAsync is just a task; you can't really return anything from it - which would be nice if it was possible to do, as it would allow us to return e.g. "Unauhorized", which usually is the case when talking about http requests.

In any case, whether it adheres to your requirements or not, here goes: As I see it, the request holds the contents as a stream, and afaik it hasn't been processed by the http trigger pipeline yet, at least not 'till you call e.g. ReadAsStringAsync or something like that.

Use a BaseController which implements IFunctionInvocationFilter, something like:

Note: IFunctionInvocationFilter is still in preview as of 2019-12-29.

internal abstract class BaseController : IFunctionInvocationFilter
{
    protected bool _IsAuthenticated = false;

    private IAuthenticationService _AuthenticationService;

    protected BaseController(IAuthenticationService authenticationService)
    {
        _AuthenticationService = authenticationService;
    }

    public virtual async Task OnExecutedAsync(FunctionExecutedContext executedContext, CancellationToken cancellationToken)
    {
        _IsAuthenticated = false;
    }

    public virtual async Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
    {
        // This part is a tad flimsy, but I never managed to find a better way of retrieving the header values
        // You could probably investigate the FunctionExecutingContext a tad more and see if you can come up with something better

        if (executingContext.Arguments.TryGetValue("request", out var request) && request is HttpRequest httpRequest)
            _IsAuthenticated = _AuthenticationService.Authenticate(httpRequest.Headers);
        else
            _IsAuthenticated = false;
    }

    protected async Task<IActionResult> DoAuthenticated(Func<Task<IActionResult>> action)
    {
        return !IsAuthenticated ? Unauthenticated() : await action();
    }

    protected virtual IActionResult Unauthenticated()
    {
        var someErrorHandlingWithSomeModelAsBody = ...
        return new UnauthorizedObjectResult(someErrorHandlingWithSomeModelAsBody);
    }
}

The AuthenticationService is the capable of determining whether or not the key in the header is valid or not.

You've probably noticed the use of dependency injection (it's just how the function app I made is implemented), you could probably easily rectify this making things static as needed.

A sample controller would be something like:

public class SampleController : BaseController
{
    private readonly ISampleService _SampleService;

    public SampleController(ISampleService sampleService, IAuthenticationService authenticationService) : base(authenticationService)
    {
        _SampleService = sampleService;
    }

    [FunctionName(nameof(SampleFunction))]
    public async Task<IActionResult> SampleFunction(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "v1/my_function")] HttpRequest request,
        ILogger log)
    {
        try
        {
            return await DoAuthenticated(async () =>
            {
                var largeFileAsString = await request.Content.ReadAsStringAsync();

                return await _SampleService.HandleLargeFile(largeFileAsString);
            }
        }
        catch (Exception e)
        {
            // If you need exception handling...
        }
    }
}

Whenever SampleFunction is triggered, it will execute in the following order:

  1. OnExecutingAsync-body
  2. SampleFunction-body - the func will only be executed if the authentication is actually successful during OnExecutingAsync
  3. OnExecutedAsync-body

The question is if the function actually still processes the entire 20MB of JSON content you speak of, even when the authentication fails. I'd actually personally be interested in the results if you test this.

like image 37
eli Avatar answered Oct 21 '22 04:10

eli