Skip to main content

Authentication flows

By default, all routes are always defined and accessible. You can use runtime logic to redirect users away from specific screens depending on whether they are authenticated.

Use global state and Route Groups

Consider the following project structure that has a /sign-in route that is always accessible and a (app) group that requires authentication:

📂 app
┃ ┣ 📜 _layout.tsx
┃ ┣ 📜 sign-in.tsx
┃ ┣ 📂 (app)
┃ ┃ ┗ 📜 _layout.tsx
┃ ┃ ┗ 📜 home.tsx
  1. Create a global state that stores the authentication status. This can be done with React Context or Redux.
Example SessionProvider.tsx
context/SessionProvider.tsx
import AsyncStorage from '@react-native-async-storage/async-storage';
import React, {createContext, useEffect, useMemo, useReducer} from 'react';

interface AuthState {
isLoading: boolean;
userToken?: string;
}

interface AuthAction {
type: string;
payload?: string;
}

interface AuthContextProps {
state: AuthState;
signIn: () => void;
signOut: () => void;
}

const SessionContext = createContext<AuthContextProps>({
state: {isLoading: true},
signIn: () => {},
signOut: () => {},
});

export const useSession = () => {
const context = React.useContext(SessionContext);
if (!context) {
throw new Error('useSession must be used within a SessionProvider');
}
return context;
};

const authReducer = (state: AuthState, action: AuthAction) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...state,
userToken: action.payload,
isLoading: false,
};
case 'SIGN_IN':
return {
...state,
userToken: action.payload,
isLoading: false,
};
case 'SIGN_OUT':
return {
...state,
userToken: undefined,
isLoading: false,
};
default:
return state;
}
};

const SessionProvider = ({children}: {children: React.ReactNode}) => {
const [state, dispatch] = useReducer(authReducer, {
isLoading: true,
});

useEffect(() => {
AsyncStorage.getItem('token').then(token => {
setTimeout(() => {
dispatch({type: 'RESTORE_TOKEN', payload: token ? token : undefined});
}, 1000);
});
}, []);

const value = useMemo(
() => ({
state,
signIn: () => {
AsyncStorage.setItem('token', 'dummy-auth-token');
dispatch({type: 'SIGN_IN', payload: 'dummy-auth-token'});
},
signOut: () => {
AsyncStorage.removeItem('token');
dispatch({type: 'SIGN_OUT'});
},
}),
[state],
);
return (
<SessionContext.Provider value={value}>{children}</SessionContext.Provider>
);
};

export default SessionProvider;
  1. Wrap your app with the global state provider
App.tsx
import React from 'react';
import RouterRoot from 'react-native-auto-route';
import SessionProvider from './context/SessionProvider';

export default function App() {
return (
<SessionProvider>
<RouterRoot />
</SessionProvider>
);
}
  1. Create a root layout with initialRouteName set to (app).
app/_layout.tsx
import React from 'react';
import {Stack} from 'react-native-auto-route';

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

export default RootLayout;

  1. Create a sign-in screen.
app/sign-in.tsx
import {View, Text, Button} from 'react-native';
import React from 'react';
import {useRouter} from 'react-native-auto-route';
import {useSession} from '../src/context/SessionProvider';

const SignIn = () => {
const session = useSession();
const router = useRouter();

return (
<View>
<Text>Sign In</Text>
<Button
title="Sign in"
onPress={() => {
session.signIn();
router.replace('home');
}}
/>
</View>
);
};

export default SignIn;

  1. Create Layout component for the (app) group.
app/(app)/_layout.tsx
import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {Redirect, Stack} from 'react-native-auto-route';
import {useSession} from '../../src/context/SessionProvider';

const AppLayout = () => {
const session = useSession();

// Show loading screen while checking authentication status
if (session.state.isLoading) {
return (
<View style={styles.container}>
<Text>Loading...</Text>
</View>
);
}

// Redirect to sign-in if user is not authenticated
if (!session.state.userToken) {
return <Redirect to="sign-in" />;
}

// Render stack if user is authenticated
return <Stack screenOptions={{headerShown: false}} />;
};

export default AppLayout;

const styles = StyleSheet.create({
container: {flex: 1, alignItems: 'center', justifyContent: 'center'},
});

References: Authentication in Expo Router