Skip to content

feat(clerk-expo): Introduce support for LocalAuth with LocalCredentials - #3663

Merged
panteliselef merged 28 commits into
mainfrom
elef/user-119-local-auth-for-expo
Aug 7, 2024
Merged

feat(clerk-expo): Introduce support for LocalAuth with LocalCredentials#3663
panteliselef merged 28 commits into
mainfrom
elef/user-119-local-auth-for-expo

Conversation

@panteliselef

@panteliselefpanteliselef commented Jul 3, 2024

Copy link
Copy Markdown
Contributor

Description

This PR introduces a new hook API

import{useLocalCredentials,}from"@clerk/clerk-expo";const{
hasCredentials,// boolean
userOwnsCredentials,// boolean | null
biometricType,// "face-recognition" | "fingerprint" | null
setCredentials,// ({ identifier?: string, password: string }) => void
authenticate,// () => Promise<SignInResource>
clearCredentials,// () => Promise<void>}=useLocalCredentials();

Video of the new UX flow

RPReplay_Final1719504401.MP4

1.Example usage in a profile page

import{useUser,useClerk,useLocalCredentials,}from"@clerk/clerk-expo";exportdefaultfunctionPage(){const{ user }=useUser();const{ signOut }=useClerk();const{
hasCredentials,
clearCredentials,}=useLocalCredentials();return(<View><Text>Settings, {user?.emailAddresses[0].emailAddress}</Text><Buttontitle="Sign out"onPress={()=>signOut()}/>{hasCredentials&&<Buttontitle="Remove biometric credentials"onPress={()=>clearCredentials()}/>}</View>);}

1.Example usage in a custom sign in flow

import{useSignIn,useLocalCredentials,}from"@clerk/clerk-expo";import{Link,Stack,useRouter}from"expo-router";import{Text,TextInput,Button,View,TouchableOpacity,StyleSheet,}from"react-native";importReactfrom"react";import{SymbolView}from"expo-symbols";exportdefaultfunctionPage(){const{ signIn, setActive, isLoaded }=useSignIn();constrouter=useRouter();const[emailAddress,setEmailAddress]=React.useState("");const[password,setPassword]=React.useState("");const{ hasCredentials, setCredentials, authenticate, biometryType }=useLocalCredentials();constonSignInPress=React.useCallback(async(useLocal=false)=>{if(!isLoaded){return;}try{constsignInAttempt=hasCredentials&&useLocal
? awaitauthenticate()
: awaitsignIn.create({identifier: emailAddress,
password,});if(signInAttempt.status==="complete"){awaitsetActive({session: signInAttempt.createdSessionId});if(!(hasCredentials&&useLocal)){awaitsetCredentials({identifier: emailAddress,
password,})}// navigate away}else{// handle other statuses of sign in }}catch(err: any){// handle any other error}},[isLoaded,emailAddress,password]);return(<View><TextInputvalue={emailAddress}onChangeText={(emailAddress)=>setEmailAddress(emailAddress)}/><TextInputvalue={password}onChangeText={(password)=>setPassword(password)}/><Buttontitle="Sign In"onPress={()=>onSignInPress()}/>{hasCredentials&&biometryType&&(<TouchableOpacityonPress={()=>onSignInPress(true)}><SymbolViewname={biometryType==="face-recognition" ? "faceid" : "touchid"}type="monochrome"/></TouchableOpacity>)}</View>);}

Checklist

  • npm test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@panteliselefpanteliselef self-assigned this Jul 3, 2024
@changeset-bot

changeset-botBot commented Jul 3, 2024

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6b6308d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/clerk-expoMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@panteliselef
panteliselefforce-pushed the elef/user-119-local-auth-for-expo branch from 44fb76b to ecce02dCompareJuly 5, 2024 11:31
@panteliselef
panteliselef requested review from LauraBeatris, brkalow, nikosdouvlis and octoper and removed request for nikosdouvlisJuly 5, 2024 13:00
@panteliselef

Copy link
Copy Markdown
ContributorAuthor

!snapshot

@clerk-cookie

Copy link
Copy Markdown
Collaborator

Hey @panteliselef - the snapshot version command generated the following package versions:

PackageVersion
@clerk/clerk-expo1.3.0-snapshot.ve946474
gatsby-plugin-clerk5.0.0-beta.45

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/clerk-expo

npm i @clerk/clerk-expo@1.3.0-snapshot.ve946474 --save-exact

gatsby-plugin-clerk

npm i gatsby-plugin-clerk@5.0.0-beta.45 --save-exact

@panteliselef
panteliselef marked this pull request as ready for review July 5, 2024 13:47
return;
}

if (numericTypes.includes(AuthenticationType.FINGERPRINT)) {

@LauraBeatrisLauraBeatrisJul 5, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓If the device supports multiple authentication methods (fingerprint and facial recognition), are we prioritizing fingerprint here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very nice question, tbh my intention here was to have AuthenticationType.IRIS behave like face-recognition so i might need to reverse the logic, in order for iris/face have priority

export function LocalCredentialsProvider(props: PropsWithChildren): JSX.Element {
const { isLoaded, signIn } = useSignIn();
const { publishableKey } = useClerk();
const key = `__clerk_local_auth_${publishableKey}_identifier`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡Nit: Could we extract this interpolation to a separate function? Or perhaps type it, to avoid accidentally changing it or misspelling between individual variables.

const [isEnrolled, setIsEnrolled] = useState(false);

useEffect(() => {
let ignore = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓Is the purpose of ignore to not re-fetch isEnrolledAsync?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the intention is to avoid race conditions in a situation where the component gets unmounted but the promise resolves after the component is already unmounted.

Comment threadpackages/expo/package.json Outdated
@panteliselefpanteliselef changed the title feat(clerk-expo): Introduce support for LocalAuth with LocalCredentials[DO NOT MERGE] feat(clerk-expo): Introduce support for LocalAuth with LocalCredentialsJul 8, 2024
@panteliselefpanteliselef changed the title [DO NOT MERGE] feat(clerk-expo): Introduce support for LocalAuth with LocalCredentialsfeat(clerk-expo): Introduce support for LocalAuth with LocalCredentialsAug 6, 2024
@panteliselef

Copy link
Copy Markdown
ContributorAuthor

!snapshot

@clerk-cookie

Copy link
Copy Markdown
Collaborator

Hey @panteliselef - the snapshot version command generated the following package versions:

PackageVersion
@clerk/clerk-expo2.1.0-snapshot.vff0337d

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/clerk-expo

npm i @clerk/clerk-expo@2.1.0-snapshot.vff0337d --save-exact

biometryType: BiometricType | null;
};

const LocalCredentialsInitValues: LocalCredentialsReturn = {

@octoperoctoperAug 6, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Should we have something like isPlatformSupported in LocalCredentialsInitValues in order to be easier to check if the hook can be used on specific platforms like Web or a device that does not has any biometric support, as now it seems you will have to check if biometryType is null which is not so clear if you don't take a look at the code

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For devices that do not support biometric, android and iOS fallback to PIN, and if a pin does not exist then biometricType will be null.

with biometricType you get the available authenticator and it is useful mostly for UI purposes. Displaying a Face ID or a touch ID icon for example.

Maybe simply changing the name to supportedBiometricType would do ?

We don't wanna indicate that the device can use biometric, but instead if we can use local authentication (biometric or not) in order to store credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mostly referring for platforms that this will not work at all like web,the biometric support was just an example as I didn't know if this fallbacks to pin

@brkalowbrkalow left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need some JSDoc comments on exported methods, and left a few non-blocking comments. Nice work!

Feel free to dismiss if you address the comments during your day tomorrow.

setUserOwnsCredentials(false);
};

const authenticate = async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 It would be nice to (optionally?) set the credentials after a successful auth

Comment threadpackages/expo/src/hooks/useLocalCredentials/useLocalCredentials.ts Outdated

@octoperoctoper left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

Comment threadpackages/expo/src/hooks/useLocalCredentials/useLocalCredentials.ts Outdated
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@panteliselef@clerk-cookie@brkalow@octoper@LauraBeatris@desiprisg