Authentication in GraphQL
In any modern application, authentication andauthorization are fundamental pillars for protecting data and ensuring that only the correct users have access to the appropriate resources. In the context of GraphQL, where all requests often go to a single endpoint, handling authentication and authorization can require a slightly different approach than traditional REST APIs. Here, GraphQL context and JSON Web Tokens (JWT) play a crucial role.
Key Concepts
- Authentication: This is the process of verifying a user's identity. That is, who are you? This is typically achieved with credentials like username/password or session tokens.
- Authorization: This is the process of determining what actions or resources an authenticated user can access. That is, what can you do?
- JSON Web Tokens (JWT): These are a standard, secure method for representing claims between two parties. They are compact and URL-safe. A JWT consists of three parts:
- Header: Token type (JWT) and signing algorithm (e.g., HS256).
- Payload: Contains "claims" about the entity (usually the user) and additional metadata.
- Signature: Created with the header, payload, and a secret server key, ensuring that the token has not been tampered with.
- GraphQL Context: In Apollo Server, the
context
object is one of the most powerful tools for handling authentication and authorization. It is an object that is built for eachGraphQL request and passed to all resolvers that execute in that request. It is the perfect place to store information such as the authenticated user.
Implementing Authentication with JWT in Apollo Server
The common strategy is for the client to send a JWT in the `Authorization` header (e.g., `Bearer <token>`). The server intercepts this request, verifies the token, and if valid, attaches the user information to the `context` object, making it available to all resolvers.
Step 1: Install necessary dependencies
npm install jsonwebtoken bcryptjs // bcryptjs for hashing passwords
Step 2: Configure your ApolloServer
to process the JWT token
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const express = require('express');
const http = require('http');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
// Secret key to sign and verify JWT (use an environment variable in production!)
const JWT_SECRET = 'your_super_secret_jwt_key';
// Example user data (simulating a database)
const users = [
{ id: '1', username: 'user1', password: 'password123', role: 'admin' },
{ id: '2', username: 'user2', password: 'password123', role: 'viewer' },
];
// --- GraphQL Schema ---
const typeDefs = `
type User {
id: ID!
username: String!
role: String!
}
type AuthPayload {
token: String!
user: User!
}
type Query {
me: User # Get the authenticated user
allUsers: [User!]! # Only accessible by admin
}
type Mutation {
login(username: String!, password: String!): AuthPayload
}
`;
// --- Resolvers ---
const resolvers = {
Query: {
me: (parent, args, context) => {
// If the user is in the context, it means they are authenticated
if (!context.user) {
throw new Error('Not authenticated. Please log in.');
}
return context.user;
},
allUsers: (parent, args, context) => {
// Validate authentication and authorization
if (!context.user) {
throw new Error('Not authenticated.');
}
if (context.user.role !== 'admin') {
throw new Error('Not authorized. Administrator role required.');
}
return users; // In a real case, this would come from the database
},
},
Mutation: {
login: async (parent, { username, password }) => {
const user = users.find(u => u.username === username);
if (!user) {
throw new Error('Invalid credentials');
}
// In a real application, you would compare the password hash
// const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
const isPasswordValid = (password === user.password); // For this simplified example
if (!isPasswordValid) {
throw new Error('Invalid credentials');
}
// Generate JWT
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET, { expiresIn: '1h' });
return { token, user };
},
},
};
// --- Configure and Start Apollo Server ---
async function startApolloServer() {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
app.use(
'/graphql',
cors(),
express.json(),
expressMiddleware(server, {
context: async ({ req }) => {
// Get the token from the Authorization header
const token = req.headers.authorization || '';
let user = null;
if (token) {
try {
// Verify and decode the token
const parsedToken = token.replace('Bearer ', '');
const decoded = jwt.verify(parsedToken, JWT_SECRET);
// Find the user in your "database"
user = users.find(u => u.id === decoded.userId);
// console.log('Authenticated user:', user);
} catch (e) {
// console.error('Error verifying token:', e.message);
// If the token is invalid, the user will still be null
}
}
// The 'user' object (or null) is passed to all resolvers
return { user };
},
}),
);
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 GraphQL Server ready at http://localhost:4000/graphql`);
}
startApolloServer();
Step 3: Run the application and test
node index.js
Open http://localhost:4000/graphql
in your browser to use Apollo Sandbox.
Examples of Queries/Mutations:
# 1. Try to access 'me' without authenticating (should fail)
query {
me {
username
role
}
}
# 2. Log in
mutation {
login(username: "user1", password: "password123") {
token
user {
id
username
role
}
}
}
# 3. Copy the returned TOKEN and use it in the "Headers" tab in Apollo Sandbox:
# {"Authorization": "Bearer <YOUR_TOKEN_HERE>"}
# 4. Now, with the token, try to access 'me' (should work)
query {
me {
username
role
}
}
# 5. With 'user1's token (admin), try to access 'allUsers' (should work)
query {
allUsers {
id
username
role
}
}
# 6. Log in with 'user2' (viewer) to get their token.
# Then, with 'user2's token, try to access 'allUsers' (should fail due to authorization)
As you can see, the context object in Apollo Server is fundamental for injecting authenticated user information into your resolvers. This allows resolvers to implementauthorization logic, deciding whether a user has permission to access or modify certain data based on their role or privileges. This architecture is scalable and secure for most GraphQL authentication needs.