Building a Blog API with Node.js: Authentication and Validation (Part 3)
Tags: api, node, jwt
Published: 2023-01-09 15:18:53

In the previous articles in [this series](https://dev.to/pda/series/21311), we designed our API, implemented the data models and established the database connection. Before we start building the actual API, we'll cover two topics that are important for any API: 
 - Authentication 
 - Validation

Let's start by installing the dependencies required for this section:

```
npm install joi jsonwebtoken passport passport-jwt passport-local passport-local-mongoose
```

## Authentication with Passport.js
To authenticate users in our API, we'll be using the popular [Passport.js](https://www.passportjs.org/) library. When a user logs in, we'll generate a JWT token for the user. The token will be used to authorise the user's requests to the API.

- In the /authentication/passport.js file, set up the JWT strategy:

   ```javascript
   const passport = require("passport");
   const localStrategy = require("passport-local").Strategy;
   const { UserModel } = require("../models");

   const JWTstrategy = require("passport-jwt").Strategy;
   const ExtractJWT = require("passport-jwt").ExtractJwt;

   passport.use(
     new JWTstrategy(
       {
          secretOrKey: process.env.JWT_SECRET,
          jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
       },
	async (token, done) => {
	   try {
		   return done(null, token.user);
		} catch (error) {
		   done(error);
		}
	}
	)
   );
   ```

The JSON Web Token (JWT) strategy that allows us to authenticate users by verifying their JWT. To use this strategy, we pass in a secret key (which we have stored in our `.env` file) and a fucntion.  The `ExtractJWT.fromAuthHeaderAsBearerToken()` method will extract the JWT from the `Authorization` header of the request as a Bearer token. If the JWT is found, it will be decoded and passed to the function along with the `done` function. The function will check the `user` object in the JWT to see if it is valid. If it's valid, it will call the `done` function with the `user` object, indicating that the user has been authenticated, else it will call the `done` function with an error.

- For the `/signup` route, we'll set up a local strategy, which uses the `passport-local` module to authenticate users with a username (in this case, their email address) and password. When a user signs up, we'll create a new `User` document in the database and return it to Passport.

```javascript
   passport.use(
	"signup",
	new localStrategy(
		{
			usernameField: "email",
			passwordField: "password",
			passReqToCallback: true
		},
		async (req, email, password, done) => {
			try {

			const user = await UserModel.create({ ...req.body, password });

				return done(null, user);
			} catch (error) {
				done(error);
			}
		}
	)
   );
```

- The login strategy will also use the `passport-local` module. When a user logs in, we search the database for a matching email address and verify that the user exists in the database. If the login is successful, we return the `user` object to Passport.

```javascript
   passport.use(
	"login",
	new localStrategy(
		{
			usernameField: "email",
			passwordField: "password",
			passReqToCallback: true
		},
		async (req, email, password, done) => {
			try {
				const user = await UserModel.findOne({ email });

				if (!user) {
					return done(null, false, { message: "User not found" });
				}

				return done(null, user, { message: "Logged in Successfully" });
			} catch (error) {
				return done(error);
			}
		}
	)
);

```

We still need to verify the user's password. In `/src/models/user.models.js`, create a method called `isValidPassword()` in the `User` model. 

```javascript
UserModel.methods.isValidPassword = async function (password) {
	const user = this;

	const match = await bcrypt.compare(password, user.password);

	return match;
};
```

`isValidPassword` takes `password` as an argument and compares it to the user's hashed password using `bcrypt`'s `compare` method. The method returns a boolean value indicating whether the passwords match. 

In src/authentication/passport.js, call `isValidPassword` to validate the user's password in the login strategy: 

```javascript
const validate = await user.isValidPassword(password);

if (!validate) {
	return done(null, false, {message: "Wrong Password" });
}
```

The complete file should look like this:

```javascript
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const { UserModel } = require("../models");

const JWTstrategy = require("passport-jwt").Strategy;
const ExtractJWT = require("passport-jwt").ExtractJwt;

passport.use(
	new JWTstrategy(
		{
			secretOrKey: process.env.JWT_SECRET,
			jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
		},
		async (token, done) => {
			try {
				return done(null, token.user);
			} catch (error) {
				done(error);
			}
		}
	)
);

passport.use(
	"signup",
	new localStrategy(
		{
			usernameField: "email",
			passwordField: "password",
			passReqToCallback: true
		},
		async (req, email, password, done) => {
			try {

				const user = await UserModel.create({ ...req.body, password });

				return done(null, user);
			} catch (error) {
				done(error);
			}
		}
	)
);

passport.use(
	"login",
	new localStrategy(
		{
			usernameField: "email",
			passwordField: "password",
			passReqToCallback: true
		},
		async (req, email, password, done) => {
			try {
				const user = await UserModel.findOne({ email });

				if (!user) {
					return done(null, false, { message: "User not found" });
				}

				const validate = await user.isValidPassword(password);

				if (!validate) {
					return done(null, false, { message: "Wrong Password" });
				}

				return done(null, user, { message: "Logged in Successfully" });
			} catch (error) {
				return done(error);
			}
		}
	)
);
```


## Validation with Joi
To validate user input, we'll be using the [Joi library](https://joi.dev/). Joi provides a simple, yet powerful way to define and validate data structures in Node.js applications.

- In the `/validators` directory, create a file called `author.validator.js`:

```javascript
const Joi = require("joi");

const newArticleValidationSchema = Joi.object({
	title: Joi.string().trim().required(),
	body: Joi.string().trim().required(),
	description: Joi.string().trim(),
	tags: Joi.string().trim(),
});

const updateArticleValidationSchema = Joi.object({
	title: Joi.string().trim(),
	body: Joi.string().trim(),
	description: Joi.string().trim(),
	tags: Joi.string().trim(),
	state: Joi.string().trim(),
});

const newArticleValidationMW = async (req, res, next) => {
	const article = req.body;
	try {
		await newArticleValidationSchema.validateAsync(article);
		next();
	} catch (error) {
		return next({ status: 406, message: error.details[0].message });
	}
};

const updateArticleValidationMW = async (req, res, next) => {
	const article = req.body;
	try {
		await updateArticleValidationSchema.validateAsync(article);
		next();
	} catch (error) {
		return next({ status: 406, message: error.details[0].message });
	}
};

module.exports = {
	newArticleValidationMW,
	updateArticleValidationMW,
};
```

We're exporting two middleware functions, `newArticleValidationMW` and `updateArticleValidationMW`. `newArticleValidationMW` uses the `newArticleValidationSchema` to verify that the request body of a new article request contains all of the required fields (`title`, `body`) and all the provided fields are in the correct format. If all of the fields are valid, it calls the next function to continue with the request. `updateArticleValidationMW`, is similar to the first, but it uses the `updateArticleValidationSchema` to validate the request body of an update article request. 

Both functions use the `validateAsync` method provided by the Joi library to perform the validation. This method takes an object (the request body) and returns a promise that is rejected if the object is invalid or resolved if it is valid.

- In the `/validators` directory, create a file called `user.validator.js`:

```javascript
const Joi = require("joi");

const validateUserMiddleware = async (req, res, next) => {
	const user = req.body;
	try {
		await userValidator.validateAsync(user);
		next();
	} catch (error) {
		return next({ status: 406, message: error.details[0].message });
	}
};

const userValidator = Joi.object({
	firstname: Joi.string().min(2).max(30).required(),
	lastname: Joi.string().min(2).max(30).required(),
	email: Joi.string().email({
		minDomainSegments: 2,
		tlds: { allow: ["com", "net"] },
	}),
	password: Joi.string()
		.pattern(new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})"))
		.required(),
});

module.exports = validateUserMiddleware;
```

- In `/validators/index.js`:

```javascript
const userValidator = require("./user.validator");
const {
	newArticleValidationMW,
	updateArticleValidationMW,
} = require("./author.validator");

module.exports = {
	userValidator,
	newArticleValidationMW,
	updateArticleValidationMW,
};
```


With authentication and validation in place, we're ready to start building the API routes and controllers. In the next article, we'll dive into the details of implementing the routes and controllers for our blog API. Stay tuned!