Skip to content

GraphQL - Authenticating Client

Authentication is the process of verifying a user’s identity. Protecting your GraphQL API often involves ensuring that only authenticated (and sometimes authorized) users can perform certain operations or access specific data.

GraphQL itself doesn’t prescribe a specific authentication mechanism. Authentication is typically handled outside of GraphQL, often using standard web techniques like tokens (JWT, OAuth), sessions, or API keys. The authenticated user’s identity is then made available to your GraphQL resolvers via the context object.

A popular method is using JSON Web Tokens (JWT). The flow generally works like this:

  1. Login: The user provides credentials (e.g., email/password) to a traditional REST endpoint (e.g., /login) or a dedicated GraphQL mutation.
  2. Token Issuance: If credentials are valid, the server generates a JWT containing user information (like user ID, roles) and signs it with a secret key.
  3. Token Storage: The server sends the JWT back to the client, which stores it securely (e.g., in localStorage, sessionStorage, or memory).
  4. Authenticated Requests: For subsequent requests to the GraphQL endpoint, the client includes the JWT in an HTTP header (commonly Authorization: Bearer <token>).
  5. Token Verification: A middleware layer on the server intercepts incoming requests, extracts the token from the header, verifies its signature using the secret key, and decodes the user information.
  6. Context Population: The verified user information is added to the GraphQL context object for that request.
  7. Resolver Access: Resolvers can access the user information from the context to perform authentication checks or fetch user-specific data.

Illustration: JWT Auth with Apollo Server & Express

Section titled “Illustration: JWT Auth with Apollo Server & Express”

We’ll build a server with a protected query and a /login endpoint, then show how clients can authenticate.

Create a project (auth-server-app), initialize npm, and install dependencies:

mkdir auth-server-app
cd auth-server-app
npm init -y
npm install @apollo/server graphql express cors body-parser jsonwebtoken
# Optional: npm install --save-dev nodemon

We added jsonwebtoken for creating and verifying JWTs.

Create a simple mock user store (e.g., db.js). In a real app, this would be a database.

// db.js (Mock Data)
const users = [
{
id: 'user-1',
email: 'test@example.com',
password: 'password123', // In real apps, store hashed passwords!
firstName: 'Test',
lastName: 'User'
}
];
export const findUserByEmail = (email) => users.find(user => user.email === email);
export const findUserById = (id) => users.find(user => user.id === id);
// Or use module.exports if not using ES Modules

Step 3: Define Schema (schema.graphql or in server.js)

Section titled “Step 3: Define Schema (schema.graphql or in server.js)”

Define a query that should require authentication:

#graphql
type User {
id: ID!
email: String!
firstName: String
}
type Query {
# This query requires authentication
me: User
# Public query (optional)
publicGreeting: String
}

The me resolver will check the context for an authenticated user.

// resolvers.js
import { GraphQLError } from 'graphql';
import { findUserById } from './db.js'; // Assuming db.js uses export
const resolvers = {
Query: {
// Resolver for the protected 'me' query
me: (parent, args, context, info) => {
// Check if user information exists in the context
if (!context.user) {
// Throw an authentication error if not logged in
throw new GraphQLError('User is not authenticated', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
// Return the user data from context
return context.user;
},
publicGreeting: () => 'Hello from the public API!',
},
};
export default resolvers; // Or module.exports

Step 5: Set up Server, Middleware & Login Route (server.js)

Section titled “Step 5: Set up Server, Middleware & Login Route (server.js)”

Configure Apollo Server, add middleware to verify JWTs and populate context, and create the /login endpoint.

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import express from 'express';
import http from 'http';
import cors from 'cors';
import bodyParser from 'body-parser';
import jwt from 'jsonwebtoken';
import { readFileSync } from 'fs';
// Mock DB functions (assuming db.js uses export)
import { findUserByEmail, findUserById } from './db.js';
// import resolvers from './resolvers.js'; // If using export default
const resolvers = require('./resolvers'); // If using module.exports
const typeDefs = readFileSync('./schema.graphql', { encoding: 'utf-8' });
// Secret key for signing JWTs (store securely in env variables in real apps!)
const JWT_SECRET = 'your-very-secret-key-change-me';
const app = express();
const httpServer = http.createServer(app);
// Middleware to parse JSON and enable CORS
app.use(cors());
app.use(bodyParser.json());
// --- Authentication Middleware ---
// This middleware runs before the GraphQL middleware
app.use((req, res, next) => {
const authHeader = req.headers.authorization || '';
const token = authHeader.startsWith('Bearer ') ? authHeader.substring(7) : null;
if (token) {
try {
// Verify the token
const decoded = jwt.verify(token, JWT_SECRET);
// Add decoded user ID to the request object (to be used in context)
req.userId = decoded.sub; // 'sub' is standard JWT claim for subject (user ID)
} catch (err) {
console.warn('Invalid token:', err.message);
// Don't throw error here, let GraphQL resolver handle unauthenticated state
}
}
next(); // Proceed to the next middleware (GraphQL)
});
// --- End Authentication Middleware ---
// Apollo Server setup
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
// Apply GraphQL middleware, passing authenticated user to context
app.use(
'/graphql',
expressMiddleware(server, {
context: async ({ req }) => {
// If middleware added userId, fetch user details
const userId = req.userId;
if (userId) {
// Fetch user from DB based on ID from token
const user = findUserById(userId);
return { user }; // Add user object to context
}
return {}; // Return empty context if no user ID
},
}),
);
// --- Login Endpoint ---
app.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).send({ error: 'Email and password required' });
}
const user = findUserByEmail(email);
// IMPORTANT: In real apps, compare hashed passwords!
if (!user || user.password !== password) {
return res.status(401).send({ error: 'Invalid credentials' });
}
// Credentials valid: Generate JWT
const token = jwt.sign(
{ sub: user.id, email: user.email }, // Payload: standard 'sub' claim for user ID
JWT_SECRET,
{ expiresIn: '1h' } // Token expires in 1 hour
);
// Send token back to client
res.send({ token });
});
// --- End Login Endpoint ---
const PORT = 9000;
await new Promise((resolve) => httpServer.listen({ port: PORT }, resolve));
console.log(`🚀 Server ready at http://localhost:${PORT}`);
console.log(`🔓 Login endpoint at POST http://localhost:${PORT}/login`);
console.log(`🧠 GraphQL endpoint at http://localhost:${PORT}/graphql`);

