Building and Securing a Modern Backend API

Learn how to easily manage and secure your API endpoints with Auth0

Free Course

Build Your First Node.js Website

Node is a powerful tool to get JavaScript on the server. Use Node to build a great website.

Many modern applications separate the backend services from the frontend user interface. In this type of architecture, the backend will expose a web based API that the frontend client consumes. Typically, the backend will handle incoming requests and return a JSON or XML encoded response. The frontend will then be in charge of formatting, styling, and displaying this response to the user. We’ve all heard the term “separation of concerns” and applying it at this scale has many benefits.

The backend services can grow and evolve independent of the frontend client. New APIs, if properly versioned, can provide new features and functionality without breaking existing integrations. A single backend can interface with multiple clients and the frontend clients will not be limited to any specific framework or programming language. This means that your single backend can work with your app for both a web based implementation, your mobile app, and even with IoT devices.

In today’s post, we are going to build and expose a RESTful API built with NodeJS. We are also going to build two front-end clients, also with NodeJS, that are going to consume our API. Our API is going to be protected with Auth0. In addition to protecting our API endpoints, we’ll also add specific roles to each of our clients and show how we can give granular access to our API.

Let’s get started.

Our Application

MovieAnalyst App

The application we are building today will be an app called Movie Analyst. Movie Analyst aggregates and displays a list of the latest movie reviews by famous critics across the web. Users can view the latest movie reviews, learn about the critics that review them, and also see the publications that Movie Analyst has partnered with. Website administrators, who will have a separate website, can see and edit these pages, but also have access to reviews-in-progress so that they can prepare and approve reviews ahead of time.

Rather than building two separate backends for the user facing side and the admin application, we are going to build and expose an API with four endpoints. The user facing app will have access to the movies, reviewers and publications endpoints, while the admin app will additionally have access to the pending endpoint which will return movie reviews that are not ready to be publically accessible. We’ll start by building our API.

Building a Node API

We’ll build our backend API with NodeJS and the Express framework. To get started let’s create a directory called movie-analyst-api, navigate into it and run npm init to setup our package.json file. You can leave all the default settings, or change them as you see fit. Once you’ve gone through the setup, the package.json file will be created in your directory.

Before we get to writing code, let’s take care of our dependencies. Run npm install express express-jwt auth0-api-jwt-rsa-validation --save to install the dependencies we are going to need. The express dependency will pull down the express framework, express-jwt library will give us functions to work with JSON Web Tokens, and finally auth0-api-jwt-rsa-validation will provide a helper function for getting our secret key.

Now that we have our dependencies in place, let’s create a new file called server.js. Since our backend API is only going to expose a few routes, we’ll write all of our code in this single file. Check out our implementation below.

// Get our dependencies
var express = require('express');
var app = express();
var jwt = require('express-jwt');
var rsaValidation = require('auth0-api-jwt-rsa-validation');

// Implement the movies API endpoint
app.get('/movies', function(req, res){
  // Get a list of movies and their review scores
  var movies = [
    {title : 'Suicide Squad', release: '2016', score: 8, reviewer: 'Robert Smith', publication : 'The Daily Reviewer'},    
    {title : 'Batman vs. Superman', release : '2016', score: 6, reviewer: 'Chris Harris', publication : 'International Movie Critic'},
    {title : 'Captain America: Civil War', release: '2016', score: 9, reviewer: 'Janet Garcia', publication : 'MoviesNow'},
    {title : 'Deadpool', release: '2016', score: 9, reviewer: 'Andrew West', publication : 'MyNextReview'},
    {title : 'Avengers: Age of Ultron', release : '2015', score: 7, reviewer: 'Mindy Lee', publication: 'Movies n\' Games'},
    {title : 'Ant-Man', release: '2015', score: 8, reviewer: 'Martin Thomas', publication : 'TheOne'},
    {title : 'Guardians of the Galaxy', release : '2014', score: 10, reviewer: 'Anthony Miller', publication : 'ComicBookHero.com'},
  ]

  // Send the response as a JSON array
  res.json(movies);
})

