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.

TIP

Apple login on iOS requires some additional setup for the best user experience possible. Please see this guide for more information.

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>
  );
}

TIP

If using Apple login, please make sure your app's Bundle ID rather than the Service ID, is configured as the Client ID within the Privy Dashboard.

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...
    },
  });

  // ...
}

Getting OAuth access tokens

Privy also enables your app to get OAuth access tokens for a given user to request richer information about their profile from the OAuth provider. This requires that your app set up custom OAuth credentials and set additional configuration in your Privy login methods.

To get a user's OAuth access tokens whenever they login or link an OAuth account, use the useOAuthTokens hook:

tsx
import {useOAuthTokens, OAuthTokens} from '@privy-io/expo';

useOAuthTokens({
  onOAuthTokenGrant({
    provider,
    access_token,
    access_token_expires_in_seconds,
    refresh_token,
    refresh_token_expires_in_seconds,
    scopes,
  }: OAuthTokens) {
    // Any logic you'd like to execute after receiving the OAuth tokens.
    api.push({accessToken: access_token, refreshToken: refresh_token});
  },
});

As parameters to useOAuthTokens, you may include an onOAuthTokenGrant callback.

TIP

The component where the useOAuthTokens hook is invoked must be mounted throughout any navigation that happens from the start of the OAuth flow to when the user returns after authorizing in order for this callback to execute.

onOAuthTokenGrant

The onOAuthTokenGrant callback will execute after a user returns to the application from an OAuth flow authorization.

Within this callback, you can access:

  • provider: the OAuth provider, is one of 'apple', 'discord', 'github', 'google', 'linkedin', 'spotify', 'tiktok', 'instagram', and 'twitter'.
  • access_token: the OAuth access token
  • access_token_expires_in_seconds: the number of seconds until the OAuth access token expires
  • refresh_token: the OAuth refresh token
  • refresh_token_expires_in_seconds: the number of seconds until the OAuth refresh token expires. If the refresh token is present and this field is undefined, it is assumed that the refresh token does not have an expiration date
  • scopes: the list of OAuth scopes the access token is approved for.

Learn more about how to use OAuth access and refresh tokens here.

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>
  );
}

Unlinking OAuth accounts

Once a user has linked OAuth accounts to their profile, you may also want to give them the option to unlink those accounts.

To do this, use the unlinkOAuth method from the useUnlinkOAuth hook. As a parameter to this method, pass an object with the following fields:

ParameterTypeDescription
providerOAuthProviderTypeThe OAuth provider of the account to unlink (Such as 'google' or 'twitter' )
subjectstringThe subject field from the OAuth linked account.
tsx
import {useUnlinkOAuth} from '@privy-io/expo';

export function LinkedOAuthAccountScreen() {
  const {unlinkOAuth} = useUnlinkOAuth();

  return (
    <View>
      <Text>Provider: {provider}</Text>
      <Button onPress={() => unlinkOAuth({provider, subject: linkedAccount.subject})}>
        Unlink Wallet
      </Button>
    </View>
  );
}

Enforcing login vs. sign-up

Depending on how your app's authentication flow is set up, you may want to provide separate routes for signing up to your app (e.g. creating their account for the first time) and logging in as a returning user (e.g. logging into an existing account). You can distinguish login vs. sign-up flows by passing an optional disableSignup boolean to your login call like so:

tsx
<Button
  onPress={() =>
    login({
      provider: 'google',
    })
  }
>
  <Text>Login</Text>
</Button>