Authorization

See the Authorization project for a more in depth implementation of the following idea. Keep in mind that alongside this project there is a similar Authorization.AspNetCore project specifically for ASP.NET Core apps.

You can write validation rules that will run before a query is executed. You can use this pattern to check that the user is authenticated or has permissions for a specific field. This example uses the Metadata dictionary available on Fields to set permissions per field.

public class MyGraphType : ObjectGraphType
{
  public MyGraphType()
  {
    this.RequirePermission("READ_ONLY");
    Field(x => x.Secret).RequirePermission("Admin");
  }
}

Validation Rule

public class RequiresAuthValidationRule : IValidationRule
{
  public Task<INodeVisitor> ValidateAsync(ValidationContext context)
  {
    var userContext = context.UserContext as GraphQLUserContext;
    var authenticated = userContext.User?.IsAuthenticated() ?? false;

    return Task.FromResult(new EnterLeaveListener(_ =>
    {
      _.Match<Operation>(op =>
      {
        if (op.OperationType == OperationType.Mutation && !authenticated)
        {
          context.ReportError(new ValidationError(
              context.Document.Source,
              "6.1.1", // the rule number of this validation error corresponding to the paragraph number from the official specification
              $"Authorization is required to access {op.Name}.",
              op) { Code = "auth-required" });
        }
      });

      // this could leak info about hidden fields in error messages
      // it would be better to implement a filter on the schema so it
      // acts as if they just don't exist vs. an auth denied error
      // - filtering the schema is not currently supported
      _.Match<Field>(fieldAst =>
      {
        var fieldDef = context.TypeInfo.GetFieldDef();
        if (fieldDef.RequiresPermissions() &&
            (!authenticated || !fieldDef.CanAccess(userContext.User.Claims)))
        {
          context.ReportError(new ValidationError(
              context.Document.Source,
              "6.1.1", // the rule number of this validation error corresponding to the paragraph number from the official specification
              $"You are not authorized to run this query.",
              fieldAst) { Code = "auth-required" });
        }
      });
    }));
  }
}

Permission Extension Methods

public static class GraphQLExtensions
{
  public static readonly string PermissionsKey = "Permissions";

  public static bool RequiresPermissions(this IProvideMetadata type)
  {
    var permissions = type.GetMetadata<IEnumerable<string>>(PermissionsKey, new List<string>());
    return permissions.Any();
  }

  public static bool CanAccess(this IProvideMetadata type, IEnumerable<string> claims)
  {
    var permissions = type.GetMetadata<IEnumerable<string>>(PermissionsKey, new List<string>());
    return permissions.All(x => claims?.Contains(x) ?? false);
  }

  public static bool HasPermission(this IProvideMetadata type, string permission)
  {
    var permissions = type.GetMetadata<IEnumerable<string>>(PermissionsKey, new List<string>());
    return permissions.Any(x => string.Equals(x, permission));
  }

  public static void RequirePermission(this IProvideMetadata type, string permission)
  {
    var permissions = type.GetMetadata<List<string>>(PermissionsKey);

    if (permissions == null)
    {
      permissions = new List<string>();
      type.Metadata[PermissionsKey] = permissions;
    }

    permissions.Add(permission);
  }

  public static FieldBuilder<TSourceType, TReturnType> RequirePermission<TSourceType, TReturnType>(
      this FieldBuilder<TSourceType, TReturnType> builder, string permission)
  {
    builder.FieldType.RequirePermission(permission);
    return builder;
  }
}