new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Expo · Router · all subjects

authentication/redirects

42 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Authentication with React Context and Route Groups pattern

A common pattern for restricting routes based on authentication status is to use React Context and Route Groups. This involves creating a SessionProvider that exposes authentication state to the entire app, and a nested layout route that checks authentication before rendering child routes. Unauthenticated users are redirected to the sign-in screen.

SessionProvider setup for authentication context

The SessionProvider should be placed in the root layout to provide authentication context to the entire app. It must be placed before the Slot component mounts to avoid runtime errors when navigation events are triggered. The provider exposes signIn, signOut, session, and isLoading properties.

Redirect unauthenticated users from protected route group

In a nested layout route for a protected route group, use useSession() hook to check authentication state. If the user is not authenticated, return <Redirect href="/sign-in" /> to redirect to the sign-in screen. This layout should not be the root layout, so it can defer rendering.

useSession hook for accessing authentication state

The useSession hook accesses the AuthContext to retrieve signIn, signOut, session, and isLoading values. It throws an error if not wrapped in a SessionProvider. This hook should be used in any component that needs to check or modify authentication state.

AuthContext type definition

AuthContext is created with type {signIn: () => void; signOut: () => void; session?: string | null; isLoading: boolean}. The signIn and signOut methods perform authentication logic, session holds the current user session, and isLoading indicates whether authentication state is being loaded.

Sign-in screen implementation outside protected group

The sign-in screen should be placed outside the protected route group (not in (app) group). This allows unauthenticated users to access it. The screen calls signIn() and then uses router.replace() to navigate after authentication. The authentication check in the protected group's layout does not run when rendering this screen.

Modal authentication pattern for preserving deep links

An alternative pattern is to render a sign-in modal over the top of the app using presentation: 'modal' option. This enables partial preservation of deep links when authentication completes. Routes rendered in the background must handle data loading without authentication. Use unstable_settings with anchor property to define the protected content group.

Error: Attempted to navigate before mounting Root Layout

This error occurs when navigation is attempted before the Root Layout component renders a Slot or navigator on the first render. The fix is to move conditional logic down to a nested layout rather than performing navigation in the root layout. The root layout must render Slot without conditional redirects.

Alternative loading states with index route as loading state

Instead of rendering a loading message in the root layout, the index route can be made a loading state while moving the initial authenticated route to something like /home. This pattern is similar to how X implements authentication flows.

SDK 53 introduced Protected routes as improved authentication method

SDK 53 introduced Protected routes, which is a more powerful method of handling authentication compared to the redirect-based approach. This redirect guide is for SDK 52 and earlier.

Example authentication context full code

```tsx ctx.tsx import { useContext, createContext, type PropsWithChildren } from 'react'; import { useStorageState } from './useStorageState'; const AuthContext = createContext<{ signIn: () => void; signOut: () => void; session?: string | null; isLoading: boolean; }>({ signIn: () => null, signOut: () => null, session: null, isLoading: false, }); export function useSession() { const value = useContext(AuthContext); if (!value) { throw new Error('useSession must be wrapped in a <SessionProvider />'); } return value; } export function SessionProvider({ children }: PropsWithChildren) { const [[isLoading, session], setSession] = useStorageState('session'); return ( <AuthContext.Provider value={{ signIn: () => { setSession('xxx'); }, signOut: () => { setSession(null); }, session, isLoading, }}> {children} </AuthContext.Provider> ); } ```

Example useStorageState hook full code

```tsx useStorageState.ts import { useEffect, useCallback, useReducer } from 'react'; import * as SecureStore from 'expo-secure-store'; import { Platform } from 'react-native'; type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void]; function useAsyncState<T>( initialValue: [boolean, T | null] = [true, null], ): UseStateHook<T> { return useReducer( (state: [boolean, T | null], action: T | null = null): [boolean, T | null] => [false, action], initialValue ) as UseStateHook<T>; } export async function setStorageItemAsync(key: string, value: string | null) { if (process.env.EXPO_OS === 'web') { if (value === null) { localStorage.removeItem(key); } else { localStorage.setItem(key, value); } } else { if (value == null) { await SecureStore.deleteItemAsync(key); } else { await SecureStore.setItemAsync(key, value); } } } export function useStorageState(key: string): UseStateHook<string> { const [state, setState] = useAsyncState<string>(); useEffect(() => { if (Platform.OS === 'web') { try { if (typeof localStorage !== 'undefined') { setState(localStorage.getItem(key)); } } catch (e) { console.error('Local storage is unavailable:', e); } } else { SecureStore.getItemAsync(key).then((value: string | null) => { setState(value); }); } }, [key]); const setValue = useCallback( (value: string | null) => { setState(value); setStorageItemAsync(key, value); }, [key] ); return [state, setValue]; } ```

