npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-fireguard

v1.0.3

Published

The easiest way to add secure Firebase authentication, protected routes, and role-based access control to your Expo React Native apps.

Readme

react-native-fireguard 🔥🛡️

npm version npm downloads License: MIT Platform

The easiest way to add secure Firebase authentication, protected routes, and role-based access control to your Expo React Native apps. Think Clerk — but for Firebase.


Why FireGuard?

Building auth in React Native with Firebase is repetitive, error-prone, and leaves security gaps. FireGuard solves this with a single package that gives you:

  • Automatic route protection — unauthenticated users are redirected immediately
  • Role-based access control — show/hide content based on user roles from Firestore
  • Declarative components<SignedIn>, <SignedOut>, <Protect> just like Clerk
  • Pre-built auth screens — drop-in <SignIn> and <SignUp> components
  • Security-first — tokens never stored in state, server-side revocation detection
  • Expo Router ready — works seamlessly with file-based routing

Installation

npm install react-native-fireguard

Install peer dependencies

npx expo install firebase @react-native-async-storage/async-storage expo-router

Quick Start

1. Wrap your root layout

// app/_layout.jsx
import { Slot } from "expo-router";
import { FirebaseAuthProvider, RouteGuard } from "react-native-fireguard";

const firebaseConfig = {
  apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
};

export default function RootLayout() {
  return (
    <FirebaseAuthProvider config={firebaseConfig}>
      <RouteGuard
        loginRoute="/(auth)/login"
        homeRoute="/(tabs)"
        authSegment="(auth)"
        adminSegment="(admin)"
      >
        <Slot />
      </RouteGuard>
    </FirebaseAuthProvider>
  );
}

2. Add a login screen

// app/(auth)/login.jsx
import { SignIn } from "react-native-fireguard";

export default function LoginScreen() {
  return <SignIn />;
}

3. Protect your screens

// app/(tabs)/home.jsx
import { View, Text, TouchableOpacity } from "react-native";
import { SignedIn, SignedOut, Protect, useAuth } from "react-native-fireguard";

export default function HomeScreen() {
  const { user, role, signOut } = useAuth();

  return (
    <View>
      <SignedIn>
        <Text>Welcome, {user?.email}!</Text>
        <Text>Role: {role}</Text>

        {/* Only visible to admins */}
        <Protect role="admin">
          <Text>🔐 Admin-only content</Text>
        </Protect>

        {/* Multiple roles */}
        <Protect role={["admin", "moderator"]}>
          <Text>Visible to admins and moderators</Text>
        </Protect>

        <TouchableOpacity onPress={signOut}>
          <Text>Sign Out</Text>
        </TouchableOpacity>
      </SignedIn>

      <SignedOut>
        <Text>Please sign in to continue</Text>
      </SignedOut>
    </View>
  );
}

Folder Structure

FireGuard works best with this Expo Router folder structure:

app/
├── _layout.jsx          ← FirebaseAuthProvider + RouteGuard
├── (auth)/
│   ├── _layout.jsx      ← Public screens (no guard)
│   └── login.jsx        ← Login/signup screen
├── (tabs)/
│   ├── _layout.jsx      ← Protected tab navigator
│   ├── home.jsx         ← Protected home screen
│   ├── profile.jsx      ← Protected profile screen
│   └── admin.jsx        ← Admin-only tab (hidden for regular users)
└── (admin)/
    ├── _layout.jsx      ← Admin stack navigator
    └── dashboard.jsx    ← Full admin dashboard

API Reference

<FirebaseAuthProvider>

The root provider. Wrap your entire app with this.

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | config | FirebaseOptions | ✅ | — | Your Firebase config object | | roleCollection | string | ❌ | "users" | Firestore collection name for user roles | | requireEmailVerification | boolean | ❌ | false | Treat unverified emails as signed out | | onTokenError | (error: Error) => void | ❌ | — | Called when token refresh fails | | onSignOut | () => void | ❌ | — | Called after sign out |

<FirebaseAuthProvider
  config={firebaseConfig}
  roleCollection="users"
  requireEmailVerification={false}
  onTokenError={(err) => console.warn('Token error:', err.message)}
  onSignOut={() => console.log('User signed out')}
>
  ...
</FirebaseAuthProvider>

<RouteGuard>

Automatically redirects unauthenticated users to your login screen and authenticated users away from auth screens.

