Skip to main content

Use Supabase Auth with React Native

Quickstart

  1. Create a new Supabase project

    Launch a new project in the Supabase Dashboard.

    Your new database has a table for storing your users. You can see that this table is currently empty by running some SQL in the SQL Editor.

     select * from auth.users;
  2. Create a React app

    Create a React app using the create-expo-app command.

    npx create-expo-app -t expo-template-blank-typescript my-app
  3. Install the Supabase client library

    Install supabase-js and the required dependencies.

    cd my-app && npx expo install @supabase/supabase-js @react-native-async-storage/async-storage @rneui/themed react-native-url-polyfill
  4. Set up your sign-in component

    Create a helper file lib/supabase.ts that exports a Supabase client using your Project URL and key.

    Rename .env.example to .env and populate with your Supabase connection variables:

    Project URL: https://<project-ref>.supabase.co (find yours in the dashboard)

    Publishable key: sb_publishable_... (find yours in the dashboard)

    import { AppState, Platform } from 'react-native'
    import 'react-native-url-polyfill/auto'
    import AsyncStorage from '@react-native-async-storage/async-storage'
    import { createClient } from '@supabase/supabase-js'
    
    const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!
    const supabasePublishableKey = process.env.EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
    
    export const supabase = createClient(supabaseUrl, supabasePublishableKey, {
      auth: {
        ...(Platform.OS !== 'web' ? { storage: AsyncStorage } : {}),
        autoRefreshToken: true,
        persistSession: true,
        detectSessionInUrl: false,
      },
    })
    
    // Tells Supabase Auth to continuously refresh the session automatically
    // if the app is in the foreground. When this is added, you will continue
    // to receive `onAuthStateChange` events with the `TOKEN_REFRESHED` or
    // `SIGNED_OUT` event if the user's session is terminated. This should
    // only be registered once.
    if (Platform.OS !== 'web') {
      AppState.addEventListener('change', (state) => {
        if (state === 'active') {
          supabase.auth.startAutoRefresh()
        } else {
          supabase.auth.stopAutoRefresh()
        }
      })
    }

    Get API details

    To interact with data in database tables, you use the client libraries that wrap the auto-generated Data API endpoints, authenticating using the Project URL and key from the project Connect dialog.

    Project URL: https://<project-ref>.supabase.co (find yours in the dashboard)

    Publishable key: sb_publishable_... (find yours in the dashboard)

    Note

    See API keys for a full explanation of all key types, their uses, and where to find them.

  5. Create a sign-in component

    Create a React Native component to manage sign-ins and sign-ups. The app later uses the getClaims method in App.tsx to validate the local JWT before showing the signed-in user.

    import React, { useState } from 'react'
    import { Alert, StyleSheet, View, Text, TextInput, TouchableOpacity } from 'react-native'
    import { supabase } from '../lib/supabase'
    
    export default function Auth() {
      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(error.message)
        setLoading(false)
      }
    
      async function signUpWithEmail() {
        setLoading(true)
        const {
          data: { session },
          error,
        } = await supabase.auth.signUp({
          email: email,
          password: password,
        })
    
        if (error) Alert.alert(error.message)
        if (!session) Alert.alert('Please check your inbox for email verification!')
        setLoading(false)
      }
    
      return (
        <View style={styles.container}>
          <View style={[styles.verticallySpaced, styles.mt20]}>
            <Text style={styles.label}>Email</Text>
            <TextInput
              onChangeText={(text) => setEmail(text)}
              value={email}
              placeholder="email@address.com"
              autoCapitalize="none"
              style={styles.input}
            />
          </View>
          <View style={styles.verticallySpaced}>
            <Text style={styles.label}>Password</Text>
            <TextInput
              onChangeText={(text) => setPassword(text)}
              value={password}
              secureTextEntry={true}
              placeholder="Password"
              autoCapitalize="none"
              style={styles.input}
            />
          </View>
          <View style={[styles.verticallySpaced, styles.mt20]}>
            <TouchableOpacity
              style={[styles.button, loading && styles.buttonDisabled]}
              onPress={() => signInWithEmail()}
              disabled={loading}
            >
              <Text style={styles.buttonText}>Sign in</Text>
            </TouchableOpacity>
          </View>
          <View style={styles.verticallySpaced}>
            <TouchableOpacity
              style={[styles.button, loading && styles.buttonDisabled]}
              onPress={() => signUpWithEmail()}
              disabled={loading}
            >
              <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,
      },
      label: {
        fontSize: 16,
        fontWeight: '600',
        color: '#86939e',
        marginBottom: 6,
      },
      input: {
        borderWidth: 1,
        borderColor: '#86939e',
        borderRadius: 4,
        padding: 12,
        fontSize: 16,
      },
      button: {
        backgroundColor: '#2089dc',
        borderRadius: 4,
        padding: 12,
        alignItems: 'center',
      },
      buttonDisabled: {
        opacity: 0.5,
      },
      buttonText: {
        color: '#fff',
        fontSize: 16,
        fontWeight: '600',
      },
    })
  6. Add the Auth component to your app

    Add the Auth component to your App.tsx file. If the user is signed in, print the user id to the screen.

    import 'react-native-url-polyfill/auto'
    import { useState, useEffect } from 'react'
    import { supabase } from './lib/supabase'
    import Auth from './components/Auth'
    import { View, Text } from 'react-native'
    import { JwtPayload } from '@supabase/supabase-js'
    
    export default function App() {
      const [claims, setClaims] = useState<JwtPayload | null>(null)
    
      useEffect(() => {
        supabase.auth.getClaims().then(({ data: { claims } }) => {
          setClaims(claims)
        })
    
        supabase.auth.onAuthStateChange(() => {
          supabase.auth.getClaims().then(({ data: { claims } }) => {
            setClaims(claims)
          })
        })
      }, [])
    
      return (
        <View>
          <Auth />
          {claims && <Text>{claims.sub}</Text>}
        </View>
      )
    }
  7. Start the app

    Start the app, and follow the instructions in the terminal.

    npm start