Starting from this chapter?

Clone the application repo and check out the creating-endpoints branch:

git clone git@github.com:auth0-blog/wab-ts-express-api.git \
express-ts-api \
--branch creating-endpoints

Make the project folder your current directory:

cd express-ts-api

Then, install the project dependencies:

npm i

Finally, create a .env hidden file:

touch .env

Populate .env with this:

PORT=7000

One of the requirements for this project is that only authorized users can write records to the store. To quickly and securely achieve that, you can use Auth0 to manage your application's user credentials.

Set Up an Auth0 API

First, you have to create a free Auth0 account if you don't have one yet.

After creating your account, head to the APIs section in the Auth0 Dashboard, and hit the Create API button.

Then, in the form that Auth0 shows:

  • Add a Name to your API. Something like Menu API, for example.

  • Set its Identifier to https://menu-api.demo.com.

  • Leave the signing algorithm as RS256 as it's the best option from a security standpoint.

Auth0 Dashboard new API form

Identifiers are unique strings that help Auth0 differentiate between your different APIs. We recommend using URLs as they facilitate predictably creating unique identifiers; however, Auth0 never calls these URLs.

With these values in place, hit the Create button.

Now, click on the Quick Start tab. This page presents instructions on how to set up different APIs. From the code box, choose Node.js. Keep this window open as you'll be using the values from the code snippet soon.

Menu API Node.js Quickstart

Your API needs these configuration variables to identity itself with Auth0: an Audience and a Domain value. The best place to store these values is within the .env file of your project.

Open .env and add the following keys to it:

PORT=7000
AUTH0_ISSUER=
AUTH0_AUDIENCE=

You can find the values needed for these keys within the code snippet of the Node.js quickstart.

The AUTH0_ISSUER is the value of the issuer property.

The AUTH0_AUDIENCE is the value of the audience property.

Do not include the quotes as part of the .env variable value. Only include the string within the quotes.

For example, my .env file looks like this:

PORT=7000
AUTH0_ISSUER=https://whatabyte.auth0.com/
AUTH0_AUDIENCE=https://menu-api.demo.com

Your .env file should have your Auth0 tenant name instead of whatabyte within the value of the AUTH0_ISSUER property.

Create Authentication Middleware

To protect an endpoint in Express, you rely on a middleware function that gets executed before the callback function of the controller that handles the request. There are two ways that you can accomplish this.

The first option is to "inject" an authorization middleware function in the controller as follows:

itemsRouter.post("/", authorizationFunction, async (req: Request, res: Response) => {
  // Controller logic...
});

Here, authorizationFunction gets called before the route handler function of itemsRouter.post. In turn, the business logic within authorizationFunction can perform two tasks:

(a) invoke the next function in the middleware chain, the router handler function, if it can determine that the user has the authorization to access the resource or,

(b) close the request-response cycle by responding with a 401 Unauthorized message, which prevents your API from executing the route handler.

The approach of adding authorization middleware by controller gives you granular and low-level control of the authorization flow. However, it can be tedious to inject the authorization middleware function per controller if you have many of them.

As an alternative, you can separate the public controllers from the protected controllers using the authorization middleware as a boundary between groups. For example, within an Express router, you could do the following:

itemsRouter.get(...);

itemsRouter.use(authorizationFunction);

itemsRouter.post(...);
itemsRouter.put(...);
itemsRouter.delete(...);

As such, the GET endpoint can be accessed by clients without presenting any "proof of authorization" — it is a public endpoint.

However, any other endpoint defined after your application mounts authorizationFunction into itemsRouter can only be accessed if authorizationFunction can determine that the client making the endpoint request has the authorization to access it. For this API, Auth0 provides the proof of authorization mentioned in the form of a JSON Web Token (JWT) called an access token.

A JWT defines a compact and self-contained way to transmit information between parties as a JSON object securely. This information can be verified and trusted because it is digitally signed, which makes JWTs useful to perform authorization.

Once the user logs in using a client application, Auth0 provides the client with an access token that defines the resources that the client has permission to access or manipulate with that token. The access token defines information about what users can do in your API in the JSON object it encapsulates. As such, the client must include the access token with each subsequent request it makes to a protected API endpoint.

You'll use the partition approach for this application as you need to protect all the endpoints that write data to the store.

Install authorization dependencies

To create your authorization middleware function, you need to install these two packages:

npm i express-jwt jwks-rsa

Here's what these packages do for you:

  • express-jwt: Validates the authorization level of HTTP requests using JWT tokens in your Node.js application.

  • jwks-rsa: A library to retrieve RSA signing keys from a JWKS (JSON Web Key Set) endpoint.

Since you are working in a TypeScript project, you also need the type definitions for these packages; however, only the express-jwt package is available in the @types npm namespace:

npm i -D @types/express-jwt

The helper function you need from the jwks-rsa package is simple and doesn't require strong typing.

Next, create a file to define your authorization middleware function:

touch src/middleware/authz.middleware.ts

Populate src/middleware/authz.middleware.ts as follows:

import jwt from "express-jwt";
import jwksRsa from "jwks-rsa";
import * as dotenv from "dotenv";

dotenv.config();

export const checkJwt = jwt({
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `${process.env.AUTH0_ISSUER}.well-known/jwks.json`
  }),

  // Validate the audience and the issuer.
  audience: process.env.AUTH0_AUDIENCE,
  issuer: `${process.env.AUTH0_ISSUER}`,
  algorithms: ["RS256"]
});

