Authentication with JSON web tokens

Topics: Node js, jsonwebtoken

In this article, we are going to talk about JSON web tokens and how they are used for authenticating your API endpoints.

What are JSON web tokens?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

They are mainly used for authentication. When a user logs in to a website they are given a token, so each request that the user will make will require the token. This indicates to the server that the user is permitted to make the requests.

JWT can be used in authorization and information exchange.

why use jsonwebtoken?

  • More reliable than cookies and sessions

  • Provide simple tokens for verification and authorization

  • json is stardard in most programming languages

Prerequisite

You need to have at least some basic knowledge of the following:

  • Javascript

  • Node

  • MongoDB

  • Postman - for making HTTP requests

Let's start

  1. Create our project directory

    create the directory that will hold our project, then initialize our node project using npm init -y. This creates a package.json file, this file records the metadata about a project which is required before publishing to NPM, and also defines functional attributes of a project that npm uses to install dependencies, run scripts, and identify the entry point to our package.

     mkdir authentication-jwt
     cd authentication-jwt
     npm init -y
    
  2. Install Dependencies

    install express mongoose dotenv JSON web token, this is are all the dependencies we need. You can install them using the following commands.

    we shall use MongoDB for the database.

     npm i express mongoose dotenv jsonwebtoken
    
  3. Create directories and files

    Create the directories and add an index.js file to act as our entry point

    Here is how our project looks like

  4. Create the server and connect it to the database

    Let's start with index.js, We create the server and connect it to our database MongoDB using mongoose.

     import express from "express";
     import dotenv from "dotenv";
     import auth from "./routes/authRoute.js";
     import mongoose from "mongoose";
    
     dotenv.config();
    
     const PORT = 9000;
    
     const app = express();
    
     app.use(express.urlencoded({ extended: true }));
     app.use(express.json());
    
     app.use("/api/v1/auth", auth);
    
     mongoose
       .connect(process.env.MONGO_URI)
       .then(() => {
         app.listen(PORT, () =>
           console.log(`Server is running on port ${PORT} ...`)
         );
       })
       .catch((err) => console.log(err));
    

    Before we start our server let us first create the .env file and place our environmental variables there.

     MONGO_URI= //place you mongoDB connection string here
     PORT=9000
    

    Also, update the start scripts in our package.json. like below:

       "scripts": {
         "start": "node index.js",
         "dev": "nodemon index.js"
       }
    

    We can now start our server with the npm run dev command. If everything is okay, your terminal should look like this

     [nodemon] restarting due to changes...
     [nodemon] starting `node index.js`
     Server is running on port 9000 ...
    
  5. Create a user model and implement login and signup functionalities.

    define the user schema which will show how the user data is organized. Also, create the signup and login functionalities by attaching a static function to our schema and then validate the user whose information will be stored in the database

    note: passwords cannot be stored raw in the database.

    This is the process that we will follow for the static sign-up function

    • Get user input.

    • Validate user input.

    • Validate if the user already exists.

    • Encrypt the user password.

    • Create a user in our database Here is the sign in process

    • Get user input.

    • Validate user input.

    • Validate if the user exists.

    • Verify the user password against the password we saved earlier in our database.

```javascript

import mongoose from "mongoose";
import bcrypt from "bcryptjs";

export const AuthSchema = new mongoose.Schema({
  full_name: {
    type: String,
    required: true,
  },

  email: {
    type: String,
    required: true,
  },

  password: {
    type: String,
    required: true,
  },
});

//static signup method

AuthSchema.statics.signup = async function (full_name, email, password) {
  if (!full_name || !email || !password) {
    throw Error("Please fill in all fields");
  }

  const exists = await this.findOne({ email: email });

  if (exists) {
    throw Error("Email already exists");
  }

  const salt = await bcrypt.genSalt(10);
  const hash = await bcrypt.hash(password, salt);

  const newUser = await this.create({
    full_name,
    email,
    password: hash,
  });

  return newUser;
};

//login method

AuthSchema.statics.login = async function (email, password) {
  if (!email || !password) {
    throw Error("Please fill in all fields");
  }

  const exists = await this.findOne({ email: email });

  if (!exists) {
    throw Error("No user with this email");
  }

  const match = await bcrypt.compare(password, exists.password);
//if no match
  if (!match) {
    throw Error("Incorrect password");
  }
  return exists;
};

const Auth = mongoose.model("Auth", AuthSchema);

export default Auth;
```
  1. Create login and sign-up routes and add the controller functions

    After we have created our login and sign-up functionalities let's create our routes. In the routes folder create authRoute.js file then add the following code:

     import express from "express";
     import { userLogin, userSignup } from "../controllers/auth.js";
    
     const router = express.Router();
    
     router.route("/login").post(userLogin);
     router.route("/signup").post(userSignup);
    
     export default router;
    

    Let's navigate to the controller folder and add an authController.js file. Here is where we shall implement and sign our JSON web token. First, we have to add our secret key in our .env file, Create the token and sign it using our secret key

     import jwt from "jsonwebtoken";
     import dotenv from "dotenv";
     import Auth from "../model/authModel.js";
    
     dotenv.config();
    
     const createToken = (_id) => {
       return jwt.sign({ _id }, process.env.SECRET_KEY, { expiresIn: "2d" });
     };
    
     export const userLogin = async (req, res) => {
       const { email, password } = req.body;
    
       try {
         const user = await Auth.login(email, password);
    
         const token = createToken(user._id);
    
         res.status(200).json({ user, token, email });
       } catch (error) {
         res.status(400).json({ error: error.message });
       }
     };
    
     export const userSignup = async (req, res) => {
       const { full_name, email, password } = req.body;
    
       try {
         const user = await Auth.signup(full_name, email, password);
    
         const token = createToken(user._id);
    
         res.status(200).json({ token, user });
       } catch (error) {
         res.status(400).json({ error: error.message });
       }
     };
    

    Now we can use postman to check if our server is running as expected. If you send a post request, This is the response you will receive.

    we can add a middleware function that will help us to add the token in the header so that during a log in a user will require a token to get authorized.

    In the middleware, folder add the file middleware.js then add the following code snippet

     import jwt from "jsonwebtoken";
    
     const auth = async (req, res, next) => {
       const token = req.headers.authorization.split(" ")[1];
       const customAuth = token.length < 500;
    
       let decodedData;
    
       if (token && customAuth) {
         decodedData = jwt.verify(token, process.env.SECRET_KEY);
         req.userId = decodedData._id;
       }
       next();
     };
    
     export default auth;
    

    then update our routes to accept the middleware

     import auth from "../middleware/middleware.js"
     router.route("/login").post(auth, userLogin);
     router.route("/signup").post(auth, userSignup);
    

    you will need to add the token in the authorization header to be able to access the API endpoints.

Conclusion

There are many ways to achieve what we have done in this article. But you have seen how to authenticate your API endpoints with JWT

you can get the whole code on

Reference

jwt.io

Node.js

Did you find this article valuable?

Support Abel wanyonyi's Blog by becoming a sponsor. Any amount is appreciated!