Key points:

  • A JWT_SECRET is defined (use environment variables in production!).
  • An Express middleware extracts the Authorization: Bearer <token> header.
  • It verifies the token using jwt.verify and attaches the userId from the token payload (sub claim) to the req object.
  • The expressMiddleware’s context function checks for req.userId. If present, it fetches the full user details (mocked here) and adds the user object to the GraphQL context.
  • The /login endpoint validates credentials (using plaintext password here - NEVER DO THIS IN PRODUCTION, use hashing!) and issues a signed JWT using jwt.sign containing the user ID (sub).

Add start scripts to package.json and run npm run dev or npm start.

  1. Try Protected Query without Token: Open Apollo Sandbox (http://localhost:9000/graphql) and run query { me { id email } }. You should get an “User is not authenticated” error.

  2. Login: Use a tool like curl, Postman, or Insomnia to send a POST request to http://localhost:9000/login with a JSON body:

{
"email": "test@example.com",
"password": "password123"
}

You should receive a response like {"token": "eyJhbGciOi..."}. Copy the token value.

  1. Try Protected Query with Token: Go back to Apollo Sandbox. Find the ‘Headers’ tab or section. Add a new header:
  • Header Name: Authorization
  • Header Value: Bearer <paste_your_token_here> (Replace <paste_your_token_here> with the actual token)

Now, re-run the query { me { id email } }. This time, it should succeed and return the user’s data.

In a simple HTML/JS client (like the one in the jQuery Integration chapter):

  • Login: Add form fields for email/password. On submit, use fetch to POST credentials to the /login endpoint.
  • Store Token: On successful login, store the received token (e.g., in localStorage or a variable).
  • Send Token: When making GraphQL requests using fetch to /graphql, include the Authorization: Bearer <token> header in the headers option.
// Example: Fetching protected data after login
const graphqlQuery = { query: '{ me { id firstName } }' };
const storedToken = localStorage.getItem('authToken'); // Get token from storage
fetch('http://localhost:9000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Add the Authorization header if token exists
...(storedToken && { 'Authorization': `Bearer ${storedToken}` })
},
body: JSON.stringify(graphqlQuery)
})
.then(response => response.json())
.then(data => {
if (data.errors) {
// Handle GraphQL errors (e.g., UNAUTHENTICATED)
console.error('GraphQL Error:', data.errors);
if (data.errors.some(err => err.extensions?.code === 'UNAUTHENTICATED')){
alert('Please log in again.');
// Redirect to login or clear token
localStorage.removeItem('authToken');
}
} else {
// Process successful data
console.log('User data:', data.data.me);
// Update UI
}
})
.catch(error => {
// Handle network errors
console.error('Network Error:', error);
});

Apollo Client provides a more robust way using Apollo Link middleware.

a) Install Link library: npm install @apollo/client @apollo/link-context graphql (if not already installed)

b) Configure Apollo Client with Auth Link:

// In your Apollo Client setup file (e.g., main.jsx or a dedicated apollo.js)
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
createHttpLink // Use createHttpLink instead of just uri
} from '@apollo/client';
import { setContext } from '@apollo/link-context'; // Import setContext
// Standard HTTP link
const httpLink = createHttpLink({
uri: 'http://localhost:9000/graphql',
});
// Middleware link to set the Authorization header
const authLink = setContext((_, { headers }) => {
// Get the authentication token from local storage if it exists
const token = localStorage.getItem('authToken');
// Return the headers to the context so httpLink can read them
return {
headers: {
...headers, // Spread existing headers
authorization: token ? `Bearer ${token}` : "", // Add Authorization header
}
}
});
// Create the Apollo Client instance using the link chain
const client = new ApolloClient({
link: authLink.concat(httpLink), // Chain authLink and httpLink
cache: new InMemoryCache(),
});
// ... rest of your setup (e.g., wrap App in ApolloProvider)

c) Login/Logout Logic:

  • Create a login component that uses fetch (or a dedicated useMutation hook if you create a login mutation) to call the /login endpoint.
  • On successful login, store the token in localStorage.setItem('authToken', receivedToken);.
  • Use client.resetStore() or client.refetchQueries() after login/logout to clear the cache and refetch data with the new auth status.
  • On logout, remove the token localStorage.removeItem('authToken'); and reset the store.

With the authLink configured, Apollo Client will automatically include the token in the headers for all subsequent GraphQL operations.

  • HTTPS: Always use HTTPS in production.
  • Secret Keys: Store JWT secrets securely (environment variables, secrets management).
  • Password Hashing: NEVER store plaintext passwords. Use strong hashing algorithms (e.g., bcrypt).
  • Token Expiration: Set reasonable expiration times for JWTs.
  • Token Storage: Be mindful of where you store tokens on the client (localStorage is common but susceptible to XSS; HttpOnly cookies are generally more secure for web apps if feasible).
  • Authorization: Authentication verifies who the user is; authorization determines what they are allowed to do. Implement authorization checks within resolvers based on user roles or permissions (often stored in the token or fetched based on user ID).