On September 25th, 2024, we released v2 of the Apps SDK. To learn what’s new and how to upgrade, see Migration FAQ and Migration guide.

OAuth integration

Let your users access resources in an external platform.

Introduction

If your app requires access to resources on a third-party platform (e.g. Google Drive), you can use OAuth to simplify the authorization process for your users. This method allows users to securely grant your app the necessary permissions with a third party service without exposing user credentials.

Canva enhances this process by handling the OAuth flow and the management of access and refresh tokens. This means that Canva does the heavy lifting in ensuring that your app maintains continuous and security-hardened access to the third-party resources it needs, streamlining the user experience and reducing the development burden on your team. Canva currently supports the authorization code flow, with and without Proof-of Key Code Exchange (PKCE).

The authentication API makes it easier to adopt the industry-standard OAuth 2, because Canva's servers are responsible for interacting with your chosen Identity Provider (IdP).

To learn more about the OAuth concepts and terminology, see Key terms.

Overview

To implement OAuth in your app, just do the following:

  1. Configure your chosen identity provider (such as Google or Facebook or your own backend that supports OAuth).
  2. Copy the configuration details into Canva's Developer Portal.
  3. Call the API method oauth.requestAuthorization method in your app.
  4. Canva retrieves and stores refresh tokens and access tokens.
  5. Use the API method getAccessToken to retrieve the latest access token.
  6. Use fetch with the authorization header Bearer <access token>

With this approach, you won't need an additional server to store client IDs, client secrets, access or refresh tokens. You also don't need to handle the token expiry, because Canva does all of this for you.

For more information about the OAuth standard, see RFC6749(opens in a new tab or window).

OAuth workflow: Authentication capability

The following diagram demonstrates the OAuth workflow with the Canva capability.

Overview of the Apps SDK OAuth process

  1. User clicks the login button.
  2. The authorization flow begins.
  3. Canva generates the authorization URL, based on the configuration you provided in the developer portal. If your IdP supports PKCE, then Canva also generates a code challenge.
  4. A popup window opens for the authorization URL.
  5. User logs into the IdP and the popup window is redirected back to Canva by the IdP, using Canva's redirect URI https://www.canva.com/apps/oauth/authorized. This redirection must include the following:
    • The state that Canva originally provided in the authorization URL.
    • The authorization code generated by the IdP.
  6. Canva receives the authorization code.
  7. Canva retrieves the access and optional refresh tokens from the IdP, and if PKCE is enabled it also uses the code verifier. Some IdPs (such as Google) require additional configuration for the response to include the refresh token; if the refresh token isn't provided then the user must re-login to the app once the access token expires, or call oauth.requestAuthorization with forceRefresh set to true.
    • Canva stores the access and refresh tokens.
    • The popup window closes and the authorization flow concludes.
    • Your app code requests a token using the getAccessToken auth API, and Canva returns the access token to the application.
  8. Your app then uses the access token to retrieve resources from the resource server.
    • If a refresh token was provided, then Canva will automatically refresh the token when the access token expires or forceRefresh is called.

This section describes some recommended security practices for adding OAuth support to an app.

This is not a complete list of all the security hardening steps you would need to apply.

For a list of additional security responsibilities you'll need to consider for your app, see Shared responsibility model for Canva Apps.

Managing the access token lifecycle

  • Canva manages the refresh and revocation of tokens on your behalf.
  • If your IdP supplies a refresh token and an expiry then Canva will preemptively refresh access tokens before they become invalid.
  • To ensure that you always have an up-to-date token, always call getAccessToken. There's no need to store or cache the token yourself or attempt to refresh the token.
  • If your IdP provides a token revocation endpoint, you should configure it in the developer portal to ensure that Canva can automatically revoke access to the IdP if the user's Canva account is deleted, the app is uninstalled, or when the user otherwise wants to log out of the app.
  • If you want to preemptively refresh an access token, or your IdP does not provide an expiry, you can forcefully refresh a token by calling forceRefresh on the getAccessToken API.
  • If your IdP doesn't support refresh tokens, you must call getAccessToken with forceRefresh enabled, otherwise your user will be logged out when the token expires (provided Canva has the expiry) or the app will receive "unauthorized" errors from the resource server (if Canva does not have the expiry).

Storing tokens

  • There's no reason to store the token, because Canva handles the caching for you. To ensure you always have an up-to-date token in a secure way, always call getAccessToken. This method will not trigger a network request or a delay for your app, because Canva updates this value automatically.

  • Web storage layers (such as localstorage, cookieJar, and session) are vulnerable to many forms of attacks that will compromise your users. For example, the risk to users of having their Google access token leaked can be devastating.

  • There's no secure way to store a token in web storage.

Proof Key for Code Exchange (PKCE)

