# Build a custom flow for authenticating with OAuth connections

> This guide is for users who want to build a custom flow. To use a _prebuilt_ UI, use the [Account Portal pages](https://clerk.com/docs/guides/account-portal/overview.md) or [prebuilt components](https://clerk.com/docs/reference/components/overview.md).

> This guide applies to the following Clerk SDKs:
>
> - `@clerk/react` v6 or higher
> - `@clerk/nextjs` v7 or higher
> - `@clerk/expo` v3 or higher
> - `@clerk/react-router` v3 or higher
> - `@clerk/tanstack-react-start` v0.26.0 or higher
>
> If you're using an older version of one of these SDKs, or are using the legacy API, refer to the [legacy API documentation](https://clerk.com/docs/guides/development/custom-flows/authentication/legacy/oauth-connections.md).

## Before you start

You must configure your application instance through the Clerk Dashboard for the social connection(s) that you want to use. Visit [the appropriate guide for your platform](https://clerk.com/docs/guides/configure/auth-strategies/social-connections/overview.md) to learn how to configure your instance.

## Build the custom flow

First, in your `.env` file, set the `CLERK_SIGN_IN_URL` environment variable to tell Clerk where the sign-in page is being hosted. Otherwise, your app may default to using the [Account Portal sign-in page](https://clerk.com/docs/guides/account-portal/overview.md#sign-in) instead. This guide uses the `/sign-in` route.

filename: .env
```env
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
```

The following example **will both sign up _and_ sign in users**, eliminating the need for a separate sign-up page. However, if you want to have separate sign-up and sign-in pages, the sign-up and sign-in flows are equivalent, meaning that all you have to do is swap out the `SignIn` object for the `SignUp` object using the [useSignUp()](https://clerk.com/docs/reference/hooks/use-sign-up.md) hook.

The following example:

1. Accesses the [SignIn](https://clerk.com/docs/reference/objects/sign-in.md) object using the [useSignIn()](https://clerk.com/docs/reference/hooks/use-sign-in.md) hook.
2. Starts the authentication process by calling [SignIn.sso(params)](https://clerk.com/docs/reference/objects/sign-in-future.md#sso). This method requires the following params:
   - `redirectUrl`: The URL that the browser will be redirected to once the user authenticates with the identity provider if no additional requirements are needed, and a session has been created.
   - `redirectCallbackUrl`: The URL that the browser will be redirected to once the user authenticates with the identity provider if additional requirements are needed.
3. Creates a route at the URL that the `redirectCallbackUrl` param points to.

**Sign in page**

filename: app/sign-in/page.tsx
```tsx
'use client'

import { OAuthStrategy } from '@clerk/shared/types'
import { useSignIn } from '@clerk/nextjs'

export default function Page() {
  const { signIn, errors } = useSignIn()

  const signInWith = async (strategy: OAuthStrategy) => {
    const { error } = await signIn.sso({
      strategy,
      redirectCallbackUrl: '/sso-callback',
      redirectUrl: '/sign-in/tasks', // Learn more about session tasks at https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
    })
    if (error) {
      // See https://clerk.com/docs/guides/development/custom-flows/error-handling
      // for more info on error handling
      console.error(JSON.stringify(error, null, 2))
      return
    }

    if (signIn.status === 'needs_second_factor') {
      // See https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication
    } else if (signIn.status === 'needs_client_trust') {
      // See https://clerk.com/docs/guides/development/custom-flows/authentication/client-trust
    } else {
      // Check why the sign-in is not complete
      console.error('Sign-in attempt not complete:', signIn)
    }
  }

  // Render a button for each supported OAuth provider
  // you want to add to your app. This example uses only Google.
  return (
    <>
      <button onClick={() => signInWith('oauth_google')}>Sign in with Google</button>
      {/* For your debugging purposes. You can just console.log errors, but we put them in the UI for convenience */}
      {errors && <p>{JSON.stringify(errors, null, 2)}</p>}
    </>
  )
}
```

**SSO callback page**

filename: app/sso-callback/page.tsx
```tsx
'use client'

import { useClerk, useSignIn, useSignUp } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import { useEffect, useRef } from 'react'

export default function Page() {
  const clerk = useClerk()
  const { signIn } = useSignIn()
  const { signUp } = useSignUp()
  const router = useRouter()
  const hasRun = useRef(false)

  const navigateToSignIn = () => {
    router.push('/sign-in')
  }

  const finalizeSignIn = async () => {
    await signIn.finalize({
      navigate: async ({ session, decorateUrl }) => {
        // Handle session tasks
        // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
        if (session?.currentTask) {
          console.log(session?.currentTask)
          return
        }

        // If no session tasks, navigate the signed-in user to the home page
        const url = decorateUrl('/')
        if (url.startsWith('http')) {
          window.location.href = url
        } else {
          router.push(url)
        }
      },
    })
  }

  const finalizeSignUp = async () => {
    await signUp.finalize({
      navigate: async ({ session, decorateUrl }) => {
        // Handle session tasks
        // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
        if (session?.currentTask) {
          console.log(session?.currentTask)
          return
        }

        // If no session tasks, navigate the signed-in user to the home page
        const url = decorateUrl('/')
        if (url.startsWith('http')) {
          window.location.href = url
        } else {
          router.push(url)
        }
      },
    })
  }

  useEffect(() => {
    ;(async () => {
      if (!clerk.loaded || hasRun.current) {
        return
      }
      // Prevent Next.js from re-running this effect when the page is re-rendered during session activation.
      hasRun.current = true

      // If this was a sign-in, and it's complete, there's nothing else to do.
      if (signIn.status === 'complete') {
        await finalizeSignIn()
        return
      }

      // If the sign-up used an existing account, transfer it to a sign-in
      if (signUp.isTransferable) {
        await signIn.create({ transfer: true })
        const signInStatus = signIn.status as typeof signIn.status | 'complete'
        if (signInStatus === 'complete') {
          await finalizeSignIn()
          return
        }

        // If sign-in is not complete, additional information is needed
        // For this example, we'll navigate back to the sign-in page assuming that it handles these cases
        return navigateToSignIn()
      }

      if (
        signIn.status === 'needs_first_factor' &&
        !signIn.supportedFirstFactors?.every((f) => f.strategy === 'enterprise_sso')
      ) {
        // The sign-in requires the use of a configured first factor, so navigate to the sign-in page
        return navigateToSignIn()
      }

      // If the sign-in used an external account not associated with an existing user, create a sign-up
      if (signIn.isTransferable) {
        await signUp.create({ transfer: true })
        if (signUp.status === 'complete') {
          await finalizeSignUp()
          return
        }

        // If sign-up is not complete, additional information is needed
        // See https://clerk.com/docs/guides/development/custom-flows/authentication/oauth-connections#handle-missing-requirements
        return router.push('/sign-in/continue')
      }

      // If sign-up is complete, finalize it
      if (signUp.status === 'complete') {
        await finalizeSignUp()
        return
      }

      // If the sign-in requires MFA or a new password
      // For this example, we'll navigate back to the sign-in page assuming that it handles these cases
      if (signIn.status === 'needs_second_factor' || signIn.status === 'needs_new_password') {
        return navigateToSignIn()
      }

      // The external account used to sign-in or sign-up was already associated with an existing user and active
      // session on this client, so activate the session and navigate to the application.
      if (signIn.existingSession || signUp.existingSession) {
        const sessionId = signIn.existingSession?.sessionId || signUp.existingSession?.sessionId
        if (sessionId) {
          // Because we're activating a session that's not the result of a sign-in or sign-up, we need to use the
          // Clerk `setActive` API instead of the `finalize` API.
          await clerk.setActive({
            session: sessionId,
            navigate: async ({ session, decorateUrl }) => {
              // Handle session tasks
              // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
              if (session?.currentTask) {
                console.log(session?.currentTask)
                return
              }

              // If no session tasks, navigate the signed-in user to the home page
              const url = decorateUrl('/')
              if (url.startsWith('http')) {
                window.location.href = url
              } else {
                router.push(url)
              }
            },
          })
          return
        }
      }
    })()
  }, [clerk, signIn, signUp])

  return (
    <div>
      {/* Because a sign-in transferred to a sign-up might require captcha verification, make sure to render the
captcha element. */}
      <div id="clerk-captcha"></div>
    </div>
  )
}
```

## Handle missing requirements

Depending on your instance settings, users might need to provide extra information before their sign-up can be completed, such as when a first and last name or accepting legal terms is required. In these cases, the `SignUp` object returns a `status` of `"missing_requirements"` along with a `missingFields` array. You can use this information to build a UI that handles the missing requirements. If the missing fields are first and last name or legal acceptance, you can pass them to the [signUp.update()](https://clerk.com/docs/reference/objects/sign-up.md#update) method. If the missing fields are identifiers, see the appropriate custom flow guide. For example, if your app's settings require a phone number, see the [phone OTP custom flow guide](https://clerk.com/docs/guides/development/custom-flows/authentication/email-sms-otp.md#sign-up-flow) to handle collecting and verifying the phone number.

> [!QUIZ]
> Why does the "Continue" page use the `useSignUp()` hook? What if a user is using this flow to sign in?
>
> ***
>
> With OAuth flows, it's common for users to try to _sign in_ with an OAuth provider, but they don't have a Clerk account for your app yet. Clerk automatically transfers the flow from the `SignIn` object to the `SignUp` object, which returns the `"missing_requirements"` status and `missingFields` array needed to handle the missing requirements flow. This is why for the OAuth flow, the "Continue" page uses the [useSignUp()](https://clerk.com/docs/reference/hooks/use-sign-up.md) hook and treats the missing requirements flow as a sign-up flow.

filename: app/sign-in/continue/page.tsx
```tsx
'use client'

import { useSignUp } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'

export default function Page() {
  const router = useRouter()
  // Use `useSignUp()` hook to access the `SignUp` object
  // `missing_requirements` and `missingFields` are only available on the `SignUp` object
  const { signUp } = useSignUp()

  const handleSubmit = async (formData: FormData) => {
    const firstName = formData.get('firstName') as string
    const lastName = formData.get('lastName') as string

    // Update the `SignUp` object with the missing fields
    // This example collects first and last name and passes it to SignUp.update() but you can modify this example for whatever settings you have enabled in the Clerk Dashboard
    await signUp.update({ firstName, lastName })

    if (signUp.status === 'complete') {
      await signUp.finalize({
        navigate: async ({ session, decorateUrl }) => {
          // Handle session tasks
          // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
          if (session?.currentTask) {
            console.log(session?.currentTask)
            return
          }

          // If no session tasks, navigate the signed-in user to the home page
          const url = decorateUrl('/')
          if (url.startsWith('http')) {
            window.location.href = url
          } else {
            router.push(url)
          }
        },
      })
    } else if (signUp.status !== 'missing_requirements') {
      // Check why the sign-up is not complete
      console.error('Sign-up attempt not complete:', signUp.status)
    }
  }

  // If the sign-up is complete, the user shouldn't be on this page
  if (signUp.status === 'complete') {
    router.push('/')
  }

  return (
    <div>
      <h1>Continue sign-up</h1>
      <form action={handleSubmit}>
        <label htmlFor="firstName">First name</label>
        <input type="text" name="firstName" id="firstName" />
        <label htmlFor="lastName">Last name</label>
        <input type="text" name="lastName" id="lastName" />

        <button type="submit">Submit</button>
      </form>

      {/* Required for sign-up flows. Clerk's bot sign-up protection is enabled by default */}
      <div id="clerk-captcha" />
    </div>
  )
}
```

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
