A toolset for authorizing access to graph types for GraphQL.NET. It provides a validation rule that checks all of the Graph Types in the given GraphQL operation (query/mutation/subscription) to see if they have authorization policies applied to them and evaluates these policies if any.
Provides the following packages:
| Package | Downloads | NuGet Latest |
|---|---|---|
| GraphQL.Authorization |
You can get all preview versions from GitHub Packages. Note that GitHub requires authentication to consume the feed. See here.
If you came here in search for GraphQL authorization for the ASP.NET Core applications, then it makes sense to look into the server project and its GraphQL.Server.Authorization.AspNetCore package. Although you will be able to integrate GraphQL authorization with the help of classes from the current repository, the GraphQL.Server.Authorization.AspNetCore package is much better adapted to work within the ASP.NET Core applications.
- Register the necessary authorization classes in your DI container:
IValidationRule/AuthorizationValidationRuleIAuthorizationService/DefaultAuthorizationServiceIClaimsPrincipalAccessor/DefaultClaimsPrincipalAccessorIAuthorizationPolicyProvider/DefaultAuthorizationPolicyProvider
- If you use
DefaultClaimsPrincipalAccessorthen provide a customUserContextclass that implementsIProvideClaimsPrincipal. - Add policies to the
AuthorizationSettings. - Apply a policy to a
GraphTypeorFieldType(both implementIProvideMetadata):- using
AuthorizeWith(string policy)extension method - or with
GraphQLAuthorizeattribute if using Schema + Handler syntax.
- using
- The
AuthorizationValidationRulewill run and verify the policies based on the registered policies. - You can write your own
IAuthorizationRequirementand an extension method to add this requirement toAuthorizationPolicyBuilder.
-
Fully functional basic Console sample.
-
Fully functional ASP.NET Core sample.
Use AuthorizeWith extension method on IGraphType or IFieldType.
public class MyType : ObjectGraphType
{
public MyType()
{
this.AuthorizeWith("AdminPolicy");
Field<StringGraphType>("name").AuthorizeWith("SomePolicy");
}
}Use GraphQLAuthorize attribute on type, method or property.
[GraphQLAuthorize(Policy = "MyPolicy")]
public class MutationType
{
[GraphQLAuthorize(Policy = "AnotherPolicy")]
public async Task<string> CreateSomething(MyInput input)
{
return await SomeMethodAsync(input);
}
[GraphQLAuthorize(Policy = "SuperPolicy")]
public string SomeProperty => Guid.NewGuid().ToString();
}You can add your own requirements to the authorization framework to extend it.
Create your own IAuthorizationRequirement class and add that requirement to your policy.
public class OnlyMondayRequirement : IAuthorizationRequirement
{
public Task Authorize(IAuthorizationContext context)
{
if (DateTime.Now.DayOfWeek == DayOfWeek.Monday)
context.Succeed(this);
}
}
public static class MyAuthorizationPolicyBuilderExtensions
{
public static AuthorizationPolicyBuilder RequireMonday(this AuthorizationPolicyBuilder builder)
{
builder.AddRequirement(new OnlyMondayRequirement());
return builder;
}
}
public static void ConfigureAuthorizationServices(ServiceCollection services)
{
services
.AddSingleton<IValidationRule, AuthorizationValidationRule>()
.AddSingleton<IAuthorizationService, DefaultAuthorizationService>()
.AddSingleton<IClaimsPrincipalAccessor, DefaultClaimsPrincipalAccessor>()
.AddSingleton<IAuthorizationPolicyProvider>(provider =>
{
var authSettings = new AuthorizationSettings();
authSettings.AddPolicy("MyPolicy", b => b.RequireMonday());
return new DefaultAuthorizationPolicyProvider(authSettings);
})
}All authorization requirements (built-in or custom ones) only check the compliance of
the current execution state to their criteria. If the requirement is satisfied, then
it is marked as 'passed' and the next requirement is checked. If all requirements are
satisfied, then the validation rule returns a successful result. Otherwise for each
unsatisfied requirement, the validation rule will add an authorization error in the
ValidationContext. The text of this error may not suit you, especially if you write
your own authorization requirements. In this case, you can override the default behavior.
Option 1. Create a descendant from AuthorizationValidationRule and override
AddValidationError method.
public class CustomAuthorizationValidationRule : AuthorizationValidationRule
{
public CustomAuthorizationValidationRule(IAuthorizationService authorizationService, IClaimsPrincipalAccessor claimsPrincipalAccessor, IAuthorizationPolicyProvider policyProvider)
: base(authorizationService, claimsPrincipalAccessor, policyProvider)
{
}
protected override void AddValidationError(INode node, ValidationContext context, OperationType? operationType, AuthorizationResult result)
{
if (result.Failure.FailedRequirements.Any(r => r is MySpecialRequirement))
context.ReportError(new AuthorizationError(node, context, "My special error message", result));
else
base.AddValidationError(node, context, operationType, result);
}
}Then register CustomAuthorizationValidationRule instead of AuthorizationValidationRule
in your DI container.
Option 2. Implement IErrorInfoProvider interface. This is one of the interfaces from
the main GraphQL.NET repository. For convenience you may use ErrorInfoProvider base class.
public class CustomErrorInfoProvider : ErrorInfoProvider
{
public override ErrorInfo GetInfo(ExecutionError executionError)
{
var info = base.GetInfo(executionError);
info.Message = executionError switch
{
AuthorizationError authorizationError => GetAuthorizationErrorMessage(authorizationError),
_ => info.Message,
};
return info;
}
private string GetAuthorizationErrorMessage(AuthorizationError error)
{
var errorMessage = new StringBuilder();
AuthorizationError.AppendFailureHeader(errorMessage, error.OperationType);
foreach (var failedRequirement in error.AuthorizationResult.Failure.FailedRequirements)
{
switch (failedRequirement)
{
case OnlyMondayRequirement onlyMondayRequirement:
errorMessage.AppendLine();
errorMessage.Append("Access is allowed only on Mondays.");
break;
default:
AuthorizationError.AppendFailureLine(errorMessage, failedRequirement);
break;
}
}
return errorMessage.ToString();
}
}Then register CustomErrorInfoProvider in your DI container.
services.AddSingleton<IErrorInfoProvider, CustomErrorInfoProvider>();- It is currently not possible to add a policy to Input objects using Schema first approach.