Appearance
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:
- 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.).
- 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.
- 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. Take a look at out guide.
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 tokenaccess_token_expires_in_seconds
: the number of seconds until the OAuth access token expiresrefresh_token
: the OAuth refresh tokenrefresh_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 datescopes
: 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:
Parameter | Type | Description |
---|---|---|
provider | OAuthProviderType | The OAuth provider of the account to unlink (Such as 'google' or 'twitter' ) |
subject | string | The 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>