ChilliCream / graphql-platform

Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.
https://chillicream.com
MIT License
5.23k stars 744 forks source link

Unable to authorize using two authorization schemes #5393

Open drewbj21 opened 2 years ago

drewbj21 commented 2 years ago

Is there an existing issue for this?

Describe the bug

We have a requirement to be able to authorize a request to our GraphQL/HC API using a bearer token or API key while also allowing anonymous calls for introspection. Up until a week or two ago we were able to accomplish this using the below set up in Program.cs. Our settings were based on a Stackoverflow answer on the topic (link below):

Link: https://stackoverflow.com/questions/61157111/specifying-an-authentication-scheme-for-a-single-route-thats-handled-by-middlew

Code (Program.cs):

app.UseEndpoints(endpoints =>  
    {  
        endpoints.MapControllerRoute("default", "{controller=User}/{action=Index}/{id?}");  
        endpoints  
            .MapGraphQL()  
                .RequireAuthorization(new AuthorizeAttribute  
                {  
                    AuthenticationSchemes = "Bearer,ApiKey"  
                })  
                .AllowAnonymous()  
                .WithOptions(new GraphQLServerOptions  
                {  
                    EnableGetRequests = false  
                });  
                endpoints.MapGraphQLVoyager("/voyager");  
                endpoints.MapGraphQLGraphiQL("/graphiql");  
});

This did not make much sense to me since I am globally requiring authorization then allowing anonymous but this worked for months so I left it. Now, the above set up allows me to authorize using a bearer token or api key but is also requiring authorization on introspection calls. We need introspection to allow anonymous calls to allow our Angular app to use it for code generation.

My first thought to fix is this was to remove the "RequireAuthorization" settings above and specify multiple authorization schemes within our individual Authorization attributes but I do not see a way to do this. Based on my research, the only way to specify multiple authorization schemes within GraphQL/HC is the above set up.

Steps to reproduce

  1. Use the settings specified in the description of this bug.
  2. Try to use the introspection call to generate schema
  3. Get an unauthorized error instead of being able to use the introspection call anonymously.

Relevant log output

No response

Additional Context?

I have also tried to different endpoint configuration combinations (as suggested in current documentation) to require authorization on GraphQL https calls but not Banana Cake Pop endpoints without any success.

Our current work around is to comment out the "RequireAuthorization" settings. This allows us to still authorize using a bearer token on calls with the [Authorize] attribute and use introspection anonymously. This set up does not allow us to authorize using an api key.

I am by no means a GraphQL/HotChocolate expert so any help finding a solution would be greatly appreciated! Thanks.

Product

Hot Chocolate

Version

12.12.1

shahabfar commented 10 months ago

I had the same issue. I've created a policy of two authorization schemes and use it in global authorization: app.MapGraphQL().RequireAuthorization("CustomPolicy"); I don't know why using attributes didn't work for me.

huysentruitw commented 1 month ago

I have a similar requirement as @drewbj21, for Angular code generation but also for StrawberryShake schema updates (running dotnet graphql update locally without having to pass a token/API-key).

As far as I understand, this seems to be a limitation of the HotChocolate Authorization implementation. It doesn't call Authenticate on schemes in the policy, so only relies on the authenticate being called on the default-scheme by the ASP.NET Core UseAuthenticate() middleware.

Current workaround is to create a custom authentication scheme that combines both, creating your own ASP.NET Core middleware to authenticate all schemes, or doing the same in a HotChocolate HttpRequestInterceptor.

Sample code I got from @tobias-tengler in slack:

services.AddGraphQLServer()
    .AddHttpRequestInterceptor<AuthenticationHttpRequestInterceptor>();

public class AuthenticationHttpRequestInterceptor : DefaultHttpRequestInterceptor {
    public override async ValueTask OnCreateAsync(HttpContext context, IRequestExecutor requestExecutor, IQueryRequestBuilder requestBuilder,
        CancellationToken cancellationToken) {
        var schemeProvider = context.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
        var schemes = (await schemeProvider.GetAllSchemesAsync()).ToArray();

        if (schemes.Length > 1) {
            foreach (var scheme in schemes) {
                var result = await context.AuthenticateAsync(scheme.Name);
                if (result.Succeeded) {
                    context.User = result.Principal;
                    break;
                }
            }
        }

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