Programming

Florencia Carle • 27 NOV 2024

Building an App with Authentication and Push Notifications using Expo

post cover picture

Creating a mobile app with robust authentication and push notification support can be an essential feature for any application. 

In this guide, we'll learn how to develop an app using Expo SDK 51, including authentication screens and push notifications, leveraging Expo's powerful tools like Expo Router, React Context, and expo notifications

Project structure overview

For this example, we’ll set up a project structure where routes are divided into authenticated and unauthenticated sections. This will allow us to control access to specific screens based on the user’s authentication state. Here's an overview of the project directory structure:

app/
│
├── (app)/ 
│   ├── (auth)/ 
│   │   ├── _layout.tsx
│   │   └── sign-in.tsx
│   ├── _layout.tsx
│   └── index.tsx
└── _layout.tsx

In this structure:

- (auth) is the folder where authentication-related screens are stored.

- (app) contains the rest of the application, including authenticated and public screens.

Step 1: setting up authentication with React Context

To manage authentication globally, we’ll use React Context to store the session state and provide it throughout the app.

Creating the SessionContext

Create a new file contexts/SessionContext.tsx:

import React, { createContext, useContext, useState, useEffect } from 'react';
import * as SecureStorage from 'expo-secure-store';

const SessionContext = createContext(null);

export const SessionProvider = ({ children }) => {
  const [session, setSession] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    SecureStorage.getItemAsync('session').then((storedSession) => {
      setSession(storedSession);
      setIsLoading(false);
    });
  }, []);

  const signIn = (sessionId) => {
    setSession(sessionId);
    SecureStorage.setItemAsync('session', sessionId);
  };

  const signOut = () => {
    setSession(null);
    SecureStorage.deleteItemAsync('session');
  };

  return (
    <SessionContext.Provider value={{ session, isLoading, signIn, signOut }}>
      {children}
    </SessionContext.Provider>
  );
};

export const useSession = () => useContext(SessionContext);

Here, we store the authentication session using expo-secure-store, which locally stores data in the user’s phone. We also provide methods for signing in and signing out.

Step 2: setting up the root layout

In app/_layout.tsx, wrap the root of your app in the SessionProvider so that the authentication context is available throughout the entire application:

import { SessionProvider } from '@/contexts/SessionContext';
import { Stack } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';

SplashScreen.preventAutoHideAsync();

const RootLayout = () => (
  <SessionProvider>
    <Stack>
      <Stack.Screen name="(app)" options={{ headerShown: false }} />
    </Stack>
  </SessionProvider>
);

export default RootLayout;

 

Step 3: Handling authenticated routes

Now, we’ll create a layout for authenticated routes. If the user is logged in, they can access these screens. Otherwise, they will be redirected to the sign-in screen.

Create app/(app)/(auth)/_layout.tsx:

import { useSession } from '@/contexts/SessionContext';
import { Redirect, Stack } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';

const AuthLayout = () => {
  const { session, isLoading } = useSession();

  useEffect(() => {
    if (!isLoading) {
      SplashScreen.hideAsync();
    }
  }, [isLoading]);

  if (isLoading) return null;

  if (!session) {
    return <Redirect href="/sign-in" />;
  }

  return (
    <Stack>
      <Stack.Screen name="Home" />
    </Stack>
  );
};

export default AuthLayout;

Here, we check if a session exists. If the user is authenticated, they can continue to the app. Otherwise, they are redirected to the sign-in screen.

Step 4: Sign-In Screen

Next, let's create a sign-in screen where users can log in. This will simulate a sign-in process and store a session in the context.

Create app/(app)/sign-in.tsx:

import { useSession } from '@/contexts/SessionContext';
import { Button, Text, SafeAreaView } from 'react-native';
import { router } from 'expo-router';

const SignIn = () => {
  const { signIn } = useSession();

  return (
    <SafeAreaView>
      <Text>Sign in</Text>
      <Button
        onPress={() => {
          signIn('mock-session');
          router.replace('/');
        }}
        title="Sign in"
      />
    </SafeAreaView>
  );
};

export default SignIn;


When the user clicks the "sign in" button, the session is created and the user is redirected to the home screen.

 

Step 5: Push Notifications Setup

Expo makes it easy to set up push notifications with a consistent API for both Android and iOS. Follow these steps to integrate push notifications into your app:

Installing Necessary Packages

You need to install the following libraries:

npx expo install expo-notifications expo-device expo-constants

These libraries allow you to handle notifications, check if the app is running on a physical device, and access configuration settings.

Setting Up Notification Handler

In your App Layout (app/(app)/_layout.tsx), set up a notification handler:

import * as Notifications from 'expo-notifications';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: false,
  }),
});

This configuration ensures that when a notification is received, it shows an alert but doesn’t play sound or modify the app badge.

Registering for Push Notifications

We need to request the user’s permission to send push notifications and retrieve the Expo Push Token. Add the following logic to app/(app)/(auth)/_layout.tsx:

import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
import { Platform } from 'react-native';

const registerForPushNotificationsAsync = async () => {
  if (Platform.OS === 'android') {
    Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    });
  }

  if (Device.isDevice) {
    const { status: existingStatus } = await Notifications.getPermissionsAsync();
    let finalStatus = existingStatus;

    if (existingStatus !== 'granted') {
      const { status } = await Notifications.requestPermissionsAsync();
      finalStatus = status;
    }

    if (finalStatus !== 'granted') {
      throw new Error('Permission not granted for push notifications');
    }

    const pushToken = await Notifications.getExpoPushTokenAsync();
    console.log('Expo push token:', pushToken.data);

    return pushToken.data;
  }
};


This function checks if the device supports push notifications, requests permission from the user, and retrieves the push token.

 

Step 6: Testing push notifications

To test push notifications, you’ll need a real device (simulators won’t work). You can generate a build using EAS and install it on your device:

eas build --profile development --platform ios

Once the app is installed on a real device, you can use Expo's Push Notification Tool to send notifications to the device using the push token retrieved earlier.

Wrapping up: Auth and notifications with Expo

By following these steps, you've successfully built a basic app with authentication and push notifications using Expo. With React Context for managing authentication state and expo-notifications for handling push notifications, your app is now equipped with essential features that will engage users effectively.

If you want to stay ahead of the latest development trends and explore more tutorials from our team, check out our blog.

 

Stay updated!

project background