How to Use Directives for Authorization in GraphQL
In GraphQL, you can use
custom directives to enforce authorization by attaching them to schema fields or types. These directives are processed in the server resolver logic to check user permissions before returning data. This approach centralizes access control and keeps your schema clean.Syntax
A custom authorization directive is defined in the GraphQL schema with @directiveName. It can be applied to fields, queries, or mutations. The server code then interprets this directive to enforce access rules.
Key parts:
directive @auth(role: String) on FIELD_DEFINITION: Defines a directive named@auththat takes aroleargument.- Attach
@auth(role: "ADMIN")to a field to require the ADMIN role. - Resolver logic checks the user's role and allows or denies access.
graphql
directive @auth(role: String) on FIELD_DEFINITION type Query { sensitiveData: String @auth(role: "ADMIN") }
Example
This example shows a simple GraphQL schema with an @auth directive and a resolver that checks user roles. If the user lacks the required role, an error is thrown.
javascript
const { ApolloServer, gql, AuthenticationError } = require('apollo-server'); // Define schema with auth directive const typeDefs = gql` directive @auth(role: String) on FIELD_DEFINITION type Query { publicInfo: String secretInfo: String @auth(role: "ADMIN") } `; // Sample user context const users = { alice: { role: 'ADMIN' }, bob: { role: 'USER' } }; // Resolver with directive logic const resolvers = { Query: { publicInfo: () => 'This is public.', secretInfo: (parent, args, context, info) => { // Since info.schema.getDirective is not a standard method, we simulate the role check here const requiredRole = 'ADMIN'; const userRole = context.user?.role; if (userRole !== requiredRole) { throw new AuthenticationError('Not authorized'); } return 'Top secret data'; } } }; // Apollo Server setup with context const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => { // Simulate user from headers const username = req.headers.user || ''; return { user: users[username] }; } }); server.listen().then(({ url }) => { console.log(`Server ready at ${url}`); });
Output
Server ready at http://localhost:4000/
Common Pitfalls
- Not implementing directive logic in resolvers: Defining a directive in schema alone does not enforce authorization.
- Ignoring context user info: Authorization checks must use user data from context.
- Applying directives inconsistently: Always apply authorization directives to all sensitive fields.
- Throwing generic errors: Use clear errors like
AuthenticationErrorfor unauthorized access.
javascript
/* Wrong: Directive declared but no check in resolver */ const resolversWrong = { Query: { secretInfo: () => 'Top secret data' // No role check } }; /* Right: Check user role in resolver */ const resolversRight = { Query: { secretInfo: (parent, args, context) => { if (context.user?.role !== 'ADMIN') { throw new AuthenticationError('Not authorized'); } return 'Top secret data'; } } };
Quick Reference
| Directive Usage | Description |
|---|---|
| directive @auth(role: String) on FIELD_DEFINITION | Defines an authorization directive with a role argument |
| field: String @auth(role: "ADMIN") | Requires ADMIN role to access this field |
| Check user role in resolver | Use context to verify user permissions before returning data |
| Throw AuthenticationError | Return clear error when user is unauthorized |
Key Takeaways
Use custom directives in your GraphQL schema to mark fields requiring authorization.
Implement directive logic in resolvers to check user roles from the context.
Always throw clear errors like AuthenticationError when access is denied.
Apply authorization directives consistently to all sensitive fields.
Directive definitions alone do not enforce security; resolver checks are essential.