Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@ EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=
# After running: eas build:configure
APPLE_TEAM_ID=
EAS_PROJECT_ID=
APP_NAME=My Clerk App
APP_SLUG=my-clerk-app
APP_SCHEME=myapp
APP_NAME=
APP_SLUG=
APP_SCHEME=

# iOS Configuration (Optional)
# Change this to your own bundle identifier
IOS_BUNDLE_IDENTIFIER=com.yourcompany.yourapp
IOS_BUNDLE_IDENTIFIER=

# Android Configuration (Optional)
# Change this to your own package name
ANDROID_PACKAGE=com.yourcompany.yourapp
ANDROID_PACKAGE=

# Google Sign-In Configuration
# Visit https://console.cloud.google.com/apis/credentials to create OAuth credentials
# iOS Client ID from Google Cloud Console
EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID=
# iOS URL Scheme (format: com.googleusercontent.apps.YOUR_IOS_CLIENT_ID_PREFIX)
EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME=
# Android Client ID from Google Cloud Console
EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID=
# Web Client ID from Google Cloud Console
EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID=4
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,16 @@ expo-env.d.ts
ios/

# android
android/
android/

# yalc
.yalc
yalc.lock

# metro
metro.config.js

# artifacts
*.ipa
*.apk
*.aab
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ After following the quickstart you'll have learned how to:
- Conditionally show content based on your auth state
- Build your sign-in and sign-up pages
- **(Optional)** Enable native Apple Sign-In on iOS
- **(Optional)** Enable native Google Sign-In on iOS and Android

## Quick Start

Expand Down Expand Up @@ -122,7 +123,7 @@ Native Apple Sign-In is **disabled by default** and requires additional setup:

**To enable:**

1. Follow the complete setup guide: TODO: link docs here.
1. Follow the [Sign in with Apple setup guide](https://clerk.com/docs/guides/configure/auth-strategies/sign-in-with-apple).

2. Uncomment the Apple Sign-In button in:

Expand All @@ -131,8 +132,6 @@ Native Apple Sign-In is **disabled by default** and requires additional setup:

3. Build with EAS or local prebuild (Apple Sign-In doesn't work in Expo Go)

For detailed instructions, see [APPLE_SIGNIN_SETUP.md](./APPLE_SIGNIN_SETUP.md).

## Building for Production

### Using EAS Build (Recommended)
Expand Down Expand Up @@ -192,16 +191,15 @@ npx expo run:android --variant release
```
├── app/
│ ├── (auth)/
│ │ ├── sign-in.tsx # Sign-in screen
│ │ └── sign-up.tsx # Sign-up screen
│ │ ├── sign-in.tsx # Sign-in screen
│ │ └── sign-up.tsx # Sign-up screen
│ ├── (home)/
│ │ └── index.tsx # Home screen (protected)
│ │ └── index.tsx # Home screen (protected)
│ ├── components/
│ │ └── AppleSignInButton.tsx # Optional Apple Sign-In component
│ └── _layout.tsx # Root layout with ClerkProvider
├── .env.example # Environment variables template
├── eas.json # EAS Build configuration
└── APPLE_SIGNIN_SETUP.md # Apple Sign-In setup guide
└── eas.json # EAS Build configuration
```

## Environment Variables
Expand Down Expand Up @@ -241,7 +239,7 @@ To learn more about Clerk and Expo, check out the following resources:

### Apple Sign-In not working

- Verify you've followed all steps in [APPLE_SIGNIN_SETUP.md](./APPLE_SIGNIN_SETUP.md)
- Verify you've followed all steps in the [Sign in with Apple setup guide](https://clerk.com/docs/guides/configure/auth-strategies/sign-in-with-apple)
- Apple Sign-In requires a native build (doesn't work in Expo Go)
- Check that the capability is enabled in your Apple Developer account

Expand Down
24 changes: 17 additions & 7 deletions app.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ExpoConfig, ConfigContext } from "expo/config";
import "dotenv/config";

export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
Expand All @@ -17,7 +18,8 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
ios: {
supportsTablet: true,
bundleIdentifier:
process.env.IOS_BUNDLE_IDENTIFIER || "com.yourcompany.yourapp",
process.env.EXPO_PUBLIC_IOS_BUNDLE_IDENTIFIER ||
"com.clerk.clerkexpoquickstart",
...(process.env.APPLE_TEAM_ID && {
appleTeamId: process.env.APPLE_TEAM_ID,
}),
Expand All @@ -30,7 +32,8 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
foregroundImage: "./assets/images/adaptive-icon.png",
backgroundColor: "#ffffff",
},
package: process.env.ANDROID_PACKAGE || "com.yourcompany.yourapp",
package:
process.env.EXPO_PUBLIC_ANDROID_PACKAGE || "com.yourcompany.yourapp",
},
web: {
bundler: "metro",
Expand All @@ -42,16 +45,23 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
"expo-secure-store",
"expo-font",
"expo-apple-authentication",
"@clerk/clerk-expo",
],
experiments: {
typedRoutes: true,
},
extra: {
router: {},
...(process.env.EAS_PROJECT_ID && {
eas: {
projectId: process.env.EAS_PROJECT_ID,
},
}),
eas: {
projectId: process.env.EAS_PROJECT_ID || "Your Project ID",
},
EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME:
process.env.EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME,
EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID:
process.env.EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID,
EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID:
process.env.EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID,
EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID:
process.env.EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID,
},
});
8 changes: 3 additions & 5 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
"expo-secure-store",
"expo-font"
],
"experiments": {
"typedRoutes": true
},
"extra": {
"EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME": ""
}
}
}
125 changes: 81 additions & 44 deletions app/(auth)/sign-in.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,106 @@
import { useSignIn } from '@clerk/clerk-expo'
import { Link, useRouter } from 'expo-router'
import { Text, TextInput, Button, View } from 'react-native'
import React from 'react'
import type { EmailCodeFactor } from '@clerk/types'
import { useSignIn } from "@clerk/clerk-expo";
import { Link, useRouter } from "expo-router";
import { Text, TextInput, View, StyleSheet, Button } from "react-native";
import React from "react";
import type { EmailCodeFactor } from "@clerk/types";
import GoogleSignInButton from "../components/GoogleSignInButton";
// import AppleSignInButton from '../components/AppleSignInButton'
// import GoogleSSOButton from '../components/GoogleSSOButton'

