Specifying an authentication scheme for a single route that's handled by middleware

Viewed 1117

I have a new ASP.Net Core 3 MVC site that I've added GraphQL to with HotChocolate. I have users signing in to the MVC side using cookie-based auth with Auth0:

services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })
    .AddCookie()
    .AddOpenIdConnect("Auth0", options =>
        {
            ...
        });

But for GraphQL requests, I need JWT auth, for which I'd use:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(config =>
        {
            ...
        });

They both independently work fine. Cookie auth allows controller use; JWT auth allows GQL use. But I can't figure out how to get the cookie auth for the controllers and JWT for the /graphql route.

A little context: HotChocolate uses a custom middleware to handle requests that come in to the /graphql route. It's not on a controller, so I can't just specify the scheme with the Authorize attribute as there isn't a controller to put it on.

Similar questions

(There were a few others, mainly focused on combining REST and MVC, but these both go to controllers so the scenario is a bit different.)

4 Answers

I had the same issue, so I thought I post my solution in case others may have the same problem.

Hot Chocolate uses the Default Authentication Scheme so in Startup I declare JWT as the default:

    services.AddAuthentication(options =>
    {
        options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

    })

And everywhere I need Cookie Authentication I simply add:

[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]

I had a quite similar issue and I wanted to share my solution. In my case, I wanted to have both authentication schemas applied to my HotChocolate api. I was able to do this for normal controller endpoints by adding this attributes:

[Authorize(AuthenticationSchemes = "Bearer" + "," + "OpenIdConnect")]

For having both schemas applied to my HotChocolate graphql endpoint I did this trick in my Startup

       endpoints.MapGraphQL().RequireAuthorization()
            .Add((endpointBuilder) =>
            {
                var authAttribute = endpointBuilder.Metadata.SingleOrDefault(x =>
                    x is Microsoft.AspNetCore.Authorization.AuthorizeAttribute);
                if (authAttribute != null)
                    ((Microsoft.AspNetCore.Authorization.AuthorizeAttribute) authAttribute)
                        .AuthenticationSchemes = "Bearer" + "," +
                                                 "OpenIdConnect";
            });

I couldn't figure out how to do this. I ended up just splitting it into two projects. One provides the GraphQL API and the other provides the website. Doing this, each can have their own auth scheme.

With inspiration from IgnacioMir's answer above, I think I've finally found what feels like a reasonably elegant way of doing this.

Assuming that you want to use Cookie authentication for most of your site, but some other scheme (e.g. JWT bearer tokens) for your Hot Chocolate endpoint, then you can define your authentication schemes with Cookie authentication as the default as normal:

services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie()
    .AddJwtBearer(...);

Then add both an AuthorizeAttribute and an AllowAnonymousAttribute to the GraphQL endpoint:

app.MapGraphQL()
    .RequireAuthorization(new AuthorizeAttribute
    {
        AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme
    })
    .AllowAnonymous(); // Adds AllowAnonymousAttribute to the endpoint metadata

This overrides the default authentication scheme for the GraphQL endpoint without ASP.NET enforcing any authorization checks.

In other words the AllowAnonymousAttribute ensures that the authorization checks are left to HotChocolate's own authorization middleware, which is especially important if you only want the checks to be applied to fields marked with HotChocolate's AuthorizeAttribute.

Related