PKCE strengthens the OAuth flow by adding an extra layer of security, which helps ensure that access tokens are not intercepted by a malicious third-party. Not all IdPs support PKCE, but for those that do, Canva recommends keeping this feature enabled because the authentication capability takes the complexity out of using it by generating the code verifier for you.

  1. At the start of the OAuth flow, Canva generates a high-entropy cryptographic key called the code_verifier.
  2. The code_verifier is transformed into a code_challenge, using SHA-256, ensuring that the verifier itself is not sent via the popup window's URL.
  3. When making the authorization request, the generated authorization URL includes the code challenge instead of the verifier.
  4. After the user authenticates, Canva requests the access token by presenting the original code_verifier. Before issuing an access token, the IdP compares the presented verifier with the previously supplied challenge, to ensure that they match.

Cross-Origin Opener Policy (COOP)

COOP affects Canva's OAuth flow when an application enforces same-origin and isolates the browsing context from other origins. A same-origin policy blocks Canva from accessing any authorization window properties using window.opener, preventing the OAuth flow from completing because the two windows can't communicate. Canva recommends reviewing your identity platform's COOP settings.

  • If you own the identity platform where the user opens the authorization prompt, and you're enforcing COOP of same-origin, ask your security team about setting COOP from same-origin to same-origin-allow-popups. This policy allows the popup window to interact with the opener in a cross-origin setting.
  • If your app integration uses a third-party identity platform to enforce COOP of same-origin, contact your platform's support team to request changing the COOP settings.

Traffic between app and resource server

Note that https or wss requests are mandatory for all apps that communicate with external backends. This is enforced by each app's Content Security Policy (CSP). The https requirement helps ensure that sensitive data (such as tokens or user personally identifiable information (PII)) cannot be intercepted by third-parties.

However, URL parameters containing sensitive fields might be mistakenly logged. When possible, always include sensitive information in the headers or body of requests.

Separate IdPs

When using an Identity Provider (IdP), you must clearly distinguish between your production and development environments.

  • Separate environments: Your production and development IdP environments must use separate environment settings, with different client IDs, secrets, and endpoints.
  • User data: Don't use real user data in a development IdP.
  • Logging: Your log files must not record sensitive information.

Revoking access

Review Canva's design guidelines for handling app disconnections and reconnections.

Canva can automatically request token revocation from the IdP, but only if you've configured the revocation exchange URL in the Developer Portal(opens in a new tab or window). For more information about the revocation setting, see Prerequisite: Configure Developer Portal.

If the revocation exchange URL is configured, Canva automatically requests token revocation in the following cases:

  • The user's Canva account is deleted.

  • When the users clicks on Remove from your apps:

    The "Remove from your apps" menu button

For information on manually requesting revocation of the refresh and access tokens, see the deauthorize function.

IdP configurations

Some IdPs (such as Google) will not automatically issue refresh tokens, and require additional configuration steps. To ensure a fluid experience for users, we highly recommend setting up IdPs to issue refresh tokens.

Reusing Client Configuration

Some IdPs (such as Google) organize authentication and access management on a project basis, instead of by individual clients or applications. This structure can make it tempting to use a single configured client across multiple apps for simplicity. When applied poorly, this approach can be an anti-pattern for the user experience. This is because reusing a client configuration across different apps can lead to confusion during the consent phase, where users see permissions requested under a single project name, instead of permissions being specific to the app they're currently using. This shared consent experience can be misleading and might not accurately represent the individual app's data access and usage, potentially eroding trust and clarity for the end user.

To ensure a transparent and app-specific consent process, developers are advised to avoid this pattern unless there's a well-justified reason that serves a clear benefit.

Dynamic IdP configurations

This is not a recommended pattern.

We don't explicitly support dynamic IdP configurations, but if you do need to switch between different tenants then you can only do this by altering the query parameters in the popup window; this can be done when calling oauth.requestAuthorization. This solution only works for the Authorization URL, and Canva will only ever call the Token Exchange and revocation URLs provided in the developer portal, so you must handle these cases yourself.

Add OAuth to your app

To help you add OAuth support to your app, the Apps SDK includes the following methods:

  • auth.initOauth: Initializes the OAuth methods.
  • oauth.requestAuthorization: Checks whether the user has granted authorization to your app.
  • oauth.getAccessToken: Retrieves the user's OAuth access token.

Prerequisite: Register with third party

Register your app with the third-party platform, also known as the authorization server. This procedure varies depending on the platform. For example, this process is described in the official Google documentation(opens in a new tab or window).

Prerequisite: Configure Developer Portal