export default function Page() {
const { signIn, setActive, isLoaded } = useSignIn()
const router = useRouter()
const { signIn, setActive, isLoaded } = useSignIn();
const router = useRouter();

const [emailAddress, setEmailAddress] = React.useState('')
const [password, setPassword] = React.useState('')
const [code, setCode] = React.useState('')
const [showEmailCode, setShowEmailCode] = React.useState(false)
const [emailAddress, setEmailAddress] = React.useState("");
const [password, setPassword] = React.useState("");
const [code, setCode] = React.useState("");
const [showEmailCode, setShowEmailCode] = React.useState(false);

// Handle the submission of the sign-in form
const onSignInPress = React.useCallback(async () => {
if (!isLoaded) return
if (!isLoaded || !signIn) return;

// Start the sign-in process using the email and password provided
try {
// Start the sign-in process using the email and password provided
const signInAttempt = await signIn.create({
identifier: emailAddress,
password,
})
});

// If sign-in process is complete, set the created session as active
// and redirect the user
if (signInAttempt.status === 'complete') {
if (signInAttempt.status === "complete") {
await setActive({
session: signInAttempt.createdSessionId,
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.log(session?.currentTask)
return
console.log(session?.currentTask);
return;
}

router.replace('/')
router.replace("/");
},
})
} else if (signInAttempt.status === 'needs_second_factor') {
});
} else if (signInAttempt.status === "needs_second_factor") {
// Check if email_code is a valid second factor
// This is required when Client Trust is enabled and the user
// is signing in from a new device.
// See https://clerk.com/docs/guides/secure/client-trust
const emailCodeFactor = signInAttempt.supportedSecondFactors?.find(
(factor): factor is EmailCodeFactor => factor.strategy === 'email_code'
)
(factor): factor is EmailCodeFactor =>
factor.strategy === "email_code"
);

if (emailCodeFactor) {
await signIn.prepareSecondFactor({
strategy: 'email_code',
strategy: "email_code",
emailAddressId: emailCodeFactor.emailAddressId,
})
setShowEmailCode(true)
});
setShowEmailCode(true);
}
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signInAttempt, null, 2))
console.error(JSON.stringify(signInAttempt, null, 2));
}
} catch (err) {
// See https://clerk.com/docs/guides/development/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
console.error(JSON.stringify(err, null, 2));
}
}, [isLoaded, emailAddress, password])
}, [isLoaded, emailAddress, password]);

// Handle the submission of the email verification code
const onVerifyPress = React.useCallback(async () => {
if (!isLoaded) return
if (!isLoaded) return;

try {
const signInAttempt = await signIn.attemptSecondFactor({
strategy: 'email_code',
strategy: "email_code",
code,
})
});

if (signInAttempt.status === 'complete') {
if (signInAttempt.status === "complete") {
await setActive({
session: signInAttempt.createdSessionId,
navigate: async ({ session }) => {
if (session?.currentTask) {
console.log(session?.currentTask)
return
console.log(session?.currentTask);
return;
}

router.replace('/')
router.replace("/");
},
})
});
} else {
console.error(JSON.stringify(signInAttempt, null, 2))
console.error(JSON.stringify(signInAttempt, null, 2));
}
} catch (err) {
console.error(JSON.stringify(err, null, 2))
console.error(JSON.stringify(err, null, 2));
}
}, [isLoaded, code])
}, [isLoaded, code]);

// Display email code verification form
if (showEmailCode) {
Expand All @@ -112,12 +116,31 @@ export default function Page() {
/>
<Button title="Verify" onPress={onVerifyPress} />
</View>
)
);
}

return (
<View>
<Text>Sign in</Text>
<View style={styles.container}>
<Text style={styles.title}>Sign in</Text>

{/*
OPTIONAL: Enable native Sign in with Apple (iOS only)

1. Uncomment the import at the top: import AppleSignInButton from '../components/AppleSignInButton'
2. Uncomment the <AppleSignInButton /> component below
3. Follow the setup guide: https://clerk.com/docs/guides/configure/auth-strategies/sign-in-with-apple
*/}
{/* <AppleSignInButton /> */}

{/*
OPTIONAL: Enable native Sign in with Google (iOS and Android)

1. Uncomment the import at the top: import GoogleSSOButton from '../components/GoogleSSOButton'
2. Uncomment the <GoogleSSOButton /> component below
3. Follow the setup guide: https://clerk.com/docs/guides/configure/auth-strategies/sign-in-with-google
*/}
<GoogleSignInButton />

<TextInput
autoCapitalize="none"
value={emailAddress}
Expand All @@ -133,12 +156,26 @@ export default function Page() {
onChangeText={(password) => setPassword(password)}
/>
<Button title="Sign in" onPress={onSignInPress} />
<View style={{ flexDirection: 'row', gap: 4 }}>
<View style={{ flexDirection: "row", gap: 4 }}>
<Text>Don't have an account?</Text>
<Link href="/sign-up">
<Text>Sign up</Text>
</Link>
</View>
</View>
)
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
justifyContent: "center",
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 20,
textAlign: "center",
},
});
Loading