How to Implement CORS and Authentication in NodeJS

Photo by FLY:D on Unsplash

How to Implement CORS and Authentication in NodeJS

Olubisi Idris Ayinde
ยทJan 6, 2022ยท

10 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

In this tutorial, we will learn how to authenticate users, secure endpoints and Cross-Origin Resource Sharing (CORS) in NodeJs.

Prerequisites

You'll need the following to follow along with this tutorial:

  • A working grasp of JavaScript.
  • A good understanding of Node.js.
  • A working knowledge of MongoDB or another database of your choice.
  • Postman and a basic understanding of how to utilize it.

What is authentication and authorization

Security uses authentication and authorization, especially when gaining access to a system. But there's a big difference between getting into a house (authentication) and what you can do once you're there (authorization).

Authentication

Authentication is the process of confirming a user's identity by obtaining credentials and utilizing those credentials to validate the user's identity. If the certificates are valid, the authorization procedure begins.

You were already familiar with the authentication procedure because we all go through it daily, whether at work (logging onto your computer) or at home (passwords) (logging into a website). However, most "things" connected to the Internet require you to provide credentials to prove your identity.

Authorization

The process of granting authenticated users access to resources by verifying whether they have system access permissions is known as authorization. In addition, authorization allows you to restrict access privileges by granting or denying specific licenses to authenticated users.

After the system authenticates your identity, authorization occurs, providing you full access to resources such as information, files, databases, finances, locations, and anything else. On the other hand, approval impacts your ability to access the system and the extent to which you can do so.

Cross-Origin Resource Sharing (CORS)

CORS is an HTTP header-based system that allows a server to specify any other origins (domain, scheme, or port) from which a browser should enable resources to be loaded other than its own. CORS also uses a system in which browsers send a "preflight" request to the server hosting the cross-origin help to ensure that it will allow the actual request.

We will be using JSON web token standard for representing claims between two parties

What is JWT

JSON Web Tokens (JWT) are an open industry standard defined by RFC 7519 to represent claims between two parties. You can use jwt.io to decode, verify, and create JWT, for example.

JWT defines a concise and self-contained way for exchanging information between two parties as a JSON object. This information may be reviewed and trusted because it is signed. JWTs can be signed with a secret (using the HMAC algorithm) or a public/private key pair from RSA or ECDSA. We'll see some examples of how to use them in a bit.

Let's get started

Node.js development using a token for authentication

To get started, we'll need to set up our project.

Please navigate to a directory of your choice on your machine and open it in the terminal to launch Visual Studio Code.

Then execute:

code.

Note: If you don't have Visual Studio Code installed on your computer, code . won't work.

A - Create a directory and set it up npm

Create a directory and initialize npm by typing the following command:

  • Windows power shell
mkdir cors-auth-project

cd cors-auth-project

npm init -y
  • Linux
mkdir cors-auth-project

cd cors-auth-project

npm init -y

B - Create files and directories

In step A, we initialized npm with the command npm init -y, which automatically created a package.json.

We will create the model, middleware, config directory and their files, for example, user.js, auth.js, database.js using the commands below.

mkdir model middleware config

touch config/database.js middleware/auth.js model/user.js

We can now create the index.js and app.js files in the root directory of our project with the command.

touch app.js index.js

As seen in the illustration below:

folder structure

C - Install dependencies

We'll install several dependencies like mongoose, jsonwebtoken, express dotenv bcryptjs cors and development dependency like nodemon to restart the server as we make changes automatically.

Because I'll be utilizing MongoDB in this lesson, we'll install mongoose, and the user credentials will be checked against what we have in our database. As a result, the entire authentication process isn't limited to the database we'll use in this tutorial.

npm install  cors mongoose express jsonwebtoken dotenv bcryptjs 

npm install nodemon -D

D - Create a Node.js server and connect your database

Now, add the following snippets to your app.js, index.js, database.js, and .env files in that order to establish our Node.js server and connect our database.

In our database.js.

config/database.js:

const mongoose = require("mongoose");

const { MONGO_URI } = process.env;

exports.connect = () => {
  // Connecting to the database
  mongoose
    .connect(MONGO_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
      useFindAndModify: false,
    })
    .then(() => {
      console.log("Successfully connected to database");
    })
    .catch((error) => {
      console.log("database connection failed. exiting now...");
      console.error(error);
      process.exit(1);
    });
};

In our app.js:

auth-cors-project/app.js

require("dotenv").config();
require("./config/database").connect();
const express = require("express");

const app = express();

app.use(express.json());

// Logic goes here

module.exports = app;

In our index.js:

auth-cors-project/index.js

const http = require("http");
const app = require("./app");
const server = http.createServer(app);

const { API_PORT } = process.env;
const port = process.env.PORT || API_PORT;

// server listening 
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Our file, as you can see, requires various environment variables. If you haven't already, create a new .env file and add your variables before running our application.

In our .env.

API_PORT=4001

MONGO_URI= // Your database URI

Edit the scripts object in our package.json to look like the one below to start our server.

"scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

The above snippet was successfully inserted into theapp.js, index.js, and database.js files. So, we started by creating our node.js server in index.js and then importing the app.js file, which already had routes configured.

Then, as mentioned in database.js, we used mongoose to build a database connection.

npm run dev is the command to start our application.

Both the server and the database should be up and running without crashing.

E - Create user model and route

After registering for the first time, we'll establish our schema for the user details, and when logging in, we'll check them against the remembered credentials.

In the model folder, add the following snippet to user.js.

model/user.js

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
  first_name: { type: String, default: null },
  last_name: { type: String, default: null },
  email: { type: String, unique: true },
  password: { type: String },
});

module.exports = mongoose.model("user", userSchema);

Let's create the routes for register and login, respectively.