| Prop | Type | Default | Description | |------|------|---------|-------------| | loginRoute | string | "/(auth)/login" | Where to redirect unauthenticated users | | homeRoute | string | "/(tabs)" | Where to redirect authenticated users on auth screens | | authSegment | string | "(auth)" | Expo Router group name for auth screens | | adminSegment | string | "(admin)" | Expo Router group name for admin screens |

<RouteGuard
  loginRoute="/(auth)/login"
  homeRoute="/(tabs)"
  authSegment="(auth)"
  adminSegment="(admin)"
>
  <Slot />
</RouteGuard>

<SignedIn>

Renders children only when the user is authenticated.

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | — | Content to show when signed in | | fallback | ReactNode | null | Content to show while auth is loading |

<SignedIn fallback={<ActivityIndicator />}>
  <Text>Welcome back!</Text>
</SignedIn>

<SignedOut>

Renders children only when the user is NOT authenticated.

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | — | Content to show when signed out | | fallback | ReactNode | null | Content to show while auth is loading |

<SignedOut>
  <Text>Please log in to continue</Text>
</SignedOut>

<Protect>

Renders children only for users with the specified role. Shows a fallback for unauthorized users.

| Prop | Type | Default | Description | |------|------|---------|-------------| | role | string \| string[] | — | Required role(s) to view content | | condition | (role: string \| null) => boolean | — | Custom permission function | | fallback | ReactNode | <Text>Access Denied</Text> | Shown when access is denied | | loadingFallback | ReactNode | null | Shown while auth loads |

// Single role
<Protect role="admin">
  <AdminPanel />
</Protect>

// Multiple roles
<Protect role={["admin", "moderator"]} fallback={<Text>No access</Text>}>
  <ModeratorTools />
</Protect>

// Custom condition
<Protect condition={(role) => role === "admin" && isPremiumUser}>
  <PremiumAdminFeature />
</Protect>

<AuthLoading>

Renders children ONLY while auth state is being determined. Useful for splash screens.

<AuthLoading>
  <SplashScreen />
</AuthLoading>

<SignIn>

Pre-built sign-in form with validation, loading states, and error handling.

| Prop | Type | Description | |------|------|-------------| | onSuccess | () => void | Called after successful sign in |

<SignIn onSuccess={() => router.replace("/(tabs)")} />

<SignUp>

Pre-built sign-up form.

| Prop | Type | Description | |------|------|-------------| | onSuccess | () => void | Called after successful sign up |

<SignUp onSuccess={() => router.replace("/(tabs)")} />

useAuth()

The main hook. Access auth state from any component inside <FirebaseAuthProvider>.

const {
  user,       // SafeUser | null — sanitized user object
  role,       // string | null — user's role from Firestore
  isLoaded,   // boolean — true once auth state is confirmed
  isSignedIn, // boolean — true if user is authenticated (derived)
  signOut,    // () => Promise<void>
  getIdToken, // (forceRefresh?: boolean) => Promise<string | null>
} = useAuth();

user object properties

{
  uid: string;
  email: string | null;
  displayName: string | null;
  photoURL: string | null;
  emailVerified: boolean;
  isAnonymous: boolean;
  phoneNumber: string | null;
  role: string;           // fetched from Firestore
}

getIdToken(forceRefresh?)

Fetches a fresh Firebase ID token for use in API calls. Always use this instead of caching tokens yourself.

const { getIdToken } = useAuth();

// Standard API call
const token = await getIdToken();

// Force refresh before sensitive operations (payments, account changes)
const freshToken = await getIdToken(true);

const response = await fetch("https://api.yourapp.com/data", {
  headers: { Authorization: `Bearer ${token}` },
});

getFriendlyAuthError(errorCode)

Converts Firebase Auth error codes into user-friendly messages. Prevents email enumeration by returning the same message for user-not-found and wrong-password.

import { getFriendlyAuthError } from "react-native-fireguard";

try {
  await signInWithEmailAndPassword(auth, email, password);
} catch (e) {
  Alert.alert("Error", getFriendlyAuthError(e.code));
}

Role-Based Access Control (RBAC)

FireGuard reads user roles from Firestore. Set up your roles like this:

Firestore structure

Collection: users
└── Document: {firebase_auth_uid}        ← Document ID must be the user's UID
    ├── role: "admin"                     ← string field
    ├── email: "[email protected]"
    └── createdAt: "2026-01-01T00:00:00"

