7
votes

Our OData endpoint is self-hosted(OWIN). For single request: creating, updating, patching and deleting everything works great, but the problem is when I send batch request with multiple actions in it, I'm having problem with basic authorization. I read a lot of articles, but still cannot solve the issue. In OData documentation it says:

Each MIME part body that represents a single request MUST NOT include:

• authentication or authorization related HTTP headers

http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752314

So, If I set Authorization to batch request, but don't set to each single request in batch, I'm getting null in actionContext.Request.Headers.Authorization in OnAuthorization method. My question is: how can I get Batch request's Authorization header from request in this Batch?

In endpoint Batch is enabled:

HttpConfiguration config = new HttpConfiguration();
var odataBatchHandler = new DefaultODataBatchHandler(new HttpServer(config));
config.MapODataServiceRoute("ODataApi", null, builder.GetEdmModel(), odataBatchHandler);
config.Count().Filter().OrderBy().Expand().MaxTop(null).Select();
appBuilder.UseWebApi(config);

Here is the authorization logic:

public class ODataBasicAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
         //Question: here Authorization property is null, because this is Get request for SAStudent
                if (actionContext.Request.Headers.Authorization == null || actionContext.Request.Headers.Authorization.Scheme != "Basic")
                {
                    HandleUnauthorizedRequest(actionContext);
                }
                else
                {
                    ISession session = Login(actionContext.Request);
                    if (session == null)
                    {
                        HandleUnauthorizedRequest(actionContext);
                    }
                    else
                    {
                        IsAuthorized(actionContext);
                    }
                }
    }
}

Here is the test:

    [TestMethod]
        public void BatchRequestTest()
        {
            var odataAddress = "https://localhost:23170/Sample/Sample/OData/";
            var batchUrl = $"{odataAddress}$batch";
            HttpClient http = new HttpClient();
            // Global batch request
            HttpRequestMessage batchRequest = new HttpRequestMessage(HttpMethod.Post, batchUrl);
            batchRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", "QWRtaW5pc3RyYXRvcjpwdw==");
            MultipartContent batchContent = new MultipartContent("mixed", "batch_" + Guid.NewGuid().ToString());

            var getStudent = new HttpRequestMessage(HttpMethod.Get, $"{odataAddress}SAStudent");
            //getStudent.Headers.Authorization = new AuthenticationHeaderValue("Basic", "QWRtaW5pc3RyYXRvcjpwdw==");
            // First message content with GET request
            HttpMessageContent getRequestContent_1 = new HttpMessageContent(getStudent);
            getRequestContent_1.Headers.Remove("Content-Type");
            getRequestContent_1.Headers.Add("Content-Type", "application/http");
            getRequestContent_1.Headers.Add("Content-Transfer-Encoding", "binary");
            // Add this GET content to the batch content
            batchContent.Add(getRequestContent_1);

            var getPassport = new HttpRequestMessage(HttpMethod.Get, $"{odataAddress}SAPassport");
            //getPassport.Headers.Authorization = new AuthenticationHeaderValue("Basic", "QWRtaW5pc3RyYXRvcjpwdw==");
            // Second message content with GET request
            HttpMessageContent getRequestContent_2 = new HttpMessageContent(getPassport);
            getRequestContent_2.Headers.Remove("Content-Type");
            getRequestContent_2.Headers.Add("Content-Type", "application/http");
            getRequestContent_2.Headers.Add("Content-Transfer-Encoding", "binary");
            // Add this GET content to the batch content
            batchContent.Add(getRequestContent_2);

            // Here we go
            batchRequest.Content = batchContent;
            HttpResponseMessage response = http.SendAsync(batchRequest).Result;
            var responseString = response.Content.ReadAsStringAsync().Result;

}

If I set Authorization to each single request in Batch request than it will work, but It seems not correct, so setting only authorization header to Batch should work.

Any ideas?

Thanks in advance,

1

1 Answers

0
votes

Late to the party, but had a similar issue recently, albeit for custom auth using Http Cookies.

Firstly, ensure Web Api and the Batch Handler are configured to use the same instance of HttpServer.

HttpConfiguration config = new HttpConfiguration();
HttpServer httpServer = new HttpServer(config);
var odataBatchHandler = new DefaultODataBatchHandler(httpServer);
config.MapODataServiceRoute("ODataApi", null, builder.GetEdmModel(), odataBatchHandler);
config.Count().Filter().OrderBy().Expand().MaxTop(null).Select();
appBuilder.UseWebApi(httpServer);

I found that without this, none of the context from the enclosing $batch request was NOT being made available to each sub request because the odata batch handler was using a different instance of HttpServer to web api.

I could now grab the headers out of the OwinContext for my custom auth - in your case...

public class ODataBasicAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        //Question: here Authorization property is null, because this is Get request for SAStudent

        var isBatch = request.Properties.TryGetValue("MS_BatchRequest", out var value) ? (bool)value : default;
        OwinContext owinContext = request.Properties.TryGetValue("MS_OwinContext", out var owinCtxObj) ? (OwinContext)owinCtxObj : default;
        if (isBatch && owinContext != null)
        {
            // adjust your auth logic to use the owin context... my case I needed a cookie, so...
            // var jwtCookieKeyValuePair = owinContext.Request.Cookies.FirstOrDefault(cookie => cookie.Key == "jwt");
        }
        // Keep your existing logic for non-batch odata requests..
        if (actionContext.Request.Headers.Authorization == null || actionContext.Request.Headers.Authorization.Scheme != "Basic")
        {
            HandleUnauthorizedRequest(actionContext);
        }
        else
        {
            ISession session = Login(actionContext.Request);
            if (session == null)
            {
                HandleUnauthorizedRequest(actionContext);
            }
            else
            {
                IsAuthorized(actionContext);
            }
        }
    }
}

All probably too late for the OP, but might help someone.