
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)
rn-auth capabilities & compatibility
- Capabilities
- oauth login · token storage · session management · protected routes
- Use cases
- api development · frontend
What rn-auth says it does
Use `expo-secure-store` for tokens (not AsyncStorage):
Missing `maybeCompleteAuthSession()`** - Auth redirects fail silently without this at module level
npx skills add https://github.com/aiskillstore/marketplace --skill rn-authAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 404 |
| Last updated | August 5, 2026 |
| Repository | aiskillstore/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
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.jsonscheme field
Common error patterns
| Error | Likely Cause |
|---|---|
| "redirect_uri_mismatch" | Redirect URI in console doesn't match app |
| Auth popup opens but nothing happens | Missing maybeCompleteAuthSession() |
| Works in Expo Go, fails in build | Using Expo Go redirect URI in standalone config |
| Token validation fails on backend | Using 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
{
"schema_version": "2.0",
"meta": {
"generated_at": "2026-01-16T21:21:23.476Z",
"slug": "cjharmath-rn-auth",
"source_url": "https://github.com/CJHarmath/claude-agents-skills/tree/main/skills/rn-auth",
"source_ref": "main",
"model": "claude",
"analysis_version": "3.0.0",
"source_type": "community",
"content_hash": "abf96cd6fbf9a27e1199c5816c1d4041e61bbf07aba7d85aed7a4219bcedfff9",
"tree_hash": "e19da0c99e3c451011da49921066c8b938f14fb932a899b3a8ccdefbdca48f19"
},
"skill": {
"name": "rn-auth",
"description": "React Native authentication patterns for Expo apps. Use when implementing login flows, Google/Apple sign-in, token management, session handling, or debugging auth issues in Expo/React Native.",
"summary": "React Native authentication patterns for Expo apps. Use when implementing login flows, Google/Apple ...",
"icon": "🔐",
"version": "1.0.0",
"author": "CJHarmath",
"license": "MIT",
"category": "coding",
"tags": [
"React Native",
"Expo",
"Authentication",
"OAuth",
"Mobile"
],
"supported_tools": [
"claude",
"codex",
"claude-code"
],
"risk_factors": [
"network",
"external_commands",
"env_access"
]
},
"security_audit": {
"risk_level": "safe",
"is_blocked": false,
"safe_to_publish": true,
"summary": "This is a pure documentation skill containing only TypeScript/Python code examples. No executable code, scripts, network calls, file system access, environment variable reads, or external commands exist. All 42 static findings are false positives from pattern misidentification. The scanner misidentified library documentation references as security issues.",
"risk_factor_evidence": [
{
"factor": "network",
"evidence": [
{
"file": "skill-report.json",
"line_start": 157,
"line_end": 157
},
{
"file": "skill-report.json",
"line_start": 6,
"line_end": 6
},
{
"file": "SKILL.md",
"line_start": 182,
"line_end": 182
},
{
"file": "SKILL.md",
"line_start": 215,
"line_end": 215
}
]
},
{
"factor": "external_commands",
"evidence": [
{
"file": "SKILL.md",
"line_start": 12,
"line_end": 12
},
{
"file": "SKILL.md",
"line_start": 12,
"line_end": 12
},
{
"file": "SKILL.md",
"line_start": 14,
"line_end": 28
},
{
"file": "SKILL.md",
"line_start": 28,
"line_end": 32
},
{
"file": "SKILL.md",
"line_start": 32,
"line_end": 34
},
{
"file": "SKILL.md",
"line_start": 34,
"line_end": 35
},
{
"file": "SKILL.md",
"line_start": 35,
"line_end": 39
},
{
"file": "SKILL.md",
"line_start": 39,
"line_end": 41
},
{
"file": "SKILL.md",
"line_start": 41,
"line_end": 68
},
{
"file": "SKILL.md",
"line_start": 68,
"line_end": 72
},
{
"file": "SKILL.md",
"line_start": 72,
"line_end": 134
},
{
"file": "SKILL.md",
"line_start": 134,
"line_end": 138
},
{
"file": "SKILL.md",
"line_start": 138,
"line_end": 167
},
{
"file": "SKILL.md",
"line_start": 167,
"line_end": 173
},
{
"file": "SKILL.md",
"line_start": 173,
"line_end": 182
},
{
"file": "SKILL.md",
"line_start": 182,
"line_end": 186
},
{
"file": "SKILL.md",
"line_start": 186,
"line_end": 202
},
{
"file": "SKILL.md",
"line_start": 202,
"line_end": 206
},
{
"file": "SKILL.md",
"line_start": 206,
"line_end": 223
},
{
"file": "SKILL.md",
"line_start": 223,
"line_end": 229
},
{
"file": "SKILL.md",
"line_start": 229,
"line_end": 232
},
{
"file": "SKILL.md",
"line_start": 232,
"line_end": 236
},
{
"file": "SKILL.md",
"line_start": 236,
"line_end": 243
},
{
"file": "SKILL.md",
"line_start": 243,
"line_end": 249
}
]
},
{
"factor": "env_access",
"evidence": [
{
"file": "SKILL.md",
"line_start": 177,
"line_end": 177
},
{
"file": "SKILL.md",
"line_start": 177,
"line_end": 177
}
]
}
],
"critical_findings": [],
"high_findings": [],
"medium_findings": [],
"low_findings": [],
"dangerous_patterns": [],
"files_scanned": 2,
"total_lines": 435,
"audit_model": "claude",
"audited_at": "2026-01-16T21:21:23.476Z"
},
"content": {
"user_title": "Implement React Native authentication in Expo apps",
"value_statement": "React Native authentication requires careful handling of OAuth flows, secure token storage, and session management. This skill provides battle-tested patterns for implementing login with Google, token storage with SecureStore, protected routes with Expo Router, and backend integration.",
"seo_keywords": [
"React Native authentication",
"Expo AuthSession",
"Google sign-in React Native",
"expo-secure-store",
"OAuth patterns",
"React Native token management",
"Expo Router protected routes",
"mobile authentication",
"Claude Code",
"Claude"
],
"actual_capabilities": [
"Implement Google OAuth using expo-auth-session with proper redirect handling",
"Store tokens securely using expo-secure-store instead of AsyncStorage",
"Create authentication context with session restoration on app launch",
"Protect routes using Expo Router segments and auth state",
"Send authenticated API requests with Bearer tokens",
"Debug common authentication issues like redirect URI mismatch"
],
"limitations": [
"Contains documentation and code patterns only, no executable implementation",
"Does not include backend server code beyond Python reference examples",
"Apple Sign-In patterns are mentioned but not fully detailed",
"Does not cover biometric authentication or passkey integration"
],
"use_cases": [
{
"target_user": "Mobile developers",
"title": "Add Google login to Expo app",
"description": "Implement OAuth flow with expo-auth-session including proper token storage and session persistence"
},
{
"target_user": "Full-stack developers",
"title": "Build auth-protected mobile API client",
"description": "Create authenticated API client that automatically attaches tokens and handles 401 responses with refresh"
},
{
"target_user": "React Native developers",
"title": "Add route protection to Expo Router",
"description": "Implement auth gates that redirect users to login when accessing protected screens"
}
],
"prompt_templates": [
{
"title": "Basic Google Sign-In",
"scenario": "Add Google authentication to Expo app",
"prompt": "Show me how to implement Google sign-in using expo-auth-session in my React Native Expo app. Include proper redirect handling and token storage."
},
{
"title": "Secure Token Storage",
"scenario": "Store authentication tokens securely",
"prompt": "How do I securely store and retrieve authentication tokens in React Native using expo-secure-store? Show me the complete implementation."
},
{
"title": "Protected Routes",
"scenario": "Guard routes behind authentication",
"prompt": "How do I protect routes in Expo Router so unauthenticated users are redirected to login? Show the auth context and route guard implementation."
},
{
"title": "API Client with Auth",
"scenario": "Make authenticated API requests",
"prompt": "Create an authenticated API client for my Expo app that automatically adds Bearer tokens, handles 401 errors, and attempts token refresh on expiry."
}
],
"output_examples": [
{
"input": "How do I implement Google sign-in in my Expo React Native app?",
"output": [
"Use expo-auth-session with expo-web-browser for OAuth flows",
"Call WebBrowser.maybeCompleteAuthSession() at module level for proper redirect handling",
"Use expo-secure-store for token storage (not AsyncStorage)",
"Get iOS client ID for device, web client ID for backend verification",
"Use AuthSession.makeRedirectUri() to handle both Expo Go and standalone builds",
"Validate tokens with your backend before trusting user data from OAuth providers"
]
},
{
"input": "How do I protect routes in my Expo Router app so unauthenticated users see a login screen?",
"output": [
"Create an AuthContext with useState for token and user state",
"Use useEffect to restore session on app launch by reading from SecureStore",
"Implement route guards in root layout using useSegments() to check route groups",
"Redirect to /login when no token exists and user is not in auth group",
"Show loading screen while session restoration is in progress"
]
}
],
"best_practices": [
"Always call maybeCompleteAuthSession() at module level to handle OAuth redirects properly",
"Use expo-secure-store for tokens, never AsyncStorage which is not encrypted",
"Validate tokens with your backend before trusting user data from OAuth providers",
"Handle 401 responses by attempting token refresh or forcing logout"
],
"anti_patterns": [
"Storing tokens in AsyncStorage - it is not encrypted and accessible to any app code",
"Skipping maybeCompleteAuthSession() - causes silent auth redirect failures",
"Using iOS client ID for backend token verification - always use web client ID server-side",
"Not handling different redirect URIs for Expo Go versus standalone builds"
],
"faq": [
{
"question": "Does this skill work with Expo Go and standalone builds?",
"answer": "Yes. Use AuthSession.makeRedirectUri() to automatically generate the correct redirect URI for each environment."
},
{
"question": "What React Native versions are supported?",
"answer": "This skill targets Expo SDK 48+ with expo-auth-session 6+. Compatible with React Native 0.73 and later."
},
{
"question": "How do I integrate with my existing backend?",
"answer": "Use the authFetch pattern that attaches Bearer tokens to requests. Handle 401 responses with token refresh or logout."
},
{
"question": "Is my authentication data stored securely?",
"answer": "The skill recommends expo-secure-store which uses iOS Keychain and Android Keystore. Never use AsyncStorage for tokens."
},
{
"question": "Why does auth fail silently in my app?",
"answer": "Most common cause is missing WebBrowser.maybeCompleteAuthSession() call at module level. Also check redirect URI matching."
},
{
"question": "How does this compare to Firebase Auth?",
"answer": "This skill shows native OAuth patterns. Firebase Auth provides a higher-level abstraction but requires the Firebase SDK and Google services."
}
]
},
"file_structure": [
{
"name": "SKILL.md",
"type": "file",
"path": "SKILL.md",
"lines": 254
}
]
}
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.