Skip to content

Commit

Permalink
feat: add SignUpWithPassKeys screen
Browse files Browse the repository at this point in the history
  • Loading branch information
onehassan committed May 24, 2024
1 parent e822be7 commit 881e53e
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 75 deletions.
5 changes: 5 additions & 0 deletions examples/react_native/src/screens/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import Profile from '@screens/Profile';
import SignIn from '@screens/SignIn';
import SignUpWithPassKeys from '@screens/SignUpWithPassKeys';
import SignUp from '@screens/SignUp';
import Storage from '@screens/Storage';
import Todos from '@screens/Todos';
Expand Down Expand Up @@ -41,6 +42,10 @@ function Main() {
<>
<Stack.Screen name="signin" component={SignIn} />
<Stack.Screen name="signup" component={SignUp} />
<Stack.Screen
name="signUpWithPassKeys"
component={SignUpWithPassKeys}
/>
</>
) : (
<Stack.Screen name="drawer" component={DrawerNavigator} />
Expand Down
80 changes: 5 additions & 75 deletions examples/react_native/src/screens/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,79 +62,6 @@ export default function SignIn({
}
};

const handleSignInWithSecurityKey = async () => {
const isSupported = Passkey.isSupported();

if (isSupported) {
try {
const res = await fetch(
'https://local.auth.nhost.run/v1/signup/webauthn',
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
email: 'hsanbenjobrane@gmail.com',
}),
},
);

if (!res.ok) {
throw new Error('Request to sign in failed');
}

const challengeRequest = await res.json();

console.log({
challengeRequest,
});

// Call the `register` method with the retrieved request in JSON format
// A native overlay will be displayed
const result: PasskeyRegistrationResult = await Passkey.register(
challengeRequest,
);

console.log({
result,
});

// const verification = await fetch(
// 'https://local.auth.nhost.run/signin/webauthn/verify',
// {
// headers: {
// 'content-type': 'application/json',
// },
// body: JSON.stringify({
// email: 'hsanbenjobrane@gmail.com',
// credential: result,
// }),
// },
// );

// if (!verification.ok) {
// throw new Error('Verification failed');
// }

// const session = await verification.json();

// console.log({
// session,
// });

// The `register` method returns a FIDO2 attestation result
// Pass it to your server for verification
} catch (error) {
console.log({error});
// Handle Error...
Alert.alert('webauthn', `An error has occurred`);
}
} else {
Alert.alert('webauthn', `${isSupported}`);
}
};

const handleSignInWithOAuth = async (providerLink: string) => {
try {
const response = await InAppBrowser.openAuth(
Expand Down Expand Up @@ -163,6 +90,9 @@ export default function SignIn({
}
};

const navigateToSignUpWithPassKeys = () =>
navigation.navigate('signUpWithPassKeys');

const handleSignInWithApple = () => handleSignInWithOAuth(apple);
const handleSignInWithGoogle = () => handleSignInWithOAuth(google);

Expand Down Expand Up @@ -227,8 +157,8 @@ export default function SignIn({
<Button
loading={isLoading}
disabled={isLoading}
label="Sign in with security key"
onPress={handleSignInWithSecurityKey}
label="Sign up with passkeys"
onPress={navigateToSignUpWithPassKeys}
/>

<View
Expand Down
146 changes: 146 additions & 0 deletions examples/react_native/src/screens/SignUpWithPassKeys.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {SafeAreaView} from 'react-native-safe-area-context';
import {View, Text, ScrollView, Alert, StyleSheet} from 'react-native';

import Button from '@components/Button';
import ControlledInput from '@components/ControlledInput';
import {useForm} from 'react-hook-form';
import {NavigationProp, ParamListBase} from '@react-navigation/native';
import {Passkey, PasskeyRegistrationResult} from 'react-native-passkey';
import {useState} from 'react';

interface SignUpWithPassKeysFormValues {
email: string;
}

export default function SignUpWithPassKeys({
navigation,
}: {
navigation: NavigationProp<ParamListBase>;
}) {
const goBack = () => navigation.goBack();
const [loading, setLoading] = useState(false);
const {control, handleSubmit} = useForm<SignUpWithPassKeysFormValues>();

const onSubmit = async (data: SignUpWithPassKeysFormValues) => {
const isSupported = Passkey.isSupported();
const {email} = data;

const baseURL = `https://local.auth.nhost.run/v1`;

if (isSupported) {
try {
setLoading(true);

const res = await fetch(`${baseURL}/signup/webauthn`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
email,
}),
});

if (!res.ok) {
throw new Error('Request to sign-in with passkeys failed');
}

const challengeRequest = await res.json();

// Call the `register` method with the retrieved request in JSON format
// A native overlay will be displayed
const credential: PasskeyRegistrationResult = await Passkey.register(
challengeRequest,
);

const verification = await fetch(`${baseURL}/signup/webauthn/verify`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
credential,
}),
});

if (!verification.ok) {
throw new Error('Verification failed');
}

const session = await verification.json();

// await nhost.auth.refreshSession(session)
} catch (error) {
console.log({error});
// Handle Error...
Alert.alert('webauthn', `An error has occurred`);
} finally {
setLoading(false);
}
} else {
Alert.alert('webauthn', `${isSupported}`);
}
};

return (
<SafeAreaView style={styles.safeAreaView}>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<View style={styles.signInHeader}>
<Text style={styles.signInText}>Sign up with passkeys</Text>
</View>
<View style={styles.formWrapper}>
<ControlledInput
control={control}
name="email"
placeholder="Email"
autoCapitalize="none"
keyboardType="email-address"
rules={{
required: true,
}}
/>

<Button
loading={loading}
disabled={loading}
label="Sign Up"
onPress={handleSubmit(onSubmit)}
/>

<View style={styles.divider} />

<Button label="Other sign-up options" onPress={goBack} />
</View>
</ScrollView>
</SafeAreaView>
);
}

const styles = StyleSheet.create({
scrollView: {backgroundColor: 'white', flex: 1},
safeAreaView: {flex: 1},
divider: {
height: 2,
backgroundColor: '#D3D3D3',
width: '50%',
marginVertical: 10,
},
formWrapper: {
gap: 15,
paddingLeft: 30,
paddingRight: 30,
alignItems: 'center',
justifyContent: 'center',
},
signInText: {
fontSize: 30,
fontWeight: 'bold',
},
signInHeader: {
height: 200,
alignItems: 'center',
justifyContent: 'center',
},
});

0 comments on commit 881e53e

Please sign in to comment.