Multi-account OAuth

Allow users to connect and manage multiple OAuth accounts from the same provider.

Multi-account OAuth enables users to connect multiple accounts from the same OAuth provider within your app. This is particularly useful for cases when users need to switch between different profiles on the same platform.

The multi-account OAuth API is provided as a preview. Preview APIs are unstable and may change without warning. You can't release public apps using a preview API until it's stable.

Key concepts

Single-account vs multi-account mode

OAuth can be initialized in two modes:

  • Single-account mode: The traditional OAuth flow where only one account per provider can be connected at a time. When a user authorizes a new account, it replaces any previously connected account.
  • Multi-account mode: Allows users to connect multiple accounts from the same provider. Each account is stored separately and can be accessed, refreshed, or disconnected individually.

Provider configuration

The provider parameter identifies which OAuth configuration to use from the Developer Portal. This allows your app to:

  • Support multiple OAuth providers (for example, both Google and Meta)
  • Create separate OAuth instances for each provider
  • Configure provider-specific settings like scopes and endpoints

Account management

In multi-account mode, each connected account includes:

  • ID: A unique identifier for the account in the external provider's system
  • Principal: The user's unique identifier from the OAuth provider (for example, email address)
  • Display name: The user's name as provided by the OAuth provider
  • Avatar URL: The user's profile picture URL
  • Expiry status: Whether the account's access token has expired

When to use multi-account OAuth

Consider using multi-account OAuth when:

  • Users need to manage multiple accounts from the same platform (for example, multiple social media accounts)
  • Your app publishes content to external platforms and users want to choose the destination account
  • Users switch between personal and business accounts on the same platform
  • Your app integrates with team or organization accounts where users manage multiple profiles

If your app only needs access to a single account per provider, use the traditional OAuth flow instead.

Add multi-account OAuth to your app

Prerequisites

Before implementing multi-account OAuth:

  1. Configure your OAuth provider in the Developer Portal(opens in a new tab or window) following the steps in the OAuth integration guide.
  2. Configure the User profile endpoint and field mappings in the Developer Portal. This endpoint is used to fetch user profile information (display name, avatar) after authentication.

Profile field mapping

When configuring your OAuth provider in the Developer Portal, you must map fields from your provider's user profile response to Canva's expected fields. This mapping tells Canva how to extract user information from your OAuth provider's profile endpoint.

The required field mappings are:

  • externalId: Maps to the unique user identifier from your OAuth provider (for example, sub for OpenID Connect providers, id for many social platforms).
  • displayName: Maps to the user's display name (for example, name, display_name, or full_name).
  • principal: Maps to the user's primary identifier, typically their email address (for example, email).
  • avatarUrl (optional): Maps to the user's profile picture URL (for example, picture, avatar_url, or profile_image_url).

For example, if your OAuth provider returns a profile response like:

{
"sub": "1234567890",
"name": "Jane Doe",
"email": "[email protected]",
"picture": "https://example.com/avatar.jpg"
}
JSON

Your field mappings in the Developer Portal would be:

  • externalId: sub
  • displayName: name
  • principal: email
  • avatarUrl: picture

The field mapping configuration is critical for multi-account OAuth to work correctly. Incorrect mappings prevents Canva from properly identifying and displaying user accounts.

Important provider configuration notes

When you create an OAuth provider in the Developer Portal, you can't change the multi-account setting. The multi-account flag is immutable after creating an OAuth provider.

If you need to switch between single-account and multi-account modes:

  1. Delete the existing provider from the Developer Portal.
  2. Create a new provider with the desired multi-account setting.
  3. Reconfigure all settings including endpoints, scopes, and field mappings.

Impact of provider deletion:

  • All existing user authentications will be revoked. Users who previously connected their accounts will need to re-authenticate.
  • All stored access and refresh tokens will be invalidated. Your app will lose access to external APIs for all users.
  • User data associated with the provider may be lost. Depending on how your app stores user information.

Consider the multi-account requirement carefully during initial setup to avoid disrupting existing users.

Step 1: Initialize OAuth in multi-account mode

Import the required libraries and initialize OAuth with multi-account options:

import { auth } from "@canva/user";
const oauth = auth.initOauth({
type: "multi_account",
provider: "meta",
});
TS

Step 2: Create state variables

Create state variables to track connected accounts and the selected account:

