DEV Community 👩‍💻👨‍💻

Cover image for Understanding API key authentication in Node.js
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Understanding API key authentication in Node.js

Written by Muhammed Ali✏️

When building an API with Node.js, there are different options available regarding authentication. Some of these include the JSON Web Token, OAuth, the API key, and more. In this article, we’ll learn how to authenticate a Node.js API using API Keys.

Using API keys has an advantage if you want to set a limit or track how often a particular user is using an API. By using API keys, the user doesn’t need to worry about multi-factor authentication with their username and password. Your API’s user will be able to automate data fetching on the application.

In this tutorial, we’ll create an API with Node.js. Then, we’ll create an authentication system that creates an API key whenever a user registers on the application. With the newly created API key, the user will be able to access all of the routes on the API.

You can find the complete code on GitHub. To follow along with this article, you’ll need:

  • Basic knowledge of Node.js
  • Node.js installed on your machine

Table of contents

Initial project setup

First, we’ll handle all the installation and initial setup to run our application. We’ll use Express to develop the API and Nodemon to run the API server and listen for changes in the code in real-time.

We’ll have to install them, but first, create a folder for your project and run the following command to create a package.json file for your project:

$ npm init -y
Enter fullscreen mode Exit fullscreen mode

Now, update the "scripts" module in your package.json file with the code below so that we'll be able to run the server with Nodemon:

...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon Server.js"
  }
...
Enter fullscreen mode Exit fullscreen mode

Later in the article, we’ll create the Server.js file where we’ll run the server from. Now, install Nodemon and Express by running the following command:

npm install express nodemon
Enter fullscreen mode Exit fullscreen mode

Build an authentication system

The authentication system takes in a given username and creates user data, containing the username, API key, and a count of usage on a particular day. We’ll need the count so that we can set a limit on how many times a user can use the API on a particular day.

We’ll start by creating a function called genAPIKey() that generates the API when a new user is created. The function will generate a base-36 string that contains 30 characters within A-Z and 0-9, which will represent the API key. You can start by creating a new JavaScript file called apiAuth.js and pasting the following code:

const genAPIKey = () => {
  //create a base-36 string that contains 30 chars in a-z,0-9
  return [...Array(30)]
    .map((e) => ((Math.random() * 36) | 0).toString(36))
    .join('');
};
Enter fullscreen mode Exit fullscreen mode

Next, we’ll develop a function that creates the user data when a username is entered. We‘ll store this new user in an array, so we need some initial data to get started. Create a new file called initialData.js and paste the following code:

const users = [
  {
    _id: 1587912,
    api_key: "rwuy6434tgdgjhtiojiosi838tjue3",
    username: "username",
    usage: [{ date: "2022-10-10", count: 17 }],
  },
];
const Countries = [
  { _id: 1, name: "Nigeria" },
  { _id: 2, name: "China" },
];
module.exports = { users, Countries };
Enter fullscreen mode Exit fullscreen mode

Now, we’ll develop the function that creates a user by pasting the following code in the apiAuth.js file:

const users = require('./initialData').users; // import initial data
...

const createUser = (_username, req) => {
  let today = new Date().toISOString().split('T')[0];
  let user = {
    _id: Date.now(),
    api_key: genAPIKey(),
    username: _username,
    usage: [{ date: today, count: 0 }],
  };

  console.log('add user');
  users.push(user);
  return user;
};
Enter fullscreen mode Exit fullscreen mode

Next, we’ll develop a function that will authenticate the API key so that you can access specified parts of the API. This function will compare the registered API key with the x-api-key, which will come with the request.

If the request goes through, the count per day increases, and if you’ve reached your max request per day, you’ll receive an error. Paste the following code into your apiAuth.js file:

const authenticateKey = (req, res, next) => {
  let api_key = req.header("x-api-key"); //Add API key to headers
  let account = users.find((user) => user.api_key == api_key);
  // find() returns an object or undefined
  if (account) {
    //If API key matches
    //check the number of times the API has been used in a particular day
    let today = new Date().toISOString().split("T")[0];
    let usageCount = account.usage.findIndex((day) => day.date == today);
    if (usageCount >= 0) {
      //If API is already used today
      if (account.usage[usageCount].count >= MAX) {
        //stop if the usage exceeds max API calls
        res.status(429).send({
          error: {
            code: 429,
            message: "Max API calls exceeded.",
          },
        });
      } else {
        //have not hit todays max usage
        account.usage[usageCount].count++;
        console.log("Good API call", account.usage[usageCount]);
        next();
      }
    } else {
      //Push todays's date and count: 1 if there is a past date
      account.usage.push({ date: today, count: 1 });
      //ok to use again
      next();
    }
  } else {
    //Reject request if API key doesn't match
    res.status(403).send({ error: { code: 403, message: "You not allowed." } });
  }
};
module.exports = { createUser, authenticateKey };
Enter fullscreen mode Exit fullscreen mode

