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,
  },
};

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.