Skip to content

Commit 5e251f7

Browse files
committed
Merge branch '16.x.x' into query-complexity-control
2 parents 9a00031 + d3483ff commit 5e251f7

File tree

10 files changed

+1473
-69
lines changed

10 files changed

+1473
-69
lines changed

cspell.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ overrides:
2727
- xlink
2828
- composability
2929
- deduplication
30+
- subschema
31+
- subschemas
32+
- NATS
33+
- benjie
34+
- codegen
35+
- URQL
36+
- tada
37+
- Graphile
3038
- precompiled
3139
- debuggable
3240

@@ -65,6 +73,7 @@ words:
6573
- ruru
6674
- oneof
6775
- vercel
76+
- unbatched
6877

6978
# used as href anchors
7079
- graphqlerror
@@ -120,3 +129,6 @@ words:
120129
- XXXF
121130
- bfnrt
122131
- wrds
132+
- overcomplicating
133+
- cacheable
134+
- pino

website/pages/docs/_meta.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@ const meta = {
1212
'object-types': '',
1313
'mutations-and-input-types': '',
1414
'authentication-and-express-middleware': '',
15+
'authorization-strategies': '',
1516
'-- 2': {
1617
type: 'separator',
1718
title: 'Advanced Guides',
1819
},
1920
'constructing-types': '',
21+
nullability: '',
2022
'abstract-types': '',
2123
'oneof-input-objects': '',
2224
'defer-stream': '',
25+
subscriptions: '',
2326
'cursor-based-pagination': '',
2427
'custom-scalars': '',
2528
'advanced-custom-scalars': '',
2629
'operation-complexity-controls': '',
2730
'n1-dataloader': '',
31+
'caching-strategies': '',
2832
'resolver-anatomy': '',
2933
'graphql-errors': '',
3034
'using-directives': '',
@@ -33,6 +37,7 @@ const meta = {
3337
title: 'FAQ',
3438
},
3539
'going-to-production': '',
40+
'scaling-graphql': '',
3641
};
3742

3843
export default meta;

website/pages/docs/authentication-and-express-middleware.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
title: Authentication and Express Middleware
3-
sidebarTitle: Authentication & Middleware
2+
title: Using Express Middleware with GraphQL.js
3+
sidebarTitle: Using Express Middleware
44
---
55

66
import { Tabs } from 'nextra/components';
@@ -100,3 +100,5 @@ In a REST API, authentication is often handled with a header, that contains an a
100100
If you aren't familiar with any of these authentication mechanisms, we recommend using `express-jwt` because it's simple without sacrificing any future flexibility.
101101

102102
If you've read through the docs linearly to get to this point, congratulations! You now know everything you need to build a practical GraphQL API server.
103+
104+
Want to control access to specific operations or fields? See [Authorization Strategies](\pages\docs\authorization-strategies.mdx).
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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

Comments
 (0)