Following on from the previous post where we got a JWT access token from Auth0. This post will go over how to secure an Express API, so that certain endpoints can only be accessed by authenticated users.
Create an express API.
Set up the project
Before starting there is a little bit of set up to do to get a project with Typescript.
In a new folder run npm init -y
- To add a package.json
file to the folder.
Add Typescript by installing the typescript
npm i --save-dev typescript
Add a tsconfig.json
file with this contents to configure Typescript.
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}
Add a build script to the package.json
file.
"scripts": {
"build": "tsc",
},
Set up express
Run npm i express @types/express
to add the express package and the relevant typings from @types
.
Make a new file src/server.ts
Add the following code to the src/server.ts
file to set up a server with a /public
endpoint that can be accessed with no authentication.
import express, { Request, Response } from "express";
const port = process.env.PORT || 8080;
const app = express();
app.use((req, res, next) => {
// allow calling from different domains
res.set("Access-Control-Allow-Origin", "*");
// allow authorization header
res.set("Access-Control-Allow-Headers", "authorization");
next();
});
// Allow requests from anyone to the /public route.
app.get("/public", (req: Request, res: Response) =>
res.json({ hello: "world" })
);
// Start the express server.
app.listen(port, () => {
console.log(`Listening on http://localhost:${port}`);
});
Adding an authenticated endpoint
With the basic server structure set up lets add an endpoint that will be protected. Lest give this an very original path of /private
.
First install some extra packages to help take care of the undifferentiated heavy lifting of parsing and validating the authentication header and JWT.
The (express-jwt
)[https://github.com/auth0/express-jwt] package that will validate the JWT access token provided and the jwks-rsa
package that will handle fetching the correct public key to use when validating the token.
npm i express-jwt jwks-rsa
Import these 2 packages into the server.ts
file.
import jwt from "express-jwt";
import jwks from "jwks-rsa";
Configure the JWKS callback to fetch the JWKS data from Auth0.
const jwksCallback = jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
// JWKS url from the Auth0 Tenant
jwksUri: "https://klee-test.au.auth0.com/.well-known/jwks.json",
});
The JWKS endpoint can be found in the application under Advanced Settings -> Endpoints
Configure the JWT middleware to use the jwks callback, accept tokens with the correct audience and issuer parameters and, limit the signing methods to RS256
to avoid potential JWT exploits.
var requireJWTAuthentication = jwt({
secret: jwksCallback,
// The same audience parameter needs to be used by the client to configure their Auth0 SDK
audience: "TheSweetestAPI",
// The Auth0 domain
issuer: "https://klee-test.au.auth0.com/",
// Has to be RS256 because that's what Auth0 uses to sign it's tokens
algorithms: ["RS256"],
});
Finally add a protected endpoint with some secret information making use of the JWT middleware to ensure that only authenticated parties can access it.
The middleware will add a user
property to the request parameter that includes the decoded body of a valid token. Return as string version of this user property with the response to prove that it's there.
app.get(
"/private",
requireJWTAuthentication,
(req: AuthenticatedRequest, res: Response) => {
// requireJWTAuthentication adds a user property with the payload from a valid JWT
return res.json({
secrets: [
`You're ${JSON.stringify(req.user)}`,
" ... I'm Batman!",
],
});
}
);
Build and run the server
Add or update the build and run scripts in the package.json
file to speed up building and running of the server.
"scripts": {
"build": "tsc",
"start": "node dist/server.js"
},
run npm run build
then npm start
to start the server.
Testing the endpoints
With the sever running the next step is to test the endpoints. To do this open up Postwoman. Postwoman is a service that can be used to test APIs from within the browser.
Fist try to connect to the http://localhost:8080/public/
endpoint. The request should return a 200
status with hello world body.
Next try the same thing with the http://localhost:8080/private/
endpoint. The request should return with a 401
status and a body stating UnauthorizedError: No authorization token was found
.
The final step is to get a token. Use the SPA application from the previous blog in this series. After logging in there and getting the token copy it out of the web page. Back in Postwoman change the Authorization to be Bearer auth and paste the token to be used as the bearer authentication.
Now when the request is sent the response will return with a 200
status and a body revealing the secrets.
If you tamper with the JWT and send the request again the request will return with a 401
status and a message stating UnauthorizedError: invalid token
.
Code for everything in this blog can be found on Github
Cover photo by Gabriel Wasylko on Unsplash
Top comments (1)
I like the addition of TypeScript