graphql-platform
graphql-platform copied to clipboard
Authorization fails on multiple authentication schemes unless RequireAuthorization() is set
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
I'm having the same issue. Did you already find a workaround?
I'm having the same issue. Did you already find a workaround?
I ended up switching everything to JWT. Not ideal, but it works.
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);
}
}
Good to know, thanks!
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
inConfigureServices
: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.
@PascalSenn should we have a go on this for 12.6?
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;
})
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
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.
This issue was reproduced by brandon in this project: https://github.com/mentallabyrinth/hc-multi-auth-schema-issue
check https://github.com/ChilliCream/graphql-platform/issues/5792