mcpField
C
clerk-expo-patterns

clerk-expo-patterns

github.com/midudevDevelopment7.0k56 installsUpdated 1w ago
Repository

About clerk-expo-patterns

A reference skill for building authentication flows with `@clerk/expo` v3+ in Expo and React Native apps. It covers secure token persistence, auth hooks, OAuth deep links, protected routes, and organization switching.

Key features

  1. 01SecureStore token caching
  2. 02Authentication hooks
  3. 03OAuth and deep-link setup
  4. 04Protected Expo Router screens
  5. 05Organization switching
  6. 06Expo configuration guidance

Use cases

  1. 01Persisting sessions across app restarts
  2. 02Adding Google, Apple, or GitHub OAuth
  3. 03Restricting Expo Router screens
  4. 04Switching organizations in an app

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/midudev/autoskills --skill clerk-expo-patterns

After installing, type/clerk-expo-patternsin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

evals/
evals.json
references/
oauth-deep-linking.md
protected-routes.md
push-notifications.md
token-storage.md
templates/expo-basic-auth/
app.json
package.json
tsconfig.json
templates/expo-basic-auth/app/
_layout.tsx
templates/expo-basic-auth/app/(auth)/
_layout.tsx
templates/expo-basic-auth/app/(home)/
_layout.tsx
index.tsx

Details

From the project README

Expo Patterns

SDK: @clerk/expo v3+. Requires Expo 53+, React Native 0.73+.

What Do You Need?

TaskReference
Persist tokens with SecureStorereferences/token-storage.md
OAuth (Google, Apple, GitHub)references/oauth-deep-linking.md
Protected screens with Expo Routerreferences/protected-routes.md
Push notifications with user datareferences/push-notifications.md

Mental Model

Clerk stores the session token in memory by default. In native apps:

  • SecureStore — encrypt token in device keychain (recommended for production)
  • tokenCache — prop on <ClerkProvider> that provides custom storage
  • useAuth — same API as web, works in any component
  • OAuth — requires useSSO + deep link scheme configured in app.json

Minimal Setup

app/_layout.tsx
import { ClerkProvider } from '@clerk/expo'
import { tokenCache } from '@clerk/expo/token-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}>
      <Stack />
    </ClerkProvider>
  )
}

CRITICAL: Use EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY — not NEXT_PUBLIC_. Env vars inside node_modules are not inlined in production builds. Always pass publishableKey explicitly.

Built-in Token Cache

import { tokenCache } from '@clerk/expo/token-cache'

This uses expo-secure-store with keychainAccessible: AFTER_FIRST_UNLOCK. Install the peer dep:

npx expo install expo-secure-store

Auth Hooks

import { useAuth, useUser, useSignIn, useSignUp, useClerk } from '@clerk/expo'

export function ProfileScreen() {
  const { isSignedIn, userId, signOut } = useAuth()
  const { user } = useUser()

  if (!isSignedIn) return <Redirect href="/sign-in" />
  return (
    <View>
      <Text>{user?.fullName}</Text>
      <Button title="Sign Out" onPress={() => signOut()} />
    </View>
  )
}

OAuth Flow (Google)

import { useSSO } from '@clerk/expo'
import * as WebBrowser from 'expo-web-browser'

WebBrowser.maybeCompleteAuthSession()

export function GoogleSignIn() {
  const { startSSOFlow } = useSSO()

  const handlePress = async () => {
    try {
      const { createdSessionId, setActive } = await startSSOFlow({
        strategy: 'oauth_google',
        redirectUrl: 'myapp://oauth-callback',
      })
      if (createdSessionId) await setActive!({ session: createdSessionId })
    } catch (err) {
      console.error(err)
    }
  }

  return <Button title="Continue with Google" onPress={handlePress} />
}

Org Switching

import { useOrganization, useOrganizationList } from '@clerk/expo'

export function OrgSwitcher() {
  const { organization } = useOrganization()
  const { setActive, userMemberships } = useOrganizationList()

  return (
    <View>
      <Text>Current: {organization?.name ?? 'Personal'}</Text>
      {userMemberships.data?.map(mem => (
        <Button
          key={mem.organization.id}
          title={mem.organization.name}
          onPress={() => setActive({ organization: mem.organization.id })}
        />
      ))}
    </View>
  )
}

Common Pitfalls

SymptomCauseFix
publishableKey undefined in prodUsing env var without EXPO_PUBLIC_Rename to EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY
Token lost on app restartNo tokenCachePass tokenCache from @clerk/expo/token-cache
OAuth redirect not workingMissing scheme in app.jsonAdd "scheme": "myapp" to app.json
WebBrowser.maybeCompleteAuthSessionNot calledCall it at the top level of the OAuth callback screen
useSSO not foundOld @clerk/expo versionuseSSO replaced useOAuth in v3+

Import Map

WhatImport From
ClerkProvider@clerk/expo
tokenCache@clerk/expo/token-cache
useAuth, useUser, useSignIn@clerk/expo
useSSO@clerk/expo
useOrganization, useOrganizationList@clerk/expo

See Also

  • clerk-setup - Initial Clerk install
  • clerk-custom-ui - Custom flows & appearance
  • clerk-orgs - B2B organizations

Docs

Expo SDK

Comments

Markdown supported