添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account Authorization fails on multiple authentication schemes unless RequireAuthorization() is set #4333 Authorization fails on multiple authentication schemes unless RequireAuthorization() is set #4333 reinux opened this issue Oct 22, 2021 · 10 comments

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

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 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.

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);
          

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;

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