Examples
App elements
Assets and media
Fundamentals
Intents
Design interaction
Drag and drop
Design elements
Localization
Content replacement
Multi-provider Authentication
Multiple OAuth provider authentication integration for external platforms.
Running this example
To run this example locally:
-
If you haven't already, create a new app in the Developer Portal(opens in a new tab or window). For more information, refer to our Quickstart guide.
-
In your app's configuration on the Developer Portal(opens in a new tab or window), ensure the "Development URL" is set to
http://localhost:8080. -
Clone the starter kit:
git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.gitcd canva-apps-sdk-starter-kitSHELL -
Install dependencies:
npm installSHELL -
Run the example:
npm run start multi_provider_authenticationSHELL -
Click the Preview URL link shown in the terminal to open the example in the Canva editor.
Example app source code
// For usage information, see the README.md file.import {Button,LoadingIndicator,Rows,Title,Text,Box,MultilineInput,FormField,Columns,Column,} from "@canva/app-ui-kit";import { useMemo, useState, useEffect, useCallback } from "react";import type { AccessTokenResponse } from "@canva/user";import { auth } from "@canva/user";import * as styles from "styles/components.css";// Provider names as defined in the Developer Portalconst META_PROVIDER = "meta" as const;const GOOGLE_PROVIDER = "google" as const;const metaScope = new Set(["openid"]);const googleScope = new Set(["openid", "profile", "email"]);const META_BACKEND_URL = `${BACKEND_HOST}/meta-route`;const GOOGLE_BACKEND_URL = `${BACKEND_HOST}/google-route`;type ProviderStatus = {accessTokenResponse: AccessTokenResponse | null | undefined;error: string | null;loading: boolean;responseBody: unknown | undefined;};export function App() {// Initialize separate OAuth clients for each providerconst metaOauth = useMemo(() => auth.initOauth({ type: "single_account", provider: META_PROVIDER }),[],);const googleOauth = useMemo(() =>auth.initOauth({type: "single_account",provider: GOOGLE_PROVIDER,}),[],);// Track state for each provider independentlyconst [metaStatus, setMetaStatus] = useState<ProviderStatus>({accessTokenResponse: undefined,error: null,loading: false,responseBody: undefined,});const [googleStatus, setGoogleStatus] = useState<ProviderStatus>({accessTokenResponse: undefined,error: null,loading: false,responseBody: undefined,});useEffect(() => {// Check if users are already authenticated when the component mountsretrieveAndSetToken(META_PROVIDER);retrieveAndSetToken(GOOGLE_PROVIDER);}, [metaOauth, googleOauth]);const getOauthClient = (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER,) => {return provider === META_PROVIDER ? metaOauth : googleOauth;};const getScope = (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER,) => {return provider === META_PROVIDER ? metaScope : googleScope;};const getStatus = (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER,) => {return provider === META_PROVIDER ? metaStatus : googleStatus;};const setStatus = (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER,updates: Partial<ProviderStatus>,) => {if (provider === META_PROVIDER) {setMetaStatus((prev) => ({ ...prev, ...updates }));} else {setGoogleStatus((prev) => ({ ...prev, ...updates }));}};const authorize = useCallback(async (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER) => {const oauth = getOauthClient(provider);const scope = getScope(provider);setStatus(provider, {accessTokenResponse: undefined,error: null,loading: true,});try {// Trigger the OAuth authorization flow for the specific providerawait oauth.requestAuthorization({ scope });await retrieveAndSetToken(provider);} catch (error) {setStatus(provider, {error: error instanceof Error ? error.message : "Unknown error",loading: false,});}},[metaOauth, googleOauth],);// IMPORTANT: Always call getAccessToken when you need a token - tokens can expire.// Canva automatically handles caching and refreshing tokens for you.const retrieveAndSetToken = useCallback(async (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER,forceRefresh = false,) => {const oauth = getOauthClient(provider);const scope = getScope(provider);setStatus(provider, { loading: true });try {const accessTokenResponse = await oauth.getAccessToken({forceRefresh,scope,});setStatus(provider, {accessTokenResponse,loading: false,});} catch (error) {setStatus(provider, {error: error instanceof Error ? error.message : "Unknown error",loading: false,});}},[metaOauth, googleOauth],);const logout = useCallback(async (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER) => {const oauth = getOauthClient(provider);setStatus(provider, {accessTokenResponse: undefined,loading: true,});// Revoke the user's authorization and clear stored tokens for the specific providerawait oauth.deauthorize();setStatus(provider, {accessTokenResponse: null,loading: false,responseBody: undefined,});},[metaOauth, googleOauth],);const fetchData = useCallback(async (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER) => {const status = getStatus(provider);const accessToken = status.accessTokenResponse?.token;if (!accessToken) {return;}const backendUrl =provider === META_PROVIDER ? META_BACKEND_URL : GOOGLE_BACKEND_URL;setStatus(provider, { loading: true });try {// Example of using the access token to make authenticated API requestsconst res = await fetch(backendUrl, {headers: {Authorization: `Bearer ${accessToken}`,},});const data = await res.json();setStatus(provider, {responseBody: data,loading: false,});} catch (error) {setStatus(provider, {error: error instanceof Error ? error.message : "Unknown error",loading: false,});}},[metaStatus.accessTokenResponse, googleStatus.accessTokenResponse],);const renderProviderSection = (provider: typeof META_PROVIDER | typeof GOOGLE_PROVIDER,providerDisplayName: string,) => {const status = getStatus(provider);const loading = status.loading && status.accessTokenResponse === undefined;const isConnected = status.accessTokenResponse != null;return (<Boxpadding="3u"border="standard"borderRadius="standard"background="neutral"><Rows spacing="2u"><Title size="small">{providerDisplayName} Provider</Title>{status.error ? (<Rows spacing="1u"><Text tone="critical">Error: {status.error}</Text><Buttonvariant="primary"onClick={() => authorize(provider)}disabled={loading}>Try again</Button></Rows>) : loading ? (<LoadingIndicator />) : !isConnected ? (<Rows spacing="2u"><Text>Connect your {providerDisplayName} account to use this provider.</Text><Buttonvariant="primary"onClick={() => authorize(provider)}disabled={loading}>{`Sign in to ${providerDisplayName}`}</Button></Rows>) : (<Rows spacing="2u"><Text>Connected to {providerDisplayName}!</Text><Columns spacing="1u"><Column><Buttonvariant="secondary"onClick={() => logout(provider)}disabled={loading}>Log out</Button></Column><Column><Buttonvariant="primary"onClick={() => fetchData(provider)}disabled={loading}>Fetch data</Button></Column></Columns>{status.responseBody ? (<FormFieldlabel="Response"value={JSON.stringify(status.responseBody, null, 2)}control={(props) => (<MultilineInput {...props} maxRows={5} autoGrow readOnly />)}/>) : null}</Rows>)}</Rows></Box>);};const result = (<div className={styles.scrollContainer}><BoxjustifyContent="center"width="full"alignItems="center"display="flex"height="full"><Rows spacing="3u"><Rows spacing="2u"><Title>Multi-provider authentication</Title><Text>This example demonstrates how apps can allow users to authorizewith multiple OAuth providers (Meta and Google) simultaneously.</Text><Text>To set up please see the README.md in the/examples/fundamentals/multi_provider_authentication folder</Text></Rows>{renderProviderSection(META_PROVIDER, "Meta")}{renderProviderSection(GOOGLE_PROVIDER, "Google")}</Rows></Box></div>);return result;}
TYPESCRIPT
// For usage information, see the README.md file.import { AppUiProvider } from "@canva/app-ui-kit";import { createRoot } from "react-dom/client";import { App } from "./app";import "@canva/app-ui-kit/styles.css";import type { DesignEditorIntent } from "@canva/intents/design";import { prepareDesignEditor } from "@canva/intents/design";async function render() {const root = createRoot(document.getElementById("root") as Element);root.render(<AppUiProvider><App /></AppUiProvider>,);}const designEditor: DesignEditorIntent = { render };prepareDesignEditor(designEditor);// Hot Module Replacement for development (automatically reloads the app when changes are made)if (module.hot) {module.hot.accept("./app", render);}
TYPESCRIPT
# Multi-provider authenticationDemonstrates how to implement OAuth authentication with multiple providers (Meta and Google) in a single app, allowing users to connect to different platforms simultaneously. Shows provider-specific token management, independent authorization flows, and authenticated API requests using tokens from different providers.For API reference docs and instructions on running this example, see: https://www.canva.dev/docs/apps/examples/multi-provider-authentication/.Related examples: See fundamentals/authentication for single-provider OAuth authentication, fundamentals/multi_account_authentication for multiple accounts from a single provider, or design_interaction/design_token for design-specific authentication patterns.NOTE: This example differs from what is expected for public apps to pass a Canva review:- Token storage and security is simplified for demonstration. Production apps must implement secure token storage and follow OAuth security best practices- Error handling for authentication failures is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes- Token refresh mechanisms are not implemented. Production apps should implement proper token lifecycle management- Internationalization is not implemented. Production apps must support multiple languages using the `@canva/app-i18n-kit` package to pass Canva review requirements- The code structure is simplified: Production apps using [intents](https://www.canva.dev/docs/apps/intents/) are recommended to call the prepareDesignEditor function from src/intents/design_editor/index.tsx
MARKDOWN
# Setup## Getting startedBefore using this example, you'll need to configure multiple OAuth providers in the [Developer Portal](https://www.canva.com/developers/apps):1. **Configure Meta OAuth provider**: Follow the [OAuth integration guide](https://www.canva.dev/docs/apps/authenticating-users/oauth#prerequisite-configure-developer-portal) to set up a Meta provider with provider name `meta`.2. **Configure Google OAuth provider**: Follow the same guide to set up a Google provider with provider name `google`.Each provider needs to be configured separately in the Developer Portal with its own:- Authorization endpoint- Token endpoint- Client ID and Client SecretOnce both providers are configured, simply run the example from the root of `canva-apps-sdk-starter-kit` with:```shnpm start multi_provider_authentication```
MARKDOWN
API reference
Need help?
- Join our Community Forum(opens in a new tab or window)
- Report issues with this example on GitHub(opens in a new tab or window)