Preventing Unauthenticated Route Access in Expo Router 4 with NativeWind

Preventing Unauthenticated Route Access in Expo Router 4 with NativeWind cover

If you're building an app with Expo Router 4, you’ll likely need to restrict access to certain routes based on whether a user is authenticated or not. For example, you might want to prevent users from accessing the /profile route unless they’re logged in. In this guide, I’ll walk you through how to implement this functionality step by step. We’ll use Expo Router 4 for routing and NativeWind for styling to keep things clean and efficient.

Prerequisites

Before diving in, make sure you have the following set up:

  1. Expo CLI installed (npm install -g expo-cli).
  2. A basic Expo project with Expo Router 4 configured. If you haven’t set this up yet, follow the Expo Router documentation.
  3. NativeWind installed and configured in your project. If you haven’t done this yet, check out the NativeWind installation guide.

Step 1: Set Up an Authentication Context

To manage the user’s authentication state, we’ll use React’s Context API. This allows us to share the authentication state across the entire app.

Create a file called context/AuthContext.js:

import React, { createContext, useState } from 'react';

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const login = () => setIsAuthenticated(true);
  const logout = () => setIsAuthenticated(false);

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

This creates a context that holds the authentication state (isAuthenticated) and methods to update it (login and logout).

Step 2: Wrap Your App with the Auth Provider

In Expo Router 4, the _layout.js file is the entry point for your app’s navigation. We’ll wrap the entire app with the AuthProvider so that all routes can access the authentication state.

Update your app/_layout.js file:

import React from 'react';
import { Stack } from 'expo-router';
import { AuthProvider } from '../context/AuthContext';

export default function RootLayout() {
  return (
    <AuthProvider>
      <Stack />
    </AuthProvider>
  );
}

This ensures that the authentication state is available everywhere in your app.

Step 3: Create a Protected Route Wrapper

Now, let’s create a ProtectedRoute component that checks if the user is authenticated before allowing access to a specific route. If the user isn’t authenticated, they’ll be redirected to the /login route.

Create a file called components/ProtectedRoute.js:

import React, { useContext } from 'react';
import { Redirect } from 'expo-router';
import { AuthContext } from '../context/AuthContext';

const ProtectedRoute = ({ children }) => {
  const { isAuthenticated } = useContext(AuthContext);

  if (!isAuthenticated) {
    // Redirect to the Login route if not authenticated
    return <Redirect href="/login" />;
  }

  return children;
};

export default ProtectedRoute;

This component acts as a gatekeeper for protected routes. If the user isn’t logged in, they’ll be redirected to the login screen.

Step 4: Set Up Routes

Expo Router 4 uses a file-based routing system, which makes it super easy to define routes. Let’s create the necessary routes for our app.

Home Route (app/index.js)

This is the default route that users will see when they open the app.

import React from 'react';
import { View, Text, Button } from 'react-native';
import { Link } from 'expo-router';
import { useAuth } from '../context/AuthContext';

export default function Home() {
  const { isAuthenticated, logout } = useAuth();

  return (
    <View className="flex-1 justify-center items-center">
      <Text className="text-2xl mb-4">Welcome to the Home Screen!</Text>
      <Link href="/profile" asChild>
        <Button title="Go to Profile" />
      </Link>
      {isAuthenticated && (
        <Button title="Log Out" onPress={logout} />
      )}
    </View>
  );
}

Here, we’re using the useAuth hook (which we’ll create in the next step) to check if the user is authenticated and display a logout button if they are.

Login Route (app/login.js)

This route allows users to log in.

import React, { useContext } from 'react';
import { View, Text, Button } from 'react-native';
import { Link } from 'expo-router';
import { AuthContext } from '../context/AuthContext';

export default function Login() {
  const { login } = useContext(AuthContext);

  return (
    <View className="flex-1 justify-center items-center">
      <Text className="text-2xl mb-4">Login Screen</Text>
      <Button title="Log In" onPress={login} />
      <Link href="/" asChild>
        <Button title="Go to Home" />
      </Link>
    </View>
  );
}

When the user clicks the "Log In" button, the login function from the AuthContext will update the authentication state.

Profile Route (app/profile.js)

This route is protected and can only be accessed if the user is authenticated.

import React from 'react';
import { View, Text } from 'react-native';
import ProtectedRoute from '../components/ProtectedRoute';

function Profile() {
  return (
    <View className="flex-1 justify-center items-center">
      <Text className="text-2xl">Welcome to your Profile!</Text>
    </View>
  );
}

export default function ProtectedProfile() {
  return (
    <ProtectedRoute>
      <Profile />
    </ProtectedRoute>
  );
}

The ProtectedRoute wrapper ensures that only authenticated users can access this screen.

Step 5: Add a Custom Hook for Auth Context

To make it easier to access the authentication context, let’s create a custom hook.

Create a file called hooks/useAuth.js:

import { useContext } from 'react';
import { AuthContext } from '../context/AuthContext';

export const useAuth = () => {
  return useContext(AuthContext);
};

Now, you can use this hook in any component to access the authentication state and methods. For example, in the Home component, we used it to check if the user is authenticated and display a logout button.

Step 6: Test Your App

  1. Run your app using expo start.
  2. Try navigating to the /profile route. If you’re not authenticated, you should be redirected to the /login route.
  3. After logging in, you should be able to access the /profile route.

Conclusion

And that’s it! You’ve successfully implemented route protection in your Expo Router 4 app. By using the Context API for state management and Expo Router’s file-based routing system, you can easily control access to specific routes based on the user’s authentication status. Plus, with NativeWind, your styles stay clean and maintainable.

This pattern is flexible and can be extended to handle more complex scenarios, like token-based authentication or role-based access control. If you have any questions or run into issues, feel free to reach out. Happy coding! 🚀

Recent Guides

Hestia Kit Premium

You have to be signed in to favorite this

Share

Hestia Kit Premium

This is Hestia Kit premium component