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:
- Configure your chosen identity provider (such as Google or Facebook or your own backend that supports OAuth).
- Copy the configuration details into Canva's Developer Portal.
- Call the API method
oauth.requestAuthorization
method in your app. - Canva retrieves and stores refresh tokens and access tokens.
- Use the API method
getAccessToken
to retrieve the latest access token. - Use
fetch
with the authorization headerBearer <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.
- User clicks the login button.
- The authorization flow begins.
- 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.
- A popup window opens for the authorization URL.
- 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.
- Canva receives the authorization code.
- 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
withforceRefresh
set totrue
.- 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.
- 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.
- If a refresh token was provided, then Canva will automatically refresh the token when the access token expires or
Recommended practices
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 thegetAccessToken
API. - If your IdP doesn't support refresh tokens, you must call
getAccessToken
withforceRefresh
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
, andsession
) 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.
- At the start of the OAuth flow, Canva generates a high-entropy cryptographic key called the
code_verifier
. - The
code_verifier
is transformed into acode_challenge
, usingSHA-256
, ensuring that the verifier itself is not sent via the popup window's URL. - When making the authorization request, the generated authorization URL includes the code challenge instead of the verifier.
- 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 fromsame-origin
tosame-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:
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).
-
Locate your app in the developer portal(opens in a new tab or window).
-
In the left menu, click Authentication.
-
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();
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);
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);}};
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();}, []);
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()}}}
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>)}
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>)}
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). |