# How to use SwiftUI components in a React Expo and Clerk app - Part 2

> Part 2 of 2. Start with [How to use SwiftUI components in a React Expo and Clerk app](https://clerk.com/articles/how-to-use-swiftui-components-in-a-react-expo-and-clerk-app.md).

This is the second part of a two-part series on building a React Native app with Expo and Clerk's native SwiftUI components. In [Part 1](https://clerk.com/articles/how-to-use-swiftui-components-in-a-react-expo-and-clerk-app.md), we covered the architectural benefits of native authentication, project setup, and implementing Google Sign-In with `<AuthView />`. In this part, we will cover user management, Apple Sign-In, route protection, and advanced topics.

## Step 5: User management with `<UserButton />` and `<UserProfileView />`

Post-auth UX usually needs two things: a visible affordance for the user to manage their account, and a place to do it. `<UserButton />` is the affordance; `<UserProfileView />` is the place.

### Place `<UserButton />` in the header

`UserButton` has no props. Size is controlled by the parent `<View>` using `width`, `height`, `borderRadius`, and `overflow: 'hidden'` ([UserButton.tsx source](https://github.com/clerk/javascript/blob/main/packages/expo/src/native/UserButton.tsx)). Put it in the Expo Router header's `headerRight`:

```tsx
import { UserButton } from '@clerk/expo/native'
import { Stack } from 'expo-router'
import { View } from 'react-native'

export default function HomeLayout() {
  return (
    <Stack
      screenOptions={{
        headerRight: () => (
          <View style={{ width: 44, height: 44, borderRadius: 22, overflow: 'hidden' }}>
            <UserButton />
          </View>
        ),
      }}
    >
      <Stack.Screen name="index" options={{ title: 'Home' }} />
    </Stack>
  )
}
```

Tapping the button opens the native profile modal, a real SwiftUI sheet on iOS, not a React Native `<Modal />`.

### Render `<UserProfileView />` for a dedicated profile screen

Since `@clerk/expo` 3.4.0, the profile view is app-presented: you render `<UserProfileView />` yourself inside a route, sheet, or React Native `<Modal />`, rather than calling an imperative "present" hook. (Early betas exposed a `useUserProfileModal()` hook; it was removed in 3.4.0 when the native views moved to app-driven presentation.) The most common placement is a dedicated profile route:

```tsx
import { UserProfileView } from '@clerk/expo/native'

export default function ProfileScreen() {
  return <UserProfileView style={{ flex: 1 }} />
}
```

To open the profile from a settings row instead, navigate to that route, or toggle a state flag that renders `<UserProfileView />` inside a React Native `<Modal />`. `UserProfileView` is the only native component that accepts a `style` prop; it also takes `isDismissible` (default `true`) and an `onDismiss` callback ([UserProfileView.tsx source](https://github.com/clerk/javascript/blob/main/packages/expo/src/native/UserProfileView.tsx)). Set `isDismissible={false}` when you wrap it in your own `<Modal />` so the native view doesn't render a second dismiss control. Visual theming is still controlled by `clerk-theme.json`.

### Sign-out flow

Both `<UserProfileView />` and the profile opened by `<UserButton />` include a Sign Out control. `@clerk/expo` keeps auth state in sync between the native SDK and the JavaScript `Clerk` instance in both directions, so signing out flips `useAuth().isSignedIn` to `false` on its own, and any route guard that reads it (like the `Stack.Protected` gate in Step 7) re-renders and returns the user to the sign-in route with no event wiring. This is why the early-beta `useNativeAuthEvents()` hook was removed: automatic sync made a dedicated sign-out event unnecessary.

To run your own side effects on sign-out (analytics, clearing local state), watch `isSignedIn` with `useAuth()` in an effect:

```tsx
import { useAuth } from '@clerk/expo'
import { useRouter } from 'expo-router'
import { useEffect } from 'react'

export function useSignOutRedirect() {
  const { isSignedIn, isLoaded } = useAuth()
  const router = useRouter()

  useEffect(() => {
    if (isLoaded && !isSignedIn) router.replace('/sign-in')
  }, [isLoaded, isSignedIn, router])
}
```

## Step 6: Add Apple Sign-In

Apple Sign-In is the only provider on iOS that renders a true native credential sheet via `ASAuthorizationController`. On Android, Apple falls back to an OAuth browser flow that `<AuthView />` handles transparently. The `@clerk/expo` config plugin writes the necessary iOS entitlement at prebuild time, so the wiring is minimal.

### Enable Apple in the Dashboard

In the Clerk Dashboard, go to **Social Connections → Apple** and enable it. For development, that's enough; you can test on a physical iPhone signed into a real Apple ID.

For production you'll also supply the Apple-side credentials ([Clerk Apple social connection setup](https://clerk.com/docs/guides/configure/auth-strategies/social-connections/apple.md)):

- Apple Team ID.
- Services ID (created in the [Apple Developer portal](https://developer.apple.com/account/resources/identifiers/list/serviceId)).
- Key ID and `.p8` private key file (one-time download; back it up immediately per [Apple's private-key guide](https://developer.apple.com/help/account/capabilities/create-a-sign-in-with-apple-private-key)).

### Rebuild the dev build

The `@clerk/expo` plugin adds the `com.apple.developer.applesignin` entitlement at prebuild time, which is a native change. Rebuild:

```bash
npx expo run:ios --device
```

A simulator rebuild works for most of the flow, but physical-device testing is required for reliable end-to-end Apple Sign-In (see prerequisites).

### Testing without a physical iPhone

If you don't have a device on hand, you can still make progress. The Sign in with Apple sheet will render on the Simulator when an Apple ID is signed into **Settings → [your name] → Media & Purchases**, and the initial authorization sometimes completes if that Apple ID has 2FA enabled ([Apple Developer Forums discussion](https://developer.apple.com/forums/thread/121940)). `getCredentialStateAsync` always throws on the Simulator, though, so any logic that checks credential state on subsequent launches will fail there.

Two pragmatic fallbacks:

- **Mock the native modules for unit tests and CI.** Expo's [official mocking guide](https://docs.expo.dev/modules/mocking/) covers the `jest-expo` preset and module mocks so your Apple Sign-In tests run green on a laptop with no device attached.
- **Borrow, ad-hoc, or rent a real device before release.** A personal device, an internal-distribution EAS build on a teammate's phone, or a cloud device farm (AWS Device Farm, BrowserStack, Firebase Test Lab) all work for pre-release verification. Apple's App Review requires a working Sign in with Apple implementation ([App Store Review Guideline 4.8](https://developer.apple.com/app-store/review/guidelines/#login-services)), so at least one physical-device pass is mandatory before shipping to the App Store.

### Test Apple Sign-In with `<AuthView />`

`<AuthView />` detects that Apple is enabled for your Clerk instance and renders the `ASAuthorizationAppleIDButton` automatically ([Apple Developer](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidbutton)). Tap it and the OS presents the credential sheet, biometric prompt, email relay option, all rendered by `ASAuthorizationController`. Clerk handles the sign-in → sign-up transfer flow internally, so a returning user with a Clerk account is signed in; a new user triggers a sign-up with the name and email that Apple provides on first authorization.

One OS constraint to plan for: Apple only returns the user's `fullName` and `email` on the first authorization. Subsequent sign-ins omit them. Clerk stores them automatically on first auth, so you don't need to handle this in your app, but it's worth knowing if you ever see a user with no name on re-sign-in during testing.

### Optional: custom Apple Sign-In UI with `useSignInWithApple()`

If you need a branded Apple button that lives outside `<AuthView />` (custom placement, haptics, extra `unsafeMetadata`, or interleaving with non-Clerk screens), use the `useSignInWithApple()` hook from the `/apple` subpath. [`@clerk/expo` 3.0.0](https://github.com/clerk/javascript/blob/main/packages/expo/CHANGELOG.md) moved the hook to a dedicated `/apple` entry point, so projects that don't need Apple Sign-In don't bundle `expo-apple-authentication` and `expo-crypto`.

Install the two additional packages and register `expo-apple-authentication` as an Expo config plugin so its entitlements and native dependencies are applied at prebuild ([Clerk Sign in with Apple (Expo)](https://clerk.com/docs/expo/guides/configure/auth-strategies/sign-in-with-apple.md); [expo-apple-authentication config plugin](https://docs.expo.dev/versions/latest/sdk/apple-authentication/)):

```bash
npx expo install expo-apple-authentication expo-crypto
```

Register the plugin in `app.json`:

```json
{
  "expo": {
    "plugins": [
      "expo-router",
      "expo-secure-store",
      "expo-apple-authentication",
      ["@clerk/expo", {}]
    ]
  }
}
```

Then build the custom button. Import the hook from `@clerk/expo/apple` and gate rendering on `Platform.OS === 'ios'` because the hook is iOS-only ([`useSignInWithApple.ios.ts`](https://github.com/clerk/javascript/blob/main/packages/expo/src/hooks/useSignInWithApple.ios.ts)):

```tsx
import { useSignInWithApple } from '@clerk/expo/apple'
import { Platform, Pressable, Text } from 'react-native'
import { useRouter } from 'expo-router'

export function AppleButton() {
  const { startAppleAuthenticationFlow } = useSignInWithApple()
  const router = useRouter()
  if (Platform.OS !== 'ios') return null

  async function onPress() {
    try {
      const { createdSessionId, setActive } = await startAppleAuthenticationFlow()
      if (createdSessionId && setActive) {
        await setActive({ session: createdSessionId })
        router.replace('/')
      }
    } catch (err: any) {
      if (err.code !== 'ERR_REQUEST_CANCELED') console.error(err)
    }
  }

  return (
    <Pressable onPress={onPress}>
      <Text>Sign in with Apple</Text>
    </Pressable>
  )
}
```

The hook auto-generates a cryptographic nonce via `expo-crypto`, requests `FULL_NAME` + `EMAIL` scopes, and transparently transfers a sign-in into a sign-up when the Apple ID is new to Clerk. User cancellation is usually swallowed inside the hook and resolves with `createdSessionId: null` (no throw), but the `ERR_REQUEST_CANCELED` guard handles edge paths where it surfaces ([Clerk `useSignInWithApple` reference](https://clerk.com/docs/reference/expo/native-hooks/use-sign-in-with-apple.md)).

For Android, Apple authentication goes through `useSSO({ strategy: 'oauth_apple' })` instead, which uses a browser flow under the hood ([`useSSO()` reference](https://clerk.com/docs/reference/expo/native-hooks/use-sso.md)).

### When to pick `useSignInWithApple` vs `<AuthView />`

`<AuthView />` is the default path: drop-in native UI for Apple + Google + email/password + MFA + recovery, no manual dependency wiring, automatic entitlement management. Pick `useSignInWithApple()` only when you need a fully custom button UI that composes into an otherwise non-Clerk screen. Mixing the two inside a single sign-in flow is possible but usually unnecessary.

Apple first shipped the `useSignInWithApple()` hook for Expo on 2025-11-13 ([Clerk changelog, 2025-11-13](https://clerk.com/changelog/2025-11-13-native-sign-in-with-apple-expo.md)).

## Step 7: Protect routes with Expo Router

Only signed-in users should reach the home stack. The cleanest pattern on Expo Router v5+ is `Stack.Protected` ([Expo Router authentication guide](https://docs.expo.dev/router/advanced/authentication/); [Stack.Protected reference](https://docs.expo.dev/router/advanced/protected/)):

```tsx
import { Stack } from 'expo-router'
import { useAuth } from '@clerk/expo'

export default function RootLayout() {
  const { isSignedIn, isLoaded } = useAuth()
  if (!isLoaded) return null

  return (
    <Stack>
      <Stack.Protected guard={isSignedIn}>
        <Stack.Screen name="(home)" />
      </Stack.Protected>
      <Stack.Protected guard={!isSignedIn}>
        <Stack.Screen name="sign-in" />
      </Stack.Protected>
    </Stack>
  )
}
```

The `isLoaded` check prevents a flash of the wrong route while Clerk's hydration completes. On Expo SDK 52 and earlier, use the legacy pattern with `useEffect` and `router.replace()`. `Stack.Protected` shipped with [Expo Router v5](https://expo.dev/blog/expo-router-v5).

If you prefer render-level gating instead of navigation-level gating, Clerk ships a `<Show>` component that replaces the legacy `<SignedIn>` / `<SignedOut>` / `<Protect>` triplet ([Clerk `<Show>` reference](https://clerk.com/docs/expo/reference/components/control/show.md)). Both approaches work; `Stack.Protected` is usually cleaner for mobile flows.

## Under the hood: the Core 3 Signal API

> **Skip this section if you're only using `<AuthView />`, `<UserButton />`, and `<UserProfileView />`.** The native components render directly through `clerk-ios` (SwiftUI) and `clerk-android` (Jetpack Compose) and do not call the JavaScript Signal API. `@clerk/expo` syncs the resulting session into `useAuth()`, `useUser()`, and `useSession()` automatically, so you get reactive React state without writing any sign-in orchestration code.
>
> Come back here the first time you need to render a custom sign-in, sign-up, or MFA screen yourself. The Signal API is the supported JavaScript path for that; the native components are the default path when the pre-built UI works.

Clerk Core 3 shipped on 2026-03-03 ([Clerk changelog, 2026-03-03](https://clerk.com/changelog/2026-03-03-core-3.md)), and `@clerk/expo` 3.1 brought it to Expo alongside the native components on 2026-03-09 ([Clerk changelog, 2026-03-09](https://clerk.com/changelog/2026-03-09-expo-native-components.md)). The Signal API is a redesign of `useSignIn`, `useSignUp`, `useCheckout`, and `useWaitlist`. Each hook returns a reactive `*Future` object (`SignInFuture`, `SignUpFuture`) that triggers re-renders automatically and exposes three structured fields:

- `fetchStatus` (`'idle' | 'fetching'`) replaces manual `setIsLoading(true)` booleans.
- `status` (`'needs_first_factor' | 'needs_second_factor' | 'complete'` and so on) tells you where you are in the flow.
- `errors.fields` gives you parsed, field-scoped errors, no try/catch parsing of `ClerkAPIError[]`.

Clerk recommends the factor-specific methods over the lower-level `signIn.create()`, which is reserved for advanced multi-step flows. A passwordless email-code sign-in is three calls:

```ts
signIn.emailCode.sendCode({ emailAddress }) // send a one-time code to the user
signIn.emailCode.verifyCode({ code }) // verify the code they entered
signIn.finalize({ navigate }) // set the new session active (replaces setActive())
```

Every other first factor follows the same shape: `signIn.password({ identifier, password })`, `signIn.passkey()`, `signIn.mfa.verifyTOTP({ code })`, and so on ([Clerk MFA custom flow guide](https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication.md)). Each method resolves to `{ error }` rather than throwing. The `navigate` callback passed to `finalize()` receives `{ session, decorateUrl }` so you can branch on `session.currentTask` (forced MFA setup, org selection, and similar "requires additional step" branches).

A minimal custom email-code screen sends the code, then verifies it:

```tsx
import { useSignIn } from '@clerk/expo'
import { useRouter } from 'expo-router'
import { useState } from 'react'
import { Text, TextInput, Button, View } from 'react-native'

export default function CustomSignIn() {
  const { signIn, errors, fetchStatus } = useSignIn()
  const router = useRouter()
  const [email, setEmail] = useState('')
  const [code, setCode] = useState('')

  async function sendCode() {
    await signIn.emailCode.sendCode({ emailAddress: email })
  }

  async function verifyCode() {
    await signIn.emailCode.verifyCode({ code })
    if (signIn.status === 'complete') {
      await signIn.finalize({ navigate: () => router.replace('/') })
    }
  }

  return (
    <View>
      <TextInput
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        keyboardType="email-address"
      />
      {errors.fields.identifier && <Text>{errors.fields.identifier.message}</Text>}
      <Button title="Send code" onPress={sendCode} />
      <TextInput value={code} onChangeText={setCode} keyboardType="number-pad" />
      {errors.fields.code && <Text>{errors.fields.code.message}</Text>}
      <Button title={fetchStatus === 'fetching' ? 'Verifying...' : 'Verify'} onPress={verifyCode} />
    </View>
  )
}
```

Legacy Core 2 required wrapping each call in `try { ... } catch (err) { /* parse ClerkAPIError[] */ }`. Core 3 exposes parsed errors on the hook itself (`errors.fields.identifier?.message`, `errors.fields.code?.message`, plus `errors.global` and raw `errors.raw`), so the component code stays linear.

`<AuthView />` does not call the JavaScript Signal API. It renders through the native Clerk SDKs (`clerk-ios` SwiftUI, `clerk-android` Compose), which implement the equivalent flow natively. When the native SDK completes authentication, `@clerk/expo` syncs the session into the JS `Clerk` instance, so `useAuth()`, `useUser()`, and `useSession()` all reflect the new state. The Signal API hooks are the custom-UI escape hatch when you need to render the sign-in screen yourself.

One stability caveat worth flagging: the `SignInFuture` instance does not have a stable identity across flow steps. If you reference `signIn` inside `useEffect`, `useCallback`, or `useMemo`, include it in the dependency array explicitly rather than relying on React identity stability ([`SignInFuture` reference](https://clerk.com/docs/reference/objects/sign-in-future.md)).

## Theming the native components

`@clerk/expo` 3.2.0 (shipped 2026-04-16) added a `theme` plugin option that points at a JSON file. Tokens are embedded at prebuild time, so the customization lives at the native layer rather than JS runtime.

Create `clerk-theme.json` at the project root:

```json
{
  "colors": {
    "primary": "#6C47FF",
    "background": "#FFFFFF",
    "foreground": "#0A0A0A"
  },
  "darkColors": {
    "background": "#121212",
    "foreground": "#F5F5F5"
  },
  "design": {
    "borderRadius": 12
  }
}
```

Reference it in the plugin config:

```json
["@clerk/expo", { "theme": "./clerk-theme.json" }]
```

Seventeen color tokens are available in the light `colors` block: `primary`, `background`, `input`, `danger`, `success`, `warning`, `foreground`, `mutedForeground`, `primaryForeground`, `inputForeground`, `neutral`, `border`, `ring`, `muted`, `shadow`, plus `secondaryButtonBackground` and `secondaryButtonForeground` (both added in `@clerk/expo` 3.6.1). The `darkColors` block accepts the same tokens (override any you want), plus `design.borderRadius` and an iOS-only `design.fontFamily`. Rebuild after changing the file because values are baked in at prebuild time ([NativeComponentQuickstart `clerk-theme.json`](https://github.com/clerk/clerk-expo-quickstart/blob/main/NativeComponentQuickstart/clerk-theme.json)).

## Offline session rehydration

Mobile apps live with flaky networks. `@clerk/expo` ships an experimental resource cache that persists environment, client state, and the last session JWT to `expo-secure-store`, so the app boots into an authenticated state on cold start without a network roundtrip. Opt in by passing `resourceCache` as the `__experimental_resourceCache` prop on `<ClerkProvider>` ([Clerk offline support](https://clerk.com/docs/guides/development/offline-support.md)):

```tsx
import { ClerkProvider } from '@clerk/expo'
import { tokenCache } from '@clerk/expo/token-cache'
import { resourceCache } from '@clerk/expo/resource-cache'
import { Stack } from 'expo-router'

const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!

export default function RootLayout() {
  return (
    <ClerkProvider
      publishableKey={publishableKey}
      tokenCache={tokenCache}
      __experimental_resourceCache={resourceCache}
    >
      <Stack />
    </ClerkProvider>
  )
}
```

Three things are cached: the Clerk environment (enabled auth strategies, organization settings, feature flags), client state (active sessions, the user object), and the session JWT. Behind the scenes, `resourceCache` chunks values across `expo-secure-store` slots with `keychainAccessible: AFTER_FIRST_UNLOCK`, so the cache is readable only after the user has unlocked the device at least once per boot ([`resource-cache.ts` source](https://github.com/clerk/javascript/blob/main/packages/expo/src/resource-cache/resource-cache.ts)).

What it does and does not do:

- **Rehydration, yes.** On a cold start with no network, `useAuth()` resolves from cache and `getToken()` returns the last cached JWT. Your gated routes render immediately.
- **Offline sign-in, no.** `signIn.create()`, `signIn.password()`, `<AuthView />`, and the native social flows all still require network. The resource cache accelerates the "already signed in" path, not the first authentication.
- **Staleness applies.** Cached tokens can be stale until the next refresh, so always treat server-side verification as the source of truth when gating sensitive data.

The `__experimental_` prefix reflects that the shape may change. Clerk's docs warn: "It is subject to change in future updates, so use it cautiously in production environments" ([Clerk offline support](https://clerk.com/docs/guides/development/offline-support.md)). The older `@clerk/expo/secure-store` export covered similar ground and is now deprecated in favor of `resourceCache`.

## Troubleshooting

Most native-component issues fall into a handful of buckets. Here's what to check first.

**"Native module not found" or `TurboModuleRegistry` errors.** You're running in Expo Go or an old dev build. Rebuild with `npx expo run:ios` or `npx expo run:android`.

**Google Sign-In `error code: 10` on Android.** The SHA-1 fingerprint you registered in the Google Cloud Console doesn't match the signing key of the build currently on the device. Re-run `./gradlew signingReport`, copy the debug SHA-1, and update the Android OAuth client ID. Release builds use a different fingerprint ([React Native Google Sign-In, Setting Up](https://react-native-google-signin.github.io/docs/setting-up/get-config-file)).

**Apple Sign-In "hangs" on the simulator.** You're hitting the Simulator's `getCredentialStateAsync` gap. Move to a physical iPhone signed into a real Apple ID ([expo-apple-authentication docs](https://docs.expo.dev/versions/latest/sdk/apple-authentication/)).

**Kotlin metadata version mismatch on Android.** Fixed in `@clerk/expo` 3.1.5. The config plugin now adds the `-Xskip-metadata-version-check` Kotlin compiler flag at prebuild time, so builds against Expo SDK 54/55 stop failing with mismatched metadata errors. Upgrade to 3.1.5 or newer ([@clerk/expo CHANGELOG](https://github.com/clerk/javascript/blob/main/packages/expo/CHANGELOG.md)).

**OAuth redirect URI mismatch.** Clerk's default mobile SSO redirect is `{bundleIdentifier}://callback`. Verify the Dashboard's "Allowlist for mobile SSO redirect" matches your app's Bundle ID ([Deploy an Expo app to production](https://clerk.com/docs/guides/development/deployment/expo.md)).

**White flash on AuthView mount (iOS).** Fixed in `@clerk/expo` 3.1.10.

**Native and JavaScript auth state disagree (a sign-in or sign-out isn't reflected in `useAuth()`).** Early 3.x builds could leave the two layers out of step. `@clerk/expo` hardened two-way sync across versions 3.4.4 through 3.5.0 (cold-start hydration of a JS-owned session into the native views, sign-out propagation from either runtime, and correct handling of native multi-session changes), so upgrade to 3.5.0 or newer if you see persistent disagreement ([@clerk/expo CHANGELOG](https://github.com/clerk/javascript/blob/main/packages/expo/CHANGELOG.md)).

**`useAuth()` reports signed-out briefly after a successful native sign-in.** This is the native-session → JS-Clerk sync window. `clerk-ios` has already created the session (as `pending`); `@clerk/expo` is still syncing it into the React tree. Since `useAuth()` defaults `treatPendingAsSignedOut: true`, the hook reads pending as signed-out. Set `useAuth({ treatPendingAsSignedOut: false })` anywhere you watch `isSignedIn` after a native flow to bridge the gap.

## Conclusion

You now have a fully functional Expo application using Clerk's native SwiftUI components. You have implemented Google and Apple Sign-In, added user management with `<UserButton />` and `<UserProfileView />`, protected your routes, and explored advanced topics like the Core 3 Signal API, theming, and offline session rehydration.

## Frequently asked questions

## FAQ

### How do native components handle multi-factor authentication?

Enable [MFA](https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication.md) in the Clerk Dashboard and `<AuthView />` prompts for the second factor (TOTP, SMS, backup codes) during sign-in with no extra code. `<UserProfileView />` manages MFA enrollment the same way.

### How do I share authentication sessions between my app and app extensions?

The `@clerk/expo` plugin exposes no keychain-sharing option as of 3.2.0. Keychain config lives on the native `clerk-ios` SDK via `Clerk.Options(keychainConfig: .init(service:, accessGroup:))` plus a matching `keychain-access-groups` entitlement on every target, so from Expo you need a custom config plugin and init-time access to the `Clerk` instance, pushing you into bare-workflow territory. See [`Clerk.Options`](https://clerk.com/docs/reference/native-mobile/configuration.md) and [Apple keychain access groups](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps).

## Next steps

- **Add [passkeys](https://clerk.com/docs/guides/configure/auth-strategies/sign-up-sign-in-options.md#passkeys).** Install `@clerk/expo-passkeys` and pass `__experimental_passkeys` to `<ClerkProvider>` to enable passkey sign-in and enrollment inside `<AuthView />` and `<UserProfileView />`. Works on iOS 16+ and Android 9+ ([Clerk Expo passkeys](https://clerk.com/docs/reference/expo/passkeys.md)).
- **Biometric sign-in.** Use `useLocalCredentials()` to authenticate with a stored password via Face ID or Touch ID on return visits. It's a password-strategy-only hook, so it complements `<AuthView />` rather than replacing it ([Clerk `useLocalCredentials`](https://clerk.com/docs/reference/expo/native-hooks/use-local-credentials.md)).
- **Authenticated backend calls.** Use `getToken()` from `useAuth()` to retrieve a short-lived session JWT and send it as an `Authorization: Bearer` header to your own backend ([Clerk session tokens](https://clerk.com/docs/guides/sessions/session-tokens.md)).

## In this series

1. [How to use SwiftUI components in a React Expo and Clerk app](https://clerk.com/articles/how-to-use-swiftui-components-in-a-react-expo-and-clerk-app.md)
2. **How to use SwiftUI components in a React Expo and Clerk app - Part 2** (you are here)
