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 745 forks source link

How to add authentication to GraphQL endpoint in .NET Core 3.0? #1189

Closed dmeenhuis closed 2 years ago

dmeenhuis commented 4 years ago

I've read the section about authorization on https://hotchocolate.io/docs/next/authorization, and it says one could opt in to the authentication provided by ASP.NET.

My project already uses authentication based on JWT bearer tokens plugged into the authentication pipeline provided by ASP.NET, but I'm still not sure how I would go about adding authentication to the GraphQL endpoint.

What's new in .NET Core 3.0 is the configuration of endpoints, which also allows you to specify which endpoints require authentication. Below is how I'm configuring my app now:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseWebSockets();

    app.UseGraphQL("/graphql");
    app.UsePlayground("/graphql");

    app.UseEndpoints(endpoints =>
    {
        endpoints
            .MapHub<NotificationHub>("/notifications")
            .RequireAuthorization();

        endpoints
            .MapControllers()
            .RequireAuthorization();

        endpoints.MapHealthChecks("/health");
    });
}

As you can see, I've configured the hub and controllers to require authentication. The current GraphQL middleware does not seem to use endpoints, and I also don't see an option where I could configure authentication for the middleware.

What does work is configuring a fallback policy in the ASP.NET authentication pipeline which requires an authenticated user. However, that is conflicting with the health endpoint, which needs to be unauthenticated since that's used by the Kubernetes cluster.

Any pointers on how to deal with this?

dmeenhuis commented 4 years ago

I took another stab at this and I managed to get it to work by creating my own endpoint extension which registers the GraphQL middleware.

So now I can do this:

app.UseEndpoints(endpoints =>
{
  endpoints
    .MapHub<NotificationHub>("/notifications")
    .RequireAuthorization();

  endpoints
    .MapControllers()
    .RequireAuthorization();

  endpoints
    .MapGraphQL("/graphql")
    .RequireAuthorization();

  endpoints.MapHealthChecks("/health");
});
michaelstaib commented 4 years ago

We do not secure the GraphQL endpoint with authorization. The Authorization is applied on field, type or schema level with authorization directives.

You just have to apply the authentication middleware before applying the GraphQL middleware.

In the schema you have to then add the authorize directives and use them throughout your schema.

I will have a look at your route implementation. I think we have to do a little more to make them work. Since with routing we essentially do not need to handle the routes anymore in the middleware itself.

dmeenhuis commented 4 years ago

We do not secure the GraphQL endpoint with authorization. The Authorization is applied on field, type or schema level with authorization directives.

You just have to apply the authentication middleware before applying the GraphQL middleware.

That's what I gathered from the docs. I don't have a requirement for roles or specific policies and having to add the authorization directive to all of my fields and types (since I need everything to be secured), seemed kind of tedious and error-prone, that's why I opted for just securing the endpoint.

I will have a look at your route implementation. I think we have to do a little more to make them work. Since with routing we essentially do not need to handle the routes anymore in the middleware itself.

Thanks! My current implementation was the simplest thing I could get to work, by just re-using the existing middleware and plug it into the endpoint builder. Going forward I suppose it makes sense to deprecate the current application builder extensions at some point and use the new routing mechanism?

michaelstaib commented 4 years ago

That's what I gathered from the docs. I don't have a requirement for roles or specific policies and having to add the authorization directive to all of my fields and types (since I need everything to be secured), seemed kind of tedious and error-prone, that's why I opted for just securing the endpoint.

Actually you only have to secure the root types. But we want to introduce schema authorization as well.

Having that said, it is not wrong to put some authorization on the route.

Thanks! My current implementation was the simplest thing I could get to work, by just re-using the existing middleware and plug it into the endpoint builder. Going forward I suppose it makes sense to deprecate the current application builder extensions at some point and use the new routing mechanism?

Exactly... we will do a little renovation of the middleware. We already kicked out classic .net owin support, this means that we can create in the first go a cleaner middleware. The next would be to look how we can add routing in a nice way so, that we still are able to support older .net core variants.

clausthalVK commented 3 years ago

@michaelstaib Is there any way i can pass authentication header to graphql middleware in hotchoclate graphql ui playground

ademchenko commented 3 years ago

@michaelstaib in the above discussion you mentioned that v.11 has to have some new feature: schema authorization. I can't find anything about that in docs. Could you explain that type of authorization or give some links to the description if any? As @dmeenhuis we also have the base requirement (apart from other more complicated authorization mechanics) for a user to be authenticated to make any query and any mutation. Following your advice, we set Authorize attribute to root Query and root Mutation type, but that would be interesting if we can achieve the goal at once to guard against possible future errors.

apazureck commented 3 years ago

@ademchenko

I accidentially could not fetch my schema anymore as I set the [Authorize] attribute on my query class.

To secure the whole query, mutation or subscription you can, as far as I experienced, just use the annotation on the base class:

[Authorize]
    public partial class Mutation
    {
        private readonly ILogger<Mutation> logger;

        public Mutation(ILogger<Mutation> logger)
        {
            this.logger = logger;
        }
    }

Edit: I use Hot Chocolate Version 11.3.2 on .NET 5

michaelstaib commented 2 years ago

This is documented now.

michaelstaib commented 2 years ago

https://chillicream.com/docs/hotchocolate/security