-
Notifications
You must be signed in to change notification settings - Fork 2k
docs: add page on authorization strategies #4396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
cc56c48
b57364d
92f9b9c
8305971
6ecfa70
86ca89e
b511dd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
--- | ||
title: Authorization Strategies | ||
--- | ||
|
||
GraphQL gives you complete control over how to define and enforce access control. | ||
That flexibility means it's up to you to decide where authorization rules live and | ||
how they're enforced. | ||
|
||
This guide covers common strategies for implementing authorization in GraphQL | ||
servers using GraphQL.js. It assumes you're authenticating requests and passing a user or | ||
session object into the `context`. | ||
|
||
## Before you start | ||
|
||
All code examples in this guide use modern JavaScript with [ES module (ESM) syntax](https://nodejs.org/api/esm.html). | ||
To run them in Node.js, make sure to: | ||
|
||
- Add `"type": "module"` to your `package.json`, or | ||
- Use the `.mjs` file extension for your files | ||
|
||
These features are supported in Node 12+ and fully stable in Node 16 and later. | ||
sarahxsanders marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## What is authorization? | ||
|
||
Authorization determines what a user is allowed to do. It's different from | ||
authentication, which verifies who a user is. | ||
|
||
In GraphQL, authorization typically involves restricting: | ||
|
||
- Access to certain queries or mutations | ||
- Visibility of specific fields | ||
- Ability to perform mutations based on roles or ownership | ||
|
||
## Resolver-based authorization | ||
|
||
The simplest approach is to enforce access rules directly inside resolvers | ||
using the `context.user` value: | ||
|
||
```js | ||
export const resolvers = { | ||
Query: { | ||
secretData: (parent, args, context) => { | ||
if (!context.user || context.user.role !== 'admin') { | ||
throw new Error('Not authorized'); | ||
} | ||
return getSecretData(); | ||
}, | ||
}, | ||
}; | ||
``` | ||
|
||
This works well for smaller schemas or one-off checks. | ||
|
||
## Centralizing access control logic | ||
|
||
As your schema grows, repeating logic like `context.user.role !=='admin'` | ||
becomes error-prone. Instead, extract shared logic into utility functions: | ||
|
||
```js | ||
export function requireUser(user) { | ||
if (!user) { | ||
throw new Error('Not authenticated'); | ||
} | ||
} | ||
|
||
export function requireRole(user, role) { | ||
requireUser(user); | ||
if (user.role !== role) { | ||
throw new Error(`Must be a ${role}`); | ||
} | ||
} | ||
``` | ||
|
||
You can use these helpers in resolvers: | ||
|
||
```js | ||
import { requireRole } from './auth.js'; | ||
|
||
export const resolvers = { | ||
Mutation: { | ||
deleteUser: (parent, args, context) => { | ||
requireRole(context.user, 'admin'); | ||
return deleteUser(args.id); | ||
}, | ||
}, | ||
}; | ||
``` | ||
|
||
This pattern makes your access rules easier to read, test, and update. | ||
|
||
## Field-level access control | ||
|
||
You can also conditionally return or hide data at the field level. This | ||
is useful when, for example, users should only see their own private data: | ||
|
||
```js | ||
export const resolvers = { | ||
User: { | ||
email: (parent, args, context) => { | ||
if (context.user.id !== parent.id && context.user.role !== 'admin') { | ||
return null; | ||
} | ||
return parent.email; | ||
}, | ||
}, | ||
}; | ||
``` | ||
|
||
Returning `null` is a common pattern when fields should be hidden from | ||
unauthorized users without triggering an error. | ||
|
||
## Declarative authorization with directives | ||
|
||
If you prefer a schema-first or declarative style, you can define custom | ||
schema directives like `@auth(role: "admin")`: | ||
|
||
```graphql | ||
type Query { | ||
users: [User] @auth(role: "admin") | ||
sarahxsanders marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
``` | ||
|
||
GraphQL.js doesn't interpret directives by default, they're just annotations. | ||
You must implement their behavior manually, usually by: | ||
|
||
- Wrapping resolvers in custom logic | ||
- Using a schema transformation library to inject authorization checks | ||
|
||
Directive-based authorization can add complexity, so many teams start with | ||
resolver-based checks and adopt directives later if needed. | ||
|
||
## Best practices | ||
|
||
- Keep authorization logic close to business logic. Resolvers are often the | ||
right place to keep authorization logic. | ||
Comment on lines
+151
to
+152
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not feel like the right messaging to me; in general we encourage that authorization logic should be inside the business logic, and that resolvers should not be used for authorization. You should be able to expose the same business logic over GraphQL, REST, RPC, or any other API approach without having to re-implement authorization - GraphQL is just a method of access to these underlying methods. From https://graphql.org/learn/authorization/:
We also highlight this approach in the "thinking in graphs" article: |
||
- Use shared helper functions to reduce duplication and improve clarity. | ||
- Avoid tightly coupling authorization logic to your schema. Make it | ||
reusable where possible. | ||
- Consider using `null` to hide fields from unauthorized users, rather than | ||
throwing errors. | ||
- Be mindful of tools like introspection or GraphQL Playground that can | ||
expose your schema. Use case when deploying introspection in production | ||
sarahxsanders marked this conversation as resolved.
Show resolved
Hide resolved
|
||
environments. | ||
|
||
## Additional resources | ||
|
||
- [Anatomy of a Resolver](./resolver-anatomy): Shows how resolvers work and how the `context` | ||
object is passed in. Helpful if you're new to writing custom resolvers or | ||
want to understand where authorization logic fits. | ||
- [GraphQL Specification, Execution section](https://spec.graphql.org/October2021/#sec-Execution): Defines how fields are | ||
resolved, including field-level error propagation and execution order. Useful | ||
background when building advanced authorization patterns that rely on the | ||
structure of GraphQL execution. | ||
- [`graphql-shield`](https://github.com/dimatill/graphql-shield): A community library for adding rule-based | ||
authorization as middleware to resolvers. | ||
- [`graphql-auth-directives`](https://github.com/the-guild-org/graphql-auth-directives): Adds support for custom directives like | ||
`@auth(role: "admin")`, letting you declare access control rules in SDL. | ||
Helpful if you're building a schema-first API and prefer declarative access | ||
control. | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it right that this is using backslashes?