Once you've registered your app with the authorization server, you should have enough information to configure the OAuth settings in the developer portal(opens in a new tab or window).

  1. Locate your app in the developer portal(opens in a new tab or window).

  2. In the left menu, click Authentication.

  3. Complete the settings below, using the information supplied by the authorization server:

    • Provider: The issuer field defined by the authorization server.
    • Client ID: A unique identifier for your app. Issued by the authorization server.
    • Client secret: A secret string that authenticates your app during OAuth processes. Issued by the authorization server.
    • Authorization server URL: The URL where your app redirects the user, letting them authenticate and authorize access for your app. Issued by the authorization server.
    • Token exchange URL: The URL where the client exchanges an authorization code for an access token. Issued by the authorization server.
    • Revocation exchange URL (Optional): The URL where the client can request the invalidation of a previously-issued token. Issued by the authorization server.
    • PKCE: Should be set to on if the authorization server supports it, and set to off if it doesn't (Enabled by default).

Step 1: Initialize OAuth

In your app, import the required libraries and initialize the OAuth method:

import React, { useEffect, useState, useMemo } from "react";
import { AccessTokenResponse, auth } from "@canva/user";
const oauth = auth.initOauth();
TS

Step 2: Create the state variable

Create a state variable to track the token and authorization status:

export const App = () => {
const [accessToken, setAccessToken] = useState<AccessTokenResponse>(null)
const isAuthorized = useMemo(() => accessToken !== null, [accessToken])
const [isLoading, setIsLoading] = useState(true);
TS

Step 3: Create the function

Create a function that fetches an access token from OAuth and then updates the state for the accessToken variable. This will check whether the user has authorized your app's access. If the user is logged in, it will return the token:

const retrieveAndSetToken = async () => {
setIsLoading(true);
try {
const accessToken = await oauth.getAccessToken();
setAccessToken(accessToken);
} finally {
setIsLoading(false);
}
};
TS

Step 4: Check for existing secret

Use the useEffect hook to trigger the retrieveAndSetToken function when the component loads; this will also attempt to fetch an access token:

useEffect(() => {
retrieveAndSetToken();
}, []);
TS

Step 5: Start the workflow

If no token is found, start the OAuth flow by triggering oauth.requestAuthorization(). You can then retrieve and hold the user's access token in state using the retrieveAndSetToken function created above.

async function login() {
const authorizeResponse = await oauth.requestAuthorization()
if (authorizeResponse.status === "completed") {
retrieveAndSetToken()
}
}
}
TS

Step 6: Show a confirmation message

If the user has granted authorization, show a confirmation message. Otherwise, show a login prompt.

return (
<div>
{isAuthorized ? (
<div>You are logged in!</div>
) : isLoading ? (
<div>Loading...</div>
) : (
<button onClick={login}>Login</button>
)}
</div>
)
}
TS

Example code

This example is the complete code from the above tutorial.

import { useEffect, useState, useMemo } from "react"
import type { AccessTokenResponse} from "@canva/user";
import { auth } from "@canva/user"
const oauth = auth.initOauth()
export const App = () => {
const [accessToken, setAccessToken] = useState<AccessTokenResponse>(null)
const isAuthorized = useMemo(() => accessToken != null, [accessToken])
const [isLoading, setIsLoading] = useState(false);
const retrieveAndSetToken = async () => {
try {
setIsLoading(true)
const accessToken = await oauth.getAccessToken()
setAccessToken(accessToken)
} finally {
setIsLoading(false)
}
}
useEffect(() => {
retrieveAndSetToken()
}, [])
async function login() {
const authorizeResponse = await oauth.requestAuthorization()
if (authorizeResponse.status === "completed") {
retrieveAndSetToken()
}
}
return (
<div>
{isAuthorized ? (
<div>You are Logged in!</div>
) : isLoading ? (
<div>Loading...</div>
) : (
<button onClick={login}>Login</button>
)}
</div>
)
}
TS

API reference

For more information about the API, see auth.initOauth.

Key terms

Term
Definition
Authorization Server
Authenticates the resource owner (typically the user), and grants or denies requests from client applications to access the user's resources on the resource server.
Client Identifier (or Client ID)
A unique string assigned to a client application by the authorization server. This is used to identify the client during the authorization process. Defined in RFC6749 - section 2.2(opens in a new tab or window)
Client Secret
A secret known only to the client and the Identity Provider (IdP). When combined with the client ID, it effectively creates a username and password for the client. Lets the authorization server identify the authenticity of the client (not the individual user). Defined in RFC6749 - Section 2.3.1(opens in a new tab or window)
Authorization Code
The client receives the authorization code from the authorization server. To obtain this, the client sends the resource owner to the authorization server, through their web browser. The authorization server then redirects the resource owner back to the client, along with the authorization code. Defined in RFC6749 - 1.3.1(opens in a new tab or window)
PKCE (Proof Key Code Exchange)
Improves security for clients by mitigating authorization code interception attacks. Defined in RFC7636(opens in a new tab or window).