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.
    JWTs are ideal for stateless APIs because all the information needed to authenticate and authorize a user is contained in the token.
  • 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.