Authentication with GraphQL
Authentication is about verifying who a user is. In GraphQL, you typically handle authentication in the context and check it in resolvers.
Setting Up Authentication Context
The most common pattern is to extract the authentication token from the request headers and add the user to the context:
const { ApolloServer } = require('@apollo/server');
async function getContext({ req }) {
// Get the token from headers
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return { user: null };
}
try {
// Verify the token and get the user
const user = await verifyToken(token);
return { user };
} catch (error) {
return { user: null };
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
});
// Pass context to Apollo
const { url } = await startStandaloneServer(server, {
context: getContext,
});
The context function runs for every request and makes the user available to all resolvers.
Checking Authentication in Resolvers
Use the context user to check authentication in resolvers:
const resolvers = {
Query: {
me: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return context.user;
},
// Public query - no auth required
publicPosts: (parent, args, context) => {
return context.db.posts.find({ published: true });
},
},
Mutation: {
createPost: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return context.db.posts.create({
...args.input,
authorId: context.user.id,
});
},
},
};
Always check authentication at the resolver level, not in the schema.
Authorization
Authorization determines what an authenticated user can do. Check roles and permissions in resolvers:
class ForbiddenError extends Error {
constructor(message = 'Not authorized') {
super(message);
this.extensions = { code: 'FORBIDDEN' };
}
}
const resolvers = {
Mutation: {
deleteUser: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
if (context.user.role !== 'ADMIN') {
throw new ForbiddenError('Only admins can delete users');
}
return context.db.users.delete(args.id);
},
updatePost: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const post = context.db.posts.find(args.id);
// Only the author can update their own posts
if (post.authorId !== context.user.id) {
throw new ForbiddenError('You can only update your own posts');
}
return context.db.posts.update(args.id, args.input);
},
},
};
Authorization logic belongs in resolvers, not middleware. This keeps your auth logic close to the data.
Using Directives for Auth
GraphQL directives let you add auth rules directly in your schema:
const typeDefs = `
directive @auth(requires: Role = USER) on FIELD_DEFINITION
enum Role {
ADMIN
USER
GUEST
}
type User {
id: ID!
name: String!
email: String!
password: String! @auth(requires: ADMIN)
}
type Query {
me: User! @auth
users: [User!]! @auth(requires: ADMIN)
}
type Mutation {
createPost(input: CreatePostInput!): Post! @auth
deleteUser(id: ID!): Boolean! @auth(requires: ADMIN)
}
`;
Directives make your schema self-documenting about which fields require authentication.