{"id":2353,"date":"2022-06-05T21:30:46","date_gmt":"2022-06-05T21:30:46","guid":{"rendered":"https:\/\/codevoweb.com\/?p=2353"},"modified":"2023-05-06T09:34:06","modified_gmt":"2023-05-06T09:34:06","slug":"graphql-api-with-node-mongodb-jwt-authentication","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/graphql-api-with-node-mongodb-jwt-authentication\/","title":{"rendered":"GraphQL API with Node.js &#038; MongoDB: JWT Authentication"},"content":{"rendered":"\n<p>This article will teach you how to implement JWT Authentication in a GraphQL API using Node.js, MongoDB, Redis, and Apollo Server.<\/p>\n\n\n\n<p>In addition, you&#8217;ll learn how to send the access and refresh tokens as HTTPOnly cookies to the user&#8217;s browser or client.<\/p>\n\n\n\n<p>Related Articles:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/typegraphql-mongodb-graphql-api-jwt-authentication\">TypeGraphQL &amp; MongoDB GraphQL API: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/graphql-api-next-mongodb-access-and-refresh-tokens\">GraphQL API with Next.js &amp; MongoDB: Access &amp; Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/golang-grpc-server-and-client-access-refresh-tokens\">Build Golang gRPC Server and Client: Access &amp; Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/node-prisma-postgresql-access-refresh-tokens\">Node.js + Prisma + PostgreSQL: Access &amp; Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/golang-mongodb-jwt-authentication-authorization\">Golang &amp; MongoDB: JWT Authentication and Authorization<\/a><\/li>\n\n\n\n<li><a href=\"\/api-node-postgresql-typeorm-jwt-authentication\">API with Node.js + PostgreSQL + TypeORM: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/node-typescript-mongodb-jwt-authentication\">Node.js + TypeScript + MongoDB: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/react-node-access-refresh-tokens-authentication\">Node.js + TypeScript + MongoDB: JWT Refresh Token<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"478\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/GraphQL-API-with-Node.js-MongoDB-JWT-Authentication.webp\" alt=\"GraphQL API with Node.js &amp; MongoDB JWT Authentication\" class=\"wp-image-2433\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/GraphQL-API-with-Node.js-MongoDB-JWT-Authentication.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/GraphQL-API-with-Node.js-MongoDB-JWT-Authentication-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/GraphQL-API-with-Node.js-MongoDB-JWT-Authentication-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/GraphQL-API-with-Node.js-MongoDB-JWT-Authentication-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/GraphQL-API-with-Node.js-MongoDB-JWT-Authentication-700x394.webp 700w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id_755908-3f .kb-table-of-content-wrap{padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id_755908-3f .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id_755908-3f .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_755908-3f .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_755908-3f .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:10px;margin-right:0px;margin-bottom:0px;margin-left:-5px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id_755908-3f .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id_755908-3f .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Project Initialization<\/h2>\n\n\n\n<p>To initialize a new Node.js project, run the following commands in your terminal:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nmkdir node_graphql_api #create project folder\ncd node_graphql_api\nyarn init -y #initialize a node project\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Node.js GraphQL API Overview<\/h2>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<h2 class=\"wp-block-heading\">Setup MongoDB and Redis with Docker-compose<\/h2>\n\n\n\n<p>The most straightforward way to get MongoDB and Redis servers running on your machine is to use Docker and Docker-compose.<\/p>\n\n\n\n<p>I assume you already have <a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker<\/a> and <a href=\"https:\/\/docs.docker.com\/compose\/install\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker-compose<\/a> installed on your computer.<\/p>\n\n\n\n<p>In the root directory, create a <code>docker-compose.yml<\/code> file and add the following code snippets to set up and run the MongoDB and Redis containers on your machine.<\/p>\n\n\n\n<p><strong>docker-compose.yml<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nversion: '3.8'\nservices:\n  mongo:\n    image: mongo\n    container_name: mongodb\n    ports:\n      - '6000:27017'\n    volumes:\n      - mongodb:\/data\/db\n    env_file:\n      - .\/.env\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}\n      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}\n      MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}\n  redis:\n    image: redis:latest\n    container_name: redis\n    ports:\n      - '6379:6379'\n    volumes:\n      - redis:\/data\nvolumes:\n  redis:\n  mongodb:\n<\/code>\n<\/pre>\n\n\n\n<p>Later, we&#8217;ll use the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=cweijan.vscode-mysql-client2\" target=\"_blank\" rel=\"noreferrer noopener\">MySQL VS Code extension<\/a> to view the data stored in either the MongoDB or Redis databases.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"964\" height=\"321\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/vs-code-mysql-extension.png\" alt=\"vs code mysql extension\" class=\"wp-image-1523\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/vs-code-mysql-extension.png 964w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/vs-code-mysql-extension-300x100.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/vs-code-mysql-extension-768x256.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/vs-code-mysql-extension-100x33.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/vs-code-mysql-extension-700x233.png 700w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/vs-code-mysql-extension-600x200.png 600w\" sizes=\"auto, (max-width: 964px) 100vw, 964px\" \/><figcaption class=\"wp-element-caption\">MySQL VS Code Extension<\/figcaption><\/figure>\n\n\n\n<p>To provide the credentials needed by the MongoDB Docker image, we&#8217;ll need to set up environment variables.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setup Environment Variables<\/h2>\n\n\n\n<p>The most crucial part of the application is setting up environment variables. Security is always important so we need a way to store sensitive data such as API Keys, passwords, etc.<\/p>\n\n\n\n<p>In this application, we&#8217;ll store the sensitive information in a <code>.env<\/code> file and load them using the <code><a href=\"https:\/\/www.npmjs.com\/package\/dotenv\" target=\"_blank\" rel=\"noreferrer noopener\">dotenv<\/a><\/code> package.<\/p>\n\n\n\n<p>Also, we&#8217;ll use the <code><a href=\"https:\/\/www.npmjs.com\/package\/config\" target=\"_blank\" rel=\"noreferrer noopener\">config<\/a><\/code> library to set and retrieve the environment variables. <\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nyarn add config dotenv\n<\/code>\n<\/pre>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nNODE_ENV=development\nPORT=8000\n\nMONGODB_URI_LOCAL=mongodb:\/\/admin:password123@localhost:6000\/node_graphql?authSource=admin\n\nMONGO_INITDB_ROOT_USERNAME=admin\nMONGO_INITDB_ROOT_PASSWORD=password123\nMONGO_INITDB_DATABASE=node_graphql\n\nJWT_ACCESS_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDV3dJQkFBS0JnR2hDMjI2ZEtBeW5NL0tpV3dKVFcySjRnLzl5QTdNWGtHOGppd1p1WGhyeWMzMURxeEdPClZhL0dIa1dxV1lnTkR5WG1lQ1RzVmsvQWdsS3FrOEJHQU1tYUpYSGM4YjVmd0QrRWFXRTVOUUFEMHQyM0VsZ1MKQkovK3ZkK1FwZTFtbjcrT1dZdmt2ODI3VlBDRXZHT1hKYVpLU0VMVDMyaHdialpUTUdTKzBEK0RBZ01CQUFFQwpnWUE4VDJCTGJoRTZzSVcyTndCYUtnblV2azNzdC9FMzZjdWMzbnlQTGN5MTNjVzhraVlrczZjUlZKTXlUVVlaCkV5VE9FYkV4K3B3NjZlcjVFcjhCRy8xYjVBUDYwL2dOZXlPWXE5d3huSWpLRmFzbFE0dDNsa29EdUNMRS9qT1QKcWw4NmpNbis3VG1CRHI2L1V1WmZmM0t1UXB5K2tPN3pSak9wUzh0bU1NUkNRUUpCQUtkcGVTSHJCUFVaWUFkWgpZei85M2Q3NU5DRHhVeGt2YTFmVUdTaFZEd3NDTEhRaVFDZXluZUc0bEpIUjNCTVRiMXlUbFhoVTUxaTR3WGNBCnhDN1JQVEVDUVFDZmJxSG5LQ2VQa2E4VENYL2FSV3NPMTFqQUZnOEZtUys0cnUvTHVpL01JNFNFSXlXMk52aTMKM0lZdStsZUJJNk9Md1gvbVVLaDNwWlBEeHM5TjgwcnpBa0JTYXRKL3FEd2dqZ1dBbUxrTDMrZEN4bHlyZXlMMQoyeXAxYXEyWDlZZ2FXMCtYUE9Wb3BiNmtTVUtiSnoyNUJuQmtteU9Td2ZuQzYvSVNyQVhwSm9tQkFrRUFuTVZSCnlKWndpRGt0MlY5ZTdBZVVwU3hXSmRQTEE2dEZCS3BvTzRFaVhPNlQ4TWNLM3lrZzJ0S3EwMmp4UUpnRnluZ0UKUnpvSzNsUGZnQVJ2ZG13RXh3SkFBVnNPblZNUk8vUkFac0w3VS9MYlB6NmVDQVdzdkN6azU1QWx4bU4zTndoOApsbEt2aElaeG9uc3dGTnB5U2d4ZmExOTVWdlZFemMxMG9KQlhLaUl6M0E9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==\nJWT_ACCESS_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZU1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTUFEQ0JpQUtCZ0doQzIyNmRLQXluTS9LaVd3SlRXMko0Zy85eQpBN01Ya0c4aml3WnVYaHJ5YzMxRHF4R09WYS9HSGtXcVdZZ05EeVhtZUNUc1ZrL0FnbEtxazhCR0FNbWFKWEhjCjhiNWZ3RCtFYVdFNU5RQUQwdDIzRWxnU0JKLyt2ZCtRcGUxbW43K09XWXZrdjgyN1ZQQ0V2R09YSmFaS1NFTFQKMzJod2JqWlRNR1MrMEQrREFnTUJBQUU9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==\n\nJWT_REFRESH_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWFFJQkFBS0JnUUN6MGhITG9zZXdQcUJTemt6MzdlWHhUb2FPMHB4aDlRb085ME1udU5YNnJwQndNYW9KCng1M1pvYmZiWkFRNktWaXZ3eFd5dkU1ZVdGTlFjRVhkRzlFMlpmQmZ5TTJ3UW9lY1BDVUViaWxjZEFEbGZuR0cKWUdaSTQ2MzBxNGUremRSUHpQdHNNSGpZNk1JSDErSUgxWDlkR3pOVUdXSTllOVRyNmRXRWk2azRZUUlEQVFBQgpBb0dCQUptSGkwQVZURzl6R1FsNk1JY2liRWdhSnBLOHlWNXRpOWY1VHpJVUEwWlVVdWduenlrb2NPS0d5K2FhClAyMjFMWTVyZnhRcTZMSnVzVHVsa0ZEWFZraXF1akVFRHk0cDR6eGJaZFVjc1hZb2dxUHk0T3M0RE43RGt5emYKVGdHZFB3Wm9OeXZlT0lpbzhBV0g5QTJ2aEhkeU5KZ0VIdi8rUHhUbnF2N1dWRXhOQWtFQS83WHB0SUlTNktOcQpUd0NGM1o0bU1IamdwcUZWZHZpM3k0TjVkN3VtTnlqaXVOTlcwd0hGN1VTeFVrV2I0Y2ZRdHZIZ3hMaEhianFtCm00NElqZHMvMXdKQkFMUUdLMEhCM2NxSzJNQUVkalZFa0ZveFYybUNFTzFxeGJFOWRobFJMVlMzY1NCcEgyMjAKZFhrY0ZsL2VTVnRtNG9oLzNQTEZNMmQzYStTek1zUHdJb2NDUVFEVjlpd1FHd3FoV0NOYTZYQVppUHdoY1BOZwoyZnYrS1l6NG9CRWlLNFNnQVBqOGQvSGRhMDFuQkNSdlY4bGdPV2FkdlhRNmhvdFdZNE1IQStpS2NodFBBa0IvCm9Td0R4NjAxam5DUzJkYndkdmFjYXdUYzhYQ00wYmpzcW5WVEI2Rkt3Vzg3bWl2RS9ENllxVmdYaWFHYVludlMKYUV3OTlaODNDSXgrcktrZUR0NTdBa0JGN3hGT0RlVHo1dS9yNE91cHFLeVNJY0gvZWUyckcydkRsVUNZYm1mVgpDeTZXL3lOWDBRTWJYa3kyS3M4d3hZRUlERGlZU1JERk4zaEtPcDlheURiQgotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==\nJWT_REFRESH_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDejBoSExvc2V3UHFCU3prejM3ZVh4VG9hTwowcHhoOVFvTzkwTW51Tlg2cnBCd01hb0p4NTNab2JmYlpBUTZLVml2d3hXeXZFNWVXRk5RY0VYZEc5RTJaZkJmCnlNMndRb2VjUENVRWJpbGNkQURsZm5HR1lHWkk0NjMwcTRlK3pkUlB6UHRzTUhqWTZNSUgxK0lIMVg5ZEd6TlUKR1dJOWU5VHI2ZFdFaTZrNFlRSURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==\n\n<\/code>\n<\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>It is a convention to use uppercase for your environment variables.<\/p>\n<\/blockquote>\n\n\n\n<p>You can add the <code>.env<\/code> file to your <code>.gitignore<\/code> to exclude it from your Git commits.<\/p>\n\n\n\n<p>Next, create a <code>config\/default.json<\/code> file in the root directory and add the following code.<\/p>\n\n\n\n<p><strong>config\/default.json<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-json\"><code>\n{\n  \"port\": 8000,\n  \"nodeEnv\": \"development\",\n  \"origin\": \"http:\/\/localhost:3000\",\n  \"jwtAccessTokenExpiresIn\": 15,\n  \"jwtRefreshTokenExpiresIn\": 60\n}\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Validating the Environment Variables<\/h3>\n\n\n\n<p>Sometimes forgetting to add an environment variable to the <code>.env<\/code> file can lead to unexpected bugs in your application causing it to malfunction. <\/p>\n\n\n\n<p>To prevent this, we&#8217;ll use the <code><a href=\"https:\/\/www.npmjs.com\/package\/envalid\" target=\"_blank\" rel=\"noreferrer noopener\">envalid<\/a><\/code> package to validate the environment variables.<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nyarn add envalid\n<\/code>\n<\/pre>\n\n\n\n<p>The package will throw an error if a defined variable is not found in the <code>.env<\/code> file or if it has the wrong type.<\/p>\n\n\n\n<p>In the root directory, create an <code>src<\/code> folder and create a <code>utils<\/code> folder in it.<\/p>\n\n\n\n<p>Next, create a <code>validateEnv.js<\/code> file within the utils folder and add the code snippets below.<\/p>\n\n\n\n<p><strong>src\/utils\/validateEnv.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { cleanEnv, port, str } from 'envalid';\n\nconst validateEnv = () =&gt; {\n  cleanEnv(process.env, {\n    NODE_ENV: str(),\n    PORT: port(),\n\n    MONGODB_URI_LOCAL: str(),\n    MONGO_INITDB_ROOT_USERNAME: str(),\n    MONGO_INITDB_ROOT_PASSWORD: str(),\n    MONGO_INITDB_DATABASE: str(),\n\n    JWT_ACCESS_PRIVATE_KEY: str(),\n    JWT_ACCESS_PUBLIC_KEY: str(),\n\n    JWT_REFRESH_PRIVATE_KEY: str(),\n    JWT_REFRESH_PUBLIC_KEY: str(),\n  });\n};\n\nexport default validateEnv;\n\n<\/code>\n<\/pre>\n\n\n\n<p>With the environment variables, and docker-compose configured correctly, let&#8217;s start the Redis and MongoDB Docker containers.<\/p>\n\n\n\n<p>Run the command below to spawn the MongoDB and Redis containers:<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\ndocker-compose up -d\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Database Schema with Mongoose<\/h2>\n\n\n\n<p>Now create a <code>models<\/code> folder in the <code>src<\/code> folder and create a <code>user.model.js<\/code> file in it.<\/p>\n\n\n\n<p>Run the following command to install Mongoose, BcryptJs, and Validator:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/mongoosejs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mongoose<\/a>: Provides a schema-based solution to model application data<\/li>\n\n\n\n<li><a href=\"https:\/\/www.npmjs.com\/package\/bcryptjs\" target=\"_blank\" rel=\"noreferrer noopener\">BcryptJs<\/a>: For hashing a string<\/li>\n\n\n\n<li><a href=\"https:\/\/www.npmjs.com\/package\/validator\" target=\"_blank\" rel=\"noreferrer noopener\">Validator<\/a>: For validating user inputs<\/li>\n<\/ul>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add mongoose validator bcryptjs\n<\/code>\n<\/pre>\n\n\n\n<p>Below is a basic Mongoose schema listing the fields needed to create a user.<\/p>\n\n\n\n<p><strong>src\/models\/user.model.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport mongoose from 'mongoose';\nimport validator from 'validator';\nimport bcrypt from 'bcryptjs';\n\nconst userSchema = new mongoose.Schema(\n  {\n    name: {\n      type: String,\n      required: true,\n    },\n    email: {\n      type: String,\n      required: true,\n      unique: true,\n      validate: [validator.isEmail, 'Please provide a valid email'],\n      lowercase: true,\n    },\n    password: {\n      type: String,\n      required: true,\n      minlength: [8, 'Password must be more than 8 characters'],\n      select: false,\n    },\n    passwordConfirm: {\n      type: String,\n      required: [true, 'Please confirm your password'],\n      validate: {\n        validator: function (val) {\n          return val === this.password;\n        },\n        message: 'Passwords do not match',\n      },\n    },\n    photo: {\n      type: String,\n      default: 'default.png',\n    },\n    role: {\n      type: String,\n      default: 'user',\n    },\n    verified: {\n      type: Boolean,\n      default: true,\n      select: false,\n    },\n  },\n  { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }\n);\n\nuserSchema.index({ email: 1 });\n\nuserSchema.pre('save', async function (next) {\n  \/\/ Check if the password has been modified\n  if (!this.isModified('password')) return next();\n\n  \/\/ Hash password with strength of 12\n  this.password = await bcrypt.hash(this.password, 12);\n\n  \/\/ Remove the password confirm field\n  this.passwordConfirm = undefined;\n  next();\n});\n\nuserSchema.methods.comparePasswords = async function (\n  candidatePassword,\n  hashedPassword\n) {\n  return await bcrypt.compare(candidatePassword, hashedPassword);\n};\n\nconst userModel = mongoose.model('User', userSchema);\nexport default userModel;\n\n<\/code>\n<\/pre>\n\n\n\n<p>Below are some key things to note in the above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>I used the <a href=\"https:\/\/mongoosejs.com\/docs\/middleware.html#pre\" target=\"_blank\" rel=\"noreferrer noopener\">pre-save hook<\/a> provided by Mongoose to hash the user&#8217;s password before persisting it in the database.<\/li>\n\n\n\n<li>I also added an <a href=\"https:\/\/mongoosejs.com\/docs\/guide.html#methods\" target=\"_blank\" rel=\"noreferrer noopener\">instance method<\/a> which will be available on the model to validate the user&#8217;s password against the hashed password stored in the database.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Define the GraphQL Schema<\/h2>\n\n\n\n<p>In this article, we will use the <a href=\"https:\/\/www.apollographql.com\/docs\/apollo-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">Apollo Server<\/a> library to build the backend server.<\/p>\n\n\n\n<p>Run the command below to install <a href=\"https:\/\/www.apollographql.com\/docs\/apollo-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">Apollo Server<\/a> and its dependencies:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add apollo-server-express apollo-server-core express graphql\n<\/code>\n<\/pre>\n\n\n\n<p>The GraphQL schema aka type definition is a description of the structure of our queries and mutations.<\/p>\n\n\n\n<p>Create an <code>src\/schemas\/index.js<\/code> file and add the code snippets below.<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { gql } from 'apollo-server-express';\n\nconst typeDefs = gql`\n  scalar DateTime\n  type Query {\n    # Auth\n    refreshAccessToken: TokenResponse!\n    logoutUser: Boolean!\n\n    # User\n    getMe: UserResponse!\n  }\n\n  type Mutation {\n    # Auth\n    loginUser(input: LoginInput!): TokenResponse!\n    signupUser(input: SignUpInput!): UserResponse!\n  }\n\n  input SignUpInput {\n    name: String!\n    email: String!\n    password: String!\n    passwordConfirm: String!\n    photo: String\n  }\n\n  input LoginInput {\n    email: String!\n    password: String!\n  }\n\n  type TokenResponse {\n    status: String!\n    access_token: String!\n  }\n\n  type UserResponse {\n    status: String!\n    user: UserData!\n  }\n\n  type UserData {\n    id: ID!\n    name: String!\n    email: String!\n    photo: String!\n    role: String!\n    createdAt: DateTime\n    updatedAt: DateTime\n  }\n`;\n\nexport default typeDefs;\n\n<\/code>\n<\/pre>\n\n\n\n<p>Let&#8217;s breakdown this code step by step:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <code>typeDefs<\/code> variable contains our GraphQL schema<\/li>\n\n\n\n<li>First, we added a scalar type <code>DateTime<\/code> to the schema since we&#8217;ll be working with MongoDB timestamps.<\/li>\n\n\n\n<li>Then I defined two queries &#8211; <code>refreshAccessToken<\/code> to refresh the access token when it expires and <code>getMe<\/code> to get the currently logged-in user&#8217;s credentials.<\/li>\n\n\n\n<li>Lastly, I defined two mutations &#8211; <code>signupUser<\/code> to register a new user and <code>loginUser<\/code> to sign in the registered user.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Connect the Express App to the Redis and MongoDB Servers<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Make sure the Redis and MongoDB Docker containers are running.<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Connecting to MongoDB Docker Container<\/h3>\n\n\n\n<p>In the <code>src<\/code> folder, create a <code>utils\/connectDB.js<\/code> file and paste the code below into it.<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport mongoose from 'mongoose';\nimport dotenv from 'dotenv';\n\ndotenv.config();\n\nconst localUri = process.env.MONGODB_URI_LOCAL;\n\nasync function connectDB() {\n  try {\n    await mongoose.connect(localUri);\n    console.log('Database connected successfully');\n  } catch (error) {\n    console.log(error.message);\n    process.exit(1);\n  }\n}\n\nexport default connectDB;\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Connecting to Redis Docker Container<\/h3>\n\n\n\n<p>To connect to the Redis server, we&#8217;ll be using the Redis package so run this command to install it.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add redis\n<\/code>\n<\/pre>\n\n\n\n<p>Next, let&#8217;s connect the app to the Redis container. Create a <code>src\/utils\/connectRedis.js<\/code> file and paste the code below into it.<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { createClient } from 'redis';\n\nconst redisUrl = 'redis:\/\/localhost:6379';\n\nconst redisClient = createClient({\n  url: redisUrl,\n});\n\nconst connectRedis = async () =&gt; {\n  try {\n    await redisClient.connect();\n  } catch (error) {\n    console.error(error.message);\n    setInterval(5000, connectRedis);\n  }\n};\n\nconnectRedis();\n\nredisClient.on('connect', () =&gt;\n  console.log('Redis client connected successfully')\n);\n\nredisClient.on('error', (err) =&gt; console.error(err));\n\nexport default redisClient;\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create an Error Handler<\/h2>\n\n\n\n<p>When designing an API, it&#8217;s recommended to return the right HTTP status code to inform the client of what happened on the server.<\/p>\n\n\n\n<p>Apollo Server provides us with an error class we can leverage to send appropriate errors to the client.<\/p>\n\n\n\n<p>In the code below, we are simply catching all the possible Mongoose errors and returning a well-formatted message to the client.<\/p>\n\n\n\n<p><strong>src\/controllers\/error.controller.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { ApolloError } from 'apollo-server-core';\n\nconst handleCastError = (error) =&gt; {\n  const message = `Invalid ${error.path}: ${error.value}`;\n  throw new ApolloError(message, 'GRAPHQL_VALIDATION_FAILED');\n};\n\nconst handleValidationError = (error) =&gt; {\n  const message = Object.values(error.errors).map((el) =&gt; el.message);\n  throw new ApolloError(\n    `Invalid input: ${message.join(', ')}`,\n    'GRAPHQL_VALIDATION_FAILED'\n  );\n};\n\nconst errorHandler = (err) =&gt; {\n if (err.name === 'CastError') handleCastError(err);\n  if (err.name === 'ValidationError') handleValidationError(err);\n  throw err;\n};\n\nexport default errorHandler;\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Define a Service to Sign and Verify JSON Web Tokens<\/h2>\n\n\n\n<p>A JSON Web Token is simply a string of characters consisting of a header, payload, and signature.<\/p>\n\n\n\n<p>We mostly use JWTs to implement stateless authentication on the server even though JWTs have some flaws. For example, a JWT can only be invalidated only when it has expired which becomes an attacking vector for hackers.<\/p>\n\n\n\n<p>To add an additional layer of security to the JWT implementation, we will use Redis to store the user&#8217;s session.<\/p>\n\n\n\n<p>To begin, run this command to install the JSON Web Token package:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add jsonwebtoken\n<\/code>\n<\/pre>\n\n\n\n<p>We&#8217;ll store the generated refresh and access tokens in HTTPOnly cookies to increase the security level. <\/p>\n\n\n\n<p>Doing it that way will prevent attackers from using Javascript to retrieve and manipulate them.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>If you want to learn more about JWT Authentication with React, check out <a href=\"\/react-redux-toolkit-jwt-authentication-and-authorization\">React + Redux Toolkit: JWT Authentication and Authorization<\/a><\/p>\n<\/blockquote>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>If you want to learn more about Refresh Tokens with React, check out <a href=\"\/react-redux-toolkit-refresh-token-authentication\">React.js + Redux Toolkit: Refresh Tokens Authentication<\/a><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">How to Generate the Private and Public Keys<\/h3>\n\n\n\n<p>I already included the private and public keys in the <code>.env<\/code> file but you can follow these steps to generate them by yourself.<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Navigate to <a href=\"http:\/\/travistidwell.com\/jsencrypt\/demo\/\" target=\"_blank\" rel=\"noreferrer noopener\">this website<\/a> and click on the blue <strong><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">&#8220;Generate New Keys&#8221;<\/mark><\/strong> button to generate new private and public keys.<\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Next, visit <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">this website<\/a> to encode each of the private and public keys into <strong>Base64<\/strong>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>You need to encode the keys to avoid getting warnings in the terminal when building the Docker containers.<\/p>\n<\/blockquote>\n\n\n\n<p><strong>Step 3:<\/strong> Update the <code>.env<\/code> file with the private and public keys for the access token.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Repeat the process for the refresh token<\/p>\n<\/blockquote>\n\n\n\n<p>Now, let&#8217;s create two functions to sign and verify the tokens. <\/p>\n\n\n\n<p>In each of these functions, you&#8217;ll notice that we decoded the encoded private and public keys into <strong>ASCII<\/strong> strings before passing them to the JSONWebToken methods.<\/p>\n\n\n\n<p><strong>src\/utils\/jwt.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport jwt from 'jsonwebtoken';\nimport errorHandler from '..\/controllers\/error.controller.js';\n\nexport const signJwt = (payload, Key, options) =&gt; {\n  const privateKey = Buffer.from(process.env[Key], 'base64').toString('ascii');\n  return jwt.sign(payload, privateKey, {\n    ...(options &amp;&amp; options),\n    algorithm: 'RS256',\n  });\n};\n\nexport const verifyJwt = (token, Key) =&gt; {\n  try {\n    const publicKey = Buffer.from(process.env[Key], 'base64').toString('ascii');\n    const decoded = jwt.verify(token, publicKey);\n    return decoded;\n  } catch (error) {\n    errorHandler(error);\n  }\n};\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Authentication Controllers<\/h2>\n\n\n\n<p>Now it&#8217;s time to create the authentication controllers responsible for registering the new user, refreshing the access token, logging in the registered user, sending the verification email, and logging out the user.<\/p>\n\n\n\n<p>Let&#8217;s define some cookie options for the access and refresh tokens:<\/p>\n\n\n\n<p><strong>src\/controllers\/auth.controller.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nconst accessTokenExpireIn = config.get('jwtAccessTokenExpiresIn');\nconst refreshTokenExpireIn = config.get('jwtRefreshTokenExpiresIn');\n\nconst cookieOptions = {\n  httpOnly: true,\n  \/\/ domain: 'localhost',\n  sameSite: 'none',\n  secure: true,\n};\n\nconst accessTokenCookieOptions = {\n  ...cookieOptions,\n  maxAge: accessTokenExpireIn * 60 * 1000,\n  expires: new Date(Date.now() + accessTokenExpireIn * 60 * 1000),\n};\n\nconst refreshTokenCookieOptions = {\n  ...cookieOptions,\n  maxAge: refreshTokenExpireIn * 60 * 1000,\n  expires: new Date(Date.now() + refreshTokenExpireIn * 60 * 1000),\n};\n\nif (process.env.NODE_ENV === 'production') cookieOptions.secure = true;\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Register User Controller<\/h3>\n\n\n\n<p>In the Mongoose schema, remember to add a lowercase of <code>true<\/code> to the email field since MongoDB is not case-sensitive. When a user tries to log in with either <code>Johndoe@gmail.com<\/code> and <code>johndoe@gmail.com<\/code> , MongoDB will recognize these emails to belong to two different users.<\/p>\n\n\n\n<p>Also, we need to check for a special error code <code>11000<\/code> returned by MongoDB indicating that a document has a unique constraint been violated.<\/p>\n\n\n\n<p>In this case, the unique constraint is the email address.<\/p>\n\n\n\n<p><strong>src\/controllers\/auth.controller.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { AuthenticationError, ForbiddenError } from 'apollo-server-core';\nimport config from 'config';\nimport userModel from '..\/models\/user.model.js';\nimport redisClient from '..\/utils\/connectRedis.js';\nimport { signJwt, verifyJwt } from '..\/utils\/jwt.js';\nimport errorHandler from '.\/error.controller.js';\n\n\/\/ ? Cookie Options\n\n\/\/ ? SignUp User\nconst signup = async (\n  parent,\n  { input: { name, email, password, passwordConfirm } },\n  { req }\n) =&gt; {\n  try {\n    const user = await userModel.create({\n      name,\n      email,\n      password,\n      passwordConfirm,\n    });\n\n    return {\n      status: 'success',\n      user,\n    };\n  } catch (error) {\n    if (error.code === 11000) {\n      throw new ForbiddenError('User already exist');\n    }\n    errorHandler(error);\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Login User Controller<\/h3>\n\n\n\n<p>Next, let&#8217;s create a handler to sign in the registered user by returning access and refresh token cookies to the client.<\/p>\n\n\n\n<p>In an upcoming article, we&#8217;ll first verify the user&#8217;s email address before allowing them to sign in to their account.<\/p>\n\n\n\n<p><strong>src\/controllers\/auth.controller.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n\/\/ ? Cookie Options\n\n\/\/ ? SignUp User\n\n\/\/ ? Sign Tokens\nasync function signTokens(user) {\n  \/\/ Create a Session\n  await redisClient.set(user.id, JSON.stringify(user), {\n    EX: 60 * 60,\n  });\n\n  \/\/ Create access token\n  const access_token = signJwt({ user: user.id }, 'JWT_ACCESS_PRIVATE_KEY', {\n    expiresIn: `${config.get('jwtAccessTokenExpiresIn')}m`,\n  });\n\n  \/\/ Create refresh token\n  const refresh_token = signJwt({ user: user.id }, 'JWT_REFRESH_PRIVATE_KEY', {\n    expiresIn: `${config.get('jwtRefreshTokenExpiresIn')}m`,\n  });\n\n  return { access_token, refresh_token };\n}\n\nconst login = async (parent, { input: { email, password } }, { req, res }) =&gt; {\n  try {\n    \/\/ Check if user exist and password is correct\n    const user = await userModel\n      .findOne({ email })\n      .select('+password +verified');\n\n    if (!user || !(await user.comparePasswords(password, user.password))) {\n      throw new AuthenticationError('Invalid email or password');\n    }\n\n    user.password = undefined;\n\n    \/\/ Create a session and tokens\n    const { access_token, refresh_token } = await signTokens(user);\n\n    \/\/ Add refreshToken to cookie\n    res.cookie('refresh_token', refresh_token, refreshTokenCookieOptions);\n    res.cookie('access_token', access_token, accessTokenCookieOptions);\n    res.cookie('logged_in', true, {\n      ...accessTokenCookieOptions,\n      httpOnly: false,\n    });\n\n    return {\n      status: 'success',\n      access_token,\n    };\n  } catch (error) {\n    errorHandler(error);\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<p>Below are some notable things you should consider in the above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In the mongoose schema, we explicitly excluded the <code>password<\/code> and <code>verified<\/code> fields from been included in the returned document so we have to tell mongoose to add them since we want to make use of them.<\/li>\n\n\n\n<li>After making use of the password field, we then need to remove it again from the document that will be returned to the user by setting it to <code>undefined<\/code> .<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Refresh Access Token Controller<\/h3>\n\n\n\n<p>Here, the handler will be called after the access token has expired to return a new access token.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Note:<\/strong> the access token can only be refreshed if the user has a valid session in the Redis database.<\/p>\n<\/blockquote>\n\n\n\n<p><strong>src\/controllers\/auth.controller.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n\/\/ ? Cookie Options\n\n\/\/ ? SignUp User\n\n\/\/ ? Sign Tokens\n\n\/\/ ? Login User\n\n\/\/ ? Refresh Tokens\nconst refreshAccessToken = async (parent, args, { req, res }) =&gt; {\n  try {\n    \/\/ Get the refresh token\n    const { refresh_token } = req.cookies;\n\n    \/\/ Validate the RefreshToken\n    const decoded = verifyJwt(refresh_token, 'JWT_REFRESH_PUBLIC_KEY');\n\n    if (!decoded) {\n      throw new ForbiddenError('Could not refresh access token');\n    }\n\n    \/\/ Check if user's session is valid\n    const session = await redisClient.get(decoded.user);\n\n    if (!session) {\n      throw new ForbiddenError('User session has expired');\n    }\n\n    \/\/ Check if user exist and is verified\n    const user = await userModel\n      .findById(JSON.parse(session)._id)\n      .select('+verified');\n\n    if (!user || !user.verified) {\n      throw new ForbiddenError('Could not refresh access token');\n    }\n\n    \/\/ Sign new access token\n    const access_token = signJwt({ user: user._id }, 'JWT_ACCESS_PRIVATE_KEY', {\n      expiresIn: config.get('jwtAccessTokenExpiresIn'),\n    });\n\n    \/\/ Send access token cookie\n    res.cookie('access_token', access_token, accessTokenCookieOptions);\n    res.cookie('logged_in', 'true', {\n      ...accessTokenCookieOptions,\n      httpOnly: false,\n    });\n\n    return {\n      status: 'success',\n      access_token,\n    };\n  } catch (error) {\n    errorHandler(error);\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Logout Controller<\/h3>\n\n\n\n<p>I believe you&#8217;ve seen a log-out functionality in most of the applications you&#8217;ve used before. The way we&#8217;ll achieve that is to return expired cookies to the client.<\/p>\n\n\n\n<p>In addition, we need to delete the user&#8217;s session from the Redis database.<\/p>\n\n\n\n<p><strong>src\/controllers\/auth.controller.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n\/\/ ? Cookie Options\n\n\/\/ ? SignUp User\n\n\/\/ ? Sign Tokens\n\n\/\/ ? Login User\n\n\/\/ ? Refresh Tokens\n\n\/\/ ? Logout User\nconst logoutHandler = async (_, args, { req, res, getAuthUser }) =&gt; {\n  try {\n    await checkIsLoggedIn(req, getAuthUser);\n\n    const user = await getAuthUser(req);\n\n    \/\/ Delete the user's session\n    await redisClient.del(user._id);\n\n    \/\/ Logout user\n    res.cookie('access_token', '', { maxAge: -1 });\n    res.cookie('refresh_token', '', { maxAge: -1 });\n    res.cookie('logged_in', '', { maxAge: -1 });\n\n    return true;\n  } catch (error) {\n    errorHandler(error);\n  }\n};\n\nexport default {\n  signup,\n  login,\n  refreshAccessToken,\n  logoutHandler,\n};\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create a User Controller<\/h2>\n\n\n\n<p>To test the authentication logic, let&#8217;s create a <em><code>getMe<\/code><\/em> handler to return the currently logged-in user&#8217;s credentials.<\/p>\n\n\n\n<p><strong>src\/controllers\/user.controller.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport errorHandler from '.\/error.controller.js';\nimport checkIsLoggedIn from '..\/middleware\/checkIsLoggedIn.js';\n\nconst getMe = async (_, args, { req, getAuthUser }) =&gt; {\n  try {\n    await checkIsLoggedIn(req, getAuthUser);\n\n    const user = await getAuthUser(req);\n\n    return {\n      status: 'success',\n      user,\n    };\n  } catch (error) {\n    errorHandler(error);\n  }\n};\n\nexport default {\n  getMe,\n};\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Define Authentication Middleware<\/h2>\n\n\n\n<p>To parse the cookies in the request headers, we&#8217;ll use <code><a href=\"https:\/\/www.npmjs.com\/package\/cookie-parser\" target=\"_blank\" rel=\"noreferrer noopener\">cookie-parser<\/a><\/code> package.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add cookie-parser\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Deserialize User Middleware<\/h3>\n\n\n\n<p>Now let&#8217;s create a middleware to serve as a guard for all the protected resources.<\/p>\n\n\n\n<p><strong>src\/middleware\/authUser.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { ForbiddenError } from 'apollo-server-core';\nimport errorHandler from '..\/controllers\/error.controller.js';\nimport userModel from '..\/models\/user.model.js';\nimport redisClient from '..\/utils\/connectRedis.js';\nimport { verifyJwt } from '..\/utils\/jwt.js';\n\nconst authUser = async (req) =&gt; {\n  try {\n    \/\/ Get the access token\n    let access_token;\n    if (\n      req.headers.authorization &amp;&amp;\n      req.headers.authorization.startsWith('Bearer')\n    ) {\n      access_token = req.headers.authorization.split(' ')[1];\n    } else if (req.cookies.access_token) {\n      const { access_token: token } = req.cookies;\n      access_token = token;\n    }\n\n    if (!access_token) return false;\n\n    \/\/ Validate the Access token\n    const decoded = verifyJwt(access_token, 'JWT_ACCESS_PUBLIC_KEY');\n\n    if (!decoded) return false;\n\n    \/\/ Check if the session is valid\n    const session = await redisClient.get(decoded.user);\n\n    if (!session) {\n      throw new ForbiddenError('Session has expired');\n    }\n\n    \/\/ Check if user exist\n    const user = await userModel\n      .findById(JSON.parse(session).id)\n      .select('+verified');\n\n    if (!user || !user.verified) {\n      throw new ForbiddenError(\n        'The user belonging to this token no logger exist'\n      );\n    }\n\n    return user;\n  } catch (error) {\n    errorHandler(error);\n  }\n};\n\nexport default authUser;\n<\/code>\n<\/pre>\n\n\n\n<p>Below is a summary of the above code:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, we retrieve the access token from either the Authorization header or the <code>req.cookies<\/code> object.<\/li>\n\n\n\n<li>Next, we validate the access token to extract the payload stored in it.<\/li>\n\n\n\n<li>Then we check if the user has a valid session and still exists in the MongoDB database.<\/li>\n\n\n\n<li>Lastly, we return the user to the next middleware if there wasn&#8217;t any error.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Require the Authenticated User<\/h3>\n\n\n\n<p>Next, let&#8217;s create a middleware to return an authentication error to the client.<\/p>\n\n\n\n<p><strong>src\/middleware\/checkIsLoggedIn.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { AuthenticationError } from 'apollo-server-core';\nimport errorHandler from '..\/controllers\/error.controller.js';\n\nconst checkIsLoggedIn = async (req, getAuthUser) =&gt; {\n  try {\n    \/\/ Check if user is logged in\n    const authUser = await getAuthUser(req);\n\n    if (!authUser) {\n      throw new AuthenticationError('You are not logged in');\n    }\n  } catch (error) {\n    errorHandler(error);\n  }\n};\n\nexport default checkIsLoggedIn;\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the resolvers<\/h2>\n\n\n\n<p>Now that we have implemented 90 percent of the application logic, let&#8217;s define the GraphQL resolvers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Define a scalar DateTime<\/h3>\n\n\n\n<p>The following&nbsp;<code><a href=\"https:\/\/www.apollographql.com\/docs\/apollo-server\/schema\/custom-scalars\/\" target=\"_blank\" rel=\"noreferrer noopener\">GraphQLScalarType<\/a><\/code>&nbsp;object specifies the interactions for a custom scalar that represents a date. It assumes the backend represents dates with the&nbsp;<code>Date<\/code>&nbsp;JavaScript object.<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport { Kind, GraphQLScalarType } from 'graphql';\n\nexport default new GraphQLScalarType({\n  name: 'DateTime',\n  description: 'DateTime Scaler Type',\n\n  serialize(value) {\n    return new Date(value).toISOString();\n  },\n  parseValue(value) {\n    return new Date(value);\n  },\n  parseLiteral(ast) {\n    if (ast.Kind === Kind.INT) {\n      return new Date(parseInt(ast.value, 10));\n    }\n    return null;\n  },\n});\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create the Query Resolvers<\/h3>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport authController from '..\/controllers\/auth.controller.js';\nimport userController from '..\/controllers\/user.controller.js';\n\nexport default {\n  \/\/ Users\n  getMe: userController.getMe,\n  \/\/ Auth\n  refreshAccessToken: authController.refreshAccessToken,\n  logoutUser: authController.logoutHandler,\n};\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create the Mutation Resolvers<\/h3>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport authController from '..\/controllers\/auth.controller.js';\n\nexport default {\n  \/\/ Auth\n  signupUser: authController.signup,\n  loginUser: authController.login,\n};\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Export the Query and Mutation Resolvers<\/h3>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport Mutation from '.\/mutation.resolver.js';\nimport Query from '.\/query.resolver.js';\n\nexport { Mutation, Query };\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configuring the Apollo Server<\/h2>\n\n\n\n<p>So far, we&#8217;ve created the schemas and resolvers, the next step is to connect them.<\/p>\n\n\n\n<p>Create an <code>src\/app.js<\/code> file and add the following code:<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport express from 'express';\nimport dotenv from 'dotenv';\nimport cookieParser from 'cookie-parser';\nimport validateEnv from '.\/utils\/validateEnv.js';\ndotenv.config();\nvalidateEnv();\n\nconst app = express();\n\n\/\/ MIDDLEWARE\napp.use(cookieParser());\n\nprocess.on('uncaughtException', (err) =&gt; {\n  console.error('UNCAUGHT EXCEPTION ? Shutting down...');\n  console.error('Error?', err.message);\n  process.exit(1);\n});\n\nexport default app;\n<\/code>\n<\/pre>\n\n\n\n<p>Run the following command to install <code>cors<\/code> and <code>nodemon<\/code>. <\/p>\n\n\n\n<p>The <code>cors<\/code> package will enable us to receive requests from the GraphQL client since it&#8217;s running on a different domain.<\/p>\n\n\n\n<p>Also, the <code>nodemon<\/code> package will be used to hot-reload the server.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add cors &amp;&amp; yarn add nodemon --global\n<\/code>\n<\/pre>\n\n\n\n<p>Now create a <code>src\/server.js<\/code> file and add the following code:<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport http from 'http';\nimport { ApolloServer } from 'apollo-server-express';\nimport { ApolloServerPluginDrainHttpServer } from 'apollo-server-core';\nimport config from 'config';\nimport cors from 'cors';\nimport connectDB from '.\/utils\/connectDB.js';\nimport typeDefs from '.\/schemas\/index.js';\nimport app from '.\/app.js';\nimport { Mutation, Query } from '.\/resolvers\/index.js';\nimport DateTime from '.\/resolvers\/datetime.js';\nimport getAuthUser from '.\/middleware\/authUser.js';\n\nconst httpServer = http.createServer(app);\n\nconst corsOptions = {\n  origin: ['https:\/\/studio.apollographql.com', 'http:\/\/localhost:8000'],\n  credentials: true,\n};\n\napp.use(cors(corsOptions));\n\nconst resolvers = {\n  DateTime,\n  Query,\n  Mutation,\n};\n\n(async function () {\n  const server = new ApolloServer({\n    typeDefs,\n    resolvers,\n    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],\n    context: async ({ req, res }) =&gt; ({ req, res, getAuthUser }),\n  });\n\n  \/\/ CONNECT DB\n  await connectDB();\n\n  \/\/ START APOLLO SERVER\n  await server.start();\n\n  server.applyMiddleware({ app, cors: corsOptions });\n\n  const port = config.get('port');\n\n  await new Promise((resolve) =&gt; httpServer.listen(port, '0.0.0.0', resolve));\n  console.log(\n    `?Server started at http:\/\/localhost:${port}${server.graphqlPath}`\n  );\n})();\n\nprocess.on('unhandledRejection', (err) =&gt; {\n  console.log('UNHANDLED REJECTION ?? Shutting down...');\n  console.error('Error?', err.message);\n\n  httpServer.close(async () =&gt; {\n    process.exit(1);\n  });\n});\n\n<\/code>\n<\/pre>\n\n\n\n<p>Run this command to start the MongoDB and Redis containers:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\ndocker-compose up -d\n<\/code>\n<\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Note:<\/strong> make sure you set the <code>type<\/code> property to <code>module<\/code> in the <code>package.json<\/code> file.<\/p>\n<\/blockquote>\n\n\n\n<p>Run this command to start the Apollo Server:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nnodemon .\/src\/server.js\n<\/code>\n<\/pre>\n\n\n\n<p>Once the server is up and running, hold <strong>CTRL<\/strong> and click on the Sandbox URL to open it in the browser.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the GraphQL API<\/h2>\n\n\n\n<p>Apart from setting the correct cookie headers, we need to tell the Sandbox to actually set&nbsp;<code>credentials: include<\/code>&nbsp;as part of the request.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"906\" height=\"939\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings.png\" alt=\"graphql api with nodejs set sandbox settings\" class=\"wp-image-2419\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings.png 906w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-289x300.png 289w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-768x796.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-96x100.png 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-434x450.png 434w\" sizes=\"auto, (max-width: 906px) 100vw, 906px\" \/><\/figure>\n\n\n\n<p>Next, turn on the <strong>&#8220;Include cookies&#8221;<\/strong> radio button<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"710\" height=\"795\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-allow-cookies.png\" alt=\"graphql api with nodejs set sandbox settings allow cookies\" class=\"wp-image-2418\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-allow-cookies.png 710w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-allow-cookies-268x300.png 268w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-allow-cookies-89x100.png 89w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-set-sandbox-settings-allow-cookies-402x450.png 402w\" sizes=\"auto, (max-width: 710px) 100vw, 710px\" \/><\/figure>\n\n\n\n<p>-Register a new user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"789\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-signup-user-1024x789.png\" alt=\"graphql api with nodejs signup user\" class=\"wp-image-2420\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-signup-user-1024x789.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-signup-user-300x231.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-signup-user-768x592.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-signup-user-100x77.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-signup-user-584x450.png 584w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-signup-user.png 1183w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Login the registered user<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"733\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-login-the-user-1024x733.png\" alt=\"graphql api with nodejs login the user\" class=\"wp-image-2416\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-login-the-user-1024x733.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-login-the-user-300x215.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-login-the-user-768x550.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-login-the-user-100x72.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-login-the-user-629x450.png 629w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-login-the-user.png 1231w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Use <code><strong>CTRL<\/strong> + <strong>i<\/strong><\/code> to open the dev-tools and click on the application tab and select the Sandbox URL from the Cookies dropdown. <\/p>\n\n\n\n<p>Take a careful look at the available cookies and you should see the access and refresh token cookies.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"633\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-inspect-the-cookies-1024x633.png\" alt=\"graphql api with nodejs inspect the cookies\" class=\"wp-image-2425\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-inspect-the-cookies-1024x633.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-inspect-the-cookies-300x186.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-inspect-the-cookies-768x475.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-inspect-the-cookies-100x62.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-inspect-the-cookies-700x433.png 700w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-inspect-the-cookies.png 1292w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Get the currently logged-in user&#8217;s credentials<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"722\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials-1024x722.png\" alt=\"graphql api with nodejs get currently logged in users credentials\" class=\"wp-image-2415\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials-1024x722.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials-300x212.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials-768x542.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials-100x71.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials-850x600.png 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials-638x450.png 638w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-get-currently-logged-in-users-credentials.png 1236w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Refresh the access token<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"741\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-refresh-access-token-1024x741.png\" alt=\"graphql api with nodejs refresh access token\" class=\"wp-image-2417\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-refresh-access-token-1024x741.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-refresh-access-token-300x217.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-refresh-access-token-768x556.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-refresh-access-token-100x72.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-refresh-access-token-622x450.png 622w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-api-with-nodejs-refresh-access-token.png 1238w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-Logout the logged-in user<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"963\" height=\"762\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-node-api-apollo-server-logout-user.png\" alt=\"graphql node api apollo server logout user\" class=\"wp-image-3486\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-node-api-apollo-server-logout-user.png 963w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-node-api-apollo-server-logout-user-300x237.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-node-api-apollo-server-logout-user-768x608.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-node-api-apollo-server-logout-user-100x79.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/graphql-node-api-apollo-server-logout-user-569x450.png 569w\" sizes=\"auto, (max-width: 963px) 100vw, 963px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Congrats for reaching the end. In this article, you learned how to implement access and refresh tokens in a GraphQL API using Node.js, MongoDB, Docker, Redis, and Apollo Server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">GraphQL, Node, MongoDB &amp; Apollo Server Source Code<\/h2>\n\n\n\n<p>Check out the source code on <a href=\"https:\/\/github.com\/wpcodevo\/node_graphql_api\/tree\/node_graphql_api_jwt_auth\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article will teach you how to implement JWT Authentication in a GraphQL API using Node.js, MongoDB, Redis, and Apollo Server. In addition, you&#8217;ll learn&#8230;<\/p>\n","protected":false},"author":1,"featured_media":2433,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[47],"tags":[75,42],"class_list":["post-2353","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-nodejs","tag-graphql","tag-nodejs-api"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/2353","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=2353"}],"version-history":[{"count":1,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/2353\/revisions"}],"predecessor-version":[{"id":11361,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/2353\/revisions\/11361"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/2433"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=2353"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=2353"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=2353"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}