website/src/docs/hotchocolate/v16/securing-your-api/authorization.md
Authorization controls what an authenticated user can access. Hot Chocolate provides the @authorize directive for field-level and type-level access control, integrating with ASP.NET Core roles and policies.
Authentication is a prerequisite. You must first validate a user's identity before evaluating their permissions.
Learn how to set up authentication
After configuring authentication, complete these steps to enable authorization.
Call AddAuthorization() on both IServiceCollection (for ASP.NET Core services) and IRequestExecutorBuilder (for the @authorize directive and middleware):
// Program.cs
builder.Services.AddAuthorization();
builder
.AddGraphQL()
.AddAuthorization()
.AddQueryType<Query>();
// Program.cs
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL();
});
The @authorize directive can be applied to types and fields. When applied to a type, it applies to every field on that type. A directive on a specific field overrides the one on the type.
<ExampleTabs> <Implementation>Use
HotChocolate.Authorization.AuthorizeAttribute, notMicrosoft.AspNetCore.Authorization.AuthorizeAttribute. The Microsoft attribute does not integrate with the Hot Chocolate authorization pipeline. Using the wrong attribute is a common source of authorization not working.
// Models/User.cs
[Authorize]
public class User
{
public string Name { get; set; }
[Authorize(Roles = ["Administrator"])]
public Address Address { get; set; }
}
With the source generator, you can apply [Authorize] to resolver methods:
// Types/UserQueries.cs
[QueryType]
public static partial class UserQueries
{
[Authorize]
public static async Task<User?> GetMeAsync(
ClaimsPrincipal claimsPrincipal,
UserService users,
CancellationToken ct)
{
var userId = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier);
return userId is not null ? await users.GetByIdAsync(userId, ct) : null;
}
}
// Types/UserType.cs
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor.Authorize();
descriptor.Field(f => f.Address).Authorize(["Administrator"]);
}
}
If no arguments are specified on [Authorize], the directive requires the user to be authenticated. Unauthenticated users who access an authorized field receive a GraphQL error with the code AUTH_NOT_AUTHENTICATED, and the field value is set to null.
{
"errors": [
{
"message": "The current user is not authorized to access this resource.",
"path": ["me"],
"extensions": {
"code": "AUTH_NOT_AUTHENTICATED"
}
}
],
"data": {
"me": null
}
}
Roles provide a straightforward way to group users by access level. Add role claims to the ClaimsPrincipal:
claims.Add(new Claim(ClaimTypes.Role, "Administrator"));
Then restrict access by role:
<ExampleTabs> <Implementation>// Models/User.cs
[Authorize(Roles = ["Guest", "Administrator"])]
public class User
{
public string Name { get; set; }
[Authorize(Roles = ["Administrator"])]
public Address Address { get; set; }
}
// Types/UserType.cs
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor.Authorize(["Guest", "Administrator"]);
descriptor.Field(f => f.Address).Authorize(["Administrator"]);
}
}
When multiple roles are specified, a user needs to match only one of them to gain access.
Learn more about role-based authorization in ASP.NET Core
Policies decouple authorization logic from your GraphQL resolvers. A policy consists of an IAuthorizationRequirement and an AuthorizationHandler<T>.
Register policies on the service collection:
// Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
options.AddPolicy("HasCountry", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c => c.Type == ClaimTypes.Country)));
});
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
Apply policies to fields:
<ExampleTabs> <Implementation>// Models/User.cs
[Authorize(Policy = "AllEmployees")]
public class User
{
public string Name { get; set; }
[Authorize(Policy = "SalesDepartment")]
public Address Address { get; set; }
}
// Types/UserType.cs
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor.Authorize("AllEmployees");
descriptor.Field(f => f.Address).Authorize("SalesDepartment");
}
}
The @authorize directive is repeatable. When multiple policies are specified, the user must satisfy all of them:
// Models/User.cs
[Authorize(Policy = "AtLeast21")]
[Authorize(Policy = "HasCountry")]
public class User
{
public string Name { get; set; }
}
// Types/UserType.cs
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor
.Authorize("AtLeast21")
.Authorize("HasCountry");
}
}
Learn more about policy-based authorization in ASP.NET Core
When you need access to GraphQL-specific data in your authorization handler, use IResolverContext as the resource type:
// Authorization/MinimumAgeHandler.cs
public class MinimumAgeHandler
: AuthorizationHandler<MinimumAgeRequirement, IResolverContext>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement,
IResolverContext resolverContext)
{
// Access GraphQL context data, arguments, etc.
// Omitted for brevity
}
}
Use [AllowAnonymous] to bypass authorization on specific fields. This is useful for registration or public content endpoints.
Use
HotChocolate.AspNetCore.Authorization.AllowAnonymousAttribute, notMicrosoft.AspNetCore.Authorization.AllowAnonymousAttribute.
// Types/AccountMutations.cs
[MutationType]
public static partial class AccountMutations
{
[Authorize]
public static async Task<User> AddAddressAsync(/* ... */)
{
// Requires authentication
}
[AllowAnonymous]
public static async Task<User> RegisterAsync(/* ... */)
{
// Open to everyone
}
}
[AllowAnonymous] removes all other authorization requirements on the field. Use it carefully to avoid exposing sensitive data.
Apply authorization to the entire GraphQL endpoint by calling RequireAuthorization():
// Program.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL().RequireAuthorization();
});
This returns HTTP 401 for unauthorized requests and blocks access to all middleware including Nitro. To keep Nitro accessible while protecting the GraphQL endpoint, split the middleware:
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQLHttp().RequireAuthorization();
endpoints.MapNitroApp();
});
Learn more about available middleware