Skip to content

Login with OAuth

To authenticate a user via a social account (Google, Apple, etc.), use the Expo SDK's useLoginWithOAuth hook

tsx
import {useLoginWithOAuth} from '@privy-io/expo';
...
const {login, state} = useLoginWithOAuth();

You can use the returned method login and variable state to authenticate your user per the instructions below.

TIP

After a user has already been authenticated, you can link their email address to an existing account by following the same flow with the useLinkWithOAuth hook instead.

How does OAuth login work?

At a high-level, the login with OAuth flow works as follows:

  1. First, your app generates an OAuth login URL and redirects the user to this URL. This URL must be newly generated for each login attempt, and is specific to each OAuth provider (Google, Twitter, Apple, etc.).
  2. Once the user has been redirected to the OAuth login URL, the user completes the login flow with the corresponding OAuth provider. Upon successfully completing the flow, the user will be redirected back to your app.
  3. When the user is redirected back to your app, the OAuth provider will include an authorization code in the redirect URL's query parameters. Your app should pass this code to Privy to authenticate your user.

INFO

Make sure you have properly configured your app's allowed URL schemes in the Privy dashboard.

Initializing the login flow

To initialize login, use the login function from useLoginWithOAuth hook to start your desired OAuth login flow.

As a parameter to login, you should pass your desired OAuth provider to specify which flow you'd like to invoke (Google, Apple, etc.). Valid provider arguments are typed in the OAuthProviderType exported from @privy-io/js-sdk-core.

tsx
import {useLoginWithOAuth} from '@privy-io/expo';

export function LoginScreen() {
  const {login} = useLoginWithOAuth();

  return (
    <View style={styles.container}>
      <Button onPress={() => login({provider: 'google'})}>Login with Google</Button>
    </View>
  );
}

When login is invoked, Privy will open a browser to complete the provider's authentication flow. Once the user completes this flow, they will be redirected back to your application and automatically authenticated by Privy.

The useLoginWithOAuth hook must mounted for the headless OAuth login to complete after being redirected back to your app from the OAuth provider.

TIP

After a user has already been authenticated, you can link an OAuth account to an existing account by following the same flow with the useLinkWithOAuth hook instead.

Callbacks

onSuccess

Pass an onSuccess function to useLoginWithOAuth to run custom logic after a successful login. Within this callback you can access the PrivyUser returned by login, as well as an isNewUser boolean indicating if this is the user's first login to your app. (You can use this callback with both the useLoginWithOAuth and useLinkWithOAuth hooks)

tsx
import {useLoginWithOAuth} from '@privy-io/expo';

export function LoginScreen() {
  const {login} = useLoginWithOAuth({
    onSuccess(user, isNewUser) {
      // show a toast, send analytics event, etc...
    },
  });

  // ...
}

onError

Pass an onError function to useLoginWithOAuth to declaratively handle errors that occur during the OAuth flow.

You can use this callback with both the useLoginWithOAuth and useLinkWithOAuth hooks.

tsx
import {useLoginWithOAuth} from '@privy-io/expo';

export function LoginScreen() {
  const {login} = useLoginWithOAuth({
    onError(error) {
      // show a toast, update form errors, etc...
    },
  });

  // ...
}

Tracking login flow state

The state variable returned from useLoginWithOAuth will always be one of the following values.

ts
export type OAuthFlowState =
  | {status: 'initial'}
  | {status: 'loading'}
  | {status: 'done'}
  | {status: 'error'; error: Error | null};

Conditional rendering

You can use the state.status variable to conditionally render your UI based on the user's current state in the login flow.

tsx
import {View, Text, TextInput, Button} from 'react-native';

import {usePrivy, useLoginWithOAuth} from '@privy-io/expo';

export function LoginScreen() {
  const {user} = usePrivy();
  const {state, login} = useLoginWithOAuth();

  return state.status === 'done' ? (
    <View>
      <Text>You logged in with Google</Text>
      <Text>{JSON.stringify(user)}</Text>
    </View>
  ) : (
    <View>
      <Button
        // Keeps button disabled while OAuth flow is in progress
        disabled={state.status === 'loading'}
        onPress={() => login({provider: 'google'})}
      >
        <Text>Login with Google</Text>
      </Button>

      {state.status === 'loading' && (
        // Only renders while OAuth flow is in progress
        <Text>Logging in...</Text>
      )}
    </View>
  );
}

Error state

When state.status is equal to 'error', the error value is accessible as state.error which can be used to render inline hints in a login form.

tsx
import {useLoginWithOAuth, hasError} from '@privy-io/expo';

export function LoginScreen() {
  const {state, login} = useLoginWithOAuth();

  return (
    <View>
      {/* other ui... */}

      {state.status === 'error' && (
        <>
          <Text style={{color: 'red'}}>There was an error</Text>
          <Text style={{color: 'lightred'}}>{state.error.message}</Text>
        </>
      )}

      {hasError(state) && (
        // The `hasError` util is also provided as a convenience
        // (for typescript users, this provides the same type narrowing as above)
        <>
          <Text style={{color: 'red'}}>There was an error</Text>
          <Text style={{color: 'lightred'}}>{state.error.message}</Text>
        </>
      )}
    </View>
  );
}