Pass auth tokens via headers, validate them in context, and restrict resolvers by role.
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import jwt from 'jsonwebtoken';
import User from './models/User.js';
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
app.use('/graphql', expressMiddleware(server, {
context: async ({ req }) => {
const token = req.headers.authorization?.split('Bearer ')[1];
let currentUser = null;
if (token) {
try {
const { id } = jwt.verify(token, process.env.JWT_SECRET);
currentUser = await User.findById(id);
} catch {}
}
return { currentUser };
}
}));const resolvers = {
Query: {
me: (_, __, { currentUser }) => {
if (!currentUser) throw new Error('Not authenticated');
return currentUser;
},
},
Mutation: {
createPost: (_, { input }, { currentUser }) => {
if (!currentUser) throw new Error('Not authenticated');
if (currentUser.role !== 'author') throw new Error('Not authorized');
return createPost({ ...input, authorId: currentUser.id });
}
}
}requireAuth(context) helper function that throws if currentUser is null. Call it at the top of every protected resolver. Consistent error messages make debugging easier.currentUser = null — don't throw.AuthenticationError → 401. ForbiddenError → 403. Use the right error type.