Verifying JWTs

How to verify a JSON Web Token (JWT).

Canva uses JSON Web Tokens (JWTs) to encode information about the current user, including their ID and the ID of their team. To extract this information, you need to decode and verify the JWT.

There are two situations where this is necessary:

  • Before an app sends an HTTP request to a backend, it should call the getCanvaUserToken method. This method returns a JWT, which the app can use to verify the authenticity of the request. To learn more, see Verifying HTTP requests.
  • When authenticating a user via a third-party platform, Canva appends a canva_user_token query parameter to the Redirect URL. This parameter contains a JWT that the app can use to identify the user. To learn more, see Authenticating users.

In both cases, the steps for decoding and verifying the JWT are the same.

JWTs are made up of three parts:

  • Header
  • Payload
  • Signature

The part we're interested in is the header.

To extract the header, we recommend using a library. By default though, some libraries don't extract the header. When using the jsonwebtoken library, for instance, you must enable the behavior:

import jwt from "jsonwebtoken";
const decoded = jwt.decode("<JWT_GOES_HERE>", { complete: true });
console.log(decoded.header);
ts

The JWT header is an object. This object has a kid property, which is short for key ID. You need to grab this property from the header object as it's required in a later step:

const { kid } = decoded.header;
console.log(kid);
ts

Canva exposes a JSON Web Key Set (JWKS) via the following endpoint:

https://api.canva.com/rest/v1/apps/<YOUR_APP_ID>/jwks

This is a JSON file that looks something like this:

{
"keys": [
{
"kid": "292e133c-2afe-4cb6-8e8d-43468affa32a",
"kty": "RSA",
"n": "39fdyga5zNmwBhc0Hsdpd_u5DrJa8-OS8KkyoD_sipY4rbD6yyBSr1kqJa3n8qG1K2d96OEVZH-_BdpeLMHmP3NkhCacT1dkzpM_b0mWLCYA-xKt-eAFVIAxiVjorjQHtX6qD-UtborDwMKMm0ul3TFJPU2LVNmLePZrfPkb3jMkzYQPixprmdh5XfR-r853RhphhkscvbLJIcSdz56_6gQZrp6peGOn_7XSxiOSDbFdEgPMAxaFP1vHStp8yj09K_UKGOFQye06Dz26DIN8U8F8_QFafLuIp0fl-2eehfUT8f_iFUE3kuOkzJsXL3Wg4kjmsVoSlVIFhM0KPVs_hw",
"e": "AQAB"
}
]
}
json

Each object in the keys array is a JSON Web Key (JWK) — also known as a public key. The array may contain multiple keys, but only one key is active at any point in time.

Your app's backend must:

  1. Download the JWKS file.
  2. Get the active key.

The active key is the key with a kid property that matches the kid property from the JWT header.

If possible, we recommend using a library to handle these steps. For example, the following code sample demonstrates how to use the jwks-rsa library:

import { JwksClient } from "jwks-rsa";
import jwt from "jsonwebtoken";
const { APP_ID } = process.env;
const CACHE_EXPIRY_MS = 60 * 60 * 1_000; // 60 minutes
const TIMEOUT_MS = 30 * 1_000; // 30 seconds
const decoded = jwt.decode("<JWT_GOES_HERE>", { complete: true });
const { kid } = decoded.header;
const jwks = new JwksClient({
cache: true,
cacheMaxAge: CACHE_EXPIRY_MS,
timeout: TIMEOUT_MS,
rateLimit: true,
jwksUri: `https://api.canva.com/rest/v1/apps/${APP_ID}/jwks`,
});
const publicKey = await jwks.getSigningKey(kid).getPublicKey();
ts

An additional benefit of this library is that it caches the JSON file for the specified amount of time — in this case, 60 minutes. This means the app doesn't have to repeatedly download the file, which:

  • Improves the performance of the app
  • Reduces the risk of DDOS attacks

If you don't use a library, we strongly recommend caching the file or redownloading it on a schedule rather than downloading it any time the backend receives a request.

Use the public key to verify the JWT:

const verified = jwt.verify("<JWT_GOES_HERE>", publicKey, {
audience: APP_ID,
});
ts

The exact syntax will depend on the library you're using.

If the token is valid, the returned object will contain the following properties:

  • aud - The ID of the app.
  • brandId - The ID of the user's team.
  • userId - The ID of the user.

If these properties are not available, then the token, key, or combination of the two is invalid.