javascript
javascript copied to clipboard
feat(clerk-expo): Introduce support for LocalAuth with LocalCredentials
Description
This PR introduces a new experimental hook API
import {
__experimental_useLocalCredentials as useLocalCredentials,
} from "@clerk/clerk-expo";
const {
hasCredentials, // boolean
setCredentials, // ({ identifier: string, password: string }) => void
authenticate, // () => Promise<SignInResource>
clearCredentials, // () => Promise<void>
} = useLocalCredentials();
Video of the new UX flow
https://github.com/clerk/javascript/assets/19269911/7b8fa397-f3e1-440c-bf9f-1fc8a3602ef1
1. LocalCrendetialsProvider
import {
ClerkProvider,
__experimental_LocalCredentialsProvider as LocalCredentialsProvider,
} from "@clerk/clerk-expo";
<ClerkProvider publishableKey={publishableKey}>
<LocalCredentialsProvider>
{...}
</LocalCredentialsProvider>
</ClerkProvider>
2.Example usage in a profile page
import {
useUser,
useClerk,
__experimental_useLocalAuthCredentials as useLocalAuthCredentials
} from "@clerk/clerk-expo";
export default function Page() {
const { user } = useUser();
const { signOut } = useClerk();
const {
hasCredentials,
clearCredentials,
} = useLocalCredentials();
return (
<View>
<Text>Settings, {user?.emailAddresses[0].emailAddress}</Text>
<Button title="Sign out" onPress={() => signOut()} />
{
hasLocalAuthCredentials &&
<Button title="Remove biometric credentials" onPress={() => clearLocalAuthAccount()} />
}
</View>
);
}
3.Example usage in a custom sign in flow
import {
useSignIn,
__experimental_useLocalCredentials as useLocalCredentials,
} from "@clerk/clerk-expo";
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, biometryType } =
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") {
await setActive({ session: signInAttempt.createdSessionId });
if (!(hasCredentials && useLocal)) {
await setCredentials({
identifier: emailAddress,
password,
})
}
// 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 && biometryType && (
<TouchableOpacity
onPress={() => onSignInPress(true)}
>
<SymbolView
name={
biometryType === "face-recognition" ? "faceid" : "touchid"
}
type="monochrome"
/>
</TouchableOpacity>
)}
</View>
);
}
Checklist
- [ ]
npm testruns as expected. - [ ]
npm run buildruns 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: