If an authentication-enabled app has a publish extension, users of the app must authenticate with a third-party platform before they can publish their designs.
This topic explains how to implement authentication in a publish extension.
Step 1: Enable authentication
By default, authentication is disabled.
To enable authentication:
- Navigate to an app via the Developer Portal.
- Click Authentication.
- Enable This app requires authentication.
The Authentication page has two fields:
- Redirect URL
- Authentication base URL
The purpose of these fields is explained in the following steps.
Step 2: Check if the user is authenticated
When a user opens a publish extension, Canva sends a POST
request to the following endpoint:
<authentication_base_url>/configuration
<authentication_base_url>
is a placeholder that refers to the app's Authentication base URL field, which can be set via the Authentication page in the Developer Portal.
The body of this request includes a user
property that contains the ID of the user. Your extension can use this ID to check if the current user is associated with a user in the backend of a third-party platform.
If the ID of the Canva user is not associated with a user of the third-party platform, respond to the request with the following object:
{"type": "ERROR","errorCode": "CONFIGURATION_REQUIRED"}
This tells Canva to render a Connect button. Users can select the button to begin the authentication flow.
If the ID of the Canva user is associated with a user of the third-party platform, respond to the request with the following object:
{"type": "SUCCESS","labels": ["PUBLISH"]}
This tells Canva to continue loading the publish extension. All subsequent requests that Canva sends to the extension include the user's ID in the request body. The extension can use this ID to:
- Show the user their files and folders. (This only applies to extensions using the Flat list or Nested list layout.)
- Associate the user's published designs with the correct user in the platform's backend.
Step 3: Redirect users to the Redirect URL
When a user clicks the Connect button, Canva opens a pop-up window.
Inside this window, Canva redirects the user to the app's Redirect URL.
The Redirect URL must point to a page that lets users authenticate with the third-party platform. For example, this could be a login form with a username and password, or the start of an OAuth 2.0 authorization flow. The exact authentication method is the responsibility of the app. Canva doesn't enforce any specific approach.
When redirecting users to the Redirect URL, Canva automatically appends a number of query parameters to the URL. All of these parameters serve a purpose, but the user
and state
parameters are of particular importance.
The user
parameter contains the ID of the user. If the user successfully authenticates, the app needs to persist this ID to a data store. When the user returns to the app, the app can use this ID to check if the user is authenticated or not.
The state
parameter contains a unique token for the authentication flow. For security reasons, the app must return this token to Canva at the end of the authentication flow. This protects the app against cross-site request forgery (CSRF) attacks.
To see the complete list of query parameters, see Redirect URL.
For guidelines on providing a friendly and familiar-looking Redirect URL, see Provide a user-friendly Redirect URL.
Step 4: Redirect the user back to Canva
At the end of the authentication flow, redirect users to the following URL from within the pop-up window:
https://canva.com/apps/configured?success=<true|false>&state=<token>
This must be a 302
redirect.
You also need to append the following parameters to the URL:
success
state
If the user has successfully authenticated, set the success
parameter to true
. This tells Canva to close the pop-up window and reload the extension. When the extension reloads, it can once again check if the user is authenticated.
If the user has not successfully authenticated, set the success
parameter to false
. This tells Canva to close the pop-up window without reloading the extension.
Always set the state
parameter to the value of the token that Canva provided at the start of the authentication flow. If the state
parameter is missing or invalid, an error occurs and the authentication fails.
Once a user has authenticated, don't require them to authenticate with any of the app's other extensions. For example, if an app has a content and a publish extension, only require them to navigate through the authentication flow once. In other words, handle authentication on an app-wide basis, not a per-extension basis. This is because users don't distinguish between apps and extensions. As far as they're concerned, they're simply using an app that supports various features.
Step 5: Let users disconnect from Canva
After a user authenticates, they have the option of revoking that authentication. Your app must support this option to be eligible for distribution via the Apps Directory.
When a user disconnects an app, Canva sends a POST
request to the following endpoint:
<authentication_base_url>/configuration/delete
The body of this request includes a user
property, which contains the ID of the user. Your app can use this ID to remove the association between the Canva user and the user in the backend of the third-party platform.
When a user disconnects an app, the app must disconnect the user from all of its extensions. This means, if an app has a content and a publish extension, it must disconnect the user from both extensions. (This relates to the earlier point about authentication being app-wide.)
To learn more about this endpoint, see the API reference.
Step 6: Verify request signatures
Before you can submit an app for review, you must implement signature verification for the following endpoints:
-
Redirect URL
-
/configuration
-
/configuration/delete
-
/publish/resources/find
-
/publish/resources/get
-
/publish/resources/upload
This prevents third-parties from sending requests to these URLs.
The redirection from Canva to the Redirect URL is a GET
request. To learn how to set up signature verification for a GET
request, see Verifying GET
requests.
All other requests from Canva are POST
requests. To learn how to set up signature verification for a POST
request, see Verifying POST
requests.
Example
import basicAuth from "express-basic-auth";import express from "express";import querystring from "querystring";import fs from "fs-extra";import jimp from "jimp";import path from "path";import url from "url";import { Low, JSONFile } from "lowdb";const app = express();app.use(express.json());app.use(express.static("public"));// Set up the database// Docs: https://github.com/typicode/lowdbconst adapter = new JSONFile("db.json");const db = new Low(adapter);await db.read();db.data ||= { loggedInUsers: [] };// Set up Redirect URLapp.get("/login",basicAuth({users: { admin: "password123" },challenge: true,}),async (request, response) => {const { loggedInUsers } = db.data;const { user } = request.query;// Add the user to the databaseif (!loggedInUsers.includes(user)) {loggedInUsers.push(user);await db.write();}// Create query parameters for redirect back to Canvaconst params = querystring.stringify({success: true,state: request.query.state,});// Redirect back to Canvaresponse.redirect(302, `https://canva.com/apps/configured?${params}`);});// Handle upload requestsapp.post("/publish/resources/upload", async (request, response) => {const { loggedInUsers } = db.data;const { user } = request.body;// The user is logged-inif (loggedInUsers.includes(user)) {// Ensure the "public" directory existsawait fs.ensureDir("public");// Get the first asset from the "assets" arrayconst [asset] = request.body.assets;// Download the assetconst image = await jimp.read(asset.url);const filePath = path.join("public", asset.name);await image.writeAsync(filePath);// Respond with the URL of the published designresponse.send({type: "SUCCESS",url: url.format({protocol: request.protocol,host: request.get("host"),pathname: asset.name,}),});return;}// The user not is logged-inresponse.send({type: "ERROR",errorCode: "CONFIGURATION_REQUIRED",});});// Handle authentication requestsapp.post("/configuration", async (request, response) => {const { loggedInUsers } = db.data;const { user } = request.body;// The user is logged-inif (loggedInUsers.includes(user)) {response.send({type: "SUCCESS",labels: ["PUBLISH"],});return;}// The user is not logged-inresponse.send({type: "ERROR",errorCode: "CONFIGURATION_REQUIRED",});});// Handle disconnection requestsapp.post("/configuration/delete", async (request, response) => {// Remove the current user from the databasedb.data.loggedInUsers = db.data.loggedInUsers.filter((user) => {return user !== request.body.user;});await db.write();response.send({type: "SUCCESS",});});app.listen(process.env.PORT || 3000);