OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
853 stars 476 forks source link

OData batch with authentication #1655

Open bdebaere opened 5 years ago

bdebaere commented 5 years ago

Based on http://odata.github.io/WebApi/04-12-batch/ and https://github.com/OData/ODataSamples/tree/master/WebApiCore/ODataBatchSample I was able to implement batching. However as soon as I add authentication the batching fails. It doesn't even hit my controller.

Authentication is implemented with JWT Bearer.

This is the body of my POST request to localhost/odata/$batch:

    --batch_d3bcb804-ee77-4921-9a45-761f98d32029
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    GET http://localhost:54866/odata/Customers  HTTP/1.1
    OData-Version: 4.0
    OData-MaxVersion: 4.0
    Accept: application/json;odata.metadata=minimal
    Accept-Charset: UTF-8
    User-Agent: Microsoft ADO.NET Data Services

    --batch_d3bcb804-ee77-4921-9a45-761f98d32029
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    GET http://localhost:54866/odata/Orders  HTTP/1.1
    OData-Version: 4.0
    OData-MaxVersion: 4.0
    Accept: application/json;odata.metadata=minimal
    Accept-Charset: UTF-8
    User-Agent: Microsoft ADO.NET Data Services

    --batch_d3bcb804-ee77-4921-9a45-761f98d32029--

This is the response when authentication is disabled:

    --batchresponse_5c899c88-2dc9-489e-aaf3-c3a14308a2ae
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    HTTP/1.1 200 OK
    Content-Type: application/json; odata.metadata=minimal; odata.streaming=true; charset=utf-8
    OData-Version: 4.0

    {"@odata.context":"http://localhost:54866/odata/$metadata#Customers","value":[{...}]}
    --batchresponse_5c899c88-2dc9-489e-aaf3-c3a14308a2ae
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    HTTP/1.1 200 OK
    Content-Type: application/json; odata.metadata=minimal; odata.streaming=true; charset=utf-8
    OData-Version: 4.0

    {"@odata.context":"http://localhost:54866/odata/$metadata#Orders","value":[{...}]}
    --batchresponse_5c899c88-2dc9-489e-aaf3-c3a14308a2ae--

This is the response when authentication is enabled:

    --batchresponse_8f51fb60-5158-4d1a-a492-87a561220374
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    HTTP/1.1 200 OK

    --batchresponse_8f51fb60-5158-4d1a-a492-87a561220374
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    HTTP/1.1 200 OK

    --batchresponse_8f51fb60-5158-4d1a-a492-87a561220374--

Is it possible to implement batching with JWT Bearer authentication enabled?

freeranger commented 5 years ago

Have you got your middleware in the right order? Authentication should come before adding the odata batching middleware I believe

bdebaere commented 5 years ago

@freeranger It must have indeed been something with the order of the middlewares. Either that or I was passing a bad JWT token, which leads me to this.

It works when I am using a valid JWT token at the moment. However, I am still getting 200 OK when I am not authenticated. I would expect a 401 instead. Any reason why this is not the case?

    --batchresponse_8f51fb60-5158-4d1a-a492-87a561220374
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    HTTP/1.1 200 OK

    --batchresponse_8f51fb60-5158-4d1a-a492-87a561220374
    Content-Type: application/http
    Content-Transfer-Encoding: binary

    HTTP/1.1 200 OK

    --batchresponse_8f51fb60-5158-4d1a-a492-87a561220374--
HeinA commented 2 years ago

'ello

I know this is an old thread, but I have the same question.

I'm using OData 8.0.10 & .net 6. My application uses windows authentication, but also allows for some anoymous calls.

Services are added in the following order

      services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();

      services.AddAuthorization(options =>
      {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
      });

      services
        .AddControllers()
        .AddOData(opt =>
            opt.AddRouteComponents("OData", model, new DefaultODataBatchHandler())
              .Select()
              .Filter()
              .OrderBy()
              .Count()
              .Expand()
              .SetMaxTop(null));

Posting the following to my Batch EndPoint using Postman & no Authentication

--changeset_3ca6628a-1f84-4772-bbe4-02192ed229f4
Content-Type: application/http
Content-Transfer-Encoding: binary

