Securing a Serverless API on Vercel using JWTs

Securing a Serverless API on Vercel using JWTs

On this page

This code example implements techniques to validate a Self Contained JWT used by a serverless API deployed on Vercel.

Vercel Serverless APIs do not have the ability to cache JWKS keys in memory. By default, this would result in a call to the JWKS endpoint of the Curity Identity Server on every API request which would impact the performance of the system. With a self-contained JWT the API has what it needs to fully validate the JWT without the need for caching or external calls to the Curity Identity Server.

The API is a simple Node.js API that uses the Jose library to perform signature validation using the EmbeddedJWK method. The API then returns a message that the JWT has been successfully validated together with a set of random data.

Prerequisites

  • An installation of The Curity Identity Server is needed. Follow the Getting Started Guide to get an installation up and running on a preferred platform.
  • A Vercel account and optionally an installation of the Vercel CLI

Version 6.4 or later

The Curity Identity Server 6.4 and later implements new JWT options used in this example.

Create Certificates

Next ensure that the OpenSSL Tool is installed, which is available by default on some operating systems, then run the createCerts.sh script. This will create a number of self signed certificate files for demo purposes, to simulate a Public Key Infrastructure (PKI) on a development computer:

CertificateDescriptionLifetime
signing.p12A token signing key + certificate file, with the password from the createCerts.sh script6 months
intermediate.pemThe public key of the intermediate key + certificate used to issue the signing certificate10 years
root.pemThe public key of the root key + certificate used to issue the intermediate certificate10 years

The signing certificate will be imported into the Curity Identity Server, and the intermediate and root certificates will be deployed with the API, which will use a Public Key Infrastructure library to verify the trust chain of received tokens.

Configure Token Signing

In the Curity Identity Server, import the signing certificate under the Facilities menu and select the Signing Keys / New option, then import the P12 file and enter its password:

Cert Import

Then view the signing key details and notice that it contains a full certificate chain that will be included in JWTs issued:

Cert Chain

Finally, navigate to Profiles Token Service Token Issuers and select the imported signing certificate, and either activate the Include X509 Certificate Chain or the Include Jwks option:

x5c Settings

This will result in the JWT header of access tokens being updated with the certificate chain:

{
  kid: '-1614245140',
  x5t: 'lDdNIsb3FxulMcYdAXxYJ_Z5950',
  x5c: [
    'MIIDqjCCApKgAwIBAgIESLNEvDA ...',
    'MIICwzCCAasCCQCKVy9eKjvi+jA ...',
    'MIIDTDCCAjSgAwIBAgIJAPlnQYH...'
  ],
  alg: 'RS256'
}

jwks Settings

This results in a full jwk field being received in the JWT header, which also includes an x5c field:

{
kid: '-1614245140',
x5t: 'lDdNIsb3FxulMcYdAXxYJ_Z5950',
jwk: {
  kty: 'RSA',
  alg: 'RSA',
  n: 'hLjc255V6xkm3zBhh1tlTj14B ...',
  e: 'AQAB',
  x5c: [
    'MIIDqjCCApKgAwIBAgIESLNEvDA ...',
    'MIICwzCCAasCCQCKVy9eKjvi+jA ...',
    'MIIDTDCCAjSgAwIBAgIJAPlnQYH...'
  ],
  x5t: 'lDdNIsb3FxulMcYdAXxYJ_Z5950'
},
alg: 'RS256'
}

Include JWKS

This example code uses the Include JWKS option and would have to be modified slightly to work with the Include X509 Certificate Chain (x5c) option. The Securing a Serverless API with JWTs code example implements the x5c option.

Extra Token Validation

When using embedded JWKs it is also important to perform extra validation of the jwt.x5c field against whitelisted issuers that are deployed together with the API.

The example API loads the certificates data into a truststore.

const trustStore = await this.getWhitelistedCertificateIssuers();

The truststore is then used to verify the certificate chain provided in the received JWT. With this check, the API can trust that the token has been issued by a valid Authorization Server.

Deploy to the Vercel Cloud

Start by cloning the GitHub repository. Install the dependencies, then use for example the Vercel CLI to deploy the code.

npm install
vercel --env ISS=https://idsvr.example.com/oauth/v2/oauth-anonymous --env AUD=www --env ALG='RS256' --env CERT_LOCATION='../certs' deploy

The ISS, AUD, ALG and CERT_LOCATION environment variables are passed as arguments when the API is deployed to Vercel. This could optionally be handled in the Vercel Web UI instead.

Note that AUD by default is the ID of the client that has issued the JWT that the API receives. This behavior could be changed in the Curity Identity Server.

Optionally add --prod at the end of the command to directly deploy the API to production.

The result of the deploy command provides a preview URL that can be used to test the deployment.

🔍  Inspect: https://vercel.com/iggbom/serverless-zero-trust-vercel-api/56eiUkJfKdWZjePqLUh8UMwer8rG [1s]
✅  Preview: https://serverless-zero-trust-vercel-api.vercel.app [copied to clipboard] [33s]
📝  To deploy to production (serverless-zero-trust-vercel-api.vercel.app), run `vercel --prod`

Test the API

Using OAuth.tools, run a Code Flow to obtain a JWT. The Custom Token Issuer article outlies how to configure a specific client in the Curity Identity Server to issue JWTs as access tokens.

Then run an External API flow in OAuth.tools. Point the request to the Vercel domain and add the /api path.

OAuth.tools External API

The response will note that the JWT validation was successful, and some random data is returned.

{
  message: "JWT successfully validated",
  data: "correct adorable amused house"
}

Conclusion

Serverless providers such as Vercel deliver convenient internet hosting for APIs, though they come with certain limitations, such as an inability to cache JWKS keys in memory. With self-contained JWTs however, it is possible to implement a performant Zero Trust architecture where the API validates JWTs on every request.