Setting up roles

Step 1 — Get the user's UID:

Firebase Console → Authentication → Users → copy the User UID

Step 2 — Create the Firestore document:

Firebase Console → Firestore Database → users collection
→ Add document
→ Document ID: {paste the UID}
→ Add field: role (string) = "admin"

Available roles

FireGuard doesn't enforce specific role names — you define them. Common patterns:

// Simple two-tier
<Protect role="admin">...</Protect>

// Three-tier
<Protect role={["admin", "moderator"]}>...</Protect>

// Custom roles
<Protect role="premium">...</Protect>
<Protect role={["editor", "publisher", "admin"]}>...</Protect>

Admin-only tab (hidden from regular users)

// app/(tabs)/_layout.jsx
import { Tabs } from "expo-router";
import { useAuth } from "react-native-fireguard";

export default function TabsLayout() {
  const { role } = useAuth();
  const isAdmin = role === "admin" || role === "moderator";

  return (
    <Tabs>
      <Tabs.Screen name="home" options={{ title: "Home" }} />
      <Tabs.Screen name="profile" options={{ title: "Profile" }} />
      <Tabs.Screen
        name="admin"
        options={{
          title: "Admin",
          href: isAdmin ? undefined : null, // hidden for non-admins
        }}
      />
    </Tabs>
  );
}

Environment Variables

Never hardcode Firebase config. Use Expo's environment variables:

# .env (add to .gitignore!)
EXPO_PUBLIC_FIREBASE_API_KEY=your_api_key
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
EXPO_PUBLIC_FIREBASE_PROJECT_ID=your_project_id
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
EXPO_PUBLIC_FIREBASE_APP_ID=your_app_id
const firebaseConfig = {
  apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
};

Firestore Security Rules

Protect your users collection so only authenticated users can read their own role, and only backend/admin can write:

// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      // Users can only read their own document
      allow read: if request.auth != null && request.auth.uid == userId;
      // Only Firebase Admin SDK (backend) can write roles
      allow write: if false;
    }
  }
}

Security Notes

FireGuard is built with security as the top priority:

  • Tokens never stored in React state — the raw Firebase User object is kept in a useRef, invisible to React DevTools
  • onIdTokenChanged instead of onAuthStateChanged — catches server-side token revocations that onAuthStateChanged misses
  • Email enumeration preventionuser-not-found and wrong-password return the same error message
  • Auto sign-out on token failure — if a token refresh fails, the user is signed out immediately
  • Role fetch timeout — Firestore role fetch has a 5-second timeout; defaults to "user" if Firestore is unreachable

Peer Dependencies

| Package | Version | |---------|---------| | firebase | >=9.0.0 | | expo-router | >=2.0.0 | | @react-native-async-storage/async-storage | >=1.0.0 | | react | >=18.0.0 | | react-native | >=0.71.0 |


Compatibility

| Expo SDK | React Native | Status | |----------|-------------|--------| | 54 | 0.81 | ✅ Tested | | 52+ | 0.76+ | ✅ Should work | | Below 52 | Below 0.76 | ⚠️ Not tested |


Troubleshooting

"useAuth must be used within FirebaseAuthProvider"

Ensure <FirebaseAuthProvider> wraps your root layout, not a group layout.

Role always shows as "user" even after setting admin in Firestore

  • Check the document ID matches the Firebase Auth UID exactly
  • Check the roleCollection prop matches your Firestore collection name (default: "users")
  • Sign out and sign back in to refresh the role

RouteGuard not redirecting

  • Make sure <RouteGuard> is inside <FirebaseAuthProvider>
  • Check authSegment matches your Expo Router group name exactly (e.g., "(auth)")
  • Ensure loginRoute is a valid Expo Router path

getReactNativePersistence error

Make sure you're using Firebase v9+ and have @react-native-async-storage/async-storage installed.


Contributing

Contributions are welcome! Please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Repository

https://github.com/Inspire00/react-native-fireguard


License

MIT © Vincent


Changelog

1.0.0 (2026-05-10)

  • 🎉 Initial release
  • FirebaseAuthProvider with Firestore role support
  • RouteGuard for automatic route protection
  • SignedIn, SignedOut, Protect, AuthLoading components
  • SignIn, SignUp pre-built screens
  • useAuth hook with getIdToken support
  • Email enumeration prevention
  • Server-side token revocation detection