Example root layout with SessionProvider

```tsx app/_layout.tsx import { Slot } from 'expo-router'; import { SessionProvider } from '../ctx'; export default function Root() { return ( <SessionProvider> <Slot /> </SessionProvider> ); } ```

Example protected app layout with authentication check

```tsx app/(app)/_layout.tsx import { Text } from 'react-native'; import { Redirect, Stack } from 'expo-router'; import { useSession } from '../../ctx'; export default function AppLayout() { const { session, isLoading } = useSession(); if (isLoading) { return <Text>Loading...</Text>; } if (!session) { return <Redirect href="/sign-in" />; } return <Stack />; } ```

Example sign-in screen implementation

```tsx app/sign-in.tsx import { router } from 'expo-router'; import { Text, View } from 'react-native'; import { useSession } from '../ctx'; export default function SignIn() { const { signIn } = useSession(); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text onPress={() => { signIn(); router.replace('/'); }}> Sign In </Text> </View> ); } ```

Example authenticated home screen with sign out

```tsx app/(app)/index.tsx import { Text, View } from 'react-native'; import { useSession } from '../../ctx'; export default function Index() { const { signOut } = useSession(); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text onPress={() => { signOut(); }}> Sign Out </Text> </View> ); } ```

Example modal authentication layout

```tsx app/(app)/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { anchor: '(root)', }; export default function AppLayout() { return ( <Stack> <Stack.Screen name="(root)" /> <Stack.Screen name="sign-in" options={{ presentation: 'modal', }} /> </Stack> ); } ```

Fix for navigation before Root Layout mounting

To fix the 'Attempted to navigate before mounting Root Layout' error, move conditional logic to a nested layout instead of the root layout. The root layout must render Slot without conditionals. Create a group (e.g., app) and move the conditional redirect logic to app/_layout.tsx. This defers rendering of the nested layout's content while allowing the root layout's Slot to mount first.

All routes are always defined and accessible in Expo Router

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

Stack.Protected guard syntax for authentication

Use Stack.Protected with a guard prop to protect routes based on authentication state. The guard prop accepts a boolean value. For example: <Stack.Protected guard={!!session}><Stack.Screen name="(app)" /></Stack.Protected> protects the (app) group when session exists.

Authentication context provider pattern

Create a React Context provider using createContext that exposes authentication session to the entire app. The provider should expose functions like signIn() and signOut(), and properties like session and isLoading. Wrap the root layout with SessionProvider to give the entire app access to authentication context.

SplashScreenController prevents auto-hide during auth loading

Create a SplashScreenController component that calls SplashScreen.preventAutoHideAsync() to keep the splash screen visible while authentication loads asynchronously. Call SplashScreen.hide() once isLoading is false. This component must be placed inside the SessionProvider in the root layout.

useStorageState hook for secure token persistence

The useStorageState hook persists tokens securely: on native platforms it uses expo-secure-store, on web it uses localStorage. It returns [[isLoading, value], setValue] and handles platform-specific storage automatically. It accepts a key parameter for the storage item.

useSession hook accesses authentication context

Create a useSession hook that uses the React use() function to access the AuthContext. It should throw an error if not wrapped in a SessionProvider. This hook returns the authentication context object containing signIn, signOut, session, and isLoading properties.

Sign-in screen placement outside protected groups

Place the sign-in screen outside the protected group (e.g., at src/app/sign-in.tsx, not inside the (app) group). This allows the group's layout and authentication check to be bypassed for sign-in, making it accessible to logged-out users.

Modal authentication pattern with background rendering

Another authentication pattern is to render a sign-in modal over the app instead of full-screen redirects. This enables dismissing and partially preserving deep links when authentication is complete. This pattern requires routes to be rendered in the background since these routes need to handle data loading without authentication. Use Stack.Screen with options={{presentation: 'modal'}} for the sign-in modal.

Expo Router web does not support server-side middleware

