Skip to main content
Version: 6.x

Authorization

You should secure the route where you app/client posts request to in any ASP.NET supports. Given GraphQL works with a schema you likely want to provide authorization within the schema. EntityGraphQL provides support for checking claims on a ClaimsPrincipal object.

Authorization Services​

EntityGraphQL supports different authorization service implementations:

  • RoleBasedAuthorization - The default. Checks roles on the ClaimsPrincipal. Use when you only need role-based authorization.
  • PolicyOrRoleBasedAuthorization - Supports both ASP.NET Core policies and roles. This is the default when calling AddGraphQLSchema() in EntityGraphQL.AspNet if IAuthorizationService is available.

Configuring Authorization Service​

When using AddGraphQLSchema() in ASP.NET, PolicyOrRoleBasedAuthorization is used by default. To use a different authorization service:

builder.Services.AddSingleton<IAuthorizationService, MyCustomAuthService>();

// Or use role-based authorization only
builder.Services.AddSingleton<IAuthorizationService, RoleBasedAuthorization>();

builder.Services.AddGraphQLSchema<DemoContext>(options =>
{
// Configure things
});

When creating a schema manually outside of ASP.NET:

var schema = new SchemaProvider<DemoContext>(
authorizationService: new MyCustomAuthService()
);

Passing in the User​

First pass in the ClaimsPrincipal to the query call

Note if you are using the AddGraphQLSchema() extension in EntityGraphQL.AspNet this is already handled for you.

// Assuming you're in a ASP.NET controller
// this.User is the current ClaimsPrincipal
var results = await schemaProvider.ExecuteRequestAsync(query, dbContext, this.HttpContext.RequestServices, this.User);

Adding Authorization on Roles or Policies​

You can add authorization requirements throughout your schema even using the AuthorizeAttribute or when building/modifying your schema.

_Note: if you provide multiple [AuthorizeAttribute] attributes on a single field/mutation they are treated as AND meaning all are required. If you provide a single [AuthorizeAttribute] attribute with multiple roles/policies in a comma-separated string they are treated as OR i.e. having any of those listed will authorize access.

info

A bare attribute with no roles or policies — [GraphQLAuthorize] or [Authorize] — requires the user to be authenticated (Identity.IsAuthenticated). Authorization fails closed: if a requirement is present but cannot be satisfied (no user, or a policy is required but no IAuthorizationService is registered), access is denied.

Introspection​

Introspection queries (__schema and __type) respect authorization — the result only includes the types and fields the requesting user is authorized to access, so protected type/field names are not discoverable by unauthorized users. Querying __type(name: ...) for a protected type returns null.

Note this applies only to introspection queries at runtime. schema.ToGraphQLSchemaString() always outputs the full schema — use it to generate SDL files for tooling and type generation.

Mutations​

Mark you mutation methods with the [Authorize(Roles = "role-name")] attribute.

Policy authorization with [Authorize(Policy = "policy-name")] is also supported when using EntityGraphQL.AspNet.

public class MovieMutations
{
[GraphQLMutation]
[Authorize(Roles = "movie-editor")]
public Movie AddActor(MyDbContext db, ActorArgs args)
{
// ...
}
}

If a ClaimsPrincipal is provided with the query call it will be required to be Authorized and have a Role of movie-editor to call this mutation.

Queries​

If you are using the SchemaBuilder.FromObject<TContext>() you can use the [Authorize(Roles = "role-name")] attribute again throughout the objects.

public class MyDbContext : DbContext {
protected override void OnModelCreating(ModelBuilder builder) {
// Set up your relations
}

// require either claim
[Authorize(Roles = "property-role,admin-property-role")]
public DbSet<Property> Properties { get; set; }
public DbSet<PropertyType> PropertyTypes { get; set; }
public DbSet<Location> Locations { get; set; }
}

public class Property {
public uint Id { get; set; }
public string Name { get; set; }
public PropertyType Type { get; set; }
// require both claims
[Authorize(Roles = "property-admin")]
[Authorize(Roles = "super-admin")]
public Location Location { get; set; }
}

// ....

You can secure whole types with the attribute too.

[Authorize(Roles = "property-user")]
public class Property {
public uint Id { get; set; }
public string Name { get; set; }
public PropertyType Type { get; set; }
public Location Location { get; set; }
}

If a ClaimsPrincipal is provided with the ExecuteRequest call it will be required to be Authorized and have the Role property-role to query the root-level properties field and the role property-admin to query the Property field location.

Authorization can be provided in the API for add/replacing fields on the schema object.

schemaProvider.AddField("myField", (db) => db.MyEntities, "Description").RequiresAllRoles("admin");
schemaProvider.AddField("myField", (db) => db.MyEntities, "Description").RequiresAnyRole("admin", "super-admin");

schemaProvider.AddType<Property>("properties", (db) => db.Properties, "Description").RequiresAllRoles("property-user");
schemaProvider.AddType<Property>("properties", (db) => db.Properties, "Description").RequiresAnyRole("property-user", "property-admin");

Note when using AddField() and AddType() these functions will automatically search for [Authorize()] attributes on the fields and types.

Authorization without ASP.Net​

You can use the GraphQLAuthorizeAttribute with role claims to provide authorization without the ASP.Net dependency.

Custom Authorization​

EntityGraphQL's authorization system is built on a flexible keyed data structure that allows you to extend it with custom authorization requirements.

How Authorization Works​

