GraphQL in Production · Security, Observability and Evolution
Authentication and Field-Level Authorization
Where auth lives in GraphQL and how to avoid data leaks.
Authenticate at the transport layer (bearer token or session cookie) and put the user in the request context. Enforce authorization in the business/data layer (or resolvers) for every object and field — not just at the top-level query — because the same object is reachable through many paths (order.customer, node(id:), search). Schema directives (@auth(requires: ADMIN)) or policy libraries keep rules declarative; field-level checks protect sensitive fields like emails or costs.
const resolvers = {
Query: {
order: async (_p, { id }, ctx) => orders.getForViewer(ctx.user, id), // auth inside the service
},
Customer: {
email: (customer, _a, ctx) =>
ctx.user?.id === customer.id || ctx.user?.roles.includes("support") ? customer.email : null,
},
};
from strawberry.permission import BasePermission
class IsSupport(BasePermission):
message = "Not authorized"
def has_permission(self, source, info, **kwargs) -> bool:
user = info.context["user"]
return user is not None and "support" in user.roles
@strawberry.type
class Customer:
id: strawberry.ID
email: str | None = strawberry.field(permission_classes=[IsSupport])
@Controller
class OrderController {
@QueryMapping
@PreAuthorize("isAuthenticated()")
Order order(@Argument String id, Authentication auth) {
return orderService.getForUser(id, auth.getName()); // ownership checked in the service
}
@SchemaMapping(typeName = "Customer", field = "email")
@PreAuthorize("hasRole('SUPPORT') or #customer.id == authentication.name")
String email(Customer customer) { return customer.email(); }
}
Common pitfalls
- Authorizing only top-level fields while
node(id:)exposes everything. - Authorization logic duplicated (and diverging) in resolvers and REST endpoints.
- Error messages revealing that a hidden object exists.
Key points
- Auth in context; authorization in business layer
- Every path to an object must be checked
- Field-level rules for sensitive data
Further reading: GraphQL: Authorization
Practice questions
Why is checking authorization only in the top-level order resolver insufficient?
Answer
The same object can be reached via other fields (node(id:), customer.orders, search results); checks must apply wherever the object is loaded, ideally in a shared business/data layer.
Where should the authenticated user be stored during GraphQL execution?
Answer
In the per-request context object created by the server, available to every resolver and DataLoader.
Open in the app to mark lessons complete, take notes and track your progress.