In a React Native application, many screens need authentication information such as the current user, JWT token, and login status. React Context allows us to keep authentication state in one central place and make it available to different components.
Auth Context is a React Context used to share authentication information throughout the application.
It can contain:
Without Context, authentication data may need to be passed through many components using props.
App
↓
Home
↓
Profile
↓
StudentProfile
Auth Context allows these components to access authentication state without passing the same props through every level.
interface User {
id: number;
name: string;
email: string;
role: "admin" | "teacher" | "student";
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
loading: boolean;
}
import {
createContext
} from "react";
interface AuthContextType {
user: User | null;
token: string | null;
isAuthenticated: boolean;
}
export const AuthContext =
createContext<AuthContextType | undefined>(
undefined
);
interface AuthContextType {
user: User | null;
token: string | null;
isAuthenticated: boolean;
loading: boolean;
login: (
token: string,
user: User
) => Promise<void>;
logout: () => Promise<void>;
}
This interface describes everything that the authentication context will provide to components.
import React, {
useState
} from "react";
export const AuthProvider = ({
children
}: {
children: React.ReactNode;
}) => {
return (
<>
{children}
</>
);
};
const [user, setUser] =
useState<User | null>(null);
Initially there may be no authenticated user, so the state starts with
null.
const [token, setToken] =
useState<string | null>(null);
The token can be stored in memory while the application is running. Persistent storage can also be used to restore authentication after the application starts again.
const isAuthenticated =
!!token;
If a valid token is available in application state, the application can consider the user authenticated. The server must still verify the token for protected API requests.
const [loading, setLoading] =
useState(true);
Loading is useful while the application checks AsyncStorage and restores a previous authentication session.
const login = async (
newToken: string,
newUser: User
) => {
await AsyncStorage.setItem(
"authToken",
newToken
);
setToken(newToken);
setUser(newUser);
};
The login function updates both persistent storage and React state.
const logout = async () => {
await AsyncStorage.removeItem(
"authToken"
);
setToken(null);
setUser(null);
};
Removing the local token prevents the client from using that stored token for future requests.
useEffect(() => {
const restoreAuth =
async () => {
const savedToken =
await AsyncStorage.getItem(
"authToken"
);
if (savedToken) {
setToken(savedToken);
}
setLoading(false);
};
restoreAuth();
}, []);
A stored token alone may not contain all the user information required by the application. After restoring the token, the app can call a protected profile endpoint.
const response =
await api.get<UserResponse>(
"/profile.php"
);
setUser(
response.data.user
);
const value: AuthContextType = {
user,
token,
isAuthenticated: !!token,
loading,
login,
logout
};
This object contains the authentication data and functions that will be available to child components.
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
Every component inside the provider can access the authentication context.
const App = () => {
return (
<AuthProvider>
<AppNavigator />
</AuthProvider>
);
};
import {
useContext
} from "react";
const auth =
useContext(AuthContext);
The component can now access authentication information from the context.
export const useAuth = () => {
const context =
useContext(AuthContext);
if (!context) {
throw new Error(
"useAuth must be used inside AuthProvider"
);
}
return context;
};
A custom hook makes authentication access easier throughout the application.
const ProfileScreen = () => {
const {
user,
logout
} = useAuth();
return (
<View>
<Text>
Welcome {user?.name}
</Text>
<Button
title="Logout"
onPress={logout}
/>
</View>
);
};
const LoginScreen = () => {
const {
login
} = useAuth();
const handleLogin = async () => {
const response =
await api.post(
"/login.php",
{
email,
password
}
);
await login(
response.data.token,
response.data.user
);
};
return (
<Button
title="Login"
onPress={handleLogin}
/>
);
};
Auth Context manages the authentication state while Axios handles communication with the REST API.
Auth Context
↓
Token
↓
AsyncStorage
↓
Axios Interceptor
↓
Authorization Header
↓
PHP API
try {
const response =
await api.get(
"/profile.php"
);
} catch (error) {
if (
axios.isAxiosError(error) &&
error.response?.status === 401
) {
await logout();
}
}
A 401 response can indicate that the token is no longer accepted by the server.
Auth Context can be used to decide which navigation flow should be displayed.
if (loading) {
return <LoadingScreen />;
}
if (isAuthenticated) {
return <MainNavigator />;
}
return <AuthNavigator />;
This controls the user experience. The backend must still protect its APIs.
import React, {
createContext,
useContext,
useEffect,
useState
} from "react";
import AsyncStorage
from "@react-native-async-storage/async-storage";
interface User {
id: number;
name: string;
email: string;
role: "admin" | "teacher" | "student";
}
interface AuthContextType {
user: User | null;
token: string | null;
isAuthenticated: boolean;
loading: boolean;
login: (
token: string,
user: User
) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext =
createContext<
AuthContextType | undefined
>(undefined);
export const AuthProvider = ({
children
}: {
children: React.ReactNode;
}) => {
const [user, setUser] =
useState<User | null>(null);
const [token, setToken] =
useState<string | null>(null);
const [loading, setLoading] =
useState(true);
const login = async (
newToken: string,
newUser: User
) => {
await AsyncStorage.setItem(
"authToken",
newToken
);
setToken(newToken);
setUser(newUser);
};
const logout = async () => {
await AsyncStorage.removeItem(
"authToken"
);
setToken(null);
setUser(null);
};
useEffect(() => {
const restoreAuth =
async () => {
const savedToken =
await AsyncStorage.getItem(
"authToken"
);
if (savedToken) {
setToken(savedToken);
}
setLoading(false);
};
restoreAuth();
}, []);
const value: AuthContextType = {
user,
token,
isAuthenticated: !!token,
loading,
login,
logout
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context =
useContext(AuthContext);
if (!context) {
throw new Error(
"useAuth must be used inside AuthProvider"
);
}
return context;
};
Screens can now simply call useAuth() instead of directly
accessing the Context object.
const restoreAuth =
async () => {
const savedToken =
await AsyncStorage.getItem(
"authToken"
);
if (!savedToken) {
setLoading(false);
return;
}
try {
const response =
await api.get<UserResponse>(
"/profile.php"
);
setToken(savedToken);
setUser(
response.data.user
);
} catch (error) {
await AsyncStorage.removeItem(
"authToken"
);
setToken(null);
setUser(null);
} finally {
setLoading(false);
}
};
This approach verifies the stored session through the protected API before treating the user as fully authenticated.
src/
│
├── context/
│ └── AuthContext.tsx
│
├── services/
│ └── api.ts
│
├── screens/
│ ├── LoginScreen.tsx
│ ├── ProfileScreen.tsx
│ └── HomeScreen.tsx
│
├── navigation/
│ └── AppNavigator.tsx
│
└── types/
└── auth.ts
Keeping authentication logic in a dedicated context makes a project easier to maintain.
Auth Context improves application architecture, but it does not replace server-side security.
Login Screen
↓
Login API
↓
JWT Token
↓
Auth Context
↓
AsyncStorage
↓
Axios Interceptor
↓
Protected API
↓
PHP JWT Verification
↓
User Identification
↓
Authorization
↓
MySQL Database
↓
JSON Response
↓
React Native Screen
Auth Context becomes the central authentication layer of the React Native application. It keeps login, logout, user state, token state, and loading state organized while Axios communicates with the protected PHP REST API.
AsyncStorage can persist the authentication token.useAuth() hook makes authentication easier to access.Question: What is the main purpose of an Auth Context in React Native?