Lesson 1
Adding Authentication to Your GraphQL Server
Introduction to Authentication in GraphQL

In this lesson, we're diving into how to add authentication to your GraphQL server. Authentication is crucial for securing your API and ensuring that only authorized users can access specific resources. We'll be using Apollo Server, a popular GraphQL server, to implement this. While Apollo Server is widely used for its simplicity and active support, other alternatives include Express-GraphQL and Relay.

Our goal for this lesson is to:

  • Set up an Apollo Server with basic authentication.
  • Implement a login system.
  • Secure certain GraphQL mutations.
Setting Up the Apollo Server

First, let's quickly revise how we set up the GraphQL types and mock data. Our GraphQL server will have two mutations - one for logging in using the provided username and password and another for adding a new book given its author and title.

TypeScript
1import { ApolloServer, gql, AuthenticationError } from 'apollo-server'; 2 3const typeDefs = gql` 4 type Book { 5 id: ID! 6 title: String! 7 author: String! 8 } 9 10 type Query { 11 books: [Book] 12 } 13 14 type Mutation { 15 login(username: String!, password: String!): String 16 addBook(title: String!, author: String!): Book 17 } 18`; 19 20const users = [{ username: 'admin', password: 'admin' }]; 21const books = [ 22 { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien' }, 23 { id: '2', title: 'Harry Potter', author: 'J.K. Rowling' } 24];
Implementing Authentication Logic

Now let's implement the authentication logic to secure our GraphQL API.

For this example, we use a simple array to mock a user database.

TypeScript
1const users = [{ username: 'admin', password: 'admin' }];

The login mutation takes a username and password and returns an authentication token if valid.

TypeScript
1login: (_: any, { username, password }: { username: string, password: string }) => { 2 const user = users.find(user => user.username === username && user.password === password); 3 if (!user) { 4 throw new AuthenticationError('Invalid credentials'); 5 } 6 return 'token'; 7}

Here, we check if the provided credentials match any user in our mock database. If they do, we return a token; otherwise, we throw an AuthenticationError.

Then, we secure the addBook mutation by checking if the provided token is valid.

TypeScript
1addBook: (_: any, { title, author }: { title: string, author: string }, { token }: { token: string }) => { 2 if (token !== 'Bearer ' + 'token') { 3 throw new AuthenticationError('You must be logged in'); 4 } 5 const newBook = { id: '3', title, author }; 6 books.push(newBook); 7 return newBook; 8}

This mutation checks if the provided token matches 'Bearer token'. If not, it throws an AuthenticationError.

Setting up the server

Finally, we start a GraphQL server, providing a proper authorization context on startup:

TypeScript
1const server = new ApolloServer({ 2 typeDefs, 3 resolvers, 4 context: ({ req }) => { 5 const token = req.headers.authorization || ''; 6 return { token }; 7 } 8}); 9 10server.listen().then(({ url }) => { 11 console.log(`🚀 Server ready at ${url}`); 12});
Testing the Implementation: Login

Let's test our implementation by doing some queries to the server we've just set up. First, we call the login mutation to authorize our user.

TypeScript
1import fetch from 'node-fetch'; 2 3const url = 'http://localhost:4000/graphql'; 4 5async function login(username: string, password: string) { 6 const query = ` 7 mutation { 8 login(username: "${username}", password: "${password}") 9 } 10 `; 11 12 const response = await fetch(url, { 13 method: 'POST', 14 headers: { 15 'Content-Type': 'application/json', 16 }, 17 body: JSON.stringify({ query }), 18 }); 19 20 const data = await response.json() as { data: { login: string } }; 21 return data.data.login; 22} 23 24(async () => { 25 const token = await login('admin', 'admin'); 26 console.log('Token:', token); 27});

This function sends a login request and retrieves the token.

Testing the Implementation: Query Books

After we have authorized, let's query our books from the server:

TypeScript
1async function queryBooks(token: string) { 2 const query = ` 3 query { 4 books { 5 id 6 title 7 author 8 } 9 } 10 `; 11 12 const response = await fetch(url, { 13 method: 'POST', 14 headers: { 15 'Content-Type': 'application/json', 16 'Authorization': `Bearer ${token}`, 17 }, 18 body: JSON.stringify({ query }), 19 }); 20 21 const data = await response.json() as { data: { books: Array<{ id: string, title: string, author: string }> } }; 22 return data; 23} 24 25(async () => { 26 const token = await login('admin', 'admin'); 27 console.log('Token:', token); 28 29 const booksData = await queryBooks(token); 30 console.log('Books:', JSON.stringify(booksData, null, 2)); 31})();

This function queries the books with the provided token.

Testing the Implementation: Adding a New Book

Finally, let's try to add a new book to the server.

TypeScript
1async function addBook(token: string, title: string, author: string) { 2 const mutation = ` 3 mutation { 4 addBook(title: "${title}", author: "${author}") { 5 id 6 title 7 author 8 } 9 } 10 `; 11 12 const response = await fetch(url, { 13 method: 'POST', 14 headers: { 15 'Content-Type': 'application/json', 16 'Authorization': `Bearer ${token}`, 17 }, 18 body: JSON.stringify({ query: mutation }), 19 }); 20 21 const data = await response.json() as { data: { addBook: { id: string, title: string, author: string } } }; 22 return data; 23} 24 25(async () => { 26 const token = await login('admin', 'admin'); 27 console.log('Token:', token); 28 29 const booksData = await queryBooks(token); 30 console.log('Books:', JSON.stringify(booksData, null, 2)); 31 32 const newBook = await addBook(token, '1984', 'George Orwell'); 33 console.log('New Book:', JSON.stringify(newBook, null, 2)); 34})();

This code logs in to get a token, queries the list of books, and attempts to add a new book.

Expected output:

JSON
1Token: token 2Books: { 3 "data": { 4 "books": [ 5 { "id": "1", "title": "The Hobbit", "author": "J.R.R. Tolkien" }, 6 { "id": "2", "title": "Harry Potter", "author": "J.K. Rowling" } 7 ] 8 } 9} 10New Book: { 11 "data": { 12 "addBook": { 13 "id": "3", 14 "title": "1984", 15 "author": "George Orwell" 16 } 17 } 18}
Lesson Summary

In this lesson, you learned how to add authentication to your GraphQL server using Apollo Server. We:

  • Set up the Apollo Server with basic authentication.
  • Implemented a login system to authenticate users.
  • Secured the addBook mutation to ensure only authenticated users can add books.

Next, you'll get hands-on practice with adding more secure queries and mutations. Great job on completing this lesson! Keep up the good work as you continue your journey in securing and optimizing GraphQL APIs.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.