Node.js API Authentication with JWT (Json Web Token) - Auth Middleware
Tags: node, mongodb, javascript, webdev
Published: 2021-06-04 17:59:20

Hi, Today we are going to implement **API** authentication with **JWT** in node.js application. Authentication is most important feature in every application. Even if you are beginner feel free to try this tutorial, we gonna start from scratch. We also gonna write **Auth** middleware, which allow only authenticated people to access the route.

[Authentication With JWT (Json Web Token) In React](https://dev.to/jahangeer/authentication-with-jwt-json-web-token-in-react-mern-auth-caj)

For better understanding watch **Demo Video**
{% youtube qdQaXSj75w4 %}
[Source Code](https://github.com/sk-Jahangeer/node-auth-jwt)

Let's Start Coding...

**App Overview :**

**Project Structure**
![Project_structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1q3iumqyv9u8g6vtrhh.png)
Following table shows the overview of the Rest APIs that be exported :

|Methods|Urls           |Actions  |
| ------| ------------- | ------- |
|POST   |/api/users      | Create user |
|POST   |/api/auth|Authenticate user|
|GET   |/api/users/me |Get authenticated user details|

**Create Node.js App and Install dependencies**
```javascript
$    mkdir node-auth-jwt
$    cd node-auth-jwt
$    npm init --yes
$    npm install express mongoose jsonwebtoken bcrypt joi dotenv
```
**express :** Express is minimal and flexible Node.js web applicaton framework.
**mongoose :** Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js.
**jsonwebtoken :** It's a compact URL of representing claims to be transferred between two parties.
**bcrypt :** It's a password hashing function.
**joi :** Joi is an object schema description language and validator for javascript objects.
**dotenv :** It loads environment variables from a .env file.

**Setup Express Web Server**
/index.js
```javascript
require("dotenv").config();
const express = require("express");
const app = express();

app.use(express.json());

const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Listening on port ${port}...`));
```
**Configure Environmental Variables**
/.env
```env
DB = "mongodb://localhost/node-auth-api/"
JWTPRIVATEKEY = "secretkey"
SALT = 10
```
**Configure MongoDB Database**
/db.js
```javascript
const mongoose = require("mongoose");

module.exports = async () => {
    try {
        const connectionParams = {
            useNewUrlParser: true,
            useUnifiedTopology: true,
            useCreateIndex: true,
        };
        await mongoose.connect(process.env.DB, connectionParams);
        console.log("connected to database.");
    } catch (error) {
        console.log("could not connect to database", error);
    }
};
```
Import **db.js** in **index.js** and call it
```javascript
//...
const connection = require("./db");
const express = require("express");
const app = express();

connection();
app.use(express.json());
//...
```
**Create User Model**
/models/user.js
```javascript
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const jwt = require("jsonwebtoken");
const Joi = require("joi");

const userSchema = new Schema({
    name: {
        type: String,
        required: true,
    },
    email: {
        type: String,
        required: true,
    },
    password: {
        type: String,
        required: true,
    },
});

userSchema.methods.generateAuthToken = function () {
    const token = jwt.sign({ _id: this._id }, process.env.JWTPRIVATEKEY);
    return token;
};

const User = mongoose.model("user", userSchema);

const validate = (user) => {
    const schema = Joi.object({
        name: Joi.string().required(),
        email: Joi.string().email().required(),
        password: Joi.string().required(),
    });
    return schema.validate(user);
};

module.exports = { User, validate };
```
What we have done : 
* We have created user table with name, email and password.
* With **JWT**, we generate token with payload of user id.
* With **Joi**, we gonna validate data.

**Register Route**
/routes/users.js
```javascript
const { User, validate } = require("../models/user");
const bcrypt = require("bcrypt");
const express = require("express");
const router = express.Router();

router.post("/", async (req, res) => {
    try {
        const { error } = validate(req.body);
        if (error) return res.status(400).send(error.details[0].message);

        const user = new User(req.body);

        const salt = await bcrypt.genSalt(Number(process.env.SALT));
        user.password = await bcrypt.hash(user.password, salt);
        await user.save();

        res.send(user);
    } catch (error) {
        console.log(error);
        res.send("An error occured");
    }
});

module.exports = router;
```
**Login Route**
/routes/auth.js
```javascript
const { User } = require("../models/user");
const bcrypt = require("bcrypt");
const Joi = require("joi");
const express = require("express");
const router = express.Router();

router.post("/", async (req, res) => {
    try {
        const { error } = validate(req.body);
        if (error) return res.status(400).send(error.details[0].message);

        const user = await User.findOne({ email: req.body.email });
        if (!user) return res.status(400).send("Invalid email or password");

        const validPassword = await bcrypt.compare(
            req.body.password,
            user.password
        );
        if (!validPassword)
            return res.status(400).send("Invalid email or password");

        const token = user.generateAuthToken();
        res.send(token);
    } catch (error) {
        console.log(error);
        res.send("An error occured");
    }
});

const validate = (user) => {
    const schema = Joi.object({
        email: Joi.string().email().required(),
        password: Joi.string().required(),
    });
    return schema.validate(user);
};

module.exports = router;
```
**Auth Middleware**
/middleware/auth.js
```javascript
const jwt = require("jsonwebtoken");

module.exports = (req, res, next) => {
    try {
        const token = req.header("x-auth-token");
        if (!token) return res.status(403).send("Access denied.");

        const decoded = jwt.verify(token, process.env.JWTPRIVATEKEY);
        req.user = decoded;
        next();
    } catch (error) {
        res.status(400).send("Invalid token");
    }
};
```
**User Get Route**
/routes/users.js
```javascript
const auth = require("../middleware/auth");
//...

router.get("/me", auth, async (req, res) => {
    try {
        const user = await User.findById(req.user._id).select("-password -__v");
        res.send(user);
    } catch (error) {
        console.log(error);
        res.send("An error occured");
    }
});

module.exports = router;
```
**Import Routes in Index.js**
```javascript
//...
const users = require("./routes/users");
const auth = require("./routes/auth");
//...

app.use(express.json());

app.use("/api/users", users);
app.use("/api/auth", auth);

//...
```
That's it Run the server and test the APIs. If you found any mistakes or making code better please let me know in comment. I hope you have learned something.

**Thank you...**