Skip to content

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

Open

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions