How to Build a Node.js Authentication API with Email Verification, Image Upload and Password Reset Using JWT, Passport.js and Sendgrid.

Build a Node.js Authentication API with Email Verification, Image Upload and Password Reset using JWT, Passport.js and Sendgrid.

Please note that this tutorial assumes you have some Javascript experience, this tutorial does not explain each line of the code instead it presents you with the code and gives you an overview of what the code does and points out the important parts of the code. I have made the code as simple as possible, if you have any questions, please do not hesitate to leave a comment.

Demo

Related

Links

What is JSON Web Tokens (JWT)?

JSON Web Tokens are an open, industry-standard RFC 7519 method for representing claims securely between two parties. JWT.IO allows you to decode, verify and generate JWT.

JWT is a type of token-based authentication. The client sends the token along with every request from to the server, the server validates it and sends back the response.

What is Passport.js?

Passport is authentication middleware for Node.js. As it’s extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies supports authentication using a username and password, Facebook, Twitter, and more.

Requirements

Node.js mLab free account for database, create a MongoDB database, take note of the database URL. Cloudinary free account for image upload. A Twilio SendGrid account, sign up for free for sending emails.

Functionalities

register with email verification login update profile upload a profile image password reset

Step 1: Create a new project

Create a new directory $ mkdir nodejsapi

$ cd nodejsapi Create the package.json

$ npm init -f

Step 2: Install Dependencies

The following packages will be used:

express, a minimal and flexible Node.js web application framework

bcrypt, A library to help you hash passwords.

jsonwebtoken, An implementation of JSON Web Tokens.

mongoose, Mongoose provides a straight-forward, schema-based solution to model your application data.

passport, an authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped into any Express-based web application.

passport-jwt, A Passport strategy for authenticating with a JSON Web Token.

cors, CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.

dotenv, Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env .

file into . express-validator, express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions.

$ npm install bcrypt express jsonwebtoken mongoose passport passport-jwt cors dotenv express-validator --save

Image Upload Dependencies

$ npm install multer datauri cloudinary --save

Email Verification and Password Reset Dependencies

jade, a templating engine, primarily used for server-side templating in NodeJS.

@sendgrid/mail, This is a dedicated service for interaction with the mail endpoint of the Sendgrid v3 API

npm install jade @sendgrid/mail --save

Development Dependencies

nodemon: nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.

$ npm install nodemon --save-dev ---- # add the following to package.json scripts section

"scripts": {

"start": "nodemon src/server.js"

},

Step 3: Create environment variables

In your project root, create a .env file.

$ touch .env

In this file, we define the JWT secret used for creating and verifying the authentication tokens. The mongo Database Url cloudinary API Key and API Secret and Cloud Name sendgrid API Key and FROM email (can be anything) for the mail options.

.env

Step 4: Create Folder Structure

In your project root create a src folder.

Main Folder

$ mkdir src

$ cd src Subfolders and server.js file

$ mkdir config routes models controllers middlewares views utils

$ touch server.js

Step 5: Configuration

In the config folder, create a new file cloudinary.js , in here, a cloudinary instance is created and configured using the API Key and API Secret and Cloud Name .

cloudinary.js

Step 6: Middleware and Utils

In the middlewares folder, create the following files.

jwt.js The Passport JWT authentication strategy is created and configured. "fromAuthHeaderAsBearerToken() creates a new extractor that looks for the JWT in the authorization header with the scheme 'bearer'".

jwt.js

authenticate.js This middleware is used to authenticate the users request. Using passport.authenticate() and specifying the 'jwt' strategy the request is authenticated by checking for the standard Authorization header and verifying the verification token, if any. If unable to authenticate request, an error message is returned.

authenticate.js

validate.js This middleware is used to check if the express-validator middleware returns an error. If so, it recreates the error object using the param and msg keys and returns the error.

validate.js

In the utils folder, create a new file index.js

index.js Two helper functions are created in this file. uploader

used for uploading images to cloudinary, accepts the request as a parameter. the Datauri package is used to recreate the image using the 'buffer' key in 'req.file'. The image's content is passed to cloudinary upload function. If the upload is successful, the uploaded image url is returned. ---- sendEmail method

used for sending emails using sendgrid package, accepts an object containing the from, to, subject and text or html info, the object is passed to sendgrid send function to send the the email. If successful, the result is returned. ----

utils/index.js

Step 7: Model

In the model's folder, create the following files user.js and token.js

The User model is created using the mongoose package. This model represents each user in the database, its schema includes various fields including resetPasswordToken and resetPasswordExpires which are used for the password reset functionality and isVerified field used to check if the user is verified.

