-
Notifications
You must be signed in to change notification settings - Fork 0
Implement authentication for SignUp and SignIn screens #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ yarn-error.* | |
| *.pem | ||
|
|
||
| # local env files | ||
| .env | ||
| .env*.local | ||
|
|
||
| # typescript | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,132 @@ | ||
| import { Image, ScrollView, Text, View } from "react-native"; | ||
| import { Alert, Image, ScrollView, Text, View } from "react-native"; | ||
| import { icons, images } from "@/constants"; | ||
| import InputField from "@/components/InputField"; | ||
| import { useState } from "react"; | ||
| import CustomButton from "@/components/CustomButton"; | ||
| import { Link } from "expo-router"; | ||
| import { Link, router } from "expo-router"; | ||
| import OAuth from "@/components/OAuth"; | ||
| import { useAuth, useSignUp } from "@clerk/expo"; | ||
| import { ReactNativeModal } from "react-native-modal"; | ||
|
|
||
| const SignUp = () => { | ||
| const { signUp, errors } = useSignUp(); | ||
| const { isLoaded } = useAuth(); | ||
| const [form, setForm] = useState({ | ||
| name: "", | ||
| email: "", | ||
| password: "", | ||
| }); | ||
| const [verification, setVerification] = useState({ | ||
| state: "default", | ||
| error: "", | ||
| code: "", | ||
| }); | ||
| const [showSuccessModal, setShowSuccessModal] = useState(false); | ||
|
|
||
| const onSignUpPress = async () => { | ||
| if (!isLoaded) return; | ||
|
|
||
| const { error } = await signUp.password({ | ||
| emailAddress: form.email, | ||
| password: form.password, | ||
| }); | ||
|
Comment on lines
+29
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect whether Expo Web is configured and whether a Clerk CAPTCHA host exists.
fd -a -t f '^(app\.json|app\.config\.(js|ts|json)|package\.json)$' . \
-x rg -n -C 3 '"web"|platforms|scripts|start' {}
rg -n --glob '*.tsx' 'nativeID\s*=\s*["'\'']clerk-captcha["'\'']' appRepository: gummiebearz/react_native-uber-clone Length of output: 756 🌐 Web query:
💡 Result: When building a custom sign-up flow with Clerk in an Expo project (compatible with Expo SDK 54 and later), including a Citations:
🏁 Script executed: #!/bin/bash
set -eu
echo '--- project files ---'
git ls-files | rg '(^|/)(package\.json|app\.json|app\.config\.(js|ts|json)|sign-up\.tsx|_layout\.tsx)$'
echo '--- dependency and web configuration ---'
cat package.json
for f in app.json app.config.js app.config.ts app.config.json; do
if [ -f "$f" ]; then
echo "--- $f ---"
cat "$f"
fi
done
echo '--- CAPTCHA hosts ---'
rg -n --glob '*.tsx' --glob '*.ts' --glob '*.jsx' --glob '*.js' \
'clerk-captcha|Captcha|captcha' app components src 2>/dev/null || true
echo '--- sign-up flow ---'
sed -n '1,240p' 'app/(auth)/sign-up.tsx'Repository: gummiebearz/react_native-uber-clone Length of output: 10013 Add the Clerk CAPTCHA host for Expo Web. This project enables Expo Web, but no 🤖 Prompt for AI Agents |
||
|
|
||
| if (error) { | ||
| console.error(JSON.stringify(error, null, 2)); | ||
|
|
||
| // setVerification((_prev) => ({ | ||
| // ..._prev, | ||
| // error: error?.errors[0]?.longMessage || "Unable to create account", | ||
| // state: "error", | ||
| // })); | ||
|
|
||
| Alert.alert("Error", error.errors[0].longMessage); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const { error: sendError } = await signUp.verifications.sendEmailCode(); | ||
| if (sendError) { | ||
| console.error(JSON.stringify(sendError, null, 2)); | ||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| error: | ||
| sendError?.errors[0]?.longMessage || | ||
| "Unable to send verification code", | ||
| state: "error", | ||
| })); | ||
|
Comment on lines
+48
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Keep authentication errors visible. Line 201 only keeps the modal open while Store modal visibility separately from verification status. Show Also applies to: 75-82, 201-201 🤖 Prompt for AI Agents |
||
|
|
||
| return; | ||
| } | ||
|
|
||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| state: "pending", | ||
| })); | ||
| }; | ||
|
|
||
| const onVerifyPress = async () => { | ||
| if (!isLoaded) return; | ||
|
|
||
| const { error } = await signUp.verifications.verifyEmailCode({ | ||
| code: verification.code, | ||
| }); | ||
|
|
||
| if (error) { | ||
| console.error(JSON.stringify(error, null, 2)); | ||
|
|
||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| error: error?.errors[0]?.longMessage || "Unable to verify account", | ||
| state: "failed", | ||
| })); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const onSignUpPress = async () => {}; | ||
| if (signUp.status !== "complete") { | ||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| error: "Verification failed", | ||
| state: "failed", | ||
| })); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| if (!signUp.createdUserId) { | ||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| error: "Unable to retrieve user information", | ||
| state: "failed", | ||
| })); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| // TODO: Insert new user to database | ||
|
|
||
| // Make new Clerk session active | ||
| const { error: finalizeError } = await signUp.finalize(); | ||
|
|
||
| if (finalizeError) { | ||
| console.error(JSON.stringify(finalizeError, null, 2)); | ||
|
|
||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| error: | ||
| finalizeError?.errors[0]?.longMessage || "Unable to complete sign up", | ||
| state: "failed", | ||
| })); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| state: "success", | ||
| })); | ||
| }; | ||
|
|
||
| return ( | ||
| <ScrollView className="flex-1 bg-white" keyboardShouldPersistTaps="handled"> | ||
|
|
@@ -64,6 +177,7 @@ const SignUp = () => { | |
| }) | ||
| } | ||
| /> | ||
|
|
||
| <CustomButton | ||
| title="Sign Up" | ||
| onPress={onSignUpPress} | ||
|
|
@@ -82,6 +196,72 @@ const SignUp = () => { | |
| </View> | ||
|
|
||
| {/* Verification modal */} | ||
|
|
||
| <ReactNativeModal | ||
| isVisible={verification.state === "pending"} | ||
| onModalHide={() => { | ||
| if (verification.state === "success") | ||
| setShowSuccessModal(() => true); | ||
| }} | ||
| > | ||
| <View className="bg-white px-7 py-9 rounded-2xl min-h-[300px]"> | ||
| <Text className="text-2xl font-JakartaExtraBold mb-2"> | ||
| Verification | ||
| </Text> | ||
| <Text className="font-Jakarta mb-5"> | ||
| We have sent a verification code to {form.email} | ||
| </Text> | ||
| <InputField | ||
| label="Code" | ||
| icon={icons.lock} | ||
| placeholder="12345" | ||
| value={verification.code} | ||
| keyboardType="numeric" | ||
| onChangeText={(code) => | ||
| setVerification((_prev) => ({ | ||
| ..._prev, | ||
| code, | ||
| })) | ||
| } | ||
| /> | ||
|
|
||
| {verification.error && ( | ||
| <Text className="text-red-500 text-sm my-2"> | ||
| {verification.error} | ||
| </Text> | ||
| )} | ||
|
|
||
| <CustomButton | ||
| title="Verify Email" | ||
| onPress={onVerifyPress} | ||
| className="mt-5 bg-success-500" | ||
| /> | ||
| </View> | ||
| </ReactNativeModal> | ||
|
|
||
| <ReactNativeModal isVisible={showSuccessModal}> | ||
| <View className="bg-white px-6 py-9 rounded-2xl min-h-[300px]"> | ||
| <Image | ||
| source={images.check} | ||
| className="w-[110px] h-[110px] mx-auto my-5" | ||
| /> | ||
| <Text className="text-3xl font-JakartaBold text-center"> | ||
| Verified | ||
| </Text> | ||
| <Text className="text-base text-gray-400 font-Jakarta text-center mt-2"> | ||
| You have successfully verified your account. | ||
| </Text> | ||
|
|
||
| <CustomButton | ||
| title="Browse Home" | ||
| onPress={() => { | ||
| router.replace("/(root)/(tabs)/home"); | ||
| setShowSuccessModal(() => false); | ||
| }} | ||
| className="mt-5" | ||
| /> | ||
| </View> | ||
| </ReactNativeModal> | ||
| </View> | ||
| </ScrollView> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,14 @@ | ||
| import { Redirect } from "expo-router"; | ||
| import { useAuth } from "@clerk/expo"; | ||
|
|
||
| export default function Index() { | ||
| const { isSignedIn, isLoaded } = useAuth(); | ||
|
|
||
| if (!isLoaded) return null; | ||
|
|
||
| if (isSignedIn) { | ||
| return <Redirect href="/(root)/(tabs)/home" />; | ||
| } | ||
|
|
||
| return <Redirect href="/(auth)/welcome" />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface TokenCache { | ||
| getToken: (key: string) => Promise<string | undefined | null>; | ||
| saveToken: (key: string, value: string) => Promise<void>; | ||
| clearToken?: (key: string) => void; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: gummiebearz/react_native-uber-clone
Length of output: 5838
🌐 Web query:
Expo SDK 54.0.0 documentation authentication and sign-in flow💡 Result:
In Expo SDK 54.0.0, authentication and sign-in flows are primarily managed through Expo Router and supporting client-side libraries [1][2]. The documentation emphasizes a separation between navigation-based access control and the underlying authentication mechanisms [2][3]. Key components for authentication in Expo SDK 54 include: 1. Protected Routes: Expo Router provides a declarative way to manage authentication states using Stack.Protected and Tabs.Protected components within your layout files [4][5]. By providing a guard prop (a boolean indicating if the user is authenticated), you can automatically redirect users between protected screens (e.g., app content) and public screens (e.g., sign-in) [4][5]. 2. Expo AuthSession: This is the core client-side library for handling browser-based OAuth and OpenID Connect flows [6][1]. It allows your app to open a system browser, manage the authentication flow, and handle the redirect back into your application [1]. 3. Specialized Providers: For specific identity providers, Expo documentation recommends using dedicated libraries when available to simplify implementation, such as: - Apple Authentication (expo-apple-authentication): Provided as part of the Expo SDK for iOS [7]. - Other Providers: Libraries like
@react-native-google-signin/google-signinor react-native-fbsdk-next are recommended for their respective platforms [6]. 4. Implementation Patterns: - Authentication Context: It is common practice to create an authentication context (e.g., SessionProvider) to manage the session state globally [2][3]. - Loading States: To avoid flickering or premature redirects, documentation recommends using a splash screen controller or a loading view while the application determines the initial authentication state [2][3]. For detailed implementation, consult the official Expo guides on Authentication in Expo and React Native apps and the specific documentation for Expo Router's protected routes [1][2][4].Citations:
🌐 Web query:
Clerk Expo signIn.password signIn.finalize complete finalize error navigation documentation💡 Result:
In Clerk's custom authentication flows (supported by
@clerk/expo), thesignInobject manages the authentication lifecycle [1][2]. When building a custom sign-in flow (such as email/password), you follow a pattern of creating a sign-in instance, verifying credentials, and finalizing the session [3][4][5]. Sign-in Flow Overview: 1. Initiate: CallsignIn.create()to start the process [1][3]. For email/password specifically, thesignIn.password()method is typically used to submit credentials [4][5]. 2. Error Handling: All primary authentication methods (e.g.,signIn.create,signIn.password) return an object containing anerrorproperty (e.g.,{ error: ClerkError | null }) instead of throwing exceptions [6][5]. You should check this property for errors (such as invalid credentials) and handle them programmatically or display them to the user [6][7]. 3. Finalize: OncesignIn.statusreaches'complete', you must callsignIn.finalize()to set the session as active [3][4][8]. 4. Navigation: ThesignIn.finalize()method accepts anavigateparameter to control post-authentication routing [8]. This allows you to handle session tasks (like MFA or pending requirements) or redirect the user after a successful login [6][7][8]. Example snippet for finalizing a sign-in: if (signIn.status === 'complete') { await signIn.finalize({ navigate: ({ decorateUrl }) => { const url = decorateUrl('/dashboard'); // Specify your destination router.push(url); // Using your router (e.g., Expo Router) }, }); } Thefinalize()method is the standard approach in Clerk Core 3, replacing the oldersetActivepattern [5]. Always ensure you check thesignIn.statusbefore attempting to finalize, as it may require additional steps like'needs_second_factor'or'needs_client_trust'[4][7][5].Citations:
🏁 Script executed:
Repository: gummiebearz/react_native-uber-clone
Length of output: 359
🌐 Web query:
site:clerk.com/docs/expo "@clerk/expo" "finalizeError" "signIn.finalize"💡 Result:
In the
@clerk/expoSDK, specifically when working with the SignInFuture object in custom authentication flows, the signIn.finalize method is used to convert a completed sign-in attempt into an active session [1]. The finalize method returns a Promise that resolves to an object containing an error property: { error: null | ClerkError } [1]. Key points regarding error handling with signIn.finalize: 1. Return Value: The method does not throw an error by default; instead, it returns an object where the error property is either null (if the operation succeeded) or a ClerkError instance (if it failed) [1]. You should check this return value to handle potential failures during session activation. 2. Accessing Errors: For more general error management in custom flows, you can access the errors property from the useSignIn hook [2]. This object contains structured error information, including field-specific errors and raw Clerk errors (ClerkAPIResponseError, etc.), which are useful for providing feedback to the user or debugging the authentication state [3]. 3. Implementation Pattern: When using finalize, it is common practice to first verify that the sign-in status is 'complete'. If it is, you call finalize, often providing a navigation parameter to handle post-authentication routing (such as redirecting the user to a home page) [1][4]. Example usage: const { signIn } = useSignIn; //... inside your verification logic if (signIn.status === 'complete') { const result = await signIn.finalize({ navigate: ({ decorateUrl }) => { router.push(decorateUrl('/')); } }); if (result.error) { console.error('Finalization failed:', result.error); // Handle the specific error (e.g., display to user) } }Citations:
🌐 Web query:
site:clerk.com/docs/expo/reference/objects/sign-in@clerk/expo4.3.0 finalize error💡 Result:
There is no specific record of an error named "finalize" associated with version 4.3.0 of
@clerk/expoin the available documentation or issue reports. If you are encountering a "finalize" related issue, it may be related to deprecated API patterns or lifecycle management in older versions of the SDK. Note that the legacy Clerk API is being phased out in favor of Core 3 [1]. To resolve authentication-related issues in@clerk/expo: 1. Upgrade to Core 3: Clerk recommends migrating to the Core 3 SDK and using the SignInFuture object instead of legacy methods [1]. 2. Check OAuth/SSO flows: If you are experiencing issues where authentication windows do not dismiss or flows freeze, ensure your implementation uses try/finally blocks around sign-in methods to properly handle browser dismissal [2]. 3. Verify Native Module Configuration: Ensure your project configuration follows current standards, particularly if using Expo's New Architecture, as previous issues involved improper module registration [3]. 4. Review Configuration Flow: If you are experiencing native synchronization errors, ensure that your application waits for ClerkExpo.configure to resolve before proceeding with other authentication logic [4]. If the error persists, please verify the exact stack trace, as finalize may be part of a custom helper or an underlying dependency's lifecycle method rather than a direct method of the@clerk/expopublic API.Citations:
UnusedModuleInterfaceParserErrorin NativeClerkModule during Android build (Expo SDK 55 / New Architecture) clerk/javascript#8150🏁 Script executed:
Repository: gummiebearz/react_native-uber-clone
Length of output: 4054
🌐 Web query:
site:clerk.com/docs/expo/reference/objects/sign-in-future "finalize()" "error" "complete"💡 Result:
The term finalize appears in different contexts, primarily in software development frameworks like Clerk and the Java programming language. In the context of the Clerk authentication library, the finalize method is used to complete a sign-in process and convert it into an active session. When called, it returns a Promise that resolves to an object containing an error property. If the process is successful, the error property will be null; otherwise, it will contain a ClerkError.
typescript function finalize(params?: SignInFutureFinalizeParams): Promise<{ error: null | ClerkError }>In the context of the Java programming language, the Object.finalize method was historically used by the garbage collector to perform cleanup before an object was reclaimed. It is important to note that this method has been deprecated since Java 9 because it is unpredictable, often dangerous, and unnecessary. If an uncaught exception is thrown during the execution of finalize, the exception is ignored, and the finalization of that object terminates prematurely. Due to these issues, developers are strongly discouraged from using it. [1]Citations:
Stop navigation when authentication finalization fails.
Return when
finalizeErroris present. Navigate only aftersignIn.finalize()succeeds. Also return whensignIn.password()returnserror.🤖 Prompt for AI Agents