| 
 | 1 | +---  | 
 | 2 | +title: Authorization Strategies  | 
 | 3 | +---  | 
 | 4 | + | 
 | 5 | +GraphQL gives you complete control over how to define and enforce access control.   | 
 | 6 | +That flexibility means it's up to you to decide where authorization rules live and   | 
 | 7 | +how they're enforced.  | 
 | 8 | + | 
 | 9 | +This guide covers common strategies for implementing authorization in GraphQL   | 
 | 10 | +servers using GraphQL.js. It assumes you're authenticating requests and passing a user or   | 
 | 11 | +session object into the `context`.  | 
 | 12 | + | 
 | 13 | +## What is authorization?  | 
 | 14 | + | 
 | 15 | +Authorization determines what a user is allowed to do. It's different from   | 
 | 16 | +authentication, which verifies who a user is.  | 
 | 17 | + | 
 | 18 | +In GraphQL, authorization typically involves restricting:  | 
 | 19 | + | 
 | 20 | +- Access to certain queries or mutations  | 
 | 21 | +- Visibility of specific fields  | 
 | 22 | +- Ability to perform mutations based on roles or ownership  | 
 | 23 | + | 
 | 24 | +## Resolver-based authorization  | 
 | 25 | + | 
 | 26 | +> **Note:**    | 
 | 27 | +> All examples assume you're using Node.js 20 or later with [ES module (ESM) support](https://nodejs.org/api/esm.html) enabled.  | 
 | 28 | +
  | 
 | 29 | +The simplest approach is to enforce access rules directly inside resolvers   | 
 | 30 | +using the `context.user` value:  | 
 | 31 | + | 
 | 32 | +```js  | 
 | 33 | +export const resolvers = {  | 
 | 34 | +  Query: {  | 
 | 35 | +    secretData: (parent, args, context) => {  | 
 | 36 | +      if (!context.user || context.user.role !== 'admin') {  | 
 | 37 | +        throw new Error('Not authorized');  | 
 | 38 | +      }  | 
 | 39 | +      return getSecretData();  | 
 | 40 | +    },  | 
 | 41 | +  },  | 
 | 42 | +};  | 
 | 43 | +```  | 
 | 44 | + | 
 | 45 | +This works well for smaller schemas or one-off checks.  | 
 | 46 | + | 
 | 47 | +## Centralizing access control logic  | 
 | 48 | + | 
 | 49 | +As your schema grows, repeating logic like `context.user.role !=='admin'`  | 
 | 50 | +becomes error-prone. Instead, extract shared logic into utility functions:  | 
 | 51 | + | 
 | 52 | +```js  | 
 | 53 | +export function requireUser(user) {  | 
 | 54 | +  if (!user) {  | 
 | 55 | +    throw new Error('Not authenticated');  | 
 | 56 | +  }  | 
 | 57 | +}  | 
 | 58 | + | 
 | 59 | +export function requireRole(user, role) {  | 
 | 60 | +  requireUser(user);  | 
 | 61 | +  if (user.role !== role) {  | 
 | 62 | +    throw new Error(`Must be a ${role}`);  | 
 | 63 | +  }  | 
 | 64 | +}  | 
 | 65 | +```  | 
 | 66 | + | 
 | 67 | +You can use these helpers in resolvers:  | 
 | 68 | + | 
 | 69 | +```js  | 
 | 70 | +import { requireRole } from './auth.js';  | 
 | 71 | + | 
 | 72 | +export const resolvers = {  | 
 | 73 | +  Mutation: {  | 
 | 74 | +    deleteUser: (parent, args, context) => {  | 
 | 75 | +      requireRole(context.user, 'admin');  | 
 | 76 | +      return deleteUser(args.id);  | 
 | 77 | +    },  | 
 | 78 | +  },  | 
 | 79 | +};  | 
 | 80 | +```  | 
 | 81 | + | 
 | 82 | +This pattern makes your access rules easier to read, test, and update.  | 
 | 83 | + | 
 | 84 | +## Field-level access control  | 
 | 85 | + | 
 | 86 | +You can also conditionally return or hide data at the field level. This   | 
 | 87 | +is useful when, for example, users should only see their own private data:  | 
 | 88 | + | 
 | 89 | +```js  | 
 | 90 | +export const resolvers = {  | 
 | 91 | +  User: {  | 
 | 92 | +    email: (parent, args, context) => {  | 
 | 93 | +      if (context.user.id !== parent.id && context.user.role !== 'admin') {  | 
 | 94 | +        return null;  | 
 | 95 | +      }  | 
 | 96 | +      return parent.email;  | 
 | 97 | +    },  | 
 | 98 | +  },  | 
 | 99 | +};  | 
 | 100 | +```  | 
 | 101 | + | 
 | 102 | +Returning `null` is a common pattern when fields should be hidden from  | 
 | 103 | +unauthorized users without triggering an error.  | 
 | 104 | + | 
 | 105 | +## Declarative authorization with directives  | 
 | 106 | + | 
 | 107 | +If you prefer a schema-first or declarative style, you can define custom  | 
 | 108 | +schema directives like `@auth(role: "admin")` directly in your SDL:  | 
 | 109 | + | 
 | 110 | +```graphql  | 
 | 111 | +type Query {  | 
 | 112 | +  users: [User] @auth(role: "admin")  | 
 | 113 | +}  | 
 | 114 | +```  | 
 | 115 | + | 
 | 116 | +To enforce this directive during execution, you need to inspect it in your resolvers  | 
 | 117 | +using `getDirectiveValues`:  | 
 | 118 | + | 
 | 119 | +```js  | 
 | 120 | +import { getDirectiveValues } from 'graphql';  | 
 | 121 | + | 
 | 122 | +function withAuthCheck(resolverFn, schema, fieldNode, variableValues, context) {  | 
 | 123 | +  const directive = getDirectiveValues(  | 
 | 124 | +    schema.getDirective('auth'),  | 
 | 125 | +    fieldNode,  | 
 | 126 | +    variableValues  | 
 | 127 | +  );  | 
 | 128 | + | 
 | 129 | +  if (directive?.role && context.user?.role !== directive.role) {  | 
 | 130 | +    throw new Error('Unauthorized');  | 
 | 131 | +  }  | 
 | 132 | + | 
 | 133 | +  return resolverFn();  | 
 | 134 | +}  | 
 | 135 | +```  | 
 | 136 | + | 
 | 137 | +You can wrap individual resolvers with this logic, or apply it more broadly using a  | 
 | 138 | +schema visitor or transformation.  | 
 | 139 | + | 
 | 140 | +GraphQL.js doesn't interpret directives by default, they're just annotations.  | 
 | 141 | +You must implement their behavior manually, usually by:  | 
 | 142 | + | 
 | 143 | +- Wrapping resolvers in custom logic  | 
 | 144 | +- Using a schema transformation library to inject authorization checks  | 
 | 145 | + | 
 | 146 | +Directive-based authorization can add complexity, so many teams start with  | 
 | 147 | +resolver-based checks and adopt directives later if needed.  | 
 | 148 | + | 
 | 149 | +## Best practices  | 
 | 150 | + | 
 | 151 | +- Keep authorization logic close to business logic. Resolvers are often the  | 
 | 152 | +right place to keep authorization logic.  | 
 | 153 | +- Use shared helper functions to reduce duplication and improve clarity.  | 
 | 154 | +- Avoid tightly coupling authorization logic to your schema. Make it  | 
 | 155 | +reusable where possible.  | 
 | 156 | +- Consider using `null` to hide fields from unauthorized users, rather than  | 
 | 157 | +throwing errors.  | 
 | 158 | +- Be mindful of tools like introspection or GraphQL Playground that can  | 
 | 159 | +expose your schema. Use caution when deploying introspection in production  | 
 | 160 | +environments.  | 
 | 161 | + | 
 | 162 | +## Additional resources  | 
 | 163 | + | 
 | 164 | +- [Anatomy of a Resolver](./resolver-anatomy): Shows how resolvers work and how the `context`   | 
 | 165 | +object is passed in. Helpful if you're new to writing custom resolvers or   | 
 | 166 | +want to understand where authorization logic fits.  | 
 | 167 | +- [GraphQL Specification, Execution section](https://spec.graphql.org/October2021/#sec-Execution): Defines how fields are   | 
 | 168 | +resolved, including field-level error propagation and execution order. Useful   | 
 | 169 | +background when building advanced authorization patterns that rely on the   | 
 | 170 | +structure of GraphQL execution.  | 
 | 171 | +- [`graphql-shield`](https://github.com/dimatill/graphql-shield): A community library for adding rule-based   | 
 | 172 | +authorization as middleware to resolvers.  | 
 | 173 | +- [`graphql-auth-directives`](https://github.com/the-guild-org/graphql-auth-directives): Adds support for custom directives like   | 
 | 174 | +`@auth(role: "admin")`, letting you declare access control rules in SDL.   | 
 | 175 | +Helpful if you're building a schema-first API and prefer declarative access   | 
 | 176 | +control.  | 
 | 177 | + | 
 | 178 | + | 
0 commit comments