clerk expo sdk
Complete authentication solution for React Native Expo applications
$ npx docs2skills add clerk-expo-authClerk Expo SDK
Complete authentication solution for React Native Expo applications
What this skill does
Clerk Expo SDK provides production-ready authentication for React Native apps built with Expo. Unlike web implementations, Expo apps require custom authentication flows since Clerk's prebuilt UI components only work in web environments. The SDK handles user registration, login, email verification, session management, and secure token storage using expo-secure-store for encrypted persistence.
The SDK integrates deeply with Expo Router for navigation-aware authentication flows and provides React hooks (useAuth, useSignUp, useSignIn) plus control components (<SignedIn>, <SignedOut>) for conditional rendering. It manages complex authentication states including email verification, second-factor authentication, and session tasks that users must complete before accessing protected content.
Built for mobile-first authentication patterns, it handles device trust, over-the-air updates, and native API integration while providing a React-friendly developer experience through context providers and declarative components.
Prerequisites
- Expo SDK 50+ with Expo Router configured
- Node.js 18+ and React Native development environment
- Clerk Dashboard account with Native API enabled
expo-secure-storefor token caching (recommended)- TypeScript support recommended for better developer experience
Quick start
npm install @clerk/clerk-expo expo-secure-store
// app/_layout.tsx
import { ClerkProvider } from '@clerk/clerk-expo'
import { tokenCache } from '@clerk/clerk-expo/token-cache'
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<ClerkProvider
publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}
tokenCache={tokenCache}
>
<Stack>
<Stack.Screen name="(home)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
</Stack>
</ClerkProvider>
)
}
// app/(home)/index.tsx
import { SignedIn, SignedOut, useUser } from '@clerk/clerk-expo'
import { Link } from 'expo-router'
export default function HomePage() {
const { user } = useUser()
return (
<>
<SignedOut>
<Link href="/(auth)/sign-in">Sign In</Link>
<Link href="/(auth)/sign-up">Sign Up</Link>
</SignedOut>
<SignedIn>
<Text>Hello {user?.emailAddresses[0].emailAddress}</Text>
</SignedIn>
</>
)
}
Core concepts
ClerkProvider Context: The root provider component that wraps your entire app, providing authentication state and session management to all child components. Must be placed at the app's entry point to enable hooks and components throughout the component tree.
Custom Authentication Flows: Unlike web apps, Expo requires building custom sign-up and sign-in flows using hooks like useSignUp() and useSignIn(). These hooks provide methods to create authentication attempts, handle verification steps, and manage authentication state transitions.
Token Caching Strategy: Authentication tokens are stored using expo-secure-store for encrypted persistence across app sessions. The SDK provides a pre-configured token cache that handles encryption, storage, and automatic token refresh without manual intervention.
Session Management: User sessions are managed automatically with support for session tasks - additional steps users must complete before accessing protected content. Sessions persist across app launches and handle token refresh seamlessly.
Control Components: Declarative components like <SignedIn> and <SignedOut> conditionally render content based on authentication state, eliminating the need for manual authentication checks in component logic.
Key API surface
| Hook/Component | Purpose |
|---|---|
useAuth() | Access authentication state, sign-out function, session ID |
useSignUp() | Create sign-up flows with email verification |
useSignIn() | Handle sign-in attempts and second-factor auth |
useUser() | Access current user data and profile information |
useSession() | Get session details and check for pending tasks |
useClerk() | Direct access to Clerk client methods |
<ClerkProvider> | Root provider with publishableKey and tokenCache |
<SignedIn> | Render content only when user is authenticated |
<SignedOut> | Render content only when user is not authenticated |
tokenCache | Pre-configured secure token storage for expo-secure-store |
Common patterns
Email/Password Sign-Up Flow:
const { signUp, setActive } = useSignUp()
const [pendingVerification, setPendingVerification] = useState(false)
const onSignUp = async () => {
await signUp.create({ emailAddress, password })
await signUp.prepareEmailAddressVerification({ strategy: 'email_code' })
setPendingVerification(true)
}
const onVerify = async () => {
const result = await signUp.attemptEmailAddressVerification({ code })
if (result.status === 'complete') {
await setActive({ session: result.createdSessionId })
}
}
Protected Route Layout:
// app/(protected)/_layout.tsx
import { useAuth } from '@clerk/clerk-expo'
import { Redirect } from 'expo-router'
export default function ProtectedLayout() {
const { isSignedIn } = useAuth()
if (!isSignedIn) {
return <Redirect href="/(auth)/sign-in" />
}
return <Stack />
}
Sign-Out with Navigation:
const { signOut } = useClerk()
const router = useRouter()
const handleSignOut = async () => {
await signOut()
router.replace('/')
}
Second-Factor Authentication:
const { signIn, setActive } = useSignIn()
const handleSignIn = async () => {
const result = await signIn.create({ identifier: email, password })
if (result.status === 'needs_second_factor') {
const emailFactor = result.supportedSecondFactors?.find(
factor => factor.strategy === 'email_code'
)
if (emailFactor) {
await signIn.prepareSecondFactor({ strategy: 'email_code' })
setShowEmailCode(true)
}
}
}
Configuration
Environment Variables:
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
ClerkProvider Options:
<ClerkProvider
publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}
tokenCache={tokenCache} // Recommended for secure storage
navigate={customNavigateFunction} // Custom navigation handler
>
Token Cache Setup:
import { tokenCache } from '@clerk/clerk-expo/token-cache'
// Pre-configured with expo-secure-store encryption
Native API Configuration: Must enable Native API in Clerk Dashboard under Native applications section before SDK will work with mobile apps.
Best practices
Always use tokenCache: Configure expo-secure-store token caching to persist sessions securely across app launches and prevent users from having to re-authenticate frequently.
Handle session tasks: Check session.currentTask to identify when users have pending requirements (like completing 2FA setup) before allowing access to protected content.
Implement proper loading states: Check isLoaded from authentication hooks before rendering UI to prevent flickering and ensure authentication state is fully initialized.
Use route groups for organization: Create (auth) and (protected) route groups with layout files that handle authentication redirects automatically based on user state.
Enable OTA updates: Implement expo-updates to receive Clerk's security patches and feature updates without App Store submissions.
Error handling patterns: Wrap authentication attempts in try-catch blocks and handle Clerk's structured error responses with proper user feedback.
For complete authentication flow implementations, read the authentication-flows.md file in this skill directory.
For detailed hook and component usage, read the hooks-and-components.md file in this skill directory.
For security configuration and deployment, read the configuration-security.md file in this skill directory.
Gotchas and common mistakes
Forgetting to enable Native API: The SDK will not work until you enable Native API in the Clerk Dashboard's Native applications section - this is a required setup step.
Not using tokenCache: Without expo-secure-store token caching, users will be signed out every time they close the app, creating a poor user experience.
Missing isLoaded checks: Authentication hooks return isLoaded: false initially. Always check this before rendering UI or the app will show incorrect authentication states.
Incorrect ClerkProvider placement: ClerkProvider must wrap your entire app at the root level, not individual screens, or hooks won't work in child components.
Not handling verification states: Sign-up and sign-in flows have multiple states (pending verification, complete, etc.). Always check the status and handle each state appropriately.
Ignoring session tasks: Users may have session.currentTask requirements that block access to your app. Check and handle these tasks before allowing normal app usage.
Web component usage: Clerk's prebuilt UI components (SignIn, SignUp) only work on web. You must build custom flows for native Expo apps using the provided hooks.
Environment variable naming: Must use EXPO_PUBLIC_ prefix for client-side environment variables or they won't be accessible in your app.
Navigation during setActive: When calling setActive(), use the navigate callback parameter instead of calling router methods directly to ensure proper authentication flow completion.
Missing route group layouts: Route groups like (auth) need _layout.tsx files with proper redirect logic or users can access protected routes while unauthenticated.