We’re exporting so that the Server.js can use these functions.

Develop routes for the server

In this section, we’ll create the routes that we’ll use to access the data in the API while applying the API key checks. We’ll create endpoints to register a user, add countries to the country list, and also get the list of countries. Requests to add or get countries will require API key authentication.

To get started, create a new file called Server.js and paste the code below. The code contains imports that we’ll use later on, as well as what will be outputted on the homepage:

const express = require('express');
const app = express();
const port = 4000;
const API = require('./apiAuth');

// Get initial data for users and countries
const { users, Countries } = require('./initialData');
//handle json body request
app.use(express.json());

app.get('/', (req, res) => {
  //home page
  res.status(200).send({ data: { message: 'You can get list of countires at /api/country.' } });
});
Enter fullscreen mode Exit fullscreen mode

Using the createUser() function we developed earlier, we’ll create a route that will add new users to the user list and generate the user data. Paste the following code in your Server.js file:

...
app.post('/api/register', (req, res) => {
  //create a new with "user:Username"
  let username = req.body.username;
  let user = API.createUser(username, req);
  res.status(201).send({ data: user });
});
Enter fullscreen mode Exit fullscreen mode

Now, we’ll develop the routes to create and get countries. In this route, we’ll include the authenticateKey() function we developed earlier so that the API key sent in the header of the request can be authenticated and the request can be counted. Then, we’ll finally set the port that the server should listen on. Paste the following code in your Server.js file:

app.get('/api/country', API.authenticateKey, (req, res) => {
  //get list of all Countries   
  let today = new Date().toISOString().split('T')[0];
  console.log(today);
  res.status(200).send({
    data: Countries,
  });
});
app.post('/api/country', API.authenticateKey, (req, res) => {
  //add a new country
  let country = {
    _id: Date.now(),
    name: req.body.country,
  };
  Countries.push(country);
  res.status(201).send({
    data: country,
  });
});

app.listen(port, function (err) {
  if (err) {
    console.error('Failure to launch server');
    return;
  }
  console.log(`Listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Now, we’re done with the coding part of this tutorial, and we can move into testing. We’ll test the API endpoints with cURL. Before testing, open up your terminal and run the following command to start the server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open another terminal window and run the following command to create a new user. Once the command is run, you’ll be provided with some user data, one of which includes the API key:

curl -d "user:User1" -X POST http://127.0.0.1:4000/api/register -w "\n"
Enter fullscreen mode Exit fullscreen mode

Create New User API Key Now that you have created a user, you can use the country endpoints. Let’s try to retrieve the countries that we have on the list using the API key that we were provided with. You can do so by running the command below. Make sure you replace 2agp59nwu8nrszm4p6kfriekoeo0s1 in the command below with your own API key:

curl http://127.0.0.1:4000/api/country -H "x-api-key: 2agp59nwu8nrszm4p6kfriekoeo0s1" -w  "\n"
Enter fullscreen mode Exit fullscreen mode

Retrieve Country Endpoints

Conclusion

In this tutorial, we created an API with Node.js and developed an authentication system that generates an API key whenever a user is registered. With the newly created API key, the user is able to access all the routes on the API and API usage can be tracked.

You can build upon the knowledge you have obtained in this article by adding API key authentication to your Node.js API that uses a database to store data. In addition to user authentication, you can use the API key for the advantages that were mentioned in the introductory section of this article.


200’s only ✔️ Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket.

LogRocket Network Request Monitoring

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.

Top comments (2)

Collapse
 
jon_snow789 profile image
Amit Sharma • Edited on

Hello thank you for posting
I add same logic in my API that you describe
github.com/amitSharma7741/Hindi_jo...
.
but i got one error my database not update count value
i took many request but count value still = 1;
i again and again check my mongoDB Atlas but count value is not changing
Please help me....

Collapse
 
restdbjones profile image
Jones - codehooks.io

Cool post when you don’t need all the bells and whistles of the Auth* libs.
Tip: check out nanoid for random key generator. npmjs.com/package/nanoid

Let's Get Wacky


Use any Linode offering to create something unique or silly in the DEV x Linode Hackathon 2022 and win the Wacky Wildcard category

Join the Hackathon <-