├── .gitignore
├── App.tsx
├── README.md
├── app.json
├── assets
├── adaptive-icon.png
├── favicon.png
├── fonts
│ └── SpaceMono-Regular.ttf
├── icon.png
├── images
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ └── splash.png
└── splash.png
├── babel.config.js
├── cache.ts
├── components
├── OAuth.tsx
└── Styles.ts
├── docs
├── clerk-logo-dark.png
├── clerk-logo-light.png
├── sign-in-oauth-demo.gif
├── sign-up-otp-demo.gif
└── whitelist-redirect-urls-for-mobile-oauth.png
├── hooks
├── useCachedResources.ts
└── useWarmUpBrowser.ts
├── index.js
├── logger.ts
├── navigation
├── LinkingConfiguration.ts
└── index.tsx
├── package-lock.json
├── package.json
├── screens
├── MyProfileScreen.tsx
├── SignInScreen.tsx
├── SignUpScreen.tsx
└── VerifyCodeScreen.tsx
├── tsconfig.json
└── types.tsx
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
16 | # @generated expo-cli sync-e7dcf75f4e856f7b6f3239b3f3a7dd614ee755a8
17 | # The following patterns were generated by expo-cli
18 |
19 | # OSX
20 | #
21 | .DS_Store
22 |
23 | # Xcode
24 | #
25 | build/
26 | *.pbxuser
27 | !default.pbxuser
28 | *.mode1v3
29 | !default.mode1v3
30 | *.mode2v3
31 | !default.mode2v3
32 | *.perspectivev3
33 | !default.perspectivev3
34 | xcuserdata
35 | *.xccheckout
36 | *.moved-aside
37 | DerivedData
38 | *.hmap
39 | *.ipa
40 | *.xcuserstate
41 | project.xcworkspace
42 |
43 | # Android/IntelliJ
44 | #
45 | build/
46 | .idea
47 | .gradle
48 | local.properties
49 | *.iml
50 | *.hprof
51 |
52 | # node.js
53 | #
54 | node_modules/
55 | npm-debug.log
56 | yarn-error.log
57 |
58 | # BUCK
59 | buck-out/
60 | \.buckd/
61 | *.keystore
62 | !debug.keystore
63 |
64 | # Bundle artifacts
65 | *.jsbundle
66 |
67 | # CocoaPods
68 | /ios/Pods/
69 |
70 | # Expo
71 | .expo/
72 | web-build/
73 | dist/
74 |
75 | # @end expo-cli
76 | yarn.lock
77 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StatusBar } from "expo-status-bar";
3 | import { SafeAreaProvider } from "react-native-safe-area-context";
4 | import useCachedResources from "./hooks/useCachedResources";
5 | import Navigation from "./navigation";
6 | import { ClerkProvider } from "@clerk/clerk-expo";
7 | import { tokenCache } from "./cache";
8 | import * as SplashScreen from "expo-splash-screen";
9 |
10 | // Your publishable Key goes here
11 | const publishableKey = "pk_XXXXXXXXXXXXXXXXXXX";
12 |
13 | export default function App() {
14 | const isLoadingComplete = useCachedResources();
15 |
16 | React.useEffect(() => {
17 | SplashScreen.preventAutoHideAsync();
18 | }, []);
19 |
20 | if (!isLoadingComplete) {
21 | return null;
22 | } else {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Clerk Expo Starter
12 |
13 | > If you want to use the new file-system based router that was introduced with [Expo Router V2](https://blog.expo.dev/introducing-expo-router-v2-3850fd5c3ca1), you can check out [this branch](https://github.com/clerkinc/clerk-expo-starter/tree/router-v2)
14 |
15 | This starter project shows how to use [Clerk](https://www.clerk.dev/?utm_source=github&utm_medium=starter_repos&utm_campaign=expo_starter) with [Expo](https://expo.dev/) to authenticate users in your [React Native](https://reactnative.dev/) application. When users sign up or sign in, Clerk handles the state of the authentication and switches between public or [protected routes](https://reactnavigation.org/docs/auth-flow).
16 |
17 | [](https://discord.com/invite/b5rXHjAg7A)
18 | [](https://docs.clerk.dev)
19 | [](https://twitter.com/intent/follow?screen_name=ClerkDev)
20 |
21 | ---
22 |
23 | **Clerk is Hiring!**
24 |
25 | Would you like to work on Open Source software and help maintain this repository? [Apply today!](https://apply.workable.com/clerk-dev/)
26 |
27 | ---
28 |
29 | ## Demo
30 |
31 | ### Sign up using One-time passcodes (OTP) & Sign in using OAuth
32 |
33 |
34 |
35 |
36 |
37 |
38 | ## Getting Started
39 |
40 | ### Prerequisites
41 |
42 | - React v16+
43 | - Node.js v14+
44 |
45 | ### Setup
46 |
47 | To run the example locally you need to:
48 |
49 | 1. Sign up at [Clerk.dev](https://www.clerk.dev/?utm_source=github&utm_medium=starter_repos&utm_campaign=expo_starter).
50 | 2. Go to your [Clerk dashboard](https://dashboard.clerk.dev/?utm_source=github&utm_medium=starter_repos&utm_campaign=expo_starter) and create an application.
51 | 3. Set your publishableKey in `App.tsx` or if you are using a legacy key frontendApi
52 | 4. `npm i` to install the required dependencies.
53 | 5. `npm run start` to launch the Expo development server.
54 |
55 | ### Sign up & Sign in configuration
56 |
57 | For the sign up flow to work as demonstrated, you need to log into your [Clerk Dashboard](https://dashboard.clerk.dev/?utm_source=github&utm_medium=starter_repos&utm_campaign=expo_starter) and make sure the following settings have been configured in **User & Authentication** and **Social login** sections:
58 |
59 | #### For development instances
60 |
61 | 1. In Contact information section enable **Email Address** and pick **Email verification code** method in the modal.
62 | 2. In Authentication factors section enable **Password** and **Email verification code**.
63 | 3. In Personal information, enable **Name** to use first and last names during sign up
64 | 4. In Social Login, enable **Google** Oauth provider.
65 |
66 | #### For production instances
67 |
68 | 1. Follow all the steps above
69 | 2. Whitelist your custom redirect URL via [Clerk Dashboard](https://dashboard.clerk.dev/last-active?path=/user-authentication/social-login) for maximum security during native OAuth flows.
70 | 
71 |
72 | ## Learn more
73 |
74 | To learn more about Clerk and Expo, take a look at our
75 | [official documentation](https://reference.clerk.dev/reference/clerk-expo?utm_source=github&utm_medium=starter_repos&utm_campaign=expo_starter).
76 |
77 | ## Support
78 |
79 | You can get in touch with us in any of the following ways:
80 |
81 | - Join our official community [Discord server](https://discord.com/invite/b5rXHjAg7A)
82 | - Open a [GitHub support issue](https://github.com/clerkinc/javascript/issues/new?assignees=&labels=question&template=ask_a_question.md&title=Support%3A+)
83 | - Contact options listed on [our Support page](https://clerk.dev/support?utm_source=github&utm_medium=clerk_expo_starter)
84 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "clerk-expo-starter",
4 | "slug": "clerk-expo-starter",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/images/icon.png",
8 | "scheme": "myapp",
9 | "userInterfaceStyle": "automatic",
10 | "splash": {
11 | "image": "./assets/images/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#ffffff"
14 | },
15 | "updates": {
16 | "fallbackToCacheTimeout": 0
17 | },
18 | "assetBundlePatterns": ["**/*"],
19 | "ios": {
20 | "supportsTablet": true,
21 | "associatedDomains": ["applinks:clerk.helping.lemming-64.lclstage.dev"],
22 | "bundleIdentifier": "dev.clerk.clerkexpostarter"
23 | },
24 | "android": {
25 | "adaptiveIcon": {
26 | "foregroundImage": "./assets/images/adaptive-icon.png",
27 | "backgroundColor": "#ffffff"
28 | },
29 | "intentFilters": [
30 | {
31 | "action": "VIEW",
32 | "autoVerify": true,
33 | "data": [
34 | {
35 | "scheme": "https",
36 | "host": "clerk.helping.lemming-64.lclstage.dev",
37 | "pathPrefix": "/v1/oauth-native-callback"
38 | }
39 | ],
40 | "category": ["BROWSABLE", "DEFAULT"]
41 | }
42 | ],
43 | "package": "com.sokratisv.clerkexpostarter"
44 | },
45 | "web": {
46 | "favicon": "./assets/images/favicon.png"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/favicon.png
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/icon.png
--------------------------------------------------------------------------------
/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/images/favicon.png
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ["babel-preset-expo"],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/cache.ts:
--------------------------------------------------------------------------------
1 | import * as SecureStore from "expo-secure-store";
2 | import { Platform } from "react-native";
3 | import { TokenCache } from "@clerk/clerk-expo/dist/cache";
4 |
5 | const createTokenCache = (): TokenCache => {
6 | return {
7 | getToken: (key) => {
8 | return SecureStore.getItemAsync(key);
9 | },
10 | saveToken: (key, token) => {
11 | return SecureStore.setItemAsync(key, token);
12 | },
13 | };
14 | };
15 |
16 | // SecureStore is not supported on the web
17 | // https://github.com/expo/expo/issues/7744#issuecomment-611093485
18 | export const tokenCache = Platform.OS !== "web" ? createTokenCache() : undefined;
19 |
--------------------------------------------------------------------------------
/components/OAuth.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as WebBrowser from "expo-web-browser";
3 | import { Text, TouchableOpacity } from "react-native";
4 | import { useOAuth } from "@clerk/clerk-expo";
5 | import { styles } from "./Styles";
6 | import { useWamUpBrowser } from "../hooks/useWarmUpBrowser";
7 |
8 | WebBrowser.maybeCompleteAuthSession();
9 |
10 | export function OAuthButtons() {
11 | // Warm up the android browser to improve UX
12 | // https://docs.expo.dev/guides/authentication/#improving-user-experience
13 | useWamUpBrowser();
14 |
15 | const { startOAuthFlow } = useOAuth({ strategy: "oauth_google" });
16 |
17 | const onPress = React.useCallback(async () => {
18 | try {
19 | const { createdSessionId, signIn, signUp, setActive } =
20 | await startOAuthFlow();
21 |
22 | if (createdSessionId) {
23 | setActive({ session: createdSessionId });
24 | } else {
25 | // Use signIn or signUp for next steps such as MFA
26 | }
27 | } catch (err) {
28 | console.error("OAuth error", err);
29 | }
30 | }, []);
31 |
32 | return (
33 |
37 | Continue with Google
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/components/Styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | export const styles = StyleSheet.create({
4 | container: {
5 | flex: 1,
6 | backgroundColor: "#fff",
7 | alignItems: "center",
8 | justifyContent: "flex-start",
9 | paddingTop: 20,
10 | },
11 |
12 | inputView: {
13 | borderRadius: 5,
14 | width: "90%",
15 | height: 45,
16 | marginBottom: 20,
17 | borderColor: "#000",
18 | borderStyle: "solid",
19 | borderWidth: 1,
20 | },
21 |
22 | textInput: {
23 | height: 50,
24 | flex: 1,
25 | padding: 10,
26 | marginLeft: 20,
27 | },
28 |
29 | primaryButton: {
30 | width: "90%",
31 | borderRadius: 5,
32 | height: 50,
33 | alignItems: "center",
34 | justifyContent: "center",
35 | marginTop: 40,
36 | backgroundColor: "#000",
37 | color: "#ffffff",
38 | },
39 |
40 | primaryButtonText: {
41 | color: "#ffffff",
42 | fontWeight: "bold",
43 | },
44 |
45 | titleText: {
46 | color: "#000",
47 | fontSize: 20,
48 | fontWeight: "bold",
49 | marginBottom: 20,
50 | },
51 |
52 | footer: {
53 | color: "#000",
54 | marginTop: 20,
55 | flex: 1,
56 | backgroundColor: "#fff",
57 | alignItems: "center",
58 | justifyContent: "flex-start",
59 | },
60 |
61 | secondaryButton: {
62 | marginTop: 15,
63 | borderRadius: 5,
64 | padding: 10,
65 | alignItems: "center",
66 | justifyContent: "center",
67 | backgroundColor: "#ffffff",
68 | borderWidth: 1,
69 | borderColor: "#000",
70 | },
71 |
72 | secondaryButtonText: {
73 | color: "#000",
74 | fontWeight: "bold",
75 | },
76 |
77 | oauthView: {
78 | width: "90%",
79 | borderBottomWidth: 1,
80 | borderBottomColor: "#000",
81 | marginBottom: 20,
82 | },
83 | });
84 |
--------------------------------------------------------------------------------
/docs/clerk-logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/docs/clerk-logo-dark.png
--------------------------------------------------------------------------------
/docs/clerk-logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/docs/clerk-logo-light.png
--------------------------------------------------------------------------------
/docs/sign-in-oauth-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/docs/sign-in-oauth-demo.gif
--------------------------------------------------------------------------------
/docs/sign-up-otp-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/docs/sign-up-otp-demo.gif
--------------------------------------------------------------------------------
/docs/whitelist-redirect-urls-for-mobile-oauth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-expo-starter/7f7dd9ac44d0f11ee48c51b8fc18580760d742f6/docs/whitelist-redirect-urls-for-mobile-oauth.png
--------------------------------------------------------------------------------
/hooks/useCachedResources.ts:
--------------------------------------------------------------------------------
1 | import { FontAwesome } from "@expo/vector-icons";
2 | import * as Font from "expo-font";
3 | import * as React from "react";
4 |
5 | export default function useCachedResources() {
6 | const [isLoadingComplete, setLoadingComplete] = React.useState(false);
7 |
8 | // Load any resources or data that we need prior to rendering the app
9 | React.useEffect(() => {
10 | async function loadResourcesAndDataAsync() {
11 | try {
12 | // Load fonts
13 | await Font.loadAsync({
14 | ...FontAwesome.font,
15 | "space-mono": require("../assets/fonts/SpaceMono-Regular.ttf"),
16 | });
17 | } catch (e) {
18 | // We might want to provide this error information to an error reporting service
19 | console.warn(e);
20 | } finally {
21 | setLoadingComplete(true);
22 | }
23 | }
24 |
25 | loadResourcesAndDataAsync();
26 | }, []);
27 |
28 | return isLoadingComplete;
29 | }
30 |
--------------------------------------------------------------------------------
/hooks/useWarmUpBrowser.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as WebBrowser from "expo-web-browser";
3 |
4 | export const useWamUpBrowser = () => {
5 | React.useEffect(() => {
6 | void WebBrowser.warmUpAsync();
7 | return () => {
8 | void WebBrowser.coolDownAsync();
9 | };
10 | }, []);
11 | };
12 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import { registerRootComponent } from "expo";
2 |
3 | import App from "./App";
4 |
5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
6 | // It also ensures that whether you load the app in Expo Go or in a native build,
7 | // the environment is set up appropriately
8 | registerRootComponent(App);
9 |
--------------------------------------------------------------------------------
/logger.ts:
--------------------------------------------------------------------------------
1 | export function log(msg = "") {
2 | return console.log(new Date() + ":> " + msg);
3 | }
4 |
--------------------------------------------------------------------------------
/navigation/LinkingConfiguration.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Learn more about deep linking with React Navigation
3 | * https://reactnavigation.org/docs/deep-linking
4 | * https://reactnavigation.org/docs/configuring-links
5 | */
6 |
7 | import { LinkingOptions } from "@react-navigation/native";
8 | import * as Linking from "expo-linking";
9 |
10 | import { RootStackParamList } from "../types";
11 |
12 | const linking: LinkingOptions = {
13 | prefixes: [Linking.makeUrl("/")],
14 | config: {
15 | screens: {
16 | Root: {
17 | screens: {
18 | SignUp: {
19 | screens: {
20 | SignUpScreen: "SignUp",
21 | },
22 | },
23 | SignIn: {
24 | screens: {
25 | SignInScreen: "SignIn",
26 | },
27 | },
28 | },
29 | },
30 | },
31 | },
32 | };
33 |
34 | export default linking;
35 |
--------------------------------------------------------------------------------
/navigation/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * If you are not familiar with React Navigation, refer to the "Fundamentals" guide:
3 | * https://reactnavigation.org/docs/getting-started
4 | *
5 | */
6 | import { NavigationContainer } from "@react-navigation/native";
7 | import { createNativeStackNavigator } from "@react-navigation/native-stack";
8 | import * as React from "react";
9 |
10 | import SignUpScreen from "../screens/SignUpScreen";
11 | import SignInScreen from "../screens/SignInScreen";
12 | import VerifyCodeScreen from "../screens/VerifyCodeScreen";
13 | import MyProfileScreen from "../screens/MyProfileScreen";
14 | import { RootStackParamList } from "../types";
15 | import LinkingConfiguration from "./LinkingConfiguration";
16 | import { ClerkLoaded, useUser } from "@clerk/clerk-expo";
17 | import * as SplashScreen from "expo-splash-screen";
18 |
19 | export default function Navigation() {
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | const Stack = createNativeStackNavigator();
28 |
29 | /**
30 | * Read more about the protected routes pattern in React Native
31 | *
32 | * https://reactnavigation.org/docs/auth-flow
33 | */
34 | const RootNavigator = () => {
35 | const { isSignedIn, isLoaded } = useUser();
36 |
37 | React.useEffect(() => {
38 | if (isLoaded) {
39 | SplashScreen.hideAsync();
40 | }
41 | }, [isLoaded]);
42 |
43 | return (
44 |
45 |
46 | {isSignedIn ? (
47 |
52 | ) : (
53 | <>
54 |
59 |
64 |
69 | >
70 | )}
71 |
72 |
73 | );
74 | };
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clerk-expo-starter",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "start": "expo start",
6 | "android": "expo run:android",
7 | "ios": "expo run:ios",
8 | "web": "expo start --web",
9 | "eject": "expo eject",
10 | "test": "jest --watchAll"
11 | },
12 | "jest": {
13 | "preset": "jest-expo"
14 | },
15 | "dependencies": {
16 | "@clerk/clerk-expo": "^0.18.12",
17 | "@expo/vector-icons": "13.0.0",
18 | "@react-navigation/bottom-tabs": "6.5.8",
19 | "@react-navigation/native": "6.1.7",
20 | "@react-navigation/native-stack": "6.9.13",
21 | "@types/expo": "33.0.1",
22 | "expo": "~49.0.0",
23 | "expo-asset": "8.10.1",
24 | "expo-auth-session": "5.0.2",
25 | "expo-constants": "14.4.2",
26 | "expo-font": "11.4.0",
27 | "expo-linking": "5.0.2",
28 | "expo-random": "13.2.0",
29 | "expo-secure-store": "12.3.1",
30 | "expo-splash-screen": "0.20.4",
31 | "expo-status-bar": "1.6.0",
32 | "expo-web-browser": "12.3.2",
33 | "react": "18.2.0",
34 | "react-dom": "18.2.0",
35 | "react-native": "0.72.1",
36 | "react-native-safe-area-context": "4.6.3",
37 | "react-native-screens": "3.22.1",
38 | "react-native-url-polyfill": "1.3.0",
39 | "react-native-web": "0.19.6",
40 | "uri-scheme": "1.1.0"
41 | },
42 | "devDependencies": {
43 | "@babel/core": "^7.22.8",
44 | "@types/react": "^18.2.14",
45 | "@types/react-native": "^0.72.2",
46 | "jest-expo": "^49.0.0",
47 | "prettier": "^2.8.4",
48 | "typescript": "^5.1.6"
49 | },
50 | "private": true
51 | }
52 |
--------------------------------------------------------------------------------
/screens/MyProfileScreen.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
3 | import { SignedIn, SignedOut, useAuth, useUser } from "@clerk/clerk-expo";
4 | import { log } from "../logger";
5 | import { RootStackScreenProps } from "../types";
6 |
7 | export default function SafeMyProfileScreen(
8 | props: RootStackScreenProps<"MyProfile">
9 | ) {
10 | return (
11 | <>
12 |
13 |
14 |
15 |
16 |
17 | Unauthorized
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | function MyProfileScreen({ navigation }: RootStackScreenProps<"MyProfile">) {
25 | const { getToken, signOut } = useAuth();
26 | const { user } = useUser();
27 |
28 | const [sessionToken, setSessionToken] = React.useState("");
29 |
30 | const onSignOutPress = async () => {
31 | try {
32 | await signOut();
33 | } catch (err: any) {
34 | log("Error:> " + err?.status || "");
35 | log("Error:> " + err?.errors ? JSON.stringify(err.errors) : err);
36 | }
37 | };
38 |
39 | React.useEffect(() => {
40 | const scheduler = setInterval(async () => {
41 | const token = await getToken();
42 | setSessionToken(token as string);
43 | }, 1000);
44 |
45 | return () => clearInterval(scheduler);
46 | }, []);
47 |
48 | return (
49 |
50 | Hello {user?.firstName}
51 |
52 | Sign out
53 |
54 | {sessionToken}
55 |
56 | );
57 | }
58 |
59 | const styles = StyleSheet.create({
60 | container: {
61 | flex: 1,
62 | alignItems: "center",
63 | justifyContent: "center",
64 | padding: 20,
65 | },
66 | title: {
67 | fontSize: 20,
68 | fontWeight: "bold",
69 | },
70 | link: {
71 | marginTop: 15,
72 | paddingVertical: 15,
73 | },
74 | linkText: {
75 | fontSize: 14,
76 | color: "#2e78b7",
77 | },
78 | token: {
79 | marginTop: 15,
80 | paddingVertical: 15,
81 | fontSize: 15,
82 | },
83 | });
84 |
--------------------------------------------------------------------------------
/screens/SignInScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Text, TextInput, TouchableOpacity, View } from "react-native";
3 | import { useSignIn } from "@clerk/clerk-expo";
4 | import { log } from "../logger";
5 | import { RootStackScreenProps } from "../types";
6 | import { OAuthButtons } from "../components/OAuth";
7 | import { styles } from "../components/Styles";
8 |
9 | export default function SignInScreen({
10 | navigation,
11 | }: RootStackScreenProps<"SignIn">) {
12 | const { signIn, setSession, isLoaded } = useSignIn();
13 |
14 | const [emailAddress, setEmailAddress] = React.useState("");
15 | const [password, setPassword] = React.useState("");
16 |
17 | const onSignInPress = async () => {
18 | if (!isLoaded) {
19 | return;
20 | }
21 |
22 | try {
23 | const completeSignIn = await signIn.create({
24 | identifier: emailAddress,
25 | password,
26 | });
27 |
28 | await setSession(completeSignIn.createdSessionId);
29 | } catch (err: any) {
30 | log("Error:> " + err?.status || "");
31 | log("Error:> " + err?.errors ? JSON.stringify(err.errors) : err);
32 | }
33 | };
34 |
35 | const onSignUpPress = () => navigation.replace("SignUp");
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 | setEmailAddress(emailAddress)}
51 | />
52 |
53 |
54 |
55 | setPassword(password)}
62 | />
63 |
64 |
65 |
66 | Sign in
67 |
68 |
69 |
70 | Have an account?
71 |
72 |
76 | Sign up
77 |
78 |
79 |
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/screens/SignUpScreen.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Text, TextInput, TouchableOpacity, View } from "react-native";
3 | import { useSignUp } from "@clerk/clerk-expo";
4 | import { log } from "../logger";
5 | import { RootStackScreenProps } from "../types";
6 | import { styles } from "../components/Styles";
7 | import { OAuthButtons } from "../components/OAuth";
8 |
9 | export default function SignUpScreen({
10 | navigation,
11 | }: RootStackScreenProps<"SignUp">) {
12 | const { isLoaded, signUp } = useSignUp();
13 | const [firstName, setFirstName] = React.useState("");
14 | const [lastName, setLastName] = React.useState("");
15 | const [emailAddress, setEmailAddress] = React.useState("");
16 | const [password, setPassword] = React.useState("");
17 |
18 | const onSignUpPress = async () => {
19 | if (!isLoaded) {
20 | return;
21 | }
22 |
23 | try {
24 | await signUp.create({
25 | firstName,
26 | lastName,
27 | emailAddress,
28 | password,
29 | });
30 |
31 | // https://docs.clerk.dev/popular-guides/passwordless-authentication
32 | await signUp.prepareEmailAddressVerification({ strategy: "email_code" });
33 |
34 | navigation.navigate("VerifyCode");
35 | } catch (err: any) {
36 | log("Error:> " + err?.status || "");
37 | log("Error:> " + err?.errors ? JSON.stringify(err.errors) : err);
38 | }
39 | };
40 |
41 | const onSignInPress = () => navigation.replace("SignIn");
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 | setFirstName(firstName)}
56 | />
57 |
58 |
59 |
60 | setLastName(lastName)}
66 | />
67 |
68 |
69 |
70 | setEmailAddress(email)}
77 | />
78 |
79 |
80 |
81 | setPassword(password)}
88 | />
89 |
90 |
91 |
92 | Sign up
93 |
94 |
95 |
96 | Have an account?
97 |
98 |
102 | Sign in
103 |
104 |
105 |
106 | );
107 | }
108 |
--------------------------------------------------------------------------------
/screens/VerifyCodeScreen.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Text, TextInput, TouchableOpacity, View } from "react-native";
3 | import { useClerk, useSignUp } from "@clerk/clerk-expo";
4 | import { RootStackScreenProps } from "../types";
5 | import { styles } from "../components/Styles";
6 | import { log } from "../logger";
7 |
8 | export default function SignUpScreen({
9 | navigation,
10 | }: RootStackScreenProps<"VerifyCode">) {
11 | const { isLoaded, signUp, setSession } = useSignUp();
12 |
13 | const [code, setCode] = React.useState("");
14 |
15 | const onPress = async () => {
16 | if (!isLoaded) {
17 | return;
18 | }
19 |
20 | try {
21 | const completeSignUp = await signUp.attemptEmailAddressVerification({
22 | code,
23 | });
24 |
25 | await setSession(completeSignUp.createdSessionId);
26 | } catch (err: any) {
27 | log("Error:> " + err?.status || "");
28 | log("Error:> " + err?.errors ? JSON.stringify(err.errors) : err);
29 | }
30 | };
31 |
32 | return (
33 |
34 |
35 | setCode(code)}
41 | />
42 |
43 |
44 | Verify Email
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/types.tsx:
--------------------------------------------------------------------------------
1 | import { NativeStackScreenProps } from "@react-navigation/native-stack";
2 |
3 | declare global {
4 | namespace ReactNavigation {
5 | interface RootParamList extends RootStackParamList {}
6 | }
7 | }
8 |
9 | export type RootStackParamList = {
10 | Root: undefined;
11 | SignUp: undefined;
12 | SignIn: undefined;
13 | MyProfile: undefined;
14 | VerifyCode: undefined;
15 | };
16 |
17 | export type RootStackScreenProps =
18 | NativeStackScreenProps;
19 |
--------------------------------------------------------------------------------