Role-Based Access Control with Apollo Server 4

Introduction to Role-Based Access Control (RBAC)

In this lesson, we'll delve into Role-Based Access Control (RBAC), a critical concept in securing applications. RBAC helps you manage user permissions based on their roles. This is important for maintaining security and ensuring that users can only access the data and functionalities they are authorized to use.

As a reminder from the previous lesson, we've already set up basic authentication on our GraphQL server using Apollo Server 4. Now, we will build on that foundation to implement more granular access control using roles.

Implementing Role-Based Access Control

To implement RBAC, we need to differentiate between user roles and permissions. For simplicity, we will use two roles: ADMIN and USER. Each role will have different permissions.

Here's an example dataset of users with their respective roles:

UsernamePasswordRole
adminadminADMIN
useruserUSER

Next, let's modify our context function to extract user roles based on a provided token.

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const users = [
  { username: 'admin', password: 'admin', role: 'ADMIN' },
  { username: 'user', password: 'user', role: 'USER' }
];

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    const token = req.headers.authorization || '';
    const username = token.split(' ')[1];
    const user = users.find(user => user.username === username);
    return { user };
  }
});

This code sets up an ApolloServer with user data and parses the authorization header from incoming requests to determine the user's identity and role. The users array defines users with a username, password, and role. The context function extracts the username from the Bearer <username> format token in the authorization header, finds the corresponding user in the users array, and returns the user object to be used in the resolvers for role-based access control.

Securing Mutations with RBAC

To secure mutations, we will use the login mutation to authenticate users and assign roles. We will then secure another mutation, addBook, ensuring only ADMIN users can add books.

import { GraphQLError } from 'graphql';

const typeDefs = `#graphql
  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type User {
    username: String!
    role: String!
  }

  type Query {
    books: [Book]
  }

  type Mutation {
    login(username: String!, password: String!): User
    addBook(title: String!, author: String!): Book
  }
`;

const books = [
  { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien' },
  { id: '2', title: 'Harry Potter', author: 'J.K. Rowling' }
];

const resolvers = {
  Mutation: {
    login: (_: any, { username, password }: { username: string, password: string }, context: any) => {
      const user = context.users.find(user => user.username === username && user.password === password);
      if (!user) {
        throw new GraphQLError('Invalid credentials');
      }
      return { username: user.username, role: user.role };
    },
    addBook: (_: any, { title, author }: { title: string, author: string }, { user }: { user: { role: string } }) => {
      if (user.role !== 'ADMIN') {
        throw new GraphQLError('You do not have permissions to add a book');
      }
      const newBook = { id: '3', title, author };
      books.push(newBook);
      return newBook;
    },
  },
};

In this setup, we check if the user role is ADMIN before allowing them to add a book. If the user does not have the necessary permissions, an AuthenticationError is thrown.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal