How to authenticate using GraphQL Cookies and JWT An authentication process example for a GraphQL API powered by Apollo, using Cookies and JWT
In this tutorial, I’ll explain how to handle a login mechanism for a GraphQL API using Apollo.
We’ll create a private area that will display different information based on the user’s login status.
Here are the steps we’ll follow:
Create a login form on the client
Send the login data to the server
Authenticate the user and send a JWT back
Store the JWT in a cookie
Use the JWT for further requests to the GraphQL API
The code for this tutorial is available on GitHub at https://github.com/flaviocopes/apollo-graphql-client-server-authentication-jwt
Let’s get started.
WARNING! This tutorial is old. Apollo now uses @apollo/xxx rather than apollo-xxx. Do your research until I update it :)
Setting up the Client Application Create the client-side part using create-react-app. Run npx create-react-app client in an empty folder.
Then navigate into the client folder and install the required dependencies:
1 npm install apollo-client apollo-boost apollo-link-http apollo-cache-inmemory react-apollo apollo-link-context @reach/router js-cookie graphql-tag
Create a Form.js file in the src folder and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import React , { useState } from 'react' import { navigate } from '@reach/router' const url = 'http://localhost:3000/login' const Form = ( ) => { const [email, setEmail] = useState ('' ) const [password, setPassword] = useState ('' ) const submitForm = event => { event.preventDefault () const options = { method : 'post' , headers : { 'Content-type' : 'application/x-www-form-urlencoded; charset=UTF-8' }, body : `email=${email} &password=${password} ` } fetch (url, options) .then (response => { if (!response.ok ) { if (response.status === 404 ) { alert ('Email not found. Please retry' ) } if (response.status === 401 ) { alert ('Email and password do not match. Please retry' ) } } return response }) .then (response => response.json ()) .then (data => { if (data.success ) { document .cookie = 'token=' + data.token navigate ('/private-area' ) } }) } return ( <div > <form onSubmit ={submitForm} > <p > Email: <input type ="text" onChange ={event => setEmail(event.target.value)} /></p > <p > Password: <input type ="password" onChange ={event => setPassword(event.target.value)} /></p > <p > <button type ="submit" > Login</button > </p > </form > </div > ) } export default Form
Here, we assume that the server will run on localhost on port 3000. This code handles form submission using fetch API, and if the login is successful, it stores the JWT token in a cookie and navigates to the /private-area URL.
Edit the index.js file in the src folder and add the following code:
1 2 3 4 5 6 7 8 9 10 11 import React from 'react' import ReactDOM from 'react-dom' import { Router } from '@reach/router' import Form from './Form' ReactDOM .render ( <Router > <Form path ="/" /> </Router > , document .getElementById ('root' ) )
Setting up the Server Create a server folder and run npm init -y to create a package.json file.
Install the required dependencies:
1 npm install express apollo-server-express cors bcrypt jsonwebtoken
Create an app.js file and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 const express = require ('express' )const cors = require ('cors' )const bcrypt = require ('bcrypt' )const jwt = require ('jsonwebtoken' )const cookieParser = require ('cookie-parser' )const app = express ()app.use (cors ()) app.use (express.urlencoded ({ extended : true })) app.use (cookieParser ()) const SECRET_KEY = 'secret!' const users = [{ id : 1 , name : 'Test user' , email : '[[email protected]](/cdn-cgi/l/email-protection)' , password : '$2b$10$ahs7h0hNH8ffAVg6PwgovO3AVzn1izNFHn.su9gcJnUWUzb2Rcb2W' }] const todos = [ { id : 1 , user : 1 , name : 'Do something' }, { id : 2 , user : 1 , name : 'Do something else' }, { id : 3 , user : 2 , name : 'Remember the milk' } ] app.post ('/login' , async (req, res) => { const { email, password } = req.body const theUser = users.find (user => user.email === email) if (!theUser) { res.status (404 ).send ({ success : false , message : `Could not find account: ${email} ` , }) return } const match = await bcrypt.compare (password, theUser.password ) if (!match) { res.status (401 ).send ({ success : false , message : 'Incorrect credentials' , }) return } const token = jwt.sign ( { email : theUser.email , id : theUser.id }, SECRET_KEY , ) res.cookie ('token' , token, { httpOnly : true }) res.send ({ success : true }) }) const context = ({ req } ) => { const token = req.cookies ['token' ] || '' try { return { id, email } = jwt.verify (token, SECRET_KEY ) } catch (e) { throw new Error ('Authentication token is invalid, please log in' ) } } const { ApolloServer , gql, } = require ('apollo-server-express' ) const typeDefs = gql` type User { id : ID! email : String! name : String! password : String! } type Todo { id : ID! user : Int! name : String! } type Query { todos : [ Todo] } ` const resolvers = { Query : { todos : () => { return todos.filter (todo => todo.user === id) } } } const server = new ApolloServer ({ typeDefs, resolvers, context })server.applyMiddleware ({ app, cors : false }) app.listen (3000 , () => { console .log ('Server listening on port 3000' ) })
Creating the Private Area Create a PrivateArea.js file in the src folder and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React from 'react' import { gql } from 'apollo-boost' import { Query } from 'react-apollo' import { navigate } from '@reach/router' const PrivateArea = ( ) => { return ( <div > <Query query ={gql ` query { todos { id name } } `} > {({ loading, error, data }) => { if (loading) return <p > Loading...</p > if (error) { navigate('/') return null } return <ul > {data.todos.map(item => <li key ={item.id} > {item.name}</li > )}</ul > }} </Query > </div > ) } export default PrivateArea
This code uses the Query component from Apollo to fetch the todos from the server and display them. If there’s a loading state, it displays a “Loading…” message. If there’s an error, it navigates back to the login form. Otherwise, it renders a list of todos.
Edit the index.js file in the src folder and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 import React from 'react' import ReactDOM from 'react-dom' import { Router } from '@reach/router' import Form from './Form' import PrivateArea from './PrivateArea' ReactDOM .render ( <Router > <Form path ="/" /> <PrivateArea path ="/private-area" /> </Router > , document .getElementById ('root' ) )
Conclusion Now you have a basic understanding of how to authenticate using GraphQL, Cookies, and JWTs in your Apollo-powered application. You can use this knowledge as a starting point to build more complex authentication systems.
Tags: GraphQL, Apollo, Authentication, Cookies, JWT