PostGraphile JWT/JWK Verification Quickstart

This guide is an adaption of the official quickstart tutorial for Node (Express) provided by Auth0. The code illustrates how to intercept and verify a JWT Access Token via a JWKS (JSON Web Key Set) using Auth0.

Although this code should work, we make no claims as to its validity or fit for production use. We disclaim all liability.

Dependencies

This guide uses the express HTTP framework and supporting Node packages authored and maintained by Auth0:

  • express-jwt - Middleware that validates a JWT and copies its contents to req.auth
  • jwks-rsa - A library to retrieve RSA public keys from a JWKS (JSON Web Key Set) endpoint
yarn add express express-jwt jwks-rsa
# Or:
npm install --save express express-jwt jwks-rsa

Prior Knowledge & Context

As a developer, the three essential aspects of Auth0 are:

To keep it simple, in this guide we will be dealing with an Access Token granted by an API which we will need to verify.

Getting Started

You will need two values from your Auth0 configuration: The Auth0 tenant domain name, and the API identifier.

const jwt = require("express-jwt");const jwksRsa = require("jwks-rsa");
// ...

// Authentication middleware. When used, the
// Access Token must exist and be verified against
// the Auth0 JSON Web Key Set.
// On successful verification, the payload of the
// decrypted Access Token is appended to the
// request (`req`) as a `user` parameter.
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://YOUR_DOMAIN/.well-known/jwks.json`,  }),

  // Validate the audience and the issuer.
  audience: "YOUR_API_IDENTIFIER",  issuer: `https://YOUR_DOMAIN/`,  algorithms: ["RS256"],
});

(note: if we were processing an ID Token instead of an Access Token, the audience would be the Client ID instead)

Remember that a JWT has three period-separated sections: header, payload, and signature. On successful verification, the payload will be available for us to save inside the PostGraphile request via the pgSettings function.

Let's look at an example payload:

{
  "iss": "https://YOUR_DOMAIN/",
  "sub": "CLIENT_ID@clients",
  "aud": "YOUR_API_IDENTIFIER",
  "iat": 1555808706,
  "exp": 1555895106,
  "azp": "CLIENT_ID",
  "scope": "read:schema", // scopes a.k.a. permissions  "gty": "client-credentials"
}

In this example payload, we can see that the only scope the API has made available is read:schema. Our user can perform no mutations, nor can they perform any queries, they are limited to fetching the schema. Not all tokens will have such simple payloads, but, in this example, the only meaningful data is in the scope value.

Now let's make use of the checkJwt middleware function:

const express = require("express");
const { postgraphile } = require("postgraphile");

const jwt = require("express-jwt");
const jwksRsa = require("jwks-rsa");

// ...

const checkJwt = jwt({
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `https://YOUR_DOMAIN/.well-known/jwks.json`,
  }),
  audience: "YOUR_API_IDENTIFIER",
  issuer: `https://YOUR_DOMAIN/`,
  algorithms: ["RS256"],
});

const app = express();

// Apply checkJwt to our graphql endpointapp.use("/graphql", checkJwt);
app.use(
  postgraphile(process.env.DATABASE_URL, process.env.DB_SCHEMA, {
    pgSettings: req => {      const settings = {};      if (req.auth) {        settings["user.permissions"] = req.auth.scopes;      }      return settings;    },    // any other PostGraphile options go here  }));

PostGraphile applies everything returned by pgSettings to the current session with set_config($key, $value, true). So inside Postgres we can read the current value of user.permissions by select current_setting('user.permissions', true)::text;.

Basic Error Handling

By default, if there is an error in the JWT verification process, the express-jwt package will send a 401 status with an HTML-formatted error message as a response. Instead, we want to follow the pattern of PostGraphile and return errors properly formatted in a GraphQL-compliant JSON response.

Let's create a basic Express middleware for handling the errors which our checkJwt function will throw:

const authErrors = (err, req, res, next) => {
  if (err.name === "UnauthorizedError") {
    console.log(err); // You will still want to log the error...
    // but we don't want to send back internal operation details
    // like a stack trace to the client!
    res.status(err.status).json({ errors: [{ message: err.message }] });
    res.end();
  }
};

// Apply error handling to the graphql endpoint
app.use("/graphql", authErrors);

So, now, for example, if someone tries to connect to our GraphQL service without any token at all, we still get a 401 status, but with the appropriate and succinct response:

{
  "errors": [
    {
      "message": "No authorization token was found"
    }
  ]
}

This article was written by BR.