Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
aiskillstore avatar

Rn Auth

  • 1 installs
  • 404 repo stars
  • Updated August 5, 2026
  • aiskillstore/marketplace

rn-auth is a skill of React Native authentication patterns for Expo apps covering OAuth, secure token storage, and protected routes.

About

rn-auth is a reference skill of React Native authentication patterns for Expo apps. It covers OAuth via expo-auth-session, secure token storage with expo-secure-store, an auth-context provider, protected routes with Expo Router, and backend Google token verification. A developer uses it when building login flows or debugging auth issues in an Expo/React Native app.

  • Expo AuthSession OAuth flows with Google/Apple sign-in
  • SecureStore token storage and refresh-token handling
  • Auth context + Expo Router protected-route pattern

Rn Auth by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #959 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

rn-auth capabilities & compatibility

Capabilities
oauth login · token storage · session management · protected routes
Use cases
api development · frontend
From the docs

What rn-auth says it does

Use `expo-secure-store` for tokens (not AsyncStorage):
SKILL.md
Missing `maybeCompleteAuthSession()`** - Auth redirects fail silently without this at module level
SKILL.md
npx skills add https://github.com/aiskillstore/marketplace --skill rn-auth

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
repo stars404
Last updatedAugust 5, 2026
Repositoryaiskillstore/marketplace

What it does

Implement Google/Apple sign-in, token storage, and session handling in an Expo React Native app.

Who is it for?

Adding Google/Apple sign-in, token management, and session handling to Expo React Native apps

Skip if: Non-Expo native auth or web-only apps

When should I use this skill?

Implementing login flows, token management, or debugging auth in an Expo/React Native app

What you get

A working OAuth login flow with secure token storage and protected routes.

  • Auth context provider
  • Token storage helper
  • Protected-route layout

By the numbers

  • 4 documented common pitfalls

Files

SKILL.mdMarkdownGitHub ↗

React Native Authentication (Expo)

Core Patterns

Expo AuthSession for OAuth

Use expo-auth-session with expo-web-browser for OAuth flows:

import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';
import * as Google from 'expo-auth-session/providers/google';

// Critical: Call this at module level for proper redirect handling
WebBrowser.maybeCompleteAuthSession();

// Inside component
const [request, response, promptAsync] = Google.useAuthRequest({
  iosClientId: 'YOUR_IOS_CLIENT_ID.apps.googleusercontent.com',
  webClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com', // For backend verification
  scopes: ['profile', 'email'],
});

Common Pitfalls

1. Missing `maybeCompleteAuthSession()` - Auth redirects fail silently without this at module level 2. Wrong client ID - iOS needs the iOS client ID, but backend verification needs the web client ID 3. Scheme mismatch - app.json scheme must match Google Cloud Console redirect URI 4. Expo Go vs standalone - Different redirect URIs; use AuthSession.makeRedirectUri() to handle both

Token Storage

Use expo-secure-store for tokens (not AsyncStorage):

import * as SecureStore from 'expo-secure-store';

const TOKEN_KEY = 'auth_token';
const REFRESH_KEY = 'refresh_token';

export const tokenStorage = {
  async save(token: string, refresh?: string) {
    await SecureStore.setItemAsync(TOKEN_KEY, token);
    if (refresh) {
      await SecureStore.setItemAsync(REFRESH_KEY, refresh);
    }
  },
  
  async get() {
    return SecureStore.getItemAsync(TOKEN_KEY);
  },
  
  async getRefresh() {
    return SecureStore.getItemAsync(REFRESH_KEY);
  },
  
  async clear() {
    await SecureStore.deleteItemAsync(TOKEN_KEY);
    await SecureStore.deleteItemAsync(REFRESH_KEY);
  },
};

Auth Context Pattern

import { createContext, useContext, useEffect, useState, ReactNode } from 'react';

type AuthState = {
  token: string | null;
  user: User | null;
  isLoading: boolean;
  signIn: (token: string, user: User) => Promise<void>;
  signOut: () => Promise<void>;
};

