Skip to content

Getting started with React Native authentication

16 Nov 2023

·

13 minute read

Authentication is the process of verifying the identity of a user who is attempting to access a system, application, or online service. In this blog post, you will learn about React Native authentication, including native mobile specific login mechanisms like "Sign in with Apple" and "Google One Tap sign-in", as well as SMS & WhatsApp based authentication.

At the end of this blog post, you will have all the components needed to create the ideal authentication experience for your mobile app users.

Prerequisites#

This article assumes you are comfortable with writing a basic application in React Native. No knowledge of Supabase is required.

We will use the following tools

  • Expo - we used Expo SDK version 49.0.0 (React Native version 0.72)
  • Supabase - create your account here if you do not have one
  • IDE of your choosing

Note: We're using Expo as that's the recommended way of getting started with React Native. However, the fundamental approach here applies to bare React Native applications as well.

Set up supabase-js for React Native#

Using supabase-js is the most convenient way of leveraging the full power of the Supabase stack as it conveniently combines all the different services (database, auth, realtime, storage, edge functions) together.

Install supabase-js and dependencies#

After you have created your Expo project, you can install supabase-js and the required dependencies using the following command:


_10
npx expo install @supabase/supabase-js @react-native-async-storage/async-storage react-native-url-polyfill

Authentication storage#

By default, supabase-js uses the browser's localStorage mechanism to persist the user's session but can be extended with platform specific storage implementations. In React Native we can build native mobile and web applications with the same code base, so we need a storage implementation that works for all these platforms: react-native-async-storage.

We need to pass an instance of react-native-async-storage to supabase-js to make sure authentication works reliably across all react native platforms:

lib/supabase.ts

_16
import 'react-native-url-polyfill/auto'
_16
_16
import AsyncStorage from '@react-native-async-storage/async-storage'
_16
import { createClient } from '@supabase/supabase-js'
_16
_16
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
_16
const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
_16
_16
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
_16
auth: {
_16
storage: AsyncStorage,
_16
autoRefreshToken: true,
_16
persistSession: true,
_16
detectSessionInUrl: false,
_16
},
_16
})

You can find your URL and anon key in the API credentials section of the Supabase dashboard.

Encrypting the user session#

If you wish to encrypt the user's session information, you can use aes-js and store the encryption key in Expo SecureStore. The aes-js library is a reputable JavaScript-only implementation of the AES encryption algorithm in CTR mode. A new 256-bit encryption key is generated using the react-native-get-random-values library. This key is stored inside Expo's SecureStore, while the value is encrypted and placed inside AsyncStorage.

Please make sure that:

  • You keep the expo-secure-storage, aes-js and react-native-get-random-values libraries up-to-date.
  • Choose the correct SecureStoreOptions for your app's needs. E.g. SecureStore.WHEN_UNLOCKED regulates when the data can be accessed.
  • Carefully consider optimizations or other modifications to the above example, as those can lead to introducing subtle security vulnerabilities.

Install the necessary dependencies in the root of your Expo project:


_10
npm install @supabase/supabase-js
_10
npm install @rneui/themed @react-native-async-storage/async-storage react-native-url-polyfill
_10
npm install aes-js react-native-get-random-values
_10
npx expo install expo-secure-store

Implement a LargeSecureStore class to pass in as Auth storage for the supabase-js client:

lib/supabase.ts