import { useState, useEffect } from "react";
import type { OauthAccount } from "@canva/user";
const scope = ["profile", "email"];
export const App = () => {
const [accounts, setAccounts] = useState<OauthAccount[]>([]);
const [selectedAccount, setSelectedAccount] = useState<OauthAccount | null>(null);
const [isLoading, setIsLoading] = useState(true);
TSX

Step 3: Fetch connected accounts

Create a function to fetch and update the list of connected accounts:

const refetchAccounts = async () => {
setIsLoading(true);
try {
const response = await oauth.getAccounts();
setAccounts(response.accounts);
// Set first account as selected if none is currently selected
if (!selectedAccount && response.accounts.length > 0) {
setSelectedAccount(response.accounts[0]);
}
} catch (error) {
console.error("Failed to fetch accounts:", error);
} finally {
setIsLoading(false);
}
};
TS

Step 4: Load accounts on mount

Use the useEffect hook to load accounts when the component mounts:

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

Step 5: Implement authorization flow

Create a function to authorize new accounts:

const authorize = async () => {
try {
// Request authorization from the OAuth provider
// This opens a popup window for the user to log in
await oauth.requestAuthorization({ scope });
// Refresh the accounts list after successful authorization
await refetchAccounts();
} catch (error) {
console.error("Authorization failed:", error);
}
};
TS

Step 6: Implement account disconnection

Create a function to disconnect individual accounts:

const disconnectAccount = async (account: OauthAccount) => {
try {
// Revoke access for the specific account
await account.deauthorize();
// Clear selected account if it was the one being disconnected
if (selectedAccount?.id === account.id) {
setSelectedAccount(null);
}
// Refresh the accounts list
await refetchAccounts();
} catch (error) {
console.error("Failed to disconnect account:", error);
}
};
TS

Step 7: Display account list and controls

Render the list of connected accounts with controls to select or disconnect them:

import { Button, Text, Title, Rows } from "@canva/app-ui-kit";
return (
<Rows spacing="2u">
{isLoading ? (
<Text>Loading accounts...</Text>
) : accounts.length === 0 ? (
<>
<Text>No accounts connected</Text>
<Button variant="primary" onClick={authorize}>
Connect account
</Button>
</>
) : (
<>
{accounts.map((account) => (
<Rows key={account.id} spacing="1u">
<Title>{account.displayName}</Title>
<Text size="small">{account.principal}</Text>
{account.expired && (
<Text size="small" tone="critical">
Access expired - reconnect required
</Text>
)}
{selectedAccount?.id === account.id ? (
<Text tone="info">Currently selected</Text>
) : (
<Button
variant="secondary"
onClick={() => setSelectedAccount(account)}
>
Select
</Button>
)}
<Button
variant="secondary"
onClick={() => disconnectAccount(account)}
>
Disconnect
</Button>
</Rows>
))}
<Button variant="primary" onClick={authorize}>
Connect another account
</Button>
</>
)}
</Rows>
);
TSX

Step 8: Use access tokens for API requests

When making API requests, use the access token from the selected account:

const fetchData = async () => {
if (!selectedAccount) {
return;
}
try {
// Get the access token for the selected account
const tokenResponse = await selectedAccount.getAccessToken();
if (!tokenResponse) {
// Access token not available, user needs to re-authorize
console.error("No access token available");
return;
}
// Make an authenticated API request
const response = await fetch("https://api.example.com/data", {
headers: {
Authorization: `Bearer ${tokenResponse.token}`,
},
});
const data = await response.json();
console.log("API response:", data);
} catch (error) {
console.error("Failed to fetch data:", error);
}
};
TS

Multiple OAuth providers

Coming soon: Support for managing multiple OAuth providers within the same app is currently in development and not yet available.

This feature will allow your app to:

  • Initialize separate OAuth instances for different providers (for example, Google, Meta, Twitter).
  • Let users connect accounts from multiple platforms simultaneously.
  • Manage multi-account authentication across different OAuth providers.

Check back for updates on this functionality.

Your app can initialize separate OAuth instances for different providers. This is useful when integrating with multiple platforms:

import { auth } from "@canva/user";
// Initialize OAuth for Meta
const metaOauth = auth.initOauth({
// Initialize OAuth for Meta in single-account mode
// In single-account mode, authorizing a new account replaces any previously connected account
const metaOauth = auth.initOauth({
type: "single_account",
provider: "meta",
});
// Initialize OAuth for Google in single-account mode
const googleOauth = auth.initOauth({
type: "single_account",
provider: "google",
});
// Request authorization from different providers
async function loginWithMeta() {
await metaOauth.requestAuthorization();
const token = await metaOauth.getAccessToken();
// Use token to call Meta APIs
}
async function loginWithGoogle() {
await googleOauth.requestAuthorization();
const token = await googleOauth.getAccessToken();
// Use token to call Google APIs
}
TS

Account identification

  • Use the principal field (for example, email address) to help users identify their accounts
  • Display the displayName and avatarUrl to provide visual context
  • Show the expired status to alert users when re-authorization is needed

User experience

  • Clearly indicate which account is currently selected
  • Provide a way to switch between accounts without disconnecting
  • Make it easy to add additional accounts
  • Show account information (name, profile picture) in selection interfaces

Error handling

  • Handle cases where getAccessToken() returns null (token unavailable or expired)
  • Provide clear error messages when authorization fails
  • Gracefully handle network errors when fetching account lists
  • Prompt users to re-authorize when tokens expire

Security

  • Always call getAccessToken() to get the latest token; don't cache tokens yourself
  • Use the deauthorize() method to properly revoke access when disconnecting accounts
  • Validate that the selected account exists before making API requests
  • Handle token refresh automatically by relying on Canva's token management

API reference

For more information about the multi-account OAuth API, see: