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

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.