JSON Web Tokens
For security reasons, Canva uses JSON Web Tokens(opens in a new tab or window) (JWTs) to encode certain information, such as the ID of the user or the ID of a user's design. To access this information, apps must decode and verify the JWTs.
Apps must only verify JWTs via the app's backend — never via the frontend.
Step 1: Get a JWT
In the Apps SDK, there are two types of tokens:
- Design tokens
- User tokens
Design tokens encode information about the current design, such as the ID of the design, while user tokens encode information about the current user, such as the ID of the user and their team.
The steps for verifying each type of token are the same, but the tokens encode different information, and apps access them in different ways:
- An app can call the
getDesignToken
method. This method returns a design token, which the app can use to get the ID of a design and associate data with that design. To learn more, see Using design IDs. - An app can start an authentication flow. This redirects the user to the app's Redirect URL. Canva appends a user token to the URL. The app's backend can use this token to authenticate the user via a third-party platform.
- An app can call the
auth.getCanvaUserToken
method. This method returns a user token, which the app can use to verify HTTP requests or identify users (for example, to check if they're authenticated or not).
Step 2: Extract the JWT header
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, 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);
To find a JWT library for your preferred language or framework, see jwt.io/libraries(opens in a new tab or window).
Step 3: Get the kid
property
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);
Step 4: Get the active public key
Canva exposes a JSON Web Key Set(opens in a new tab or window) (JWKS) via the following endpoint:
https://api.canva.com/rest/v1/apps/YOUR_APP_ID/jwks
YOUR_APP_ID
is a placeholder for the ID of the app. You can find the ID of the app in the Developer Portal, via the Your apps(opens in a new tab or window) page.
The endpoint returns 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"}]}
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:
- Download the JWKS file.
- Get the active public key.
The active public 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 and wrapping the logic in a function that's compatible with all of Canva's JWTs. The following code sample demonstrates how to do this:
import { JwksClient } from "jwks-rsa";import jwt from "jsonwebtoken";const CACHE_EXPIRY_MS = 60 * 60 * 1_000; // 60 minutesconst TIMEOUT_MS = 30 * 1_000; // 30 secondsasync function getActivePublicKey({appId,token,cacheExpiryMs = CACHE_EXPIRY_MS,timeoutMs = TIMEOUT_MS,}: {appId: string;token: string;cacheExpiryMs?: number;timeoutMs?: number;}) {const decoded = jwt.decode(token, {complete: true,});const { kid } = decoded.header;const jwks = new JwksClient({cache: true,cacheMaxAge: cacheExpiryMs,timeout: timeoutMs,rateLimit: true,jwksUri: `https://api.canva.com/rest/v1/apps/${appId}/jwks`,});const key = await jwks.getSigningKey(decoded.header.kid);return key.getPublicKey();}
When calling getActivePublicKey
, pass in the ID of the app and the JWT to be verified:
const publicKey = await getActivePublicKey({appId: "YOUR_APP_ID",token: "JWT_GOES_HERE",});
Caching the JWKS
In the above example, the jwks-rsa
library 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, either cache the file or download it on a schedule. Do not download the file every time the backend receives a request, as this is inefficient and makes the backend vulnerable to DDOS attacks.
Your app's backend can safely overwrite the JWKS file. You don't need to store multiple versions of it. Your app only needs the latest version of the file.
Step 5: Verify the token
Use the public key to verify the JWT:
const verified = jwt.verify("JWT_GOES_HERE", publicKey, {audience: "YOUR_APP_ID",});
The exact syntax will depend on the library you're using.
If the token is valid, the verified token will be an object. The properties in this object will depend on whether the token is a design token or a user token.
Design tokens
If the token is a valid design token, the object will contain the following properties:
aud
- The ID of the app.designId
- The ID of the current design.
If these properties are not available, it means the token or public key are invalid:
if (!verified.aud || !verified.designId) {throw new Error("The design token is not valid");}
User tokens
If the token is a valid user token, the 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, it means the token or public key are invalid:
if (!verified.aud || !verified.brandId || !verified.userId) {throw new Error("The user token is not valid");}