Implementing GraphQL Authentication

1. Understanding GraphQL Auth Challenges

ChallengeDetail
Single endpointCannot do URL-based authorization
Query depthUntrusted clients can craft expensive queries
Nested resolversAuth must happen at each level
IntrospectionSchema disclosure in prod

2. Using JWT in GraphQL

Example: Apollo Server context

const server = new ApolloServer({
  schema,
  context: async ({ req }) => {
    const token = req.headers.authorization?.replace("Bearer ", "");
    const user = token ? await verifyJwt(token) : null;
    return { user };
  }
});

3. Implementing Context for Identity

PatternDetail
Auth in contextResolvers read ctx.user
DataLoaderBatch with user-scoped cache
Per-requestNever share across requests

4. Implementing Field-Level Authorization

Example: Resolver-level check

const resolvers = {
  User: {
    email: (parent, _, { user }) => {
      if (user.id !== parent.id && !user.roles.includes("admin")) return null;
      return parent.email;
    }
  }
};

5. Using Directive-Based Authorization

Example: @auth directive

directive @auth(requires: Role = USER) on FIELD_DEFINITION

type Query {
  adminPanel: AdminPanel @auth(requires: ADMIN)
}

6. Implementing Query Complexity Analysis

LibraryUse
graphql-cost-analysisAssign cost weights per field
LimitReject above threshold (e.g., 1000)
TieredHigher quota for paid plans

7. Implementing Depth Limiting

Example: graphql-depth-limit

import depthLimit from "graphql-depth-limit";
new ApolloServer({ schema, validationRules: [depthLimit(7)] });

8. Using Persisted Queries

AspectDetail
MechanismClient sends hash; server resolves to pre-registered query
BenefitPrevents arbitrary queries in prod
ToolApollo APQ, Relay persisted queries
Disable introspectionIn prod when APQ is on

9. Handling Errors in Resolvers

PatternDetail
AuthenticationErrorHTTP 401-equivalent in GraphQL
ForbiddenError403-equivalent
Partial responseReturn data + errors; client handles
MaskHide stack traces in prod

10. Implementing DataLoader for Authorization

Example: Permission-aware loader

const orderLoader = new DataLoader(async ids => {
  const orders = await Order.findByIds(ids);
  return orders.map(o => can(ctx.user, "orders:read", o) ? o : null);
});