In app.js in the root directory, add the following snippet for the registration and login.

app.js

// importing user context
const User = require("./model/user");

// Register
app.post("/register", (req, res) => {
// our register logic goes here...
});

// Login
app.post("/login", (req, res) => {
// our login logic goes here
});

F - Implement register and login functionality

These two routes will be implemented in our application. Before storing the credentials in our database, we'll use JWT to sign and bycrypt to encrypt.

We will: - Get user input from the /register route.

  • Verify the user's input.
  • Check to see if the user has already been created.
  • Protect the user's password by encrypting it.
  • Make a user account in our database.
  • Finally, construct a JWT token that is signed.

Modify the /register route structure we created earlier, as shown below.

app.js

// ...

app.post("/register", async (req, res) => {

  // Our register logic starts here
   try {
    // Get user input
    const { firstName, lastName, email, password } = req.body;

    // Validate user input
    if (!(email && password && firstName && lastName)) {
      res.status(400).send("All input is required");
    }

    // check if user already exist
    // Validate if user exist in our database
    const oldUser = await User.findOne({ email });

    if (oldUser) {
      return res.status(409).send("User Already Exist. Please Login");
    }

    //Encrypt user password
    encryptedUserPassword = await bcrypt.hash(password, 10);

    // Create user in our database
    const user = await User.create({
      first_name: firstName,
      last_name: lastName,
      email: email.toLowerCase(), // sanitize
      password: encryptedUserPassword,
    });

    // Create token
    const token = jwt.sign(
      { user_id: user._id, email },
      process.env.TOKEN_KEY,
      {
        expiresIn: "5h",
      }
    );
    // save user token
    user.token = token;

    // return new user
    res.status(201).json(user);
  } catch (err) {
    console.log(err);
  }
  // Our register logic ends here
});

// ...

Note: Update your .env file with a TOKEN_KEY, which can be a random string.

Using Postman to test the endpoint, we'll get the below response after a successful registration.

user registration

We will: - Get user input for the /login route.

  • Verify the user's input.
  • Check to see if the user is genuine.
  • Compare the user password to the one we saved earlier in our database.
  • Finally, construct a JWT token that is signed.

Make the /login route structure that we defined earlier look like this.

// ...

app.post("/login", async (req, res) => {

  // Our login logic starts here
   try {
    // Get user input
    const { email, password } = req.body;

    // Validate user input
    if (!(email && password)) {
      res.status(400).send("All input is required");
    }
    // Validate if user exist in our database
    const user = await User.findOne({ email });

    if (user && (await bcrypt.compare(password, user.password))) {
      // Create token
      const token = jwt.sign(
        { user_id: user._id, email },
        process.env.TOKEN_KEY,
        {
          expiresIn: "5h",
        }
      );

      // save user token
      user.token = token;

      // user
      return res.status(200).json(user);
    }
    return res.status(400).send("Invalid Credentials");

  // Our login logic ends here
});

// ...

Using Postman to test, we'll get the response shown below after a successful login.

user login

G - Create middleware for authentication

A user can be created and logged in successfully. Despite this, we'll establish a route that requires a user token in the header, which will be the JWT token we created before.

Add the following snippet inside auth.js.

middleware/auth.js

const jwt = require("jsonwebtoken");

const config = process.env;

const verifyToken = (req, res, next) => {
  const token =
    req.body.token || req.query.token || req.headers["x-access-token"];

  if (!token) {
    return res.status(403).send("A token is required for authentication");
  }
  try {
    const decoded = jwt.verify(token, config.TOKEN_KEY);
    req.user = decoded;
  } catch (err) {
    return res.status(401).send("Invalid Token");
  }
  return next();
};

module.exports = verifyToken;

Create the /welcome route and edit app.js with the following code to test the middleware.

app.js

const auth = require("./middleware/auth");

app.post("/welcome", auth, (req, res) => {
  res.status(200).send("Welcome to FreeCodeCamp ๐Ÿ™Œ");
});

When we try to access the /welcome route we just built without sending a token in the header with the x-access-token key, we get the following response.

failed response

We can now re-test by adding a token in the header with the key x-access-token.

The response is seen in the image below.

success response

Implementing Cross-Origin Resource Sharing (CORS)

CORS is a node.js package that provides a Connect/Express middleware that can be used to enable CORS with a variety of parameters.

  1. Easy to Use (Enable All CORS Requests)

Adding the following snippet to app.js allows us to add cors to our application and enable all CORS requests.

// ...

const cors = require("cors") //Newly added
const app = express();

app.use(cors()) // Newly added


app.use(express.json({ limit: "50mb" }));

// ...
  1. Enable CORS for a Single Route

Using the /welcome route as an example, We may activate CORS for a single route in our application by adding the following snippet in app.js.

app.get('/welcome', cors(), auth, (req, res) => {
  res.status(200).send("Welcome to FreeCodeCamp ๐Ÿ™Œ ");
});
  1. Configuring CORS

As shown below, we can set options in the cors package by adding parameters to configure it.

// ...

const corsOptions = {
  origin: 'http://example.com',
  optionsSuccessStatus: 200 // for some legacy browsers
}

app.get('/welcome', cors(corsOptions), auth, (req, res) => {
  res.status(200).send("Welcome to FreeCodeCamp ๐Ÿ™Œ ");
});

// ...

Kindly check out NPM CORS PACKAGE to read more about Cross-Origin Resource Sharing.

You can click here to check the complete code on GitHub.

Conclusion

In this article, we learned about JWT, authentication, authorization, and CORS and how to create an API in Node.js that uses a JWT token for authentication.

Thank you!

I'd love to connect with you at Twitter | LinkedIn | GitHub | Portfolio

See you in my next blog article. Take care!!!

ย 
Share this