The Token model is also created using the mongoose package, this model represents each email verification token generated, it stores the user’s id, the token and created date.

pre-save hook

used to hash the user’s password using the bcrypt package whenever a user is created or their password is changed before saving in the database. ---- comparePassword method

used to compare the password entered by the user during login to the user’s password currently in the database. ---- generateJWT method

used for creating the authentication tokens using the jwt package. This token will be returned to the user and will be required for accessing protected routes. The token payload includes the user’s first name, last name, username and email address and is set to expire 60 days in the future. ---- generatePasswordReset method

used to generate a password reset token using the crytpo package and and calculates an expiry time (1 hour), the user object is updated with this data. ---- getVerificationToken method

used to generate a token and creates and returns an instance of the Token model.

models

Step 8: Controller

In the controller's folder, create the following files auth.js , user.js and password.js

auth.js

The authentication controller includes the following functions. register

the database is queried using the email address to check if the user already exist. If the user doesn't exist, a new user is created and saved in the database, the sendVerificationEmail function is called. -- login

the database is queried using the email address to check if the user exist. If the user is found, the user object is used to call the comparePassword method. If the comparison is successful, check if the user has been verified. if yes, the generateJWT method is called to generate the authentication token which is then passed to the user along with the user object. -- verify

using the token passed as part of the verification link, the database is queried, if the token is found, check if the user has already being verified, if not, the isVerfied field in the user's object is updated and saved. -- resendToken

the database is queried using the users email address to retrieve the users object, if found, check if the user has already being verified, if not, the sendVerificationEmail function is called. -- sendVerificationEmail

calls the getVerificationToken method, returning an instance of the Token model which is then saved in the database. A link is created and the from, to, subject and html for the email is set and passed to the sendEmail util function.

controllers/auth.js

password.js The password controller includes the following functions. recover

the database is queried using the user's email address to retrieve the user's object, if found, the generatePasswordReset method is called to generate a password reset token and set its expiry time (1 hour) which is then added to the users object and saved. A reset link is created and the from, to, subject and html for the email is set and passed to the sendEmail util function. -- reset

queries the database for the users object using the password reset token and verifying it's still valid by adding resetPasswordExpires: {$gt: Date.now()}. If user is found, the password reset page is displayed. --- resetPassword

queries the database using the password reset token and veryfying it is still valid by adding resetPasswordExpires: {$gt: Date.now()}. if the token is still valid, the users password is updated, the resetPasswordToken and resetPasswordExpires fields are set to undefined and the user object is saved and an email is sent to the user confirming the change.

controllers/password.js

user.js update

the user id is extracted from the request params variable, there's a check to make sure the data being updated belongs to the user. An update object is created using the request body and Mongoose findByIdAndUpdate is used to query and update the user’s data. The req.file variable is checked to determine if an image was uploaded, if yes, the req variable is passed to the uploader util function. If upload is succesful, the url is added to the users object and saved.

controllers/user.js

Step 9: Routes

In the routes folder, create the following files auth.js user.js and index.js

index.js The /api/auth uses the auth routes.

The /api/user uses the user routes. A middleware is attached to the user endpoint, the middleware checks and authenticates the jwt token passed before accessing the user routes. -- auth.js This validate middleware is used to check and validate all inputs during registration and login. -- user.js In this file, the multer package is used to create the middleware upload which accept a single file with the field name profileImage. This middleware is attached to the update endpoint to allow for multipart/form-data.

const multer = require('multer');

const upload = multer().single('profileImage');



router.put('/:id', upload, User.update);

routes

Step 10: Create Reset Password View

In the views folder, create the file reset.jade

reset.jade

Step 11: Create Server

Update the server.js file.

server.js

Run the command below to start the server

npm start

Server running on

MongoDB -- database connection established successfully!

>> << - OUTPUTServer running on http://localhost:3000/ MongoDB -- database connection established successfully!>>

Testing

Use Postman to test.

User Index

Try accessing the user index. /api/user

Register and Login

Create a POST request to /api/auth/register .

Create a POST request to /api/auth/login .

Register and Login

Verification Email

Update User Info and Upload Profile Image

Try updating the user information and uploading a profile image using endpoint /api/user/[your_user_id] passing the token.

Login and Recover Password

Login with your current password.

Create a POST request to /api/auth/recover .

Login and Password Recovery

Reset Password and Login with new Password

Reset the password then attempt to login with your old password. This should fail. Login with your new password.

Reset Password and Login

That’s all folks!

That’s all folks!

Related Tutorials