graphql-platform icon indicating copy to clipboard operation
graphql-platform copied to clipboard

Authorization fails on multiple authentication schemes unless RequireAuthorization() is set

Open reinux opened this issue 3 years ago • 10 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Describe the bug

If multiple authentication schemes are set and GraphQLEndpointConventionBuilder.RequireAuthorization() isn't set, it only recognizes the default authentication scheme.

JWT (the second specified auth method) will fail with this setup, even if ASP.NET appears to recognize the token.

Authentication does appear to pass, as it will return a null result, but the user claims are empty.

Steps to reproduce

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  .AddCookie(option => { })
  .AddJwtBearer(options =>
  {
    options.TokenValidationParameters =
      new TokenValidationParameters();
  })
  ;

services.AddAuthorization(options =>
 {
   var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme);
   defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
   options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
 });


services
  .AddGraphQLServer()
  .AddAuthorization();

app.UseEndpoints(endpoints =>
{
  endpoints.MapGraphQL()
  // .RequireAuthorization(); // Uncommenting this causes it to work again, but locks down the entire schema
}

Relevant log output

{
  "errors": [
    {
      "message": "The current user is not authorized to access this resource.",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "me",
        "contact"
      ],
      "extensions": {
        "code": "AUTH_NOT_AUTHENTICATED"
      }
    }
  ],
  "data": {
    "me": {
      "contact": null
    }
  }
}

Additional Context?

No response

Product

Hot Chocolate

Version

12.0.0-rc.12

reinux avatar Oct 22 '21 23:10 reinux

I'm having the same issue. Did you already find a workaround?

daanlenaerts avatar Nov 08 '21 17:11 daanlenaerts

I'm having the same issue. Did you already find a workaround?

I ended up switching everything to JWT. Not ideal, but it works.

reinux avatar Nov 08 '21 19:11 reinux

I took another look at it and found a workable solution. It is based on the HttpRequestInterceptor, which you can read more about here. In the example below I implemented the default cookie authentication as well as a simple basic authentication I quickly implemented myself.

By adding a HTTP request interceptor I was able to authenticate the user through either cookie or basic authentication.

I registered a HttpRequestInterceptor in ConfigureServices:

services.AddHttpRequestInterceptor<HttpRequestInterceptor>()

which I then implemented as follows:

public class HttpRequestInterceptor : DefaultHttpRequestInterceptor
{
    private IPolicyEvaluator _policyEvaluator;
    public HttpRequestInterceptor(IPolicyEvaluator policyEvaluator)
    {
        _policyEvaluator = policyEvaluator;
    }
    public override async ValueTask OnCreateAsync(HttpContext context,
        IRequestExecutor requestExecutor, IQueryRequestBuilder requestBuilder,
        CancellationToken cancellationToken)
    {        
        var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, "BasicAuthentication");
        defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
        
        await _policyEvaluator.AuthenticateAsync(defaultAuthorizationPolicyBuilder.Build(), context);

        await base.OnCreateAsync(context, requestExecutor, requestBuilder,
            cancellationToken);
    }
}

daanlenaerts avatar Nov 09 '21 08:11 daanlenaerts

Good to know, thanks!

reinux avatar Nov 09 '21 08:11 reinux

I took another look at it and found a workable solution. It is based on the HttpRequestInterceptor, which you can read more about here. In the example below I implemented the default cookie authentication as well as a simple basic authentication I quickly implemented myself.

By adding a HTTP request interceptor I was able to authenticate the user through either cookie or basic authentication.

I registered a HttpRequestInterceptor in ConfigureServices:

services.AddHttpRequestInterceptor<HttpRequestInterceptor>()

which I then implemented as follows:

public class HttpRequestInterceptor : DefaultHttpRequestInterceptor
{
    private IPolicyEvaluator _policyEvaluator;
    public HttpRequestInterceptor(IPolicyEvaluator policyEvaluator)
    {
        _policyEvaluator = policyEvaluator;
    }
    public override async ValueTask OnCreateAsync(HttpContext context,
        IRequestExecutor requestExecutor, IQueryRequestBuilder requestBuilder,
        CancellationToken cancellationToken)
    {        
        var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, "BasicAuthentication");
        defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
        
        await _policyEvaluator.AuthenticateAsync(defaultAuthorizationPolicyBuilder.Build(), context);

        await base.OnCreateAsync(context, requestExecutor, requestBuilder,
            cancellationToken);
    }
}

I tried the workaround but seems it's missing a DI for IPolicyEvaluator, have you got any written? and as per Hotchocolate documentation you have referred in the post it kind of says HttpInterceptor will be invoked after authentication, any suggestions please.

muralimaddu avatar Dec 10 '21 16:12 muralimaddu

@PascalSenn should we have a go on this for 12.6?

michaelstaib avatar Dec 10 '21 21:12 michaelstaib

Great workaround @daanlenaerts !

To prevent the duplication of authorization policies, you can use the IAuthorizationPolicyProvider:

public class HttpRequestInterceptor : DefaultHttpRequestInterceptor
{
    private readonly IPolicyEvaluator _policyEvaluator;
    private readonly IAuthorizationPolicyProvider _policyProvider;

    public HttpRequestInterceptor(IPolicyEvaluator policyEvaluator,
       IAuthorizationPolicyProvider policyProvider)
    {
        _policyEvaluator = policyEvaluator;
        _policyProvider = policyProvider;
    }

    public override async ValueTask OnCreateAsync(HttpContext context,
        IRequestExecutor requestExecutor, IQueryRequestBuilder requestBuilder,
        CancellationToken cancellationToken)
    {
        await _policyEvaluator.AuthenticateAsync(await _policyProvider.GetDefaultPolicyAsync(), context);
        await base.OnCreateAsync(context, requestExecutor, requestBuilder, cancellationToken);
    }
}

rohinz avatar Jan 27 '22 15:01 rohinz

I am having a similar issue, adding DefaultAuthenticateScheme cause a 401 furthermore the interceptor is not even working

    services.AddAuthentication(opt =>
            {
                opt.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })

Log:

info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET https://localhost:44337/graphql/?query=query{%20employees%20{%20firstName%20}%20} - - trce: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[2] All hosts are allowed. dbug: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[3] The request path does not match the path filter dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1001] 1 candidate(s) found for the request path '/graphql/' dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1005] Endpoint 'Hot Chocolate GraphQL Pipeline' with route pattern '/graphql/{**slug}' is valid for the request path '/graphql/' dbug: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[1] Request matched endpoint 'Hot Chocolate GraphQL Pipeline' info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: Handler assertion should evaluate to true. info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[2] Successfully validated the token. info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12] AuthenticationScheme: Bearer was challenged. info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished HTTP/1.1 GET https://localhost:44337/graphql/?query=query{%20employees%20{%20firstName%20}%20} - - - 401 - - 23.1461ms

zulander1 avatar Feb 05 '22 16:02 zulander1

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar May 04 '22 00:05 stale[bot]

This issue was reproduced by brandon in this project: https://github.com/mentallabyrinth/hc-multi-auth-schema-issue

PascalSenn avatar Sep 21 '22 16:09 PascalSenn

check https://github.com/ChilliCream/graphql-platform/issues/5792

michaelstaib avatar Feb 07 '23 16:02 michaelstaib