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>
);
}
Signing up with a passkey
INFO
Passkey sign up is not allowed by default, and needs to be explicitly enabled in the Dashboard, under the "Passkeys" config in "Login Methods"
Unlike other login methods, allowing user to sign up with a passkey, creating a brand new account, requires an explicit decision, by using the useSignupWithPasskey
hook instead.
tsx
import {useSignupWithPasskey} from '@privy-io/expo/passkey';
...
const {signupWithPasskey} = useSignupWithPasskey();
Use the signupWithPasskey
method to prompt your user to create a brand new passkey and a new account on your application.
The signupWithPasskey
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 signupWithPasskey
is invoked, the user will receive a system prompt for creating a new passkey. After that, Privy will use the newly created 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 use with their Passkey on both mobile and web.
tsx
import {useSignupWithPasskey} from '@privy-io/expo/passkey';
export function SignupScreen() {
const {signupWithPasskey} = useSignupWithPasskey();
return (
<View style={styles.container}>
<Button
onPress={() => signupWithPasskey({relyingParty: '<your-applications-relying-party>'})}
>
Sign up
</Button>
</View>
);
}
If signupWithPasskey
succeeds, it will return an object with a user: PrivyUser
property with details about the authenticated user.
Reasons signupWithPasskey
might fail include:
- the native app has not been correctly set up for passkey support
- the user has not accepted creating a new passkeys for your app on their device
To handle these failures, the signupWithPasskey
method returns a promise that will reject with the returned error.
WARNING
Accounts that signed up via a Passkey have no other login method available by default. This means that losing the passkey means they will lose access to their account.