Build a custom flow for managing SSO connections
Before you start
You must configure your application instance through the Clerk Dashboard for the SSO connections that you want to use.
- For social (OAuth) connection(s), see the appropriate guide for your platform.
- For enterprise connection(s), see the appropriate guide for your platform.
This guide uses Discord, Google, and GitHub as examples.
Build the custom flow
- The
useUser()
hook is used to get the current user'sUser
object. TheisLoaded
boolean is used to ensure that Clerk is loaded. - The
options
array is used to create a list of supported SSO connections. This example uses OAuth strategies. You can edit this array to include all of the SSO connections that you've enabled for your app in the Clerk Dashboard. You can also add custom SSO connections by using theoauth_custom_<name>
strategy. - The
addSSO()
function is used to add a new external account using thestrategy
that is passed in.- It uses the
user
object to access thecreateExternalAccount()
method. - The
createExternalAccount()
method is used to create a new external account using thestrategy
that is passed in.
- It uses the
- The
unconnectedOptions
array is used to filter out any existing external accounts from theoptions
array. - In the UI, the
unconnectedOptions
array is used to create a list of buttons for the user to add new external accounts. - In the UI, the
User
object is used to access theexternalAccounts
property, which is mapped through to create a list of the user's existing external accounts. If there is an error, it is displayed to the user in the 'Status' column. If the account didn't verify when the user added it, a 'Reverify' button is displayed, which will redirect the user to the provider in order to verify their account.
'use client'
import { useUser } from '@clerk/nextjs'
import { OAuthStrategy } from '@clerk/types'
import { useRouter } from 'next/navigation'
// Capitalize the first letter of the provider name
// E.g. 'discord' -> 'Discord'
const capitalize = (provider: string) => {
return `${provider.slice(0, 1).toUpperCase()}${provider.slice(1)}`
}
// Remove the prefix from the provider name
// E.g. 'oauth_discord' -> 'discord'
const normalizeProvider = (provider: string) => {
return provider.split('_')[1]
}
export default function AddAccount() {
const router = useRouter()
// Use Clerk's `useUser()` hook to get the current user's `User` object
const { isLoaded, user } = useUser()
// List the options the user can select when adding a new external account
// Edit this array to include all of your enabled SSO connections
const options: OAuthStrategy[] = ['oauth_discord', 'oauth_google', 'oauth_github']
// Handle adding the new external account
const addSSO = async (strategy: OAuthStrategy) => {
await user
?.createExternalAccount({
strategy,
redirectUrl: '/account/manage-external-accounts',
})
.then((res) => {
if (res.verification?.externalVerificationRedirectURL) {
router.push(res.verification.externalVerificationRedirectURL.href)
}
})
.catch((err) => {
console.log('ERROR', err)
})
.finally(() => {
console.log('Redirected user to oauth provider')
})
}
// Show a loading message until Clerk loads
if (!isLoaded) return <p>Loading...</p>
// Find the external accounts from the options array that the user has not yet added to their account
// This prevents showing an 'add' button for existing external account types
const unconnectedOptions = options.filter(
(option) =>
!user?.externalAccounts.some((account) => account.provider === normalizeProvider(option)),
)
return (
<>
<div>
<p>Connected accounts</p>
{user?.externalAccounts.map((account) => {
return (
<ul key={account.id}>
<li>Provider: {capitalize(account.provider)}</li>
<li>Scopes: {account.approvedScopes}</li>
<li>
Status:{' '}
{/* This example uses the `longMessage` returned by the API. You can use account.verification.error.code to determine the error and then provide your own message to the user. */}
{account.verification?.status === 'verified'
? capitalize(account.verification?.status)
: account.verification?.error?.longMessage}
</li>
{account.verification?.status !== 'verified' &&
account.verification?.externalVerificationRedirectURL && (
<li>
<a href={account.verification?.externalVerificationRedirectURL?.href}>
Reverify {capitalize(account.provider)}
</a>
</li>
)}
<li>
<button onClick={() => account.destroy()}>
Remove {capitalize(account.provider)}
</button>
</li>
</ul>
)
})}
</div>
{unconnectedOptions.length > 0 && (
<div>
<p>Add a new external account</p>
<ul>
{unconnectedOptions.map((strategy) => {
return (
<li key={strategy}>
<button onClick={() => addSSO(strategy)}>
Add {capitalize(normalizeProvider(strategy))}
</button>
</li>
)
})}
</ul>
</div>
)}
</>
)
}
Feedback
Last updated on