Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions docs/custom-flows/transfer-flows.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
## OAuth account transfer flows

It is common for users who are authenticating with OAuth to use a sign-in button when they mean to sign-up, and vice versa. For those cases, the `SignIn` and `SignUp` objects have a `transferable` status that indicates whether the user can be transferred to the other flow.

**If you would like to keep your sign-in and sign-up flows completely separate, then you can skip this section.**

The following example demonstrates how to handle these cases in your sign-in flow. To apply the same logic to the sign-up flow, simply replace `signIn.authenticateWithRedirect()` with `signUp.authenticateWithRedirect()` in your code.

<Tabs items={["Next.js", "iOS (beta)"]}>
<Tab>
```tsx {{ filename: 'app/sign-in/page.tsx', collapsible: true }}
'use client'

import * as React from 'react'
import { OAuthStrategy } from '@clerk/types'
import { useSignIn, useSignUp } from '@clerk/nextjs'

export default function OauthSignIn() {
const { signIn } = useSignIn()
const { signUp, setActive } = useSignUp()

if (!signIn || !signUp) return null

const signInWith = (strategy: OAuthStrategy) => {
return signIn.authenticateWithRedirect({
strategy,
redirectUrl: '/sign-up/sso-callback',
redirectUrlComplete: '/',
})
}

async function handleSignIn(strategy: OAuthStrategy) {
if (!signIn || !signUp) return null

// If the user has an account in your application, but does not yet
// have an OAuth account connected to it, you can transfer the OAuth
// account to the existing user account.
const userExistsButNeedsToSignIn =
signUp.verifications.externalAccount.status === 'transferable' &&
signUp.verifications.externalAccount.error?.code === 'external_account_exists'

if (userExistsButNeedsToSignIn) {
const res = await signIn.create({ transfer: true })

if (res.status === 'complete') {
setActive({
session: res.createdSessionId,
})
}
}

// If the user has an OAuth account but does not yet
// have an account in your app, you can create an account
// for them using the OAuth information.
const userNeedsToBeCreated = signIn.firstFactorVerification.status === 'transferable'

if (userNeedsToBeCreated) {
const res = await signUp.create({
transfer: true,
})

if (res.status === 'complete') {
setActive({
session: res.createdSessionId,
})
}
} else {
// If the user has an account in your application
// and has an OAuth account connected to it, you can sign them in.
signInWith(strategy)
}
}

// Render a button for each supported OAuth provider
// you want to add to your app. This example uses only Google.
return (
<div>
<button onClick={() => handleSignIn('oauth_google')}>Sign in with Google</button>
</div>
)
}
```
</Tab>

<Tab>
```swift {{ filename: 'OAuthView.swift', collapsible: true }}
// If a user attempted to sign in but doesn't have an account,
// Clerk will try to handle the transfer to sign up for you.
// If that's not possible (i.e. maybe signing up requires a CAPTCHA Token),
// authenticateWithRedirect() will return the original sign in to you
// so you can manually perform the transfer to sign up.

// Check if the sign in needs to be transferred to a sign up
if let signIn = externalAuthResult?.signIn,
signIn.firstFactorVerification?.status == .transferable {
// Create a new sign up with the strategy `.transfer`
let signUp = try await SignUp.create(
strategy: .transfer,
captchaToken: "TOKEN"
)
}
```
</Tab>
</Tabs>