Signature verification

What is signature verification? Why verify request signatures?

Before you can release an app, it must verify that any HTTP requests it receives originate are actually being sent from Canva (and not from some nefarious third-party). This protects your app — and our users — from a variety of attacks.

What requests need to be verified?

All HTTP requests that Canva sends to an app need to be verified. This includes POST requests, such as the request sent to the /content/resources/find endpoint, and GET requests, such as the request sent to the app's Redirect URL.

If an app supports authentication, it must also verify the authenticity of the authentication flow. To learn more, refer to Authentication.

How Canva confirms that requests are verified

Before submitting an app for review, must run a signature verification test via the Developer Portal. This test sends requests to the app's endpoints and confirms if the requests are verified. You can't submit the app until the test has passed.

To learn more, refer to Test signature verification.

How to verify a request

The steps for verifying a request depend on whether it's a GET request or a POST request. To learn more, refer to the following guides:

Example

This example demonstrates how to verify POST and GET requests in Express.js.

const { createHmac } = require('crypto');
const express = require('express');
const app = express();
app.use(
express.json({
verify: (request, response, buffer) => {
request.rawBody = buffer.toString();
},
}),
);
app.post('/content/resources/find', async (request, response) => {
if (!isValidPostRequest(process.env.CLIENT_SECRET, request)) {
response.sendStatus(401);
return;
}
response.send({
type: 'SUCCESS',
resources: [],
});
});
app.get('/my-redirect-url', async (request, response) => {
if (!isValidGetRequest(process.env.CLIENT_SECRET, request)) {
response.sendStatus(401);
return;
}
response.sendStatus(200);
});
const isValidPostRequest = (secret, request) => {
// Verify the timestamp
const sentAtSeconds = request.header('X-Canva-Timestamp');
const receivedAtSeconds = new Date().getTime() / 1000;
if (!isValidTimestamp(sentAtSeconds, receivedAtSeconds)) {
return false;
}
// Construct the message
const version = 'v1';
const timestamp = request.header('X-Canva-Timestamp');
const path = getPathForSignatureVerification(request.path);
const body = request.rawBody;
const message = `${version}:${timestamp}:${path}:${body}`;
// Calculate a signature
const signature = calculateSignature(secret, message);
// Reject requests with invalid signatures
if (!request.header('X-Canva-Signatures').includes(signature)) {
return false;
}
return true;
};
const isValidGetRequest = (secret, request) => {
// Verify the timestamp
const sentAtSeconds = request.query.time;
const receivedAtSeconds = new Date().getTime() / 1000;
if (!isValidTimestamp(sentAtSeconds, receivedAtSeconds)) {
return false;
}
// Construct the message
const version = 'v1';
const { time, user, brand, extensions, state } = request.query;
const message = `${version}:${time}:${user}:${brand}:${extensions}:${state}`;
// Calculate a signature
const signature = calculateSignature(secret, message);
// Reject requests with invalid signatures
if (!request.query.signatures.includes(signature)) {
return false;
}
return true;
};
const isValidTimestamp = (
sentAtSeconds,
receivedAtSeconds,
leniencyInSeconds = 300,
) => {
return (
Math.abs(Number(sentAtSeconds) - Number(receivedAtSeconds)) <
Number(leniencyInSeconds)
);
};
const getPathForSignatureVerification = (input) => {
const paths = [
'/configuration',
'/configuration/delete',
'/content/resources/find',
'/editing/image/process',
'/editing/image/process/get',
'/publish/resources/find',
'/publish/resources/get',
'/publish/resources/upload',
];
return paths.find((path) => input.endsWith(path));
};
const calculateSignature = (secret, message) => {
// Decode the client secret
const key = Buffer.from(secret, 'base64');
// Calculate the signature
return createHmac('sha256', key).update(message).digest('hex');
};
app.listen(process.env.PORT || 3000);