Skip to content

Commit 3b396ab

Browse files
update expo docs (#1666)
Co-authored-by: victoria <[email protected]>
1 parent a24a17f commit 3b396ab

29 files changed

+866
-879
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
The following example demonstrates how to create a custom OAuth sign-in flow for [Google accounts](/docs/authentication/social-connections/google).
2+
3+
```tsx {{ filename: 'app/(auth)/sign-in.tsx', collapsible: true }}
4+
import React from 'react'
5+
import * as WebBrowser from 'expo-web-browser'
6+
import { Text, View, Button } from 'react-native'
7+
import { Link } from 'expo-router'
8+
import { useOAuth } from '@clerk/clerk-expo'
9+
import * as Linking from 'expo-linking'
10+
11+
export const useWarmUpBrowser = () => {
12+
React.useEffect(() => {
13+
// Warm up the android browser to improve UX
14+
// https://docs.expo.dev/guides/authentication/#improving-user-experience
15+
void WebBrowser.warmUpAsync()
16+
return () => {
17+
void WebBrowser.coolDownAsync()
18+
}
19+
}, [])
20+
}
21+
22+
WebBrowser.maybeCompleteAuthSession()
23+
24+
export default function Page() {
25+
useWarmUpBrowser()
26+
27+
const { startOAuthFlow } = useOAuth({ strategy: 'oauth_google' })
28+
29+
const onPress = React.useCallback(async () => {
30+
try {
31+
const { createdSessionId, signIn, signUp, setActive } = await startOAuthFlow({
32+
redirectUrl: Linking.createURL('/dashboard', { scheme: 'myapp' }),
33+
})
34+
35+
// If sign in was successful, set the active session
36+
if (createdSessionId) {
37+
setActive!({ session: createdSessionId })
38+
} else {
39+
// Use signIn or signUp returned from startOAuthFlow
40+
// for next steps, such as MFA
41+
}
42+
} catch (err) {
43+
// See https://clerk.com/docs/custom-flows/error-handling
44+
// for more info on error handling
45+
console.error(JSON.stringify(err, null, 2))
46+
}
47+
}, [])
48+
49+
return (
50+
<View>
51+
<Link href="/">
52+
<Text>Home</Text>
53+
</Link>
54+
<Button title="Sign in with Google" onPress={onPress} />
55+
</View>
56+
)
57+
}
58+
```

docs/custom-flows/email-password-mfa.mdx

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This guide will walk you through how to build a custom email/password sign-in fl
2828

2929
1. In the Clerk Dashboard, navigate to the [**Multi-factor**](https://dashboard.clerk.com/last-active?path=user-authentication/multi-factor) page.
3030
1. For the purpose of this guide, toggle on both the **Authenticator application** and **Backup codes** strategies.
31+
1. Select **Save**.
3132

3233
### Sign-in flow
3334

@@ -309,19 +310,34 @@ This guide will walk you through how to build a custom email/password sign-in fl
309310

310311
### Build the flow
311312

312-
Create a component that handles the sign-in with multi-factor authentication flow.
313+
1. Create the `(auth)` route group. This groups your sign-up and sign-in pages.
314+
1. In the `(auth)` group, create a `_layout.tsx` file with the following code. The [`useAuth()`](/docs/references/react/use-auth) hook is used to access the user's authentication state. If the user's already signed in, they'll be redirected to the home page.
313315

314-
> [!NOTE]
315-
> You can render this component in a custom sign-in or sign-up flow, which you can find examples of in [the Expo quickstart](/docs/quickstarts/expo).
316+
```tsx {{ filename: 'app/(auth)/_layout.tsx' }}
317+
import { Redirect, Stack } from 'expo-router'
318+
import { useAuth } from '@clerk/clerk-expo'
316319

317-
```tsx {{ filename: 'SignInMFAForm.tsx', collapsible: true }}
320+
export default function AuthenticatedLayout() {
321+
const { isSignedIn } = useAuth()
322+
323+
if (isSignedIn) {
324+
return <Redirect href={'/'} />
325+
}
326+
327+
return <Stack />
328+
}
329+
```
330+
331+
In the `(auth)` group, create a `sign-in.tsx` file with the following code. The [`useSignIn()`](/docs/references/react/use-sign-in) hook is used to create a sign-in flow. The user can sign in using their email and password and will be prompted to verify their account with a code from their authenticator app or with a backup code.
332+
333+
```tsx {{ filename: 'app/(auth)/sign-in.tsx', collapsible: true }}
318334
import React from 'react'
319335
import { useSignIn } from '@clerk/clerk-expo'
320336
import { useRouter } from 'expo-router'
321-
import { Text, TextInput, TouchableOpacity, View } from 'react-native'
337+
import { Text, TextInput, Button, View } from 'react-native'
322338
import Checkbox from 'expo-checkbox'
323339

324-
export default function SignInMFAForm() {
340+
export default function Page() {
325341
const { signIn, setActive, isLoaded } = useSignIn()
326342

327343
const [email, setEmail] = React.useState('')
@@ -335,18 +351,24 @@ This guide will walk you through how to build a custom email/password sign-in fl
335351
const handleFirstStage = async () => {
336352
if (!isLoaded) return
337353

354+
// Attempt to sign in using the email and password provided
338355
try {
339356
const attemptFirstFactor = await signIn.create({
340357
identifier: email,
341358
password,
342359
})
343360

361+
// If the sign-in was successful, set the session to active
362+
// and redirect the user
344363
if (attemptFirstFactor.status === 'complete') {
345364
await setActive({ session: attemptFirstFactor.createdSessionId })
346-
router.replace('/dashboard')
365+
router.replace('/')
347366
} else if (attemptFirstFactor.status === 'needs_second_factor') {
367+
// If the sign-in requires a second factor, display the TOTP form
348368
setDisplayTOTP(true)
349369
} else {
370+
// If the sign-in failed, check why. User might need to
371+
// complete further steps.
350372
console.error(JSON.stringify(attemptFirstFactor, null, 2))
351373
}
352374
} catch (err) {
@@ -356,6 +378,7 @@ This guide will walk you through how to build a custom email/password sign-in fl
356378
}
357379
}
358380

381+
// Handle the submission of the TOTP or backup code
359382
const onPressTOTP = React.useCallback(async () => {
360383
if (!isLoaded) return
361384

@@ -371,13 +394,13 @@ This guide will walk you through how to build a custom email/password sign-in fl
371394
if (attemptSecondFactor.status === 'complete') {
372395
await setActive({ session: attemptSecondFactor.createdSessionId })
373396

374-
router.replace('/dashboard')
397+
router.replace('/')
375398
} else {
376399
// If the status is not complete, check why. User may need to
377400
// complete further steps.
378401
console.error(JSON.stringify(attemptSecondFactor, null, 2))
379402
}
380-
} catch (err: any) {
403+
} catch (err) {
381404
// See https://clerk.com/docs/custom-flows/error-handling
382405
// for more info on error handling
383406
console.error(JSON.stringify(err, null, 2))
@@ -390,48 +413,45 @@ This guide will walk you through how to build a custom email/password sign-in fl
390413
<Text>Verify your account</Text>
391414

392415
<View>
393-
<Text>Code</Text>
394416
<TextInput
395417
value={code}
396-
placeholder="OTP or Backup Code"
418+
placeholder="Enter the code"
419+
placeholderTextColor="#666666"
397420
onChangeText={(c) => setCode(c)}
398421
/>
399422
</View>
400-
<View>
401-
<Text>This code is a backup code</Text>
423+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
424+
<Text>Check if this code is a backup code</Text>
402425
<Checkbox value={useBackupCode} onValueChange={() => setUseBackupCode((prev) => !prev)} />
403426
</View>
404-
<TouchableOpacity onPress={onPressTOTP}>
405-
<Text>Verify</Text>
406-
</TouchableOpacity>
427+
<Button title="Verify" onPress={onPressTOTP} />
407428
</View>
408429
)
409430
}
410431

411432
return (
412433
<View>
413-
<Text>Sign In</Text>
434+
<Text>Sign in</Text>
414435
<View>
415436
<TextInput
416437
value={email}
417-
placeholder="Email..."
418-
placeholderTextColor="#000"
438+
placeholder="Enter email"
439+
placeholderTextColor="#666666"
419440
onChangeText={(email) => setEmail(email)}
420441
/>
421442
</View>
422443

423444
<View>
424445
<TextInput
425446
value={password}
426-
placeholder="Password..."
427-
placeholderTextColor="#000"
447+
placeholder="Enter password"
448+
placeholderTextColor="#666666"
449+
secureTextEntry={true}
428450
onChangeText={(password) => setPassword(password)}
429451
/>
430452
</View>
431453

432-
<TouchableOpacity onPress={handleFirstStage}>
433-
<Text>Sign In</Text>
434-
</TouchableOpacity>
454+
<Button title="Continue" onPress={handleFirstStage} />
435455
</View>
436456
)
437457
}

0 commit comments

Comments
 (0)