const AuthContext = createContext<AuthState | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [token, setToken] = useState<string | null>(null);
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // Restore session on mount
    async function restore() {
      try {
        const savedToken = await tokenStorage.get();
        if (savedToken) {
          // Validate token with backend before trusting it
          const userData = await validateToken(savedToken);
          setToken(savedToken);
          setUser(userData);
        }
      } catch {
        await tokenStorage.clear();
      } finally {
        setIsLoading(false);
      }
    }
    restore();
  }, []);

  const signIn = async (newToken: string, userData: User) => {
    await tokenStorage.save(newToken);
    setToken(newToken);
    setUser(userData);
  };

  const signOut = async () => {
    await tokenStorage.clear();
    setToken(null);
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ token, user, isLoading, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be inside AuthProvider');
  return ctx;
};

Protected Routes with Expo Router

// app/_layout.tsx
import { Slot, useRouter, useSegments } from 'expo-router';
import { useAuth } from '@/contexts/auth';
import { useEffect } from 'react';

export default function RootLayout() {
  const { token, isLoading } = useAuth();
  const segments = useSegments();
  const router = useRouter();

  useEffect(() => {
    if (isLoading) return;

    const inAuthGroup = segments[0] === '(auth)';

    if (!token && !inAuthGroup) {
      router.replace('/(auth)/login');
    } else if (token && inAuthGroup) {
      router.replace('/(app)/home');
    }
  }, [token, isLoading, segments]);

  if (isLoading) {
    return <LoadingScreen />;
  }

  return <Slot />;
}

Backend Integration

Sending Auth Headers

// api/client.ts
import { tokenStorage } from '@/utils/tokenStorage';

const API_BASE = process.env.EXPO_PUBLIC_API_URL;

async function authFetch(path: string, options: RequestInit = {}) {
  const token = await tokenStorage.get();
  
  const response = await fetch(`${API_BASE}${path}`, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...(token && { Authorization: `Bearer ${token}` }),
      ...options.headers,
    },
  });

  if (response.status === 401) {
    // Token expired - try refresh or force logout
    const refreshed = await attemptTokenRefresh();
    if (!refreshed) {
      await tokenStorage.clear();
      // Trigger auth state update (emit event or use callback)
    }
  }

  return response;
}

Google Token Verification (FastAPI backend)

# For reference: backend should verify Google tokens like this
from google.oauth2 import id_token
from google.auth.transport import requests

def verify_google_token(token: str, client_id: str) -> dict:
    """Verify Google ID token and return user info."""
    idinfo = id_token.verify_oauth2_token(
        token, 
        requests.Request(), 
        client_id  # Use WEB client ID here, not iOS
    )
    return {
        "google_id": idinfo["sub"],
        "email": idinfo["email"],
        "name": idinfo.get("name"),
    }

Debugging Auth Issues

Check redirect URI configuration

// Log the redirect URI being used
console.log('Redirect URI:', AuthSession.makeRedirectUri());

Compare this with what's configured in:

  • Google Cloud Console > Credentials > OAuth 2.0 Client IDs
  • app.json scheme field

Common error patterns

ErrorLikely Cause
"redirect_uri_mismatch"Redirect URI in console doesn't match app
Auth popup opens but nothing happensMissing maybeCompleteAuthSession()
Works in Expo Go, fails in buildUsing Expo Go redirect URI in standalone config
Token validation fails on backendUsing iOS client ID instead of web client ID for verification

Test auth flow

1. Clear all tokens: await tokenStorage.clear() 2. Force kill app 3. Reopen and verify redirect to login 4. Complete sign-in flow 5. Force kill and reopen - should stay logged in

Related skills

FAQ

Where should Expo auth tokens be stored?

Use expo-secure-store, not AsyncStorage, to save the access and refresh tokens.

Which Google client ID does the backend need?

iOS needs the iOS client ID, but backend verification needs the web client ID.

Mobile Developmentbackendfrontend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.