Expo Router on the web currently only supports build-time static generation and has no support for custom middleware or serving. This means authentication on web must be implemented using client-side redirects and loading state rather than server-side route protection.

Use unstable_settings initialRouteName for modal pattern

When using the modal authentication pattern, set unstable_settings with initialRouteName property in the (app)/_layout.tsx to specify which route loads first. For example, unstable_settings = { initialRouteName: '(root)' } makes the (root) group the initial screen.

Deep links redirect based on authentication state

When a user visits a deep link to any protected routes while not authenticated, they will be automatically redirected to the sign-in screen based on the guard condition in Stack.Protected.

Protected routes overview and redirect behavior

Protected screens prevent users from accessing certain routes using client-side navigation. If a user tries to navigate to a protected screen, or if a screen becomes protected while it is active, they are redirected to the anchor route (usually the index screen) or the first available screen in the stack.

Stack.Protected guard attribute usage

Use Stack.Protected with a guard prop containing a boolean condition to protect screens. When guard is false, the route is inaccessible. When guard changes from true to false, all history entries for that screen are removed from navigation history.

Cannot have duplicate screen declarations

A screen can only exist in one active route group at a time. You should only declare a screen once in the most appropriate group or stack. If a screen's availability depends on logic, wrap it in a conditional group instead of duplicating the screen.

Nesting protected screens for hierarchical access control

Protected screens can be nested to define hierarchical access control logic. A nested protected screen requires all parent guard conditions to be true in addition to its own guard condition.

Protected routes with Stack example

Example of Stack with protected screens: const isLoggedIn = false; export function AppLayout() { return (<Stack><Stack.Protected guard={!isLoggedIn}><Stack.Screen name="login" /></Stack.Protected><Stack.Protected guard={isLoggedIn}><Stack.Screen name="private" /></Stack.Protected></Stack>); }. When guard is false for /private, users are redirected to the index screen.

Fallback screen configuration for protected routes

When all screens in a protected group have guard=false, the router redirects to the first available unprotected screen. If you protect the index screen and it has guard=false, the router redirects to the next available screen, such as login.

Static rendering and protected routes security

Protected screens are evaluated on the client side only. During static site generation, no HTML files are created for protected routes. However, protected screens are not a replacement for server-side authentication or access control, as users who know the URLs can still request the corresponding HTML or JavaScript files directly.

Nested protected screens hierarchical example

Example of nested protected screens: const isLoggedIn = true; const isAdmin = true; export function AppLayout() { return (<Stack><Stack.Protected guard={isLoggedIn}><Stack.Protected guard={isAdmin}><Stack.Screen name="private" /></Stack.Protected><Stack.Screen name="about" /></Stack.Protected></Stack>); }. The /private route is protected if user is logged in AND is admin. The /about route is protected only if user is logged in.

Stack.Protected component for authentication-based route visibility

Use Stack.Protected with a guard prop to conditionally show routes based on authentication state. Example: <Stack.Protected guard={isLoggedIn}><Stack.Screen name="(tabs)" /></Stack.Protected> will only show the (tabs) group when isLoggedIn is true. Protected routes are checked even during deep linking, so unauthenticated users redirected from deep links will see appropriate screens.

Layout re-rendering on authentication state changes

When auth state changes in a component wrapped with Stack.Protected, the layout will re-render and automatically navigate accordingly. For example, if isLoggedIn changes from false to true, the app will automatically navigate to the root of the protected group.

Tabs.Protected for conditionally showing specific tabs

Use Tabs.Protected inside a Tabs layout to conditionally show individual tabs based on user properties. Example: <Tabs.Protected guard={isVip}><Tabs.Screen name="vip" /></Tabs.Protected> will only show the vip tab to authenticated VIP users.

Modal overlays in layout files for read-only authentication

For apps that allow unauthenticated users to browse in read-only mode, show a login modal over the main app instead of redirecting to a login page. This can be implemented by rendering a Stack navigator alongside a Modal component in the layout file, with the modal's visibility controlled by authentication state.

Redirect component example: authentication check

The following code shows how to use Redirect for conditional authentication: import { View, Text } from 'react-native'; import { Redirect } from 'expo-router'; export default function Page() { const { user } = useAuth(); if (!user) { return <Redirect href="/login" />; } return ( <View><Text>Welcome Back!</Text></View> ); }

Give your agent this brain