Docs

Build a custom flow for authenticating with OAuth connections

Warning

This guide is for users who want to build a custom user interface using the Clerk API. To use a prebuilt UI, use the Account Portal pages or prebuilt components.

Before you start

You must configure your application instance through the Clerk Dashboard for the social connection(s) that you want to use, as described at the top of the OAuth guide.

Create the sign-up and sign-in flow

When using OAuth, the sign-up and sign-in flows are equivalent.

A successful OAuth flow consists of the following steps:

  1. Start the OAuth flow by calling SignIn.authenticateWithRedirect(params) or SignUp.authenticateWithRedirect(params). Both of these methods require a redirectUrl param, which is the URL that the browser will be redirected to once the user authenticates with the OAuth provider.
  2. Create a route at the URL that the redirectUrl param points to. The following example names this route /sso-callback. This route should call the Clerk.handleRedirectCallback() method or simply render the prebuilt <AuthenticateWithRedirectCallback/> component.

The following example shows two files:

  1. The sign-in page where the user can start the OAuth flow.
  2. The SSO callback page where the OAuth flow is completed.
app/sign-in/page.tsx
'use client'

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

export default function OauthSignIn() {
  const { signIn } = useSignIn()

  if (!signIn) return null

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

  // Render a button for each supported OAuth provider
  // you want to add to your app. This example uses only Google.
  return (
    <div>
      <button onClick={() => signInWith('oauth_google')}>Sign in with Google</button>
    </div>
  )
}
app/sign-up/sso-callback.tsx
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs'

export default function SSOCallback() {
  // Handle the redirect flow by rendering the
  // prebuilt AuthenticateWithRedirectCallback component.
  // This is the final step in the custom OAuth flow.
  return <AuthenticateWithRedirectCallback />
}

Before you start

Install expo-linking to handle linking.

terminal
npm install expo-linking
terminal
yarn add expo-linking
terminal
pnpm add expo-linking

Build the flow

The following example demonstrates a component that creates a custom OAuth sign-in flow for Google accounts.

Note

You can render this component in a custom sign-in flow, which you can find examples of in the Expo quickstart.

components/SignInWithOAuth.tsx
import React from 'react'
import * as WebBrowser from 'expo-web-browser'
import { Text, View, Button } from 'react-native'
import { Link } from 'expo-router'
import { useOAuth } from '@clerk/clerk-expo'
import * as Linking from 'expo-linking'

export const useWarmUpBrowser = () => {
  React.useEffect(() => {
    // Warm up the android browser to improve UX
    // https://docs.expo.dev/guides/authentication/#improving-user-experience
    void WebBrowser.warmUpAsync()
    return () => {
      void WebBrowser.coolDownAsync()
    }
  }, [])
}

WebBrowser.maybeCompleteAuthSession()

const SignInWithOAuth = () => {
  useWarmUpBrowser()

  const { startOAuthFlow } = useOAuth({ strategy: 'oauth_google' })

  const onPress = React.useCallback(async () => {
    try {
      const { createdSessionId, signIn, signUp, setActive } = await startOAuthFlow({
        redirectUrl: Linking.createURL('/dashboard', { scheme: 'myapp' }),
      })

      if (createdSessionId) {
        setActive!({ session: createdSessionId })
      } else {
        // Use signIn or signUp for next steps such as MFA
      }
    } catch (err) {
      console.error('OAuth error', err)
    }
  }, [])

  return (
    <View>
      <Link href="/">
        <Text>Home</Text>
      </Link>
      <Button title="Sign in with Google" onPress={onPress} />
    </View>
  )
}
export default SignInWithOAuth
OAuthView.swift
import SwiftUI
import ClerkSDK

struct OAuthView: View {
  var body: some View {
    // Render a button for each supported OAuth provider
    // you want to add to your app. This example uses only Google.
    Button("Sign In with Google") {
      Task { await signInWithOAuth(provider: .google) }
    }
  }
}

extension OAuthView {

  func signInWithOAuth(provider: OAuthProvider) async {
    do {
      // Start the sign-in process using the selected OAuth provider.
      let signIn = try await SignIn.create(strategy: .oauth(provider))

      // Start the OAuth process
      let externalAuthResult = try await signIn.authenticateWithRedirect()

      // 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.
      // Clerk will handle this transfer for you if possible.
      // Therefore, an ExternalAuthResult can contain either a SignIn or SignUp.

      // Check if the result of the OAuth was a sign in
      if let signIn = externalAuthResult?.signIn {
        switch signIn.status {
        case .complete:
          // If sign-in process is complete, navigate the user as needed.
          dump(Clerk.shared.session)
        default:
          // If the status is not complete, check why. User may need to
          // complete further steps.
          dump(signIn.status)
        }
      }

      // Check if the result of the OAuth was a sign up
      if let signUp = externalAuthResult?.signUp {
        switch signUp.status {
        case .complete:
          // If sign-up process is complete, navigate the user as needed.
          dump(Clerk.shared.session)
        default:
          // If the status is not complete, check why. User may need to
          // complete further steps.
          dump(signUp.status)
        }
      }
    } catch {
      // See https://clerk.com/docs/custom-flows/error-handling
      // for more info on error handling.
      dump(error)
    }
  }
}

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.

app/sign-in/page.tsx
'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>
  )
}
OAuthView.swift
// 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"
    )
}

Feedback

What did you think of this content?

Last updated on