When you call the checkJwt function, it invokes the jwt function, which verifies that any JSON Web Token (JWT) present in the request payload to authorize the request is well-formed and valid. Auth0 determines the validity of the JWT. As such, you pass the jwt function some variables to help it contact Auth0 and present it with all the JWT information it needs:

  • The audience and issuer of the JWT, which are defined in your .env file and loaded into this module using dotenv.config().

  • The algorithms used to sign the JWT.

  • The secret used to sign the JWT.

To obtain the secret, you need to do some additional work: you use the expressJwtSecret helper function from the jwks-rsa library to query the JSON Web Key Set (JWKS) endpoint of your Auth0 tenant. This endpoint has a set of keys containing the public keys that your application can use to verify any JSON Web Token (JWT) issued by the authorization server and signed using the RS256 signing algorithm.

The checkJwt function implicitly receives the request, req, and response, res, object from Express, as well as the next() function, which it can use to invoke the next middleware function in the chain.

All that's left to do is to mount the checkJwt middleware function before you mount your itemsRouter write endpoints.

Open src/items/items.router.ts and import checkJwt under the Required External Modules and Interfaces section:

/**
 * Required External Modules and Interfaces
 */

import express, { Request, Response } from "express";
import * as ItemService from "./items.service";
import { Item } from "./item.interface";
import { Items } from "./items.interface";

import { checkJwt } from "../middleware/authz.middleware";

Then, under the Controller Definitions section, locate the definition of the POST items/ endpoint, and right above it, add the following code to mount the authorization middleware, itemsRouter.use(checkJwt):

/**
 * Controller Definitions
 */

// GET items/
itemsRouter.get(...);

// GET items/:id

itemsRouter.get(...);

// Mount authorization middleware

itemsRouter.use(checkJwt);

// POST items/

itemsRouter.post(...);

// PUT items/

itemsRouter.put(...);

// DELETE items/:id

itemsRouter.delete(...);

To test that your write endpoints are indeed protected, issue the following requesting in the terminal:

curl -X POST -H 'Content-Type: application/json' -d '{
  "item": {
    "name": "Salad",
    "price": 4.99,
    "description": "Fresh",
    "image": "https://cdn.auth0.com/blog/whatabyte/salad-sm.png"
  }
}' http://localhost:7000/items -i

The server replies with an HTTP/1.1 401 Unauthorized response status, and the message No authorization token was found, confirming that your write endpoints are protected. To access them, you need a JWT issued by Auth0. The fastest way to get that token is to use a client just like any of your users would.

Creating an Auth0 Client Application

You can use the WHATABYTE client application to create an Auth0 user, log in, and request protected data from your API. To configure this client, you need to create an Auth0 Single-Page Application in the Auth0 dashboard, which gives you the Auth0 Domain and Auth0 Client ID values needed to configure the demo client application to talk to the Auth0 authentication server and get access tokens for your logged-in users.

The process of creating an Auth0 client application is quite straightforward:

  • Open the Auth0 Applications section of the Auth0 Dashboard.

  • Click on the Create Application button.

  • Provide a Name value such as WAB Dashboard.

  • Choose Single Page Web Applications as the application type.

  • Click on the Create button.

On the application page that loads, click on the Settings tab. Use the Auth0 values present there to fill the missing values in Auth0 Demo Settings form of the WAB Dashboard client, namely Auth0 Domain and Auth0 Client ID.

WHATABYTE Dashboard demo settings form

Click the Save button below the form. The WAB Dashboard is a client to your Express server. To test this connection, click on the Menu tab and observe how it populates with the menu items defined in your API store.

Connecting a Client Application With Auth0

Click on the Settings tab of your Auth0 client application and update the following setting fields:

Allowed Callback URLs

Use the value of Auth0 Callback URL from the Auth0 Demo Settings form, https://dashboard.whatabyte.now.sh/home.

After a user authenticates, Auth0 only calls back any of the URLs listed there. You can specify multiple valid URLs by comma-separating them (typically to handle different environments like QA or testing). Make sure to specify the protocol, http:// or https://, otherwise the callback may fail in some cases.

Allowed Web Origins

Use https://dashboard.whatabyte.now.sh.

This field holds a comma-separated list of allowed origins for use with web message response mode, which makes it possible to log in using a pop-up, as you'll soon see in the next section.

Allowed Logout URLs

Use https://dashboard.whatabyte.now.sh/home.

This field holds a set of URLs that Auth0 can redirect to after a user logs out of your application. The demo client has been configured to use the provided value for redirecting users.

With these values in place, you can scroll to the bottom of the "Settings" page and click on the Save Changes button.

Signing In

Head back to the WAB Dashboard and click on the Sign In button. Since this may be the first user you are adding to Auth0, go ahead and click on the Sign Up tab in the pop-up that comes up. Then, provide an email and password to register your new user.

Auth0 sign-up form

Once you sign in, the user interface of the WAB Dashboard changes:

  • The Sign In button becomes a Sign Out button

  • A user tab is now displayed below the Sign Out button.

Click on the user tab to see a custom page with your email as the title.

The WAB Dashboard caters to two types of users: regular users and users with a menu-admin role. This role allows the user to create, update, and delete menu items in the WAB Dashboard.

In the next section, you create the menu-admin role, associate permissions with it, and assign it to a new user that you create through the Auth0 Dashboard. This privileged user can unlock the admin features of the WAB Dashboard.

I'm ready to manage permissions to access my Express API