GraphQL Authentication Directives
graphql-directive-auth is a utility library designed to simplify common authentication and authorization tasks in GraphQL APIs by providing schema directives. It offers `@isAuthenticated` and `@hasRole` directives, which can be used to protect fields and types based on JWT tokens and user roles. The current stable version is 0.3.2, indicating it's still in a pre-1.0 development phase, which typically implies an irregular release cadence and potential for API changes. Its key differentiators include a straightforward setup with environment variables for default behavior and highly customizable authentication and role-checking functions for more complex scenarios. It ships with TypeScript types, enhancing developer experience in TypeScript projects. It integrates with `graphql-tools` and expects a `graphql` peer dependency, providing a declarative approach to security within the GraphQL schema itself.
Common errors
-
Error: Not authenticated!
cause The `@isAuthenticated` directive was applied, but no valid JWT token was provided in the `Authorization: Bearer <token>` HTTP header, or the token was invalid/expired.fixEnsure a valid, unexpired JWT token is sent in the `Authorization` header. Verify `APP_SECRET` is correctly set and matches the key used to sign the token. -
Error: Unauthorized! User does not have the required role.
cause The `@hasRole` directive was applied, but the `role` property in the authenticated user's JWT payload does not match the role specified in the directive argument.fixCheck the `role` value in the JWT token (e.g., using jwt.io). Ensure it exactly matches the `role` argument provided to the `@hasRole` directive in your schema. If using a custom `checkRoleFunc`, debug its logic. -
TypeError: Cannot read properties of undefined (reading 'id')
cause A resolver attempted to access `ctx.auth.user.id` or similar, but `ctx.auth` (or its `user` property) was `undefined` because authentication failed or a custom `authenticateFunc` did not return the expected structure.fixAdd null/undefined checks before accessing properties of `ctx.auth` within resolvers (e.g., `if (ctx.auth?.user?.id) { ... }`). Review your custom `authenticateFunc` to ensure it always returns an object with the expected `user` property if authentication is successful.
Warnings
- breaking As of version 0.3.x, the library is still pre-1.0. Future major or minor versions may introduce breaking changes to the API, particularly to the signature of custom authentication or role-checking functions, or the structure of the `AuthDirective()` factory.
- gotcha The default `@isAuthenticated` and `@hasRole` directives rely on the `APP_SECRET` environment variable for JWT verification and expect the JWT token to contain a `role` property for role-based access control. Misconfiguration or missing these elements will lead to authentication failures.
- gotcha The authenticated user object is made available in the GraphQL `context` under `context.auth`. If you provide a custom `authenticateFunc`, ensure it returns an object with a consistent structure, as resolvers will depend on it. For example, if your custom function returns `{ user: { id: '...' } }`, your resolvers should access `ctx.auth.user.id`.
Install
-
npm install graphql-directive-auth -
yarn add graphql-directive-auth -
pnpm add graphql-directive-auth
Imports
- AuthDirective
const AuthDirective = require('graphql-directive-auth');import { AuthDirective } from 'graphql-directive-auth'; - AuthDirective().isAuthenticated
import { AuthDirective } from 'graphql-directive-auth'; // ... auth: AuthDirective().isAuthenticated - AuthDirective().hasRole
import { AuthDirective } from 'graphql-directive-auth'; // ... role: AuthDirective().hasRole
Quickstart
import { makeExecutableSchema } from 'graphql-tools';
import { AuthDirective } from 'graphql-directive-auth';
// Set APP_SECRET for default JWT verification.
// In a real application, use proper environment management (e.g., dotenv, Kubernetes secrets).
process.env.APP_SECRET = process.env.APP_SECRET ?? 'your_super_secret_key_for_dev';
const typeDefs = `
directive @isAuthenticated on FIELD_DEFINITION | OBJECT
directive @hasRole(role: String!) on FIELD_DEFINITION | OBJECT
type User @isAuthenticated {
id: ID!
name: String!
role: String! @hasRole(role: "admin")
}
type Query {
me: User @isAuthenticated
adminPanel: String @hasRole(role: "admin")
publicGreeting: String
}
`;
const resolvers = {
Query: {
me: (root, args, ctx) => {
// ctx.auth will contain the decoded JWT payload or custom auth object
if (!ctx.auth || !ctx.auth.user) throw new Error('Not authenticated');
return { id: ctx.auth.user.id, name: ctx.auth.user.name, role: ctx.auth.user.role };
},
adminPanel: () => 'Welcome to the admin panel!',
publicGreeting: () => 'Hello, world!'
}
};
const schema = makeExecutableSchema({
typeDefs,
resolvers,
schemaDirectives: {
...AuthDirective(), // Registers @isAuthenticated and @hasRole directives
// Optionally, you can rename them:
// auth: AuthDirective().isAuthenticated,
// role: AuthDirective().hasRole
}
});
// To use this schema, you would typically pass it to a GraphQL server (e.g., Apollo Server).
// For brevity, the server setup is omitted here.
// console.log(schema); // You can inspect the generated schema