Authorization requirements are stored in a RequiredAuthorization object which uses a keyed data dictionary. This allows different authorization implementations to store their own custom data:

  • Core library uses "egql:core:roles" key for role-based authorization
  • EntityGraphQL.AspNet uses "egql:aspnet:policies" key for policy-based authorization
  • Your custom implementation can use any namespaced key (e.g., "myapp:custom-auth")

Creating a Custom Authorization Service​

To implement custom authorization, create a class that extends RoleBasedAuthorization or implements IGqlAuthorizationService:

Note: IsAuthorized is synchronous as it is called while compiling the query. If your authorization needs async work (e.g. a database or network lookup), override PrepareForRequestAsync — it is called once at the start of each request in an async context and can return a request-scoped service that answers IsAuthorized from pre-fetched results. This is how PolicyOrRoleBasedAuthorization evaluates ASP.NET Core policies.

public class CustomAuthorizationService : RoleBasedAuthorization
{
private const string CustomDataKey = "myapp:custom-permissions";

public override bool IsAuthorized(ClaimsPrincipal? user, RequiredAuthorization? requiredAuthorization)
{
if (requiredAuthorization != null && requiredAuthorization.Any())
{
// Check your custom authorization data
// The data is List<List<string>> to support AND/OR combinations
// Each inner list is OR'd together, outer lists are AND'd
// Example: [["perm1", "perm2"], ["perm3"]] means (perm1 OR perm2) AND perm3
if (requiredAuthorization.TryGetData(CustomDataKey, out var permissionGroups))
{
foreach (var permissionGroup in permissionGroups)
{
// User must have at least one permission from this group (OR)
var hasAnyPermission = permissionGroup.Any(permission =>
UserHasPermission(user, permission));

if (!hasAnyPermission)
return false; // User doesn't have any permission from this group (AND failed)
}
}

// Also check roles
return base.IsAuthorized(user, requiredAuthorization);
}
return true;
}

private bool UserHasPermission(ClaimsPrincipal? user, string permission)
{
// Your custom permission logic
return user?.HasClaim("permission", permission) ?? false;
}
}

Adding Custom Authorization Data​

You can add custom authorization requirements using extension methods:

public static class CustomAuthorizationExtensions
{
private const string CustomDataKey = "myapp:custom-permissions";

public static IField RequiresAnyPermission(this IField field, params string[] permissions)
{
field.RequiredAuthorization ??= new RequiredAuthorization();

// Get existing permission groups or create new list
if (!field.RequiredAuthorization.TryGetData(CustomDataKey, out var permissionGroups))
{
permissionGroups = new List<List<string>>();
field.RequiredAuthorization.SetData(CustomDataKey, permissionGroups);
}

// Add as a new group where any permission satisfies (OR within group)
permissionGroups.Add(permissions.ToList());
return field;
}

public static IField RequiresAllPermissions(this IField field, params string[] permissions)
{
field.RequiredAuthorization ??= new RequiredAuthorization();

if (!field.RequiredAuthorization.TryGetData(CustomDataKey, out var permissionGroups))
{
permissionGroups = new List<List<string>>();
field.RequiredAuthorization.SetData(CustomDataKey, permissionGroups);
}

// Add each permission as a separate group (AND across groups)
foreach (var permission in permissions)
{
permissionGroups.Add(new List<string> { permission });
}
return field;
}

public static SchemaType<TBaseType> RequiresAnyPermission<TBaseType>(
this SchemaType<TBaseType> schemaType, params string[] permissions)
{
schemaType.RequiredAuthorization ??= new RequiredAuthorization();

if (!schemaType.RequiredAuthorization.TryGetData(CustomDataKey, out var permissionGroups))
// Use in your schema
schemaProvider.AddField("sensitiveData", (db) => db.SensitiveEntities, "Sensitive data")
.RequiresAnyPermission("read:sensitive-data");

schemaProvider.Type<User>()
.ReplaceField("salary", u => u.Salary, "User's salary")
.RequiresAllPermissions("read:salaries", "read:user-data");
``` }

public static SchemaType<TBaseType> RequiresAllPermissions<TBaseType>(
this SchemaType<TBaseType> schemaType, params string[] permissions)
{
schemaType.RequiredAuthorization ??= new RequiredAuthorization();

if (!schemaType.RequiredAuthorization.TryGetData(CustomDataKey, out var permissionGroups))
{
permissionGroups = new List<List<string>>();
schemaType.RequiredAuthorization.SetData(CustomDataKey, permissionGroups);
}

foreach (var permission in permissions)
{
permissionGroups.Add(new List<string> { permission });
}
return schemaType;
}
}

Using Custom Authorization​

// Configure your custom authorization service
services.AddGraphQLSchema<DemoContext>(options => {
options.Schema.AuthorizationService = new CustomAuthorizationService();
});

// Use in your schema
schemaProvider.AddField("sensitiveData", (db) => db.SensitiveEntities, "Sensitive data")
.RequiresPermission("read:sensitive-data");

schemaProvider.Type<User>()
.ReplaceField("salary", u => u.Salary, "User's salary")
.RequiresPermission("read:salaries");

Combining Multiple Authorization Types​

The keyed data structure allows multiple authorization requirements to coexist:

schemaProvider.AddField("adminData", (db) => db.AdminData, "Admin only data")
.RequiresAllRoles("admin") // Role-based auth
.RequiresAnyPermission("read:admin") // Custom permission auth
.RequiresAllPolicies("AdminPolicy"); // ASP.NET policy auth (requires EntityGraphQL.AspNet)

All authorization requirements must be satisfied for access to be granted.