Node.js API Implementation (Mobile Apps + API)
This document is part of the Mobile + API Architecture Scenario and it explains how to implement the API in Node.js. The full source code for the Node.js API implementation can be found in this GitHub repository.
Please refer to the scenario for information on the implemented solution.
This implementation uses the Express web application framework to build a Node.js API.
Create a package.json File
Create a folder for your API, navigate into it, and run npm init
. This sets up your package.json
file.
Leave the default settings or change them as you see fit.
Our sample's package.json
looks like the following:
{
"name": "timesheets-api",
"version": "1.0.0",
"description": "API used to add timesheet entries for employees and contractors",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/auth0-samples/auth0-pnp-timesheets.git"
},
"author": "Auth0",
"license": "MIT",
"bugs": {
"url": "https://github.com/auth0-samples/auth0-pnp-timesheets/issues"
},
"homepage": "https://github.com/auth0-samples/auth0-pnp-timesheets#readme"
}
Was this helpful?
Install the Dependencies
Next, set the dependencies with the following modules:
express: This module adds the Express web application framework.
cors: This module adds support for enabling CORS, which is required since the API is called from a Single-Page Application that runs on a different domain inside a web browser.
jwks-rsa: This library retrieves RSA signing keys from a JWKS (JSON Web Key Set) endpoint. Using
expressJwtSecret
, we can generate a secret provider that provides the right signing key toexpress-jwt
based on thekid
in the JWT header. To learn more, refer to the node-jwks-rsa GitHub repository.express-jwt: This module authenticates HTTP requests using JWT tokens in your Node.js applications. It provides several functions that make working with JWTs easier. For more information, refer to the express-jwt GitHub repository.
body-parser: This is a Node.js body parsing middleware. It extracts the entire body portion of an incoming request stream and exposes it on
req.body
as something easier with which to interface.
To install these dependencies run the following:
npm install express cors express-jwt jwks-rsa body-parser express-jwt-authz --save
Was this helpful?
Implement the Endpoints
Navigate to your API directory and create a server.js
file. Your code needs to:
Get the dependencies.
Implement the endpoint(s).
Launch the API server.
This is our sample implementation:
const express = require('express');
const app = express();
const { expressjwt: jwt } = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const cors = require('cors');
const bodyParser = require('body-parser');
// Enable CORS
app.use(cors());
// Enable the use of request body parsing middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
// Create timesheets API endpoint
app.post('/timesheets', function(req, res){
res.status(201).send({message: "This is the POST /timesheets endpoint"});
})
// Launch the API Server at localhost:8080
app.listen(8080);
Was this helpful?
Launch your API server using node server
and make an HTTP POST request to localhost:8080/timesheets
. You should see a JSON response with the message This is the POST /timesheets endpoint
.
So now we have our endpoint but anyone can call it. Continue to the next step to see how we can fix this.
In order to validate our token, use the jwt
function, provided by the express-jwt middleware, and the jwks-rsa
to retrieve our secret. The libraries do the following:
express-jwt
decodes the token and pass the request, the header, and the payload tojwksRsa.expressJwtSecret
.jwks-rsa
downloads all signing keys from the JWKS endpoint and see if a one of the signing keys matches thekid
in the header of the JWT. If none of the signing keys match the incomingkid
, an error will be thrown. If we have a match, pass the right signing key toexpress-jwt
.express-jwt
continues its own logic to validate the signature of the token, the expiration,audience
and theissuer
.
The steps we will follow in our code are:
Create the middleware function to validate the access token.
Enable the use of the middleware in our routes.
You can also write some code to actually save the timesheet to a database. This is our sample implementation (some code is omitted for brevity):
// set dependencies - code omitted
// Enable CORS - code omitted
// Create middleware for checking the JWT
const checkJwt = jwt({
// Dynamically provide a signing key based on the kid in the header and the signing keys provided by the JWKS endpoint
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://{yourDomain}/.well-known/jwks.json`
}),
// Validate the audience and the issuer
audience: '{YOUR_API_IDENTIFIER}', //replace with your API's audience, available at Dashboard > APIs
issuer: 'https://{yourDomain}/',
algorithms: [ 'RS256' ]
});
// Enable the use of request body parsing middleware - code omitted
// create timesheets API endpoint - code omitted
app.post('/timesheets', checkJwt, function(req, res){
var timesheet = req.body;
// Save the timesheet to the database...
//send the response
res.status(201).send(timesheet);
});
// launch the API Server at localhost:8080 - code omitted
Was this helpful?
If we launch our server now and do an HTTP POST to localhost:8080/timesheets
we should get the error message Missing or invalid token
(which is accurate since we didn’t send an access token in our request).
In order to test the working scenario as well we need to:
Get an access token. For details on how to do so refer to: Get an Access Token.
Invoke the API while adding an
Authorization
header to our request with the valueBearer ACCESS_TOKEN
(whereACCESS_TOKEN
is the value of the token we retrieved in the first step).
In this step, we add the ability to check if the application has permissions (or scopes) and use our endpoint in order to create a timesheet. In particular, we want to ensure that the token has the correct scope, which is batch:upload
.
In order to do this, we make use of the express-jwt-authz
Node.js package, so add that to your project:
npm install express-jwt-authz --save
Was this helpful?
Now add a call to jwtAuthz(...)
to your middleware to ensure that the JWT contain a particular scope in order to execute a particular endpoint.
We add an additional dependency. The express-jwt-authz library, which is used in conjunction with express-jwt, validates the JWT and ensures it bears the correct permissions to call the desired endpoint. For more information, refer to the express-jwt-authz GitHub repository.
This is our sample implementation (some code is omitted for brevity):
// set dependencies - some code omitted
const jwtAuthz = require('express-jwt-authz');
// Enable CORS - code omitted
// Create middleware for checking the JWT - code omitted
// Enable the use of request body parsing middleware - code omitted
// create timesheets API endpoint
app.post('/timesheets', checkJwt, jwtAuthz(['create:timesheets'], { customUserKey: 'auth' }), function(req, res){
var timesheet = req.body;
// Save the timesheet to the database...
//send the response
res.status(201).send(timesheet);
})
// launch the API Server at localhost:8080 - code omitted
Was this helpful?
If we invoke our API with a token that does not include this scope, then we should get the error message Forbidden with the HTTP status code 403
. You can test this by removing this scope from your API.
The express-jwt
middleware that is used to validate the JWT also sets req.user
with the information contained in the JWT. If you want to use the sub
claim to identify the user uniquely, you can use req.user.sub
. For the timesheets application, we want to use the email address of the user as a unique identifier.
Create an Action
First, create a new Action that will add the email address of the user to the access token.
Navigate to Auth0 Dashboard > Actions > Library, and select Build Custom.
Enter a descriptive Name for your Action (for example,
Add email to access token
), select the Login / Post Login trigger, and select Create.Locate the Actions Code Editor, copy the following JavaScript code into it, and select Save Draft to save your changes:
exports.onExecutePostLogin = async (event, api) => { const namespace = 'https://my-app.example.com'; api.accessToken.setCustomClaim(`${namespace}/email`, event.user.email); }
Was this helpful?
/From the Actions Code Editor sidebar, select Test (play icon), then select Run to test your code.
When you’re ready for the Action to go live, select Deploy.
Add your Action to the Login Flow
Next, add the Action you created to the Login Flow. To learn how to attach Actions to Flows, read the "Attach the Action to a flow" section in Write Your First Action.
Retrieve the unique identifier
Finally, from inside your API, retrieve the value of the claim from req.auth
. Use that value as the unique user identifier to associate with timesheet entries.
app.get('/timesheets', checkJwt, jwtAuthz(['read:timesheets'], { customUserKey: 'auth' }), function(req, res) {
var timesheet = req.body;
// Associate the timesheet entry with the current user
var userId = req.auth['https://api.exampleco.com/email'];
timesheet.user_id = userId;
// Save the timesheet to the database...
//send the response
res.status(201).send(timesheet);
});
Was this helpful?