Programming
Florencia Carle • 27 NOV 2024
Building an App with Authentication and Push Notifications using Expo
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.