DEV Community

Cover image for Implementing JWT Authentication in Node.js
john mbugua
john mbugua

Posted on

Implementing JWT Authentication in Node.js

Page Content

Getting started

Introduction to JSON Web Token

What's up, techies? đź‘‹In this guide, we are going to explore the magic of JSON Web Tokens (JWT) and how they can make your authentication process secured. Buckle up,and get ready to implement robust authentication in your Node.js apps. Let's get started!

What is JSON Web Token

JSON Web Token (JWT) is a compact and self-contained way to securely transmit information between parties as a JSON object. It is an open standard (RFC 7519) that ensures the information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (HMAC) or a public/private key pair (RSA or ECDSA).

Why use JSON Web Token

JWT is stateless, meaning they don't require server-side session storage, making them highly scalable. They provide a secure way to transmit information and can be easily verified. JWT are also versatile, working well for authentication, authorization, and information exchange in web applications.

When You Should Use JSON Web Token

Authorization:

  • JWT are ideal for authorization. Once logged in, users can access routes, services, and resources with their JWT. Single Sign-On (SSO) commonly uses JWT due to its small overhead and cross-domain capabilities.

Information Exchange:

  • JWT securely transmit information between parties. With digital signatures, you can verify the sender's identity and ensure the content has not been tampered with.

The Structure of JSON Web Token

JWT consists of three parts separated by dots (.):

  1. Header
  • Specifies the token type (JWT) and signing algorithm (e.g., HS256). Encoded in Base64Url.
{
  "alg": "HS256",
  "typ": "JWT"
}

Enter fullscreen mode Exit fullscreen mode
  1. Payload

Contains claims about an entity (usually the user). There are three types of claims:

  • Registered claims: Predefined, optional claims like iss (issuer), exp (expiration), sub (subject), and aud (audience).

  • Public claims: Custom claims defined in the IANA JWT Registry or as a URI to avoid collisions.

  • Private claims: Custom claims agreed upon by parties using the token.

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Enter fullscreen mode Exit fullscreen mode

3.Signature

Verifies the token's integrity. Created using the encoded header, encoded payload, a secret, and the specified algorithm.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Enter fullscreen mode Exit fullscreen mode

The resulting JWT looks like this: xxxxx.yyyyy.zzzzz. This format is compact and easily used in web environments.

Setting Up JSON Web Token in a Node.js Application

Before we dive into implementing JSON Web Tokens (JWT) in your Node.js application, let's start with the basics, installation. To get started with JWT, you will need to install the jsonwebtoken package. This library will help you create, sign, and verify JWTs in your application.

Installation

First, you need to install the jsonwebtoken package using npm. Run the following command in your Node.js project directory

npm install jsonwebtoken

This will add the jsonwebtoken package to your project's dependencies.

Next Steps

Now that we have JWT installed, we are ready to implement it in our Node.js application. In the next section, we will cover how to use JSON Web Tokens for user authentication, including creating and verifying tokens using instance methods. Stay tuned!

stay tuned

Using JSON Web Token with a Node.js Application

I know this article is not about Mongoose, schemas, and models, but to effectively implement JSON Web Tokens (JWT) in our Node.js application, we will need to touch on these concepts briefly. Let's assume you have already set up your schema logic using Mongoose. For example:


const mongoose = require('mongoose');
const Schema = mongoose.Schema;


const UserSchema = new Schema({
  name: {
    type: String,
    required: [true, "Please insert your name"],
    minLength: [3, "Your name is too short"],
  },
  email: {
    type: String,
    required: [true, "Please insert your email"],
    match: [
      /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    ],
    unique: true,
  },
  password: {
    type: String,
    required: [true, "Please insert  password"],
    minLength: [8, "Password is less than 8 character"],
  },
});
Enter fullscreen mode Exit fullscreen mode

Now, we will focus on creating a JWT using an instance method.

Creating a JSON Web Token Using an Instance Method

To start, we will create an instance method in our Mongoose model that will generate a JWT for a user. This instance method will be defined in the file where we have our model logic based on Mongoose.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const jwt = require("jsonwebtoken");


const UserSchema = new Schema({
  // your schema logic code
});

UserSchema.methods.createToken = function () {
  return jwt.sign(
    { userId: this._id, userName: this.name },
    process.env.SECRET,
    { expiresIn: process.env.JWT_LIFETIME }
  );
};

Enter fullscreen mode Exit fullscreen mode

Explanation

  • We assume your schema for example my case UserSchema is already defined in your Mongoose model. We add an instance method createToken ** to the UserSchema. This method uses **jwt.sign to create a token with the user's ID and name ** as **payload, a secret key from environment variables, and an expiration time also from environment variables.

