Skip to content

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