Docs

Allow returning users to sign in with biometrics in Expo

Clerk's useLocalCredentials() hook enables you to store a user's password credentials on their device and subsequently use biometrics for sign-in.

This guide shows you how to use the useLocalCredentials() hook to enhance your user experience by allowing users to sign in using biometrics when they re-sign in to your Expo app.

Warning

This API is available only for @clerk/clerk-expo v2 >=2.2.0. Be aware that this works only for sign in attempts with the password strategy.

Install the necessary peer dependencies

These packages are required to be installed in order to use the useLocalCredentials() hook.

terminal
npm install expo-local-authentication expo-secure-store
terminal
yarn add expo-local-authentication expo-secure-store
terminal
pnpm add expo-local-authentication expo-secure-store

Update app.json

Update your app.json file as instructed in the Expo documentation:

Securely store/access the user's credentials during sign in

The following example demonstrates how to use useLocalCredentials() in a custom flow for signing users in.

app/sign-in.tsx
import { useSignIn } from '@clerk/clerk-expo'
import { useLocalCredentials } from '@clerk/clerk-expo/local-credentials'
import { Link, Stack, useRouter } from 'expo-router'
import { Text, TextInput, Button, View, TouchableOpacity, StyleSheet } from 'react-native'
import React from 'react'
import { SymbolView } from 'expo-symbols'

export default function Page() {
  const { signIn, setActive, isLoaded } = useSignIn()
  const router = useRouter()

  const [emailAddress, setEmailAddress] = React.useState('')
  const [password, setPassword] = React.useState('')
  const { hasCredentials, setCredentials, authenticate, biometricType } = useLocalCredentials()

  const onSignInPress = React.useCallback(
    async (useLocal = false) => {
      if (!isLoaded) {
        return
      }

      try {
        const signInAttempt =
          hasCredentials && useLocal
            ? await authenticate()
            : await signIn.create({
                identifier: emailAddress,
                password,
              })

        if (signInAttempt.status === 'complete') {
          if (!useLocal) {
            await setCredentials({
              identifier: emailAddress,
              password,
            })
          }
          await setActive({ session: signInAttempt.createdSessionId })

          // navigate away
        } else {
          // handle other statuses of sign in
        }
      } catch (err: any) {
        // handle any other error
      }
    },
    [isLoaded, emailAddress, password],
  )

  return (
    <View>
      <TextInput
        value={emailAddress}
        onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
      />
      <TextInput value={password} onChangeText={(password) => setPassword(password)} />
      <Button title="Sign In" onPress={() => onSignInPress()} />
      {hasCredentials && biometricType && (
        <TouchableOpacity onPress={() => onSignInPress(true)}>
          <SymbolView
            name={biometricType === 'face-recognition' ? 'faceid' : 'touchid'}
            type="monochrome"
          />
        </TouchableOpacity>
      )}
    </View>
  )
}

Delete credentials while user is logged in

The following example demonstrates how to use the userOwnsCredentials and clearCredentials properties of the useLocalCredentials() hook in order to remove the stored credentials if those belong to the signed in user.

app/user.tsx
import { useUser, useClerk } from '@clerk/clerk-expo'
import { useLocalCredentials } from '@clerk/clerk-expo/local-credentials'

export default function Page() {
  const { user } = useUser()
  const { signOut } = useClerk()

  const { userOwnsCredentials, clearCredentials } = useLocalCredentials()

  return (
    <View>
      <Text>Settings, {user?.emailAddresses[0].emailAddress}</Text>
      <Button title="Sign out" onPress={() => signOut()} />
      {userOwnsCredentials && (
        <Button title="Remove biometric credentials" onPress={() => clearCredentials()} />
      )}
    </View>
  )
}

Update credentials while user is logged in

The following example demonstrates how to use userOwnsCredentials and setCredentials properties of the useLocalCredentials() hook in order to update the stored credentials if those belong to the signed in user.

app/update-user.tsx
import { useUser, useClerk } from '@clerk/clerk-expo'
import { useLocalCredentials } from '@clerk/clerk-expo/local-credentials'

export default function Page() {
  const { user } = useUser()
  const [currentPassword, setCurrentPassword] = React.useState('')
  const [password, setPassword] = React.useState('')

  const { userOwnsCredentials, setCredentials } = useLocalCredentials()

  const changePassword = React.useCallback(async () => {
    try {
      await user?.updatePassword({
        currentPassword: currentPassword,
        newPassword: password,
      })

      if (userOwnsCredentials) {
        await setCredentials({
          password,
        })
      }
    } catch (err: any) {
      console.error(JSON.stringify(err, null, 2))
    }
  }, [currentPassword, password])

  return (
    <View>
      <TextInput
        autoCapitalize="none"
        value={currentPassword}
        placeholder="Current password..."
        secureTextEntry={true}
        onChangeText={(currentPassword) => setCurrentPassword(currentPassword)}
      />
      <TextInput
        value={password}
        placeholder="Password..."
        secureTextEntry={true}
        onChangeText={(password) => setPassword(password)}
      />
      <Button title="Update password" onPress={changePassword} />
    </View>
  )
}

More resources

Use the following guides to learn more about Clerk components, how to build custom flows for your native apps, and how to use Clerk's client-side helpers.

Expo SDK

Use Clerk with Expo to authenticate users in your React Native application.

Custom flows

Expo native apps require custom flows in place of prebuilt components. Learn more about custom flows.

Client-side helpers

Clerk's client-side helpers enable you to access user data and perform actions on the client-side.

Feedback

What did you think of this content?

Last updated on