ChilliCream / ChilliCream/graphql-platform

Is there a way to protect all graphql operations by authorization except introspection (global authorization) ?

Open
#5,435 4 comments 9 reactions 1 assignee View on GitHub

@michaelstaib is already working on this.

Since Oct 30, 2022.

🌶️ hot chocolate 📚 documentation Area: Authorization
Dominant language
C#
Stars
5.8k
Forks
810
Avg merge
15h 39m
Merged PRs (30d)
98

Description

Is there an existing issue for this?
  • I have searched the existing issues
Describe the bug

Cannot find a way to make introspection queries available for anonymours users with global authorization.

I would expect to find an way to specify exclusion for introspection.

I saw similar issue 5056, but I didn't understand why it hadn't pull attention and had been marked as stale, therefore I decided to open this issue.

Steps to reproduce

1. Build code

HotChocolateAuthDemo.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="HotChocolate.AspNetCore" Version="12.14.0" />
    <PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="12.14.0" />
  </ItemGroup>
</Project>

Properties/launchSettings.json
{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "profiles": {
    "HotChocolateAuthDemo": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "applicationUrl": "http://localhost:5014",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
Program.cs
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGraphQLServer().AddQueryType<Query>().AddAuthorization();
builder.Services.AddSingleton<NotesRepository>();
builder.Services.AddAuthentication(defaultScheme: "UserId")
    .AddScheme<AuthenticationSchemeOptions, UserIdAuthHandler>(authenticationScheme: "UserId", _ => { });
builder.Services.AddAuthorization();

var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapGraphQLHttp("/graphql").RequireAuthorization();
    endpoints.MapGraphQLSchema("/graphql/schema");
    endpoints.MapBananaCakePop("/graphql/ui");

});
app.Run();

public class UserIdAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public UserIdAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
    { }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue("UserId", out var userId))
        {
            return Task.FromResult(AuthenticateResult.NoResult());
        }
        var ticket = new AuthenticationTicket(
            authenticationScheme: "UserId",
            principal: new ClaimsPrincipal(
                identity: new ClaimsIdentity(
                    authenticationType: "UserId",
                    claims: new []{ new Claim("UserId", userId) }
                )));
        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}

[ObjectType]
public class Query
{
    public NoteViewModel[] GetMyNotes(
        [Service] NotesRepository repository,
        ClaimsPrincipal user)
    {
        var userId = user.FindFirstValue("UserId");
        
        var viewModels = repository.Value
            .Where(note => note.OwnerId == userId)
            .Select(note => note.ToViewModel())
            .ToArray();
        return viewModels;
    }
}
[ObjectType("Note")]
public record NoteViewModel(string Id, string Title, string Body);


public class NotesRepository
{
    public readonly NoteDto[] Value = new[]
    {
        new NoteDto(Id: "1", OwnerId: "1", Title: "user 1 note 1 title", Body: "user 1 note 1 body"),
        new NoteDto(Id: "1", OwnerId: "2", Title: "user 2 note 1 title", Body: "user 2 note 1 body")
    };
}
public record NoteDto(string Id, string OwnerId, string Title, string Body)
{
    public NoteViewModel ToViewModel() => new NoteViewModel(Id, Title, Body);
}

2. Open http://localhost:5014/graphql/ui

You will see that introspection query to http://localhost:5014/graphql is failed with http status code 401.

query introspection_phase_1 {
  schema: __type(name: "__Schema") {
    name
    fields {
      name
    }
  }
  directive: __type(name: "__Directive") {
    name
    fields {
      name
    }
  }
}
Relevant log output

No response

Additional Context?

No response

Product

Hot Chocolate

Version

12.14.0

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.