When it comes to API development, there is often a need to protect certain endpoints or rate-limit the API in general. Because you are working with endpoints from clients possibly on a different domain, you can’t authenticate users with sessions and cookies. It would also be a bad idea to pass around a username and password with each request. Typically endpoints are protected with tokens that are passed with each request and these tokens are often JSON Web Tokens (JWT) that work very well.
We’re going to see how to create a very simple API using Node.js with protected endpoints that require a valid JWT in order for requests to succeed.
In our project we’re going to have two endpoints that will be accessible from any client interface such as mobile, desktop, or web framework. One endpoint will take a username and password and generate a JSON Web Token from it. This token will be returned to the client at which point the client will need to use it to access the protected endpoint. Without providing a valid JWT will result in an error from our protected API endpoint.
For our best chance of success, we’re going to work with a fresh project. This project will be with Node.js and JavaScript and have several package dependencies.
Assuming you already have Node.js installed, execute the following command:
npm init --y
The above command will create a package.json file in your current working directory. This file will hold all the information about our project dependencies.
Now we need to install our project dependencies. Execute the following from your Command Prompt (Windows) or Terminal (Mac and Linux):
npm install express body-parser bcryptjs jsonwebtoken --save
So what are all these dependencies?
We are going to be creating a RESTful API using Express Framework for Node.js. Because we plan to use POST requests, we’ll need the body-parser
package. In a production scenario, you’ll never save passwords as plain text, so we’ll need bcryptjs
to validate our hashed passwords. Finally, we’ll need to generate a JWT, hence the jsonwebtoken
package.
Where does our code go in this application?
For simplicity, we are going to keep all application code in a single JavaScript file. We’re going to design our entire API within an app.js file. As your API becomes more complex and you include a database, you’ll probably want to spread it out a bit.
The project we’re creating is actually quite simple. It will only be around sixty lines of code, most of which is basic boilerplate logic. Open the project’s app.js file and include the following JavaScript code:
var Express = require("express");
var BodyParser = require("body-parser");
var JsonWebToken = require("jsonwebtoken");
var Bcrypt = require("bcryptjs");
var app = Express();
app.use(BodyParser.json());
app.set("jwt-secret", "bNEPp6H70vPo01yGe5lptraU4N9v005y");
var validateToken = function(request, response, next) {};
app.post("/authenticate", function(request, response) {});
app.get("/protected", validateToken, function(request, response) {});
var server = app.listen("3000", function() {
console.log("Listening on port 3000...");
});
I’ve purposefully left out the endpoint code. We just want to add some core application bootstrapping logic first.
var Express = require("express");
var BodyParser = require("body-parser");
var JsonWebToken = require("jsonwebtoken");
var Bcrypt = require("bcryptjs");
In the above set of imports, we are including all of the dependencies that we had previously downloaded and installed. After initializing Express and enabling JSON POST body requests, we define the following:
app.set("jwt-secret", "bNEPp6H70vPo01yGe5lptraU4N9v005y");
We are setting a variable used for signing and verifying JWT data. The variable name and value can be whatever you want, but it will be used for the next steps. It is incredibly important that the value remain a secret from the public.
After setting up each of the two endpoints, we start the server. So what do the endpoints look like?
First we have the authenticate
endpoint which will trade our authentication data for a JSON Web Token:
app.post("/authenticate", function(request, response) {
var user = {
username: "nraboy",
password: "$2a$10$LiMweWit2woRvc2IGpSfcuOM23EeRYu5X9f09Fxsw3hUsdLZBoj/q"
};
if(!request.body.username) {
return response.status(401).send({ "success": false, "message": "A `username` is required"});
} else if(!request.body.password) {
return response.status(401).send({ "success": false, "message": "A `password` is required"});
}
Bcrypt.compare(request.body.password, user.password, function(error, result) {
console.log(result);
if(error || !result) {
return response.status(401).send({ "success": false, "message": "Invalid username and password" });
}
var token = JsonWebToken.sign(user, app.get("jwt-secret"), {});
response.send({"token": token});
});
});
Since we’re not using a database in this example, we define some static data. This will represent potential user information in a database. The example data includes a bcrypt password hash for protection, exactly like you’d have it in a production database.
Provided a username
and password
is included in the request, we would typically do a lookup in the database. With the database information, we’d verify the bcrypt password and if matched, we would sign the object with our secret and return the signed token back to the client.
The JWT token returned should be used with every future request to a protected API endpoint. So let’s say we have the following protected endpoint:
app.get("/protected", validateToken, function(request, response) {
response.send({ "message": "Welcome to the protected page" });
});
Notice we have a parameter called validateToken
which is actually a function. This is a middleware function that will do our token validation. This middleware will either progress us into the endpoint, or return an error on our behalf.
var validateToken = function(request, response, next) {
var authHeader = request.headers["authorization"];
if(authHeader) {
bearerToken = authHeader.split(" ");
if(bearerToken.length == 2) {
JsonWebToken.verify(bearerToken[1], app.get("jwt-secret"), function(error, decodedToken) {
if(error) {
return response.status(401).send({ "success": false, "error": "Invalid authorization token" });
}
request.decodedToken = decodedToken;
next();
});
}
} else {
response.status(401).send({ "success": false, "error": "An authorization header is required" });
}
};
The validateToken
function above does a few things.
First, we expect that the token is passed around as a Bearer token in an authorization header. Really it can be passed around however you want, but most people will tell you it belongs in the header. If the header exists, we split the token and use the second half. Using the JWT secret we can verify the token to see if it is legit and the user is truly authorized to access the API. If the token is valid, we can add it to the request and progress from the middleware to our protected endpoint.
At this point the protected endpoint will have access to the decoded value set in the middleware. This middleware can be used on every protected endpoint.
This project doesn’t have a client facing front-end, but it can still be tested. You can run the server by executing the following command:
node app.js
If no errors were thrown, the API can be accessed from http://localhost:3000. A great testing tool if you’re not using a client front-end is Postman.
Don’t want to go through all the steps of this article? You can download the full project source code here. After extracting the project, execute the following:
npm install
The above command will install all the dependencies found in the package.json file.
You just saw how to use JSON Web Tokens (JWT) in your Node.js RESTful API. This is useful if you want to protect certain endpoints because of the lack of cookie and session support for cross domain communication. In this scenario you would pass JWT tokens to each endpoint and the endpoint would check the validity of the token. This is much safer than passing username and passwords with requests.
The full source code to this project can be downloaded here.