Getting started
Developer resources
Intents
Overview
Content Publisher
Data Connector
Design Editor
URL Expander
Designing apps
App UI Kit
Design guidelines
App components
Figma resource
Developing apps
App configuration
Authenticating users
Creating elements
Working with elements
Localization
Security
Design interaction
Using design IDsExporting designs
Testing

Using design IDs

How to store and retrieve data against a user's design.

Sometimes, apps need to associate data with a user's design. For example, an app could present the user with settings that persist on a per-design basis. To allow for this, apps can use the Apps SDK to access the ID of the current design.

If your app uses design IDs, we strongly encourage you to follow our Security guidelines.

Step 1: Get a design and user token

For security reasons, Canva uses JSON Web Tokens(opens in a new tab or window) (JWTs) to encode certain information. To access this information, apps must decode and verify the JWTs.

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.

Your app needs to use both tokens to securely store data against a user's design.

To get a user token:

  1. Import the auth constant from the @canva/user package:

    import { auth } from "@canva/user";
    TSX
  2. Call the getCanvaUserToken method:

    const userToken = await auth.getCanvaUserToken();
    TSX

To get a design token:

  1. Import the getDesignToken method from the @canva/design package:

    import { getDesignToken } from "@canva/design";
    TSX
  2. Call the getDesignToken method:

    const designToken = await getDesignToken();
    TSX

Step 2: Send the tokens to the app's backend

For security reasons, apps must decode and verify tokens through their backend — never through the frontend.

In the same request, the app should also send whatever data it wants to store against the user's design, such as any settings the user has configured through the app's frontend. The shape of this data is highly dependent on the behavior of the app and isn't specific to the Apps SDK, so it's not demonstrated here.

The following code snippet demonstrates how an app can send tokens to a backend:

const response = await fetch(
`http://localhost:3001/my/api/endpoint?designToken=${designToken}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${userToken}`,
},
}
);
TSX

In this case, the design token is sent as a query parameter, but this isn't a strict requirement. You could send the token in some other way, such as in the body of a POST request.

The user token must always be sent as an Authorization header. Learn more about HTTP request verification.

Step 3: Verify the tokens

After the backend receives the tokens, it must decode and verify them before it can access the encoded data. For a step-by-step walkthrough of how to do this, see JSON Web Tokens.

For Node.js backends: We recommend using the @canva/app-middleware(opens in a new tab or window) package, which provides built-in JWT verification, JWKS caching, and error handling. This package simplifies the verification process and follows security best practices.

Installation

npm install @canva/app-middleware
SHELL

Express.js middleware

For Express.js applications, use the built-in middleware:

import express from "express";
import { design, tokenExtractors } from "@canva/app-middleware/express";
const app = express();
// Design token middleware requires explicit token extraction
app.post(
"/my/api/design",
design.verifyToken({
appId: process.env.CANVA_APP_ID,
tokenExtractor: tokenExtractors.fromQuery("designToken"),
}),
(req, res) => {
// Access verified design information
const { designId, appId } = req.canva.design;
// Your application logic here
res.sendStatus(200);
}
);
app.listen(process.env.PORT || 3000);
TYPESCRIPT

Design tokens require an explicit tokenExtractor parameter. The available extractors are tokenExtractors.fromQuery(), tokenExtractors.fromCookie(), and tokenExtractors.fromBearerAuth().

Framework-agnostic function

For other Node.js environments (Next.js, AWS Lambda, Cloudflare Workers, and so on):

import { initDesignTokenVerifier } from "@canva/app-middleware";
// Initialize once at app startup
const designTokenVerifier = initDesignTokenVerifier({
appId: process.env.CANVA_APP_ID,
});
// In your request handler
async function handleRequest(request) {
// Extract design token from query parameter
const url = new URL(request.url);
const designToken = url.searchParams.get("designToken");
if (!designToken) {
return { status: 401, body: "Unauthorized" };
}
try {
const { designId, appId } = await designTokenVerifier.verify(designToken);
// Your application logic here
return { status: 200, body: "Success" };
} catch (error) {
return { status: 401, body: "Unauthorized" };
}
}
TYPESCRIPT

Manually verifying JWTs

If you're using a non-Node.js backend, you can manually verify JWTs. The following steps show how to implement JWT verification from scratch in TypeScript, which you can then adapt to your preferred language or framework.

Step 1: 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);
TS

To find a JWT library for your preferred language or framework, see jwt.io/libraries(opens in a new tab or window).

Step 2: 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);
TS

Step 3: Get the active public key

Canva exposes a JSON Web Key Set(opens in a new tab or window) (JWKS) through 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, using 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"
}
]
}
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 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 minutes
const TIMEOUT_MS = 30 * 1_000; // 30 seconds
async 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();
}
TS

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",
});
TSX

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. Don't 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 4: Verify the tokens

Use the public key to verify both the user token and design token:

const verifiedUserToken = jwt.verify("USER_JWT_GOES_HERE", publicKey, {
audience: "YOUR_APP_ID",
});
const verifiedDesignToken = jwt.verify("DESIGN_JWT_GOES_HERE", publicKey, {
audience: "YOUR_APP_ID",
});
TS

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

If the tokens are valid, the verified tokens will be dictionaries. The user token 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.

The design token will contain the following properties:

  • aud - The ID of the app.
  • designId - The ID of the current design.

If these properties aren't available, it means the tokens or public key are invalid:

if (!verifiedUserToken.aud || !verifiedUserToken.brandId || !verifiedUserToken.userId) {
throw new Error("The user token is not valid");
}
if (!verifiedDesignToken.aud || !verifiedDesignToken.designId) {
throw new Error("The design token is not valid");
}
TSX

Step 4: Store data against the design

After the app's backend verifies the tokens, it will have access to:

  • The ID of the design
  • The ID of the user
  • The ID of the user's team

The backend can then use the combination of these properties to store data that's linked to the design. The key word here is combination, as it's important to note that:

  • A design may have multiple users collaborating on it.
  • A user may belong to multiple teams.

Therefore, any data shouldn't only be linked with the ID of the design, as this would allow data to be leaked between users or between teams. The data should be linked with the ID of the design, the user, and the user's team.

This means a database table containing data linked to a design would likely have the following columns:

  • design_id
  • team_id

But the exact implementation details may be different.

Step 5: Retrieve data for the design

To retrieve data linked with a design, repeat the previous steps but for an endpoint that performs a read operation instead of a write operation. Be sure the data is scoped to the ID of the design, the user, and the user's team.

Security guidelines

To ensure that data is always linked with the correct design, user, and team, follow these guidelines:

  • Decode and verify tokens through the backend. Your app should never attempt to decode and verify tokens through its frontend. To learn more, see JSON Web Tokens.
  • Get fresh tokens from Canva before sending the tokens to the app's backend. Don't attempt to cache or reuse tokens across multiple HTTP requests.
  • Send tokens to an app's backend with any data relevant to the request, such as data required to perform a read or write operation related to a particular design. Don't send tokens and relevant data in separate requests.

The following code sample demonstrates what these guidelines look like in practice:

import { auth } from "@canva/user";
import { getDesignToken } from "@canva/design";
// Get fresh tokens before every request
const designToken = await getDesignToken();
const userToken = await auth.getCanvaUserToken();
// Send tokens to the app's backend
const response = await fetch(
`http://localhost:3001/my/api/endpoint?designToken=${designToken}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${userToken}`,
"Content-Type": "application/json",
},
// Include relevant data in the same request as the tokens
body: JSON.stringify({
name: "David",
age: 33,
location: "Australia",
}),
}
);
TSX