PUT /OData/CompanyInfo('6008079') HTTP/1.1
Accept: application/json, text/javascript, */*; q=0.01
Content-Id: 0
Content-Type: application/json; charset=utf-8
Content-Length: 75

{"GS1CompanyPrefix":"6008079","Description":"Namib Mills","Prefix":"NM"}
--changeset_3ca6628a-1f84-4772-bbe4-02192ed229f4--

returns

--batchresponse_a137b646-3272-4866-baaf-8c6b98667509
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1 200 OK

--batchresponse_a137b646-3272-4866-baaf-8c6b98667509--

The PUT request silently fails, so te update does not apply, but one would expect a 401...

Any light on this subject will be appreciated.

Regards

garrardkitchen commented 1 year ago

Hi @HeinA , did you resolve this? I've noticed that with OData 8.0.11 & .NET 6.0 the batch looses the Identity from the HttpContext. We're using a custom batch handler and can clearly see that this is empty.

DanielRBG commented 1 year ago

Hi , im using net 7 and Odata v8.0.11 and when i use the bath the services variables (IHttpContextAccessor acessor) its come null .

        services.AddControllers().AddOData(opt =>
        opt.AddRouteComponents("Odata", GetEdmModel() , new DefaultODataBatchHandler()).Select().Filter().OrderBy().Expand().Count().SetMaxTop(null)
        );

app.UseODataBatching(); app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); app.UseHttpsRedirection(); app.UseAuthentication(); app.MapControllers(); app.UseAuthorization();

HeinA commented 1 year ago

@garrardkitchen , @DanielRBG I never got a fix for my specific issue, but I can help you with the null IHttpContextAccessor...

Create a Middleware Callback Type:

using log4net;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace Nmi.Application.Server.MiddleWare
{
  internal class ODataBatchHttpContextMiddleware
  {
    private readonly RequestDelegate _next;
    private readonly IHttpContextAccessor _httpAccessor;
    private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public ODataBatchHttpContextMiddleware(RequestDelegate next, IHttpContextAccessor httpAccessor)
    {
      _next = next;
      _httpAccessor = httpAccessor;
    }

    public async Task InvokeAsync(HttpContext context)
    {
      if (context is null) log.Error("Cannot set HttpContext");
      _httpAccessor.HttpContext ??= context;
      await _next(context);
    }
  }
}

Add it in the following sequence:

      app.UseAuthentication();  // (Not in recomended order)
      app.UseBlazorFrameworkFiles();
      app.UseStaticFiles();
      app.UseODataBatching(); // Must be before UseRouting(), Must be after UseAuthentication()
      app.UseRouting();
      app.UseAuthorization();

      // Custom Middleware goes here
      app.UseMiddleware<ODataBatchHttpContextMiddleware>(); // Must be after UseODataBatching()

      app.UseEndpoints(endpoints =>...);
p6345uk commented 4 months ago

@garrardkitchen , @DanielRBG I never got a fix for my specific issue, but I can help you with the null IHttpContextAccessor...

Create a Middleware Callback Type:

using log4net;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace Nmi.Application.Server.MiddleWare
{
  internal class ODataBatchHttpContextMiddleware
  {
    private readonly RequestDelegate _next;
    private readonly IHttpContextAccessor _httpAccessor;
    private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public ODataBatchHttpContextMiddleware(RequestDelegate next, IHttpContextAccessor httpAccessor)
    {
      _next = next;
      _httpAccessor = httpAccessor;
    }

    public async Task InvokeAsync(HttpContext context)
    {
      if (context is null) log.Error("Cannot set HttpContext");
      _httpAccessor.HttpContext ??= context;
      await _next(context);
    }
  }
}

Add it in the following sequence:

      app.UseAuthentication();  // (Not in recomended order)
      app.UseBlazorFrameworkFiles();
      app.UseStaticFiles();
      app.UseODataBatching(); // Must be before UseRouting(), Must be after UseAuthentication()
      app.UseRouting();
      app.UseAuthorization();

      // Custom Middleware goes here
      app.UseMiddleware<ODataBatchHttpContextMiddleware>(); // Must be after UseODataBatching()

      app.UseEndpoints(endpoints =>...);

Cheers this solved it for me