_71
import 'react-native-url-polyfill/auto'
_71
_71
import AsyncStorage from '@react-native-async-storage/async-storage'
_71
import { createClient } from '@supabase/supabase-js'
_71
import * as aesjs from 'aes-js'
_71
import * as SecureStore from 'expo-secure-store'
_71
_71
import 'react-native-get-random-values'
_71
_71
// As Expo's SecureStore does not support values larger than 2048
_71
// bytes, an AES-256 key is generated and stored in SecureStore, while
_71
// it is used to encrypt/decrypt values stored in AsyncStorage.
_71
class LargeSecureStore {
_71
private async _encrypt(key: string, value: string) {
_71
const encryptionKey = crypto.getRandomValues(new Uint8Array(256 / 8))
_71
_71
const cipher = new aesjs.ModeOfOperation.ctr(encryptionKey, new aesjs.Counter(1))
_71
const encryptedBytes = cipher.encrypt(aesjs.utils.utf8.toBytes(value))
_71
_71
await SecureStore.setItemAsync(key, aesjs.utils.hex.fromBytes(encryptionKey))
_71
_71
return aesjs.utils.hex.fromBytes(encryptedBytes)
_71
}
_71
_71
private async _decrypt(key: string, value: string) {
_71
const encryptionKeyHex = await SecureStore.getItemAsync(key)
_71
if (!encryptionKeyHex) {
_71
return encryptionKeyHex
_71
}
_71
_71
const cipher = new aesjs.ModeOfOperation.ctr(
_71
aesjs.utils.hex.toBytes(encryptionKeyHex),
_71
new aesjs.Counter(1)
_71
)
_71
const decryptedBytes = cipher.decrypt(aesjs.utils.hex.toBytes(value))
_71
_71
return aesjs.utils.utf8.fromBytes(decryptedBytes)
_71
}
_71
_71
async getItem(key: string) {
_71
const encrypted = await AsyncStorage.getItem(key)
_71
if (!encrypted) {
_71
return encrypted
_71
}
_71
_71
return await this._decrypt(key, encrypted)
_71
}
_71
_71
async removeItem(key: string) {
_71
await AsyncStorage.removeItem(key)
_71
await SecureStore.deleteItemAsync(key)
_71
}
_71
_71
async setItem(key: string, value: string) {
_71
const encrypted = await this._encrypt(key, value)
_71
_71
await AsyncStorage.setItem(key, encrypted)
_71
}
_71
}
_71
_71
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
_71
const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
_71
_71
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
_71
auth: {
_71
storage: new LargeSecureStore(),
_71
autoRefreshToken: true,
_71
persistSession: true,
_71
detectSessionInUrl: false,
_71
},
_71
})

Email and password authentication in React Native#

Once we've set up the storage mechanism, building an email and password sign in flow becomes pretty straight forward. Install @rneui/themed to get some nice cross platform button and input fields:


_10
npm install @rneui/themed

Set up a simple email form component:

components/EmailForm.tsx

_84
import { Button, Input } from '@rneui/themed'
_84
import React, { useState } from 'react'
_84
import { Alert, StyleSheet, View } from 'react-native'
_84
_84
import { supabase } from '../lib/supabase'
_84
_84
export default function EmailForm() {
_84
const [email, setEmail] = useState('')
_84
const [password, setPassword] = useState('')
_84
const [loading, setLoading] = useState(false)
_84
_84
async function signInWithEmail() {
_84
setLoading(true)
_84
const { error } = await supabase.auth.signInWithPassword({
_84
email: email,
_84
password: password,
_84
})
_84
_84
if (error) Alert.alert(error.message)
_84
setLoading(false)
_84
}
_84
_84
async function signUpWithEmail() {
_84
setLoading(true)
_84
const {
_84
data: { session },
_84
error,
_84
} = await supabase.auth.signUp({
_84
email: email,
_84
password: password,
_84
})
_84
_84
if (error) Alert.alert(error.message)
_84
if (!session) Alert.alert('Please check your inbox for email verification!')
_84
setLoading(false)
_84
}
_84
_84
return (
_84
<View style={styles.container}>
_84
<View style={[styles.verticallySpaced, styles.mt20]}>
_84
<Input
_84
label="Email"
_84
leftIcon={{ type: 'font-awesome', name: 'envelope' }}
_84
onChangeText={(text) => setEmail(text)}
_84
value={email}
_84
placeholder="email@address.com"
_84
autoCapitalize={'none'}
_84
/>
_84
</View>
_84
<View style={styles.verticallySpaced}>
_84
<Input
_84
label="Password"
_84
leftIcon={{ type: 'font-awesome', name: 'lock' }}
_84
onChangeText={(text) => setPassword(text)}
_84
value={password}
_84
secureTextEntry={true}
_84
placeholder="Password"
_84
autoCapitalize={'none'}
_84
/>
_84
</View>
_84
<View style={[styles.verticallySpaced, styles.mt20]}>
_84
<Button title="Sign in" disabled={loading} onPress={() => signInWithEmail()} />
_84
</View>
_84
<View style={styles.verticallySpaced}>
_84
<Button title="Sign up" disabled={loading} onPress={() => signUpWithEmail()} />
_84
</View>
_84
</View>
_84
)
_84
}
_84
_84
const styles = StyleSheet.create({
_84
container: {
_84
marginTop: 40,
_84
padding: 12,
_84
},
_84
verticallySpaced: {
_84
paddingTop: 4,
_84
paddingBottom: 4,
_84
alignSelf: 'stretch',
_84
},
_84
mt20: {
_84
marginTop: 20,
_84
},
_84
})

Note, by default Supabase Auth requires email verification before a session is created for the users. To support email verification you need to implement deep link handling which is outlined in the next section.

While testing, you can disable email confirmation in your project's email auth provider settings.

