Expo Router Supabase Authentication Flow and Tab Navigation
Tags: supabase, reactnative, react, expo
Published: 2023-08-25 15:53:32

## Overview
This blog post is to complement the [Expo Router Tab and Supabase Authentication Video]() I create to help you learn to use Supabase with Expo Router by building a simple authentication flow based on a Tab-based application template.

In this tutorial, we’ll be using Expo, Supabase, and Expo Router.  I stuck to the basics like the other [related videos in this series](https://www.youtube.com/playlist?list=PL2PY2-9rsgl2DikpQG-GgO7TBgRtdB6NT) so you can build your own idea based on this video.

{% youtube 6IzrH-1M0uE %}

- We have leveraged this blog post from Supabase when creating this video.[Build a User Management App with Expo](https://supabase.com/docs/guides/getting-started/tutorials/with-expo#initialize-a-react-native-app)

- I have only included the new files that have been added to the project and existing files that have changed. You can see the source code for the original Expo Router Tabs Project [here in the video](https://www.youtube.com/watch?v=ZCj85YJZF04&t=0s) or [here in this blog post](https://dev.to/aaronksaunders/expo-router-tab-navigation-from-the-docs-3c38)

### Install required packages
```
npm install @supabase/supabase-js
npm install react-native-elements @react-native-async-storage/async-storage react-native-url-polyfill
npx expo install expo-secure-store
```

### Create a Supabase client library

Normally I used the [AsyncStorage](https://react-native-async-storage.github.io/async-storage/) library from the react-native community, but since we are working with Expo and this is the library used in the blog post, I figured I would give it a try.

The Supabase credentials come from an `.env` file that you will need to create. 
```jsx
EXPO_PUBLIC_SUPABASE_URL=
EXPO_PUBLIC_SUPABASE_ANON_KEY=
```

The original source is from a Supabase project that is required for the application, that topic is covered here in the Supabase documentation. [Create New Project In Supabase Dashboard.](https://supabase.com/dashboard/projects)

```jsx
// app/lib/supabase-client.js
import 'react-native-url-polyfill/auto'
import * as SecureStore from 'expo-secure-store'
import { createClient } from '@supabase/supabase-js'

const ExpoSecureStoreAdapter = {
  getItem: (key) => {
    return SecureStore.getItemAsync(key)
  },
  setItem: (key, value) => {
    SecureStore.setItemAsync(key, value)
  },
  removeItem: (key) => {
    SecureStore.deleteItemAsync(key)
  },
}

const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: ExpoSecureStoreAdapter,
    autoRefreshToken: true
  },
})
```

### Index Page 
Checking for authentication status using the supabase client we created above.

in the `useEffect` hook we check for a current session using `supabase.auth.getSession()` and redirect to a route if we have a user.

the `supabase.auth.onAuthStateChange()` is setup to allow the app to automatically redirect to appropriate route when the user's authentication state changes; meaning when you logout we dont need to directly tell the application to route to the login page, this code will do it for you.

```jsx
// app/index.js

import { router } from "expo-router";
import { useEffect } from "react";
import { supabase } from "./lib/supabase-client";

export default function IndexPage() {
  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      if (session) {
        router.replace("/(tabs)/home/");
      } else {
        console.log("no user");
      }
    });

    supabase.auth.onAuthStateChange((_event, session) => {
      if (session) {
        router.replace("/(tabs)/home/");
      } else {
        console.log("no user");
        router.replace("/(auth)/login");
      }
    });
  }, []);

}
```

## The (auth) Segment

### AuthLayout
We add this code for the _layout 

```jsx
// app/(auth)/_layout.js

import { Slot } from "expo-router";

export default function AuthLayout() {
  return <Slot />;
}
```

### Login / Create Account Page
We have created a simple page to create an account or login to the application. This code is straight from the Supabase documentation except for the styling.

```jsx
// app/(auth)/login.js

import React, { useState } from "react";
import { Alert, StyleSheet, TextInput, View, Button, Text } from "react-native";
import { supabase } from "../lib/supabase-client";
import { TouchableOpacity } from "react-native-gesture-handler";
import { Stack } from "expo-router";

export default function AuthPage() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);

  async function signInWithEmail() {
    setLoading(true);
    const { error } = await supabase.auth.signInWithPassword({
      email: email,
      password: password,
    });

    if (error) Alert.alert("Sign In Error", error.message);
    setLoading(false);
  }

  async function signUpWithEmail() {
    setLoading(true);
    const { error } = await supabase.auth.signUp({
      email: email,
      password: password,
    });

    if (error) Alert.alert("Sign Up Error", error.message);
    setLoading(false);
  }

  return (
    <View style={styles.container}>
      <Stack.Screen options={{ headerShown: true, title: "Supabase Expo Router App" }} />
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <TextInput
          style={styles.textInput}
          label="Email"
          onChangeText={(text) => setEmail(text)}
          value={email}
          placeholder="email@address.com"
          autoCapitalize={"none"}
        />
      </View>
      <View style={styles.verticallySpaced}>
        <TextInput
          style={styles.textInput}
          label="Password"
          onChangeText={(text) => setPassword(text)}
          value={password}
          secureTextEntry={true}
          placeholder="Password"
          autoCapitalize={"none"}
        />
      </View>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <TouchableOpacity
          disabled={loading}
          onPress={() => signInWithEmail()}
          style={styles.buttonContainer}
        >
          <Text style={styles.buttonText}>SIGN IN</Text>
        </TouchableOpacity>
      </View>
      <View style={styles.verticallySpaced}>
        <TouchableOpacity
          disabled={loading}
          onPress={() => signUpWithEmail()}
          style={styles.buttonContainer}
        >
          <Text style={styles.buttonText}>SIGN UP</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    marginTop: 40,
    padding: 12,
  },
  verticallySpaced: {
    paddingTop: 4,
    paddingBottom: 4,
    alignSelf: "stretch",
  },
  mt20: {
    marginTop: 20,
  },
  buttonContainer: {
    backgroundColor: "#000968",
    borderRadius: 10,
    paddingVertical: 10,
    paddingHorizontal: 12,
    margin: 8,
  },
  buttonText: {
    fontSize: 18,
    color: "#fff",
    fontWeight: "bold",
    alignSelf: "center",
    textTransform: "uppercase",
  },
  textInput: {
    borderColor: "#000968",
    borderRadius: 4,
    borderStyle: "solid",
    borderWidth: 1,
    padding: 12,
    margin: 8,
  },
});

```

### Settings Page
In the settings page we show the current authenticated user using the Supabase client and we also provide a logout button to log the user our and clear the session.

Notice there is no router redirect here because the authentication listener we created in the `IndexPage` will detect the change in state and redirect the application to the `LoginPage`

```jsx
// app/(settings)/index.js

import { Stack } from "expo-router";
import { SafeAreaView, Text, View, TouchableOpacity, StyleSheet } from "react-native";
import { supabase } from "../../lib/supabase-client";
import { useEffect, useState } from "react";

export default function SettingsPage() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    supabase.auth.getUser().then(({ data: { user } }) => {
      if (user) {
        setUser(user);
      } else {
        Alert.alert("Error Accessing User");
      }
    });
  }, []);

  const doLogout = async () => {
    const {error} = await supabase.auth.signOut();
    if (error) {
      Alert.alert("Error Signing Out User", error.message);
    }
  }

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <Stack.Screen options={{ headerShown: true, title: "Settings" }} />
      <View style={{ padding: 16 }}>
        <Text>{JSON.stringify(user, null, 2)}</Text>
        <TouchableOpacity onPress={doLogout} style={styles.buttonContainer}>
          <Text style={styles.buttonText}>LOGOUT</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
}


const styles = StyleSheet.create({
  container: {
    marginTop: 40,
    padding: 12,
  },
  verticallySpaced: {
    paddingTop: 4,
    paddingBottom: 4,
    alignSelf: "stretch",
  },
  mt20: {
    marginTop: 20,
  },
  buttonContainer: {
    backgroundColor: "#000968",
    borderRadius: 10,
    paddingVertical: 10,
    paddingHorizontal: 12,
    margin: 8,
  },
  buttonText: {
    fontSize: 18,
    color: "#fff",
    fontWeight: "bold",
    alignSelf: "center",
    textTransform: "uppercase",
  },
  textInput: {
    borderColor: "#000968",
    borderRadius: 4,
    borderStyle: "solid",
    borderWidth: 1,
    padding: 12,
    margin: 8,
  },
});

```

###Links
- Expo Router Tabs Video - https://youtu.be/ZCj85YJZF04
- Expo Router Doc - https://docs.expo.dev/routing/installation/#manual-installation
- Supabase Blog - https://supabase.com/docs/guides/getting-started/tutorials/with-expo

###Social Media 
- Twitter - https://twitter.com/aaronksaunders
- Facebook - https://www.facebook.com/ClearlyInnovative
- Instagram - https://www.instagram.com/aaronksaunders
- Dev.to - https://dev.to/aaronksaunders
- YouTube - https://www.youtube.com/@Aaronsaundersci