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