As you saw above, we specified detectSessionInUrl: false when initializing supabase-js. By default, in a web based environment, supabase-js will automatically detect OAuth and magic link redirects and create the user session.

In native mobile apps, however, OAuth callbacks require a bit more configuration and the setup of deep linking.

To link to your development build or standalone app, you need to specify a custom URL scheme for your app. You can register a scheme in your app config (app.json, app.config.js) by adding a string under the scheme key:


_10
{
_10
"expo": {
_10
"scheme": "com.supabase"
_10
}
_10
}

In your project's auth settings add the redirect URL, e.g. com.supabase://**.

Finally, implement the OAuth and linking handlers. See the supabase-js reference for instructions on initializing the supabase-js client in React Native.

./components/Auth.tsx

_68
import { supabase } from 'app/utils/supabase'
_68
import { makeRedirectUri } from 'expo-auth-session'
_68
import * as QueryParams from 'expo-auth-session/build/QueryParams'
_68
import * as Linking from 'expo-linking'
_68
import * as WebBrowser from 'expo-web-browser'
_68
import { Button } from 'react-native'
_68
_68
WebBrowser.maybeCompleteAuthSession() // required for web only
_68
const redirectTo = makeRedirectUri()
_68
_68
const createSessionFromUrl = async (url: string) => {
_68
const { params, errorCode } = QueryParams.getQueryParams(url)
_68
_68
if (errorCode) throw new Error(errorCode)
_68
const { access_token, refresh_token } = params
_68
_68
if (!access_token) return
_68
_68
const { data, error } = await supabase.auth.setSession({
_68
access_token,
_68
refresh_token,
_68
})
_68
if (error) throw error
_68
return data.session
_68
}
_68
_68
const performOAuth = async () => {
_68
const { data, error } = await supabase.auth.signInWithOAuth({
_68
provider: 'github',
_68
options: {
_68
redirectTo,
_68
skipBrowserRedirect: true,
_68
},
_68
})
_68
if (error) throw error
_68
_68
const res = await WebBrowser.openAuthSessionAsync(data?.url ?? '', redirectTo)
_68
_68
if (res.type === 'success') {
_68
const { url } = res
_68
await createSessionFromUrl(url)
_68
}
_68
}
_68
_68
const sendMagicLink = async () => {
_68
const { error } = await supabase.auth.signInWithOtp({
_68
email: 'example@email.com',
_68
options: {
_68
emailRedirectTo: redirectTo,
_68
},
_68
})
_68
_68
if (error) throw error
_68
// Email sent.
_68
}
_68
_68
export default function Auth() {
_68
// Handle linking into app from email app.
_68
const url = Linking.useURL()
_68
if (url) createSessionFromUrl(url)
_68
_68
return (
_68
<>
_68
<Button onPress={performOAuth} title="Sign in with GitHub" />
_68
<Button onPress={sendMagicLink} title="Send Magic Link" />
_68
</>
_68
)
_68
}

For the best user experience, it is recommended to use universal links which require a more elaborate setup. You can find the detailed setup instructions in the Expo docs.

Native mobile login mechanisms#

Some native mobile operating systems, like iOS and Android, offer a built-in identity provider for convenient user authentication.

For iOS, apps that use a third-party or social login service to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option.

There are several benefits and reasons why you might want to add social login to your applications:

  • Improved user experience: Users can register and log in to your application using their existing app store accounts, which can be faster and more convenient than creating a new account from scratch. This makes it easier for users to access your application, improving their overall experience.

  • Better user engagement: You can access additional data and insights about your users, such as their interests, demographics, and social connections. This can help you tailor your content and marketing efforts to better engage with your users and provide a more personalized experience.

  • Increased security: Social login can improve the security of your application by leveraging the security measures and authentication protocols of the social media platforms that your users are logging in with. This can help protect against unauthorized access and account takeovers.

Sign in with Apple#

Supabase Auth supports using Sign in with Apple on the web and in native apps for iOS, macOS, watchOS, or tvOS.

For detailed setup and implementation instructions please refer to the docs and the video tutorial.

Sign in with Google#

Supabase Auth supports Sign in with Google on the web, native Android applications, and Chrome extensions.

For detailed set up and implementation instructions please refer to the docs and the video tutorial.

One time passwords#

Supabase supports various forms of passwordless authentication:

Passwordless login mechanisms have similar benefits as the native mobile login options mentioned above.

Conclusion#

In this post, we learned various authentication mechanisms we can use in React Native applications to provide a delightful experience for our users across native mobile and web.

More React Native and Supabase resources#

Build in a weekend, scale to millions