Appearance
Login with passkey
Prerequisites
Before setting up passkey login:
- Go through the passkey specific setup guide.
- Create an account with a different login method (e.g. email or sms). You cannot create an account with a passkey.
- Link a passkey to your account with the
useLinkWithPasskey
hook
Authenticate with a passkey
To authenticate a user via a passkey, use the Expo SDK's useLoginWithPasskey
hook
tsx
import {useLoginWithPasskey} from '@privy-io/expo/passkey';
...
const {loginWithPasskey} = useLoginWithPasskey();
You can use the returned method loginWithPasskey
to authenticate your user per the instructions below.
TIP
After a user has already been authenticated, you can link a passkey to an existing account by following the same flow with the useLinkWithPasskey
hook instead.
The loginWithPasskey
method takes a single argument relyingParty
which should be the URL origin where your Apple App Site Association or Digital Asset Links are available (e.g. https://example.com
).
When loginWithPasskey
is invoked, the user will receive a system prompt for a matching passkey. After the user makes a selection, Privy will use the selected passkey to authenticate the user.
INFO
If you are also building a web app with the same app ID, the relying party must be the domain of your web app. This will allow users to login with their Passkey on mobile and web.
tsx
import {useLoginWithPasskey} from '@privy-io/expo/passkey';
export function LoginScreen() {
const {loginWithPasskey} = useLoginWithPasskey();
return (
<View style={styles.container}>
<Button onPress={() => loginWithPasskey({relyingParty: '<your-applications-relying-party>'})}>
Login
</Button>
</View>
);
}
If loginWithPasskey
succeeds, it will return a PrivyUser
object with details about the authenticated user.
Reasons loginWithPasskey
might fail include:
- the native app has not been correctly set up for passkey support
- the user has no registered passkeys for your app on their device
To handle these failures, use the onError
callback as described below.
Callbacks
onSuccess
Pass an onSuccess
function to useLoginWithPasskey
to run custom logic after a successful login. Within this callback you can access the PrivyUser
returned by loginWithPasskey
, as well as an isNewUser
boolean indicating if this is the user's first login to your app.
tsx
import {useLoginWithPasskey} from '@privy-io/expo/passkey';
export function LoginScreen() {
const {loginWithPasskey} = useLoginWithPasskey({
onSuccess(user, isNewUser) {
// show a toast, send analytics event, etc...
},
});
// ...
}
onError
Pass an onError
function to useLoginWithPasskey
to declaratively handle errors that occur during the flow.
You can use this callback with both the useLoginWithPasskey
and useLinkWithPasskey
hooks.
tsx
import {useLoginWithPasskey} from '@privy-io/expo/passkey';
export function LoginScreen() {
const {loginWithPasskey} = useLoginWithPasskey({
onError(error) {
// show a toast, update form errors, etc...
},
});
// ...
}
Tracking login flow state
The state
variable returned from useLoginWithPasskey
will always be one of the following values.
ts
export type PasskeyFlowState =
| {status: 'initial'}
| {status: 'error'; error: Error | null}
| {status: 'generating-challenege'}
| {status: 'awaiting-passkey'}
| {status: 'submitting-response'}
| {status: 'done'};
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 {useLoginWithPasskey} from '@privy-io/expo/passkey';
export function LoginScreen() {
const {state, loginWithPasskey} = useLoginWithPasskey();
return (
<View>
<Button
// Keeps button disabled until the code has been sent
disabled={state.status === 'submitting-response'}
onPress={() => loginWithPasskey({relyingParty: '<your-applications-relying-party>'})}
>
<Text>Login with passkey</Text>
</Button>
{state.status === 'submitting-response' && (
// Shows only while the login is being attempted
<Text>Logging in...</Text>
)}
</View>
);
}
Error states
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 {hasError} from '@privy-io/expo';
import {useLoginWithPasskey, hasError} from '@privy-io/expo/passkey';
export function LoginScreen() {
const {state, loginWithPasskey} = useLoginWithPasskey();
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>
);
}