// Implement the reviewers API endpoint
app.get('/reviewers', function(req, res){
  // Get a list of all of our reviewers
  var authors = [
    {name : 'Robert Smith', publication : 'The Daily Reviewer', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/angelcolberg/128.jpg'},
    {name: 'Chris Harris', publication : 'International Movie Critic', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/bungiwan/128.jpg'},
    {name: 'Janet Garcia', publication : 'MoviesNow', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/grrr_nl/128.jpg'},
    {name: 'Andrew West', publication : 'MyNextReview', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/d00maz/128.jpg'},
    {name: 'Mindy Lee', publication: 'Movies n\' Games', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/laurengray/128.jpg'},
    {name: 'Martin Thomas', publication : 'TheOne', avatar : 'https://s3.amazonaws.com/uifaces/faces/twitter/karsh/128.jpg'},
    {name: 'Anthony Miller', publication : 'ComicBookHero.com', avatar : 'https://s3.amazonaws.com/uifaces/faces/twitter/9lessons/128.jpg'}
  ];

  // Send the list of reviewers as a JSON array
  res.json(authors);
})

// Implement the publications API endpoint
app.get('/publications', function(req, res){
  // Get a list of publications
  var publications = [
    {name : 'The Daily Reviewer', avatar: 'glyphicon-eye-open'},
    {name : 'International Movie Critic', avatar: 'glyphicon-fire'},
    {name : 'MoviesNow', avatar: 'glyphicon-time'},
    {name : 'MyNextReview', avatar: 'glyphicon-record'},
    {name : 'Movies n\' Games', avatar: 'glyphicon-heart-empty'},
    {name : 'TheOne', avatar : 'glyphicon-globe'},
    {name : 'ComicBookHero.com', avatar : 'glyphicon-flash'}
  ];

  // Send the list of publications as a JSON array
  res.json(publications);
})

// Implement the pending reviews API endpoint
app.get('/pending', function(req, res){
  // Get a list of pending movie reviews
  var pending = [
    {title : 'Superman: Homecoming', release: '2017', score: 10, reviewer: 'Chris Harris', publication: 'International Movie Critic'},
    {title : 'Wonder Woman', release: '2017', score: 8, reviewer: 'Martin Thomas', publication : 'TheOne'},
    {title : 'Doctor Strange', release : '2016', score: 7, reviewer: 'Anthony Miller', publication : 'ComicBookHero.com'}
  ]

  // Send the list of pending movie reviews as a JSON array
  res.send(pending);
})

// Launch our API Server and have it listen on port 8080.
app.listen(8080);

If we launch our API server and navigate to any of the four routes we’ve created, we should get the expected response. Let’s test this by running node server and navigating to localhost:8080/movies. You should see a JSON response with the list of movie reviews and their associated data.

We have our API but now anyone can call it. Let’s fix this by securing our endpoints with Auth0. Auth0 makes user identity simple and recently they released a new feature to make securing API endpoints a breeze as well. If you don’t already have an Auth0 account, signup for a free account here. Once you’ve signed up, navigate to your Auth0 dashboard and click on the New Client button to create a new Auth0 application.

For the client type, select Non-Interactive Client and name it MovieAnalyst Website. Click on the Create button to create the client.

Creating a Client in Auth0

As this is a new feature that is still in beta, you will be prompted to enable the API functionality. You can do so by simply flipping the switch and a new menu option titled “API’ will be available. Click on this new menu option to continue.

Enable API Beta Feature

The API tab will already have one API created automatically, this is the Auth0 Management API. Utilizing this API, you can create your own Auth0 dashboard or manage any of the Auth0 features programmatically. To learn more about the features of the Management API, click here. We won’t be using this API, so let’s instead click on the Create API button to create our own API.

You can name your API whatever you want, we’ve chosen MovieAnalystAPI. The identifier field will be a unique identifier for your API. This field does not have to be a URL, so feel free to put an identifier that makes the most sense to you - just know that this field cannot be changed. We’ll leave the signing algorithm as RS256 and click the Create API button.

Creating API Client with Auth0

We are also going to create one more client for our Admin front end. This will follow the same process as when we created the first client. Navigate to the Clients section and click on Create New Client. This time name the client MovieAnalyst Admin and make it a non-interactive client as well. Click on the Create button to create this client.

Creating a Client with Auth0

With our two clients and API created in the Auth0 dashboard, we can now go ahead and secure our API endpoints. Open up the server.js file again and make the following updates.

// dependencies omitted

// We’ll create a middleware function to validate the access token when our API is called
// Note that the audience field is the identifier you gave to your API.
var jwtCheck = jwt({
  secret: rsaValidation(),
  algorithms: ['RS256'],
  issuer: "https://YOUR-AUTH0-DOMAIN.auth0.com/",
  audience: 'https://movieanalyst.com'
});

// Enable the use of the jwtCheck middleware in all of our routes
app.use(jwtCheck);

// If we do not get the correct credentials, we’ll return an appropriate message
app.use(function (err, req, res, next) {
  if (err.name === 'UnauthorizedError') {
    res.status(401).json({message:'Missing or invalid token'});
  }
});

// routes omitted

If we launch our server now and navigate to any of the routes, we’ll get an error message as we are not providing the correct information on our requests to the server.

Unauthorized attempt to API endpoint

This is intended as we don’t want just anyone to have access to our API endpoints. In addition to protecting our API routes, we are also going to fine-tune access to specific endpoints. Luckily, we can do this with ease in the Auth0 management dashboard.

Head over the management dashboard, navigate to the API section and click into the MovieAnalyst API we created earlier. From here, click on the Scopes tab.

Scopes allow us to grant specific permissions to clients that are authorized to use our API. For our demo, we are going to create two scopes, general and admin. In an actual application, you could further narrow down the scopes by giving read or write permissions and even go as far to protect each individual route with a separate scope. We’ll keep it fairly general here though. Go ahead and create the two scopes now.

Creating API Scopes

Now that we have our scopes in place, the last thing we’ll need to do is authorize our two clients to work with the API we created. Within the MovieAnalystAPI section, navigate to the Non-Interactive Clients tab. Here you will see a list of all the clients that can interface with our API. By default, when we created our MovieAnalystAPI a test client was created for us. We’ll also see the two clients we created but they will be displayed as Unauthorized.

Displaying Available Clients for API

To authorize our clients, flip the switch to Authorized and you will see additional information displayed. In addition to enabling access to the client, we can easily manage which scopes the client will have. For the admin client, let’s enable both the general and admin scopes, and for the website client, we’ll just enable general access. Be sure to hit the Update button once you’ve made the scope changes.

Authorizing a Client to consume API

Now that we have our scopes in place, let’s go back to the server.js file for our API to make some final edits. What we are adding now is the ability to check if the client has permissions to view the endpoint requested. To do this, we’ll create another middleware that will look at the decoded JWT and see if it the token has the correct scope. If it doesn’t we’ll send an appropriate forbidden message, otherwise we’ll send the data. Take a look at our implementation of this functionality below.

// dependencies

// existing jwtCheck middleware

var guard = function(req, res, next){
  // we’ll use a case switch statement on the route requested
  switch(req.path){
    // if the request is for movie reviews we’ll check to see if the token has general scope
    case '/movies' : {
      var permissions = ['general'];
      for(var i = 0; i < permissions.length; i++){
        if(req.user.scope.includes(permissions[i])){
          next();
        } else {
          res.send(403, {message:'Forbidden'});
        }
      }
      break;
    }
    // Same for the reviewers
    case '/reviewers': {
      var permissions = ['general'];
      for(var i = 0; i < permissions.length; i++){
        if(req.user.scope.includes(permissions[i])){
          next();
        } else {
          res.send(403, {message:'Forbidden'});
        }
      }
      break;
    }
    // Same for publications
    case '/publications': {
      var permissions = ['general'];
      for(var i = 0; i < permissions.length; i++){
        if(req.user.scope.includes(permissions[i])){
          next();
        } else {
          res.send(403, {message:'Forbidden'});
        }
      }
      break;
    }
    // For the pending route, we’ll check to make sure the token has the scope of admin before returning the results.
    case '/pending': {
      var permissions = ['admin'];
      console.log(req.user.scope);
      for(var i = 0; i < permissions.length; i++){
        if(req.user.scope.includes(permissions[i])){
          next();
        } else {
          res.send(403, {message:'Forbidden'});
        }
      }
      break;
    }
  }

// existing app.use middleware

app.use(guard);

// routes

Our guard middleware will be called on each request and will ensure that the token has the correct scope. If it does, we’ll send the data, otherwise we’ll return a 403 Forbidden status and appropriate message.

This wraps up our API implementation. If we launch our server now and try to navigate to any of the routes, we’ll still see the 401 Unauthenticated response. That’s still ok, as our API is meant to interface with other clients and not end-user browsers. Next, we’ll implement our first client, the MovieAnalyst Website.

User Facing Frontend

We have our API ready, now let’s build a UI to consume it. Create a new directory titled movieanalyst-website and navigate into it. Run npm init for this directory to create a package.json file just like for the API. For our website dependencies run npm install express ejs superagent --save. The express dependency is self-explanatory, we will use ejs as our templating engine and finally the superagent library will handle making HTTP requests to our API.

In addition to creating a server.js file, you’ll also want to create a public directory and in this directory, create a subdirectory titled views. We are going to have four views for our user facing website so let’s create them now. The four views we are going to create are:

  • index.ejs - this will be our homepage
  • movies.ejs - this will be the view that display movie reviews
  • authors.ejs - this will display the list of critics
  • publications.ejs - this will will display our publication partners

With the scaffolding done, open up the server.js file to get started with the implementation. Let’s take a look at the implementation and we’ll explain what’s going on line by line.

// Declare our dependencies
var express = require('express');
var request = require('superagent');

// Create our express app
var app = express();

// Set the view engine to use EJS as well as set the default views directory
app.set('view engine', 'ejs');
app.set('views', __dirname + '/public/views/');

// This tells Express out of which directory to serve static assets like CSS and images
app.use(express.static(__dirname + '/public'));

// These two variables we’ll get from our Auth0 MovieAnalyst-Website Client.
// Head over the the management dashboard at https://manage.auth0.com
// Find the MovieAnalyst Website Client and copy and paste the Client ID and Secret
var NON_INTERACTIVE_CLIENT_ID = 'YOUR-AUTH0-CLIENT-ID';
var NON_INTERACTIVE_CLIENT_SECRET = 'YOUR-AUTH0-CLIENT-SECRET';

// Next, we’ll define an object that we’ll use to exchange our credentials for an access token.
var authData = {
  client_id: NON_INTERACTIVE_CLIENT_ID,
  client_secret: NON_INTERACTIVE_CLIENT_SECRET,
  grant_type: 'client_credentials',
  audience: 'https://movieanalyst.com'
}

// We’ll create a middleware to make a request to the oauth/token Auth0 API with our authData we created earlier.
// Our data will be validated and if everything is correct, we’ll get back an access token.
// We’ll store this token in the req.access_token variable and continue the request execution.
// It may be repetitive to call this endpoint each time and not very performant, so you can cache the access_token once it is received.
function getAccessToken(req, res, next){
  request
    .post('https://YOUR-AUTH0-DOMAIN.auth0.com/oauth/token')
    .send(authData)
    .end(function(err, res) {
      if(req.body.access_token){
        req.access_token = res.body.access_token;
        next();
      } else {
        res.send(401, ‘Unauthorized’);
      }
    })
}

// The homepage route of our application does not interface with the MovieAnalyst API and is always accessible. We won’t use the getAccessToken middleware here. We’ll simply render the index.ejs view.
app.get('/', function(req, res){
  res.render('index');
})

// For the movies route, we’ll call the getAccessToken middleware to ensure we have an access token. If we do have a valid access_token, we’ll make a request with the superagent library and we’ll be sure to add our access_token in an Authorization header before making the request to our API.
// Once the request is sent out, our API will validate that the access_token has the right scope to request the /movies resource and if it does, will return the movie data. We’ll take this movie data, and pass it alongside our movies.ejs template for rendering
app.get('/movies', getAccessToken, function(req, res){
  request
    .get('http://localhost:8080/movies')
    .set('Authorization', 'Bearer ' + req.access_token)
    .end(function(err, data) {
      if(data.status == 403){
        res.send(403, '403 Forbidden');
      } else {
        var movies = data.body;
        res.render('movies', { movies: movies} );
      }
    })
})

// The process will be the same for the remaining routes. We’ll make sure to get the acess_token first and then make the request to our API to get the data.
// The key difference on the authors route, is that for our client, we’re naming the route /authors, but our API endpoint is /reviewers. Our route on the client does not have to match the API endpoint route.
app.get('/authors', getAccessToken, function(req, res){
  request
    .get('http://localhost:8080/reviewers')
    .set('Authorization', 'Bearer ' + req.access_token)
    .end(function(err, data) {
      if(data.status == 403){
        res.send(403, '403 Forbidden');
      } else {
        var authors = data.body;
        res.render('authors', {authors : authors});
      }
    })
})

app.get('/publications', getAccessToken, function(req, res){
  request
    .get('http://localhost:8080/publications')
    .set('Authorization', 'Bearer ' + req.access_token)
    .end(function(err, data) {
      if(data.status == 403){
        res.send(403, '403 Forbidden');
      } else {
        var publications = data.body;
        res.render('publications', {publications : publications});
      }
    })
})

// We’ve added the pending route, but calling this route from the MovieAnalyst Website will always result in a 403 Forbidden error as this client does not have the admin scope required to get the data.
app.get('/pending', getAccessToken, function(req, res){
  request
    .get('http://localhost:8080/pending')
    .set('Authorization', 'Bearer ' + req.access_token)
    .end(function(err, data) {
      if(data.status == 403){
        res.send(403, '403 Forbidden');
      }
    })
})

// Our MovieAnalyst Website will listen on port 3000. Feel free to change this as you see fit, just know that you can’t have multiple processes listening on the same port.
app.listen(3000);

We have our server.js implementation complete. Let’s build out our UI pages. We’ll start with the index.ejs page. We will make use of the Bootstrap and Font Awesome libraries to create a good looking UI fast. The homepage will just display links to the other sections of the website. Take a look at the implementation and screenshot of what the completed product will look like.

<!doctype html>
<html>
<head>
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
  <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
  <style>
    body {
      background-color: #ecedec;
    }
  </style>
</head>
<body>

<div class="container">
  <div class="jumbotron text-center">
    <h1><span class="glyphicon glyphicon-bullhorn"></span></h1>
    <h2>Movie Analyst</h2>
  </div>

  <div class="row">
    <div class="col-sm-4">
      <div class="panel">
        <div class="panel-heading">
          <h3 class="panel-title">Latest Reviews</h3>
        </div>
        <div class="panel-body text-center">
          <h1><span class="glyphicon glyphicon-bullhorn"></span></h1>
        </div>
        <div class="panel-footer small">
          <a href="/movies" class="btn btn-block btn-primary">View Latest Reviews</a>
        </div>
      </div>
    </div>

    <div class="col-sm-4">
      <div class="panel">
        <div class="panel-heading">
          <h3 class="panel-title">View Latest Reviews</h3>
        </div>
        <div class="panel-body text-center">
          <h1><span class="glyphicon glyphicon-user"></span></h1>
        </div>
        <div class="panel-footer small">
          <a href="/authors" class="btn btn-block btn-primary">View Authors</a>
        </div>
      </div>
    </div>

    <div class="col-sm-4">
      <div class="panel">
        <div class="panel-heading">
          <h3 class="panel-title">Our Partners</h3>
        </div>
        <div class="panel-body text-center">
          <h1><span class="glyphicon glyphicon-tower"></span></h1>
        </div>
        <div class="panel-footer small">
          <a href="/publications" class="btn btn-block btn-primary">View Publications</a>
        </div>
      </div>
    </div>
  </div>
</div>

</body>
</html>

MovieAnalyst App

Next, we’ll build out the movies.ejs view. In the interest of time, we’ll omit some the head section. Feel free to copy and paste the contents from the index.ejs file to save some time.

<div class="row">
  <!-- This is the EJS syntax to create a loop -->
  <% movies.forEach(function(movie, index) { %>
    <div class="col-sm-4">
      <div class="panel">
        <div class="panel-heading">
          <h3 class="panel-title"><%= movie.title %> (<%= movie.release %>)</h3>
        </div>
        <div class="panel-body text-center">
          <h1><%= movie.score %> <small>/ 10</small></h1>
        </div>
        <div class="panel-footer small">
          <p>By <%= movie.reviewer %> for <%= movie.publication %>
        </div>
      </div>
    </div>
  <!-- We’ll also have to close our loop -->
  <% }) %>
  </div>

MovieAnalyst Movie Reviews Page

The authors page will look similar as well. The implementation for authors.ejs is below.

<div class="container">
  <div class="jumbotron text-center">
    <h1><span class="glyphicon glyphicon-user"></span></h1>
    <h2>Our Movie Critics</h2>
  </div>

  <div class="row">
  <% authors.forEach(function(author, index) { %>
    <div class="col-sm-4">
      <div class="panel">
        <div class="panel-heading">
          <h3 class="panel-title"><%= author.name %></h3>
        </div>
        <div class="panel-body text-center">
          <img class="img-circle" src="<%= author.avatar %>" />
        </div>
        <div class="panel-footer small">
          <p>Writes for <%= author.publication %>
        </div>
      </div>
    </div>
  <% }) %>
  </div>
</div>

MovieAnalyst Authors Page

Finally, the publications.ejs implementation is below.

<div class="container">
  <div class="jumbotron text-center">
    <h1><span class="glyphicon glyphicon-tower"></span></h1>
    <h2>Our Publication Partners</h2>
  </div>

  <div class="row">
  <% publications.forEach(function(publication, index) { %>
    <div class="col-sm-4">
      <div class="panel">
        <div class="panel-heading">
          <h3 class="panel-title"><%= publication.name %></h3>
        </div>
        <div class="panel-body text-center">
          <h1><span class="glyphicon <%= publication.avatar %>"></span></h1>
        </div>
      </div>
    </div>
  <% }) %>
  </div>
</div>

MovieAnalyst Publications Page

Let’s test our app and make sure that it works. Launch the API server by running the node server command from the movieanalyst-api directory. Launch the Website server by running the node server command from the movieanalyst-website directory. Now you will have two node applications running, the API at localhost:8080 and the client facing website at localhost:3000.

Navigate to localhost:3000 and you should see the homepage we’ve designed. Click on any of the links, and you should see the appropriate page displayed with the data we’ve defined in our API backend project. Try navigating to localhost:3000/pending. Remember, we created this route, but the response will be forbidden as our client does not have the admin scope applied to it. Let’s build our Admin frontend that will have this scope.

Admin Frontend

Our admin frontend will be very similar to the user centric frontend. In fact, to get started quickly, let’s just copy and paste the entire directory and rename it movieanalyst-admin.

Let’s open up the server.js file next as we’ll need to make some updates. We are going to need to update the NON_INTERACTIVE_CLIENT_ID and NON_INTERACTIVE_CLIENT_SECRET variables with the values from our MovieAnalyst Admin client. Head over to the management dashboard and get those values and replace them here. The other change we will need to make is to change the port the app will listen on. Since our user website listens on 3000 and our API is on 8080, we’ll set the app.listen() port for our admin interface to 4000.

For our UI, we are going to change it up a bit. Since this is an admin based website, rather than just displaying the content, we’ll make it all editable. We won’t actually save changes. Another new feature with the admin website is that we are going to write a partial view for the main navigational menu. Let’s create this partial view first. Create a new subdirectory in your public/views directory and name it includes. We’ll call our partial view navbar.ejs. Create the file and open it. Our navbar implementation will be as follows:

<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">Movie Analyst Admin</a>
    </div>
    <div class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <li><a href="/movies">Reviews</a></li>
        <li><a href="/authors">Authors</a></li>
        <li><a href="/publications">Publications</a></li>
        <li><a href="/pending">Pending Reviews</a></li>
      </ul>
    </div>
  </div>
</nav>

Next, we’ll update each of the views to display a form rather than just the content. We’ll also add a save button, so if an admin makes any updates they can save them. Finally, we’ll be sure to include our navbar partial in each of the views. We’ll only show the movies.ejs file change here. You can follow the process shown here, or get all the code from our demo GitHub repo.

<!doctype html>
<html>
<head>
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
  <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
  <style>
    body {
      background-color: #ecedec;
    }
  </style>
</head>
<body>
<!-- We are including the navbar here -->
<% include ./includes/navbar %>

<div class="container">
  <h2>Editing: <strong>Movies</strong></h2>

  <div class="row clearfix">
  <% movies.forEach(function(movie, index) { %>
    <div class="col-sm-4">
      <div class="panel">
        <div class="panel-heading">
          <h3 class="panel-title">Edit: <%= movie.title %></h3>
        </div>
        <div class="panel-body text-center">
          <label>Name</label>
          <input type="text" class="form-control" value="<%= movie.title %>" />
          <label>Release</label>
          <input type="text" class="form-control" value="<%= movie.release %>" />
          <label>Score</label>
          <input type="text" class="form-control" value="<%= movie.score %>" />
          <label>Reviewer</label>
          <input type="text" class="form-control" value="<%= movie.reviewer %>" />
          <label>Publication</label>
          <input type="text" class="form-control" value="<%= movie.publication %>" />
        </div>
      </div>
    </div>
  <% }) %>
  </div>
  <a class="btn btn-primary btn-block">Save</a>
</div>

</body>
</html>

MovieAnalyst Admin Movie Reviews Page

With the server.js and our views updated, let’s launch the application and navigate to localhost:4000. If all went well, you should see the homepage showing the new navbar as well as the links to separate pages. Let’s click on the movies link to see how the design is different. We can now also visit the /pending link by either typing it in the browser bar or clicking on Pending Reviews in the navbar. This time, instead of seeing an error, we’ll get the data successfully returned and will be able to to make edits to it.

Our API works! We have successfully built an API with two disparate clients that have different levels of access to our API. We can add as many additional clients as we want now, and they don’t have to be Node based either. We can create a mobile application or even another web application with say Laravel to consume our API. We’ll just need to create a client and get the Client ID and Secret pair and write some code to exchange this pair of credentials for an access token.

Considerations

The services API and clients we built today were fairly simple. Real applications will have many more routes, features, and likely need for granularity. Auth0 luckily supports many of these features out-of-the-box.

Other considerations that will be up to you are handling how your backend API interacts with clients. It is recommended that each endpoint be rate-limited so clients don’t overload your API. This is especially important if you have many different clients interacting with your backend. You can use the Auth0 Management API to handle the generation of clients if you don’t like using the Auth0 dashboard. The Management API can handle everything the dashboard can and more which may be useful in the event that you need to revoke access from a particular client, or need to adjust scopes. For more best practices, check out the Auth0 docs concerning API Authentication and Authorization.

Conclusion

Today, we built a backend API that exposed RESTful data to be consumed by independent UI clients. We created two clients, each with a different set of scopes and permissions, to show how we can granularly control access to our API. The Auth0 API Authorization and Authentication feature allowed us to easily protect our API, as well as create clients to interact with our newly secured API. Sign up for a free Auth0 account today and start securing your APIs with ease.