This instance method can be called on any user document to generate a JWT, making it easy to handle user authentication in your application.

Integrating JWT with Login and Registration

Now that we have the instance method to create JWTs, let us see how we can use it in the login and registration logic of our Node.js application. Here is how you can integrate the createToken method:

User registration

During user registration, once the user is successfully created, we can generate a JWT and send it back to the client.

const UserModel = require("../models/User");
const { StatusCodes } = require("http-status-codes");
const { BadRequestError, UnauthenticatedError } = require("../errors");

const register = async (req, res) => {
  const { name, email, password } = req.body;

  // Create a new user
  const user = await UserModel.create({ ...req.body });

  // Generate a JWT
  const token = user.createToken();

  // Respond with the user name and token
  res.status(StatusCodes.CREATED).json({ user: { name: user.getName() }, token });
};

module.exports = {
  register,
};

Enter fullscreen mode Exit fullscreen mode

User login

For user login, after validating the user's credentials, we can generate a JWT and send it back to the client.

const UserModel = require("../models/User");
const { StatusCodes } = require("http-status-codes");
const { BadRequestError, UnauthenticatedError } = require("../errors");

const login = async (req, res) => {
  const { email, password } = req.body;

  // Validate the request
  if (!email || !password) {
    throw new BadRequestError("Please provide email and password");
  }

  // Find the user by email
  const userLogin = await UserModel.findOne({ email });

  // If user is not found
  if (!userLogin) {
    throw new UnauthenticatedError("Invalid credentials");
  }

  // Check if the password is correct
  const isPasswordCorrect = await userLogin.comparePassword(password);

  // If the password is incorrect
  if (!isPasswordCorrect) {
    throw new UnauthenticatedError("Invalid credentials");
  }

  // Generate a JWT
  const token = userLogin.createToken();

  // Respond with the user name and token
  res.status(StatusCodes.OK).json({ user: { name: userLogin.getName() }, token });
};

module.exports = {
  login,
};

Enter fullscreen mode Exit fullscreen mode

In these examples, after a user registers or logs in successfully, we generate a JWT using the createToken instance method and send it back in the response. This token can then be used by the client to authenticate subsequent requests.

Next Steps

With the instance method in place and integrated into your login and registration logic, you can now secure your routes and verify tokens in your Node.js application. In the following section, we will cover how to use this token for securing routes. Stay tuned!

Securing Routes with JSON Web Token in a Node.js Application

With our JWT creation logic in place, the next step is to secure our routes. To do this, we will create a file to store our authentication logic and use it as middleware in our application.

Creating Middleware for Securing Routes

We will create a middleware function that will verify the JWT in incoming requests. This middleware will check the authorization header, verify the token, and attach the user information to the request object if the token is valid.

const UserModel = require("../models/User");
const jwt = require("jsonwebtoken");
const { UnauthenticatedError } = require("../errors");

const auth = async (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    throw new UnauthenticatedError("Authentication invalid");
  }

  const token = authHeader.split(" ")[1];

  try {
    const payload = jwt.verify(token, process.env.SECRET);
    req.user = { userId: payload.userId };
    next();
  } catch (err) {
    console.log(err);
    throw new UnauthenticatedError("Authentication invalid");
  }
};

module.exports = auth;

Enter fullscreen mode Exit fullscreen mode

Using the Middleware

Now that we have our authentication middleware, we can use it to secure our routes in various ways. Here are three different ways to apply the middleware:

1. Applying Middleware to Specific Routes

const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const { getUserInfo } = require('../controllers/userController');

router.get('/user-info', auth, getUserInfo);

module.exports = router;

Enter fullscreen mode Exit fullscreen mode

2. Applying Middleware to All Routes in a Router

const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const { getUserInfo, getJobs } = require('../controllers/userController');

router.use(auth);

router.get('/user-info', getUserInfo);
router.get('/jobs', getJobs);

module.exports = router;

Enter fullscreen mode Exit fullscreen mode

3. Applying Middleware in the Main File

const express = require('express');
const app = express();
const auth = require('./middleware/auth');
const jobRoutes = require('./routes/jobRoutes');

app.use("/api/v1/jobs", auth, jobRoutes);

Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, we covered the basics of JSON Web Tokens (JWT) and how to use them in a Node.js application. We discussed what JWT is, why and when to use it, and how to implement it for user authentication. We also looked at securing routes using middleware in different ways. With these steps, you can enhance the security of your Node.js applications by ensuring only authenticated users can access protected resources.

For more detailed information on JSON Web Tokens, you can visit the jsonwebtoken documentation.

Happy coding image

Top comments (0)