Sending HTTP requests
At their most basic, apps are web pages embedded in an iframe. As a result, they have access to many standard web APIs, including the Fetch API.
Apps can use the Fetch API for a variety of purposes, such as fetching data from an API or offloading tasks to a server, such as machine learning or image processing tasks.
For the most part, using the Fetch API in an app is identical to using it anywhere else. Canva doesn't monkey-patch the API or otherwise modify its behavior.
The one caveat is that, before an app is approved for release, its backend must verify any incoming requests. This verification needs to confirm that requests are arriving from the app's frontend and not from some other, potentially malicious source. This is a security precaution that protects your app and our users from a variety of threats.
This page explains everything you need to know about how to verify requests.
Step 1: Get a JWT from Canva
Before an app sends an HTTP request to its backend, it must request a JWT from Canva. The app's backend can use this JWT to verify that the request is legitimate.
To get a JWT, initialize the Authentication capability:
import { getAuthentication } from "@canva/authentication";const auth = getAuthentication();
Then call the getCanvaUserToken
method:
const token = await auth.getCanvaUserToken();
The method sends a request to Canva and returns a JWT.
Step 2: Send a request
-
Call the
fetch
method:const response = await fetch("http://localhost:3001/my/api/endpoint"); -
In the request's headers, include an
Authorization
header that contains the JWT:const response = await fetch("http://localhost:3001/my/api/endpoint", {headers: {Authorization: `Bearer ${token}`,},});Be sure to include the
Bearer
prefix.
Step 3: Verify the request
When an app's backend receives a request, it must:
- Verify that the JWT is valid
- Reject the request if it's not valid
In the starter kit, the example backend handles this for you. You only need to add the ID of the app to the .env
file. The rest of this document explains how to set up the logic in your own backend.
Extract the JWT
When the backend receives the request, extract the JWT from the Authorization
header. The following snippet demonstrates how to extract a JWT from the Express.js Request
object:
import express from "express";const app = express();app.post("/my/api/endpoint", async (request, response) => {const token = getTokenFromHeader(request);if (!token) {return response.sendStatus(401);}console.log(token);return response.sendStatus(200);});app.listen(process.env.PORT || 3000);function getTokenFromHeader(request: express.Request) {const header = request.headers["authorization"];if (!header) {return;}const parts = header.split(" ");if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") {return;}const [, token] = parts;return token;}
If the request doesn't contain a token, reject it with a 401
error.
Decode the JWT
JWTs are made up of three parts:
- Header
- Payload
- Signature
After extracting the JWT from the Authorization
header, use a library to:
- Decode the JWT
- Extract the
kid
property from the JWT header
By default, some libraries don't extract the header. For example, if you're using the jsonwebtoken
library for Node.js, you must enable this behavior with the complete
property:
import express from "express";import jwt from "jsonwebtoken";const app = express();app.post("/my/api/endpoint", async (request, response) => {const token = getTokenFromHeader(request);if (!token) {return response.sendStatus(401);}const decoded = jwt.decode(token, { complete: true });const { kid } = decoded.header;console.log(kid);return response.sendStatus(200);});
Get a public key
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"}]}
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 — that is, the key with a
kid
property that matches thekid
from the JWT header
To do this, we recommend using a JWKS library, such as jwks-rsa
:
import express from "express";import { JwksClient } from "jwks-rsa";import jwt from "jsonwebtoken";const { APP_ID } = process.env;const CACHE_EXPIRY_MS = 60 * 60 * 1_000; // 60 minutesconst TIMEOUT_MS = 30 * 1_000; // 30 secondsconst app = express();app.post("/my/api/endpoint", async (request, response) => {// Decode the tokenconst token = getTokenFromHeader(request);if (!token) {return response.sendStatus(401);}const decoded = jwt.decode(token, { complete: true });const { kid } = decoded.header;// Get the public keyconst 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();return response.sendStatus(200);});
Under the hood, this library:
- Uses the
kid
property from the JWT header to find a key with a matchingkid
property - Caches the JSON file for the specified amount of time — in this case, 60 minutes
By caching the JSON file, the backend doesn't have to download the file every time an endpoint receives a request.
An alternative to caching is to:
- Download the JWKS file when the server starts up (rather than when an endpoint is hit)
- Redownload the JWKS file on a set schedule — for example, with the
setInterval
function
The exact mechanism isn't important. The key is to avoid downloading the JWKS file more times than is necessary, as this is more performant and reduces the risk of DDOS attacks.
Verify the JWT
Use a JWT library to verify that the JWT is valid.
This requires two ingredients:
- The JWT
- The public key
The following snippet demonstrates how to verify the JWT with the jsonwebtoken
library:
app.post("/my/api/endpoint", async (request, response) => {// Decode the tokenconst token = getTokenFromHeader(request);if (!token) {return response.sendStatus(401);}const decoded = jwt.decode(token, { complete: true });// Get the public keyconst 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 { kid } = decoded.header;const publicKey = await jwks.getSigningKey(kid).getPublicKey();// Verify the tokenconst verified = jwt.verify(token, publicKey, {audience: APP_ID,});return response.sendStatus(200);});
If the verified token is valid, it will contain the following properties:
aud
- The ID of the appbrandId
- The ID of the user's teamuserId
- The ID of the user
If these properties are not available, reject the request:
if (!verified.aud || !verified.brandId || !verified.userId) {return response.sendStatus(401);}