In the previous lessons, we learned how to create JWT tokens and store authentication information in React Native. Now we will use Axios to automatically send the JWT token with protected API requests.
An authenticated Axios request includes the JWT token in the
Authorization header.
Authorization: Bearer YOUR_JWT_TOKEN
This allows the PHP API to identify the authenticated user.
Login
↓
PHP Login API
↓
JWT Token
↓
React Native
↓
Store Token
↓
Axios Request
↓
Authorization Header
↓
PHP JWT Verification
↓
Protected Data
import axios from "axios";
Axios provides methods such as get(),
post(), put(), and delete()
for communicating with REST APIs.
const api = axios.create({
baseURL: "https://example.com/api",
headers: {
"Content-Type": "application/json"
}
});
The Axios instance provides a common configuration for API requests.
import AsyncStorage
from "@react-native-async-storage/async-storage";
const token =
await AsyncStorage.getItem(
"authToken"
);
The returned value is either a string or null.
const token: string | null =
await AsyncStorage.getItem(
"authToken"
);
TypeScript requires us to handle the possibility that the token does not exist.
const token =
await AsyncStorage.getItem(
"authToken"
);
const response =
await api.get(
"/profile.php",
{
headers: {
Authorization:
`Bearer ${token}`
}
}
);
const token =
await AsyncStorage.getItem(
"authToken"
);
if (!token) {
console.log(
"Authentication required"
);
return;
}
const response =
await api.get(
"/profile.php",
{
headers: {
Authorization:
`Bearer ${token}`
}
}
);
interface User {
id: number;
name: string;
email: string;
role: "admin" | "teacher" | "student";
}
interface UserResponse {
success: boolean;
message: string;
user: User;
}
Axios can use this interface to provide type checking for the API response.
const response =
await api.get<UserResponse>(
"/profile.php",
{
headers: {
Authorization:
`Bearer ${token}`
}
}
);
console.log(
response.data.user.name
);
const getToken =
async (): Promise<
string | null
> => {
return await AsyncStorage.getItem(
"authToken"
);
};
A helper function avoids repeating the AsyncStorage code.
const getProfile =
async (): Promise<UserResponse> => {
const token =
await getToken();
if (!token) {
throw new Error(
"Authentication required"
);
}
const response =
await api.get<UserResponse>(
"/profile.php",
{
headers: {
Authorization:
`Bearer ${token}`
}
}
);
return response.data;
};
An Axios request interceptor can automatically add the token to requests.
api.interceptors.request.use(
async config => {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (token) {
config.headers.Authorization =
`Bearer ${token}`;
}
return config;
}
);
Without an interceptor, every protected request may need to manually add the Authorization header.
api.get("/profile.php", {
headers: {
Authorization: `Bearer ${token}`
}
});
api.get("/students.php", {
headers: {
Authorization: `Bearer ${token}`
}
});
An interceptor centralizes this repeated behavior.
api.interceptors.request.use(
async config => {
const token =
await getToken();
if (token) {
config.headers.Authorization =
`Bearer ${token}`;
}
return config;
}
);
Now the token can be added automatically before the request is sent.
const response =
await api.get<UserResponse>(
"/profile.php"
);
console.log(
response.data.user.name
);
If the interceptor is configured correctly, the token is added automatically.
const response =
await api.post(
"/students.php",
{
name: "Ravi",
course: "React Native"
}
);
The request interceptor can also attach the JWT to POST requests.
const response =
await api.put(
"/students.php?id=10",
{
name: "Ravi Kumar",
course: "TypeScript"
}
);
The same authentication mechanism can protect update operations.
const response =
await api.delete(
"/students.php?id=10"
);
The JWT is also available to protected DELETE endpoints through the interceptor.
If the JWT is missing, invalid, or expired, the server may return HTTP 401.
try {
const response =
await api.get(
"/profile.php"
);
} catch (error) {
if (
axios.isAxiosError(error) &&
error.response?.status === 401
) {
console.log(
"Please login again"
);
}
}
A response interceptor can centralize handling of authentication errors.
api.interceptors.response.use(
response => response,
async error => {
if (
error.response?.status === 401
) {
await AsyncStorage.removeItem(
"authToken"
);
console.log(
"Authentication expired"
);
}
return Promise.reject(error);
}
);
interface AuthState {
token: string | null;
user: User | null;
isAuthenticated: boolean;
}
The token stored locally and the current authentication state should be kept consistent.
import AsyncStorage
from "@react-native-async-storage/async-storage";
export const getToken =
async (): Promise<
string | null
> => {
return await AsyncStorage.getItem(
"authToken"
);
};
export const saveToken =
async (token: string) => {
await AsyncStorage.setItem(
"authToken",
token
);
};
export const removeToken =
async () => {
await AsyncStorage.removeItem(
"authToken"
);
};
import axios from "axios";
import AsyncStorage
from "@react-native-async-storage/async-storage";
const api = axios.create({
baseURL: "https://example.com/api",
headers: {
"Content-Type": "application/json"
}
});
api.interceptors.request.use(
async config => {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (token) {
config.headers.Authorization =
`Bearer ${token}`;
}
return config;
}
);
export default api;
const result =
await login({
email,
password
});
if (result.success) {
await AsyncStorage.setItem(
"authToken",
result.token
);
console.log(
"Login successful"
);
}
Future requests made through the configured Axios instance can automatically use this token.
interface User {
id: number;
name: string;
email: string;
role: "admin" | "teacher" | "student";
}
interface UserResponse {
success: boolean;
message: string;
user: User;
}
export const getProfile =
async (): Promise<UserResponse> => {
const response =
await api.get<UserResponse>(
"/profile.php"
);
return response.data;
};
export const getStudents =
async () => {
const response =
await api.get(
"/students.php"
);
return response.data;
};
const loadProfile = async () => {
try {
setLoading(true);
const result =
await getProfile();
if (result.success) {
setUser(result.user);
}
} catch (error) {
if (
axios.isAxiosError(error) &&
error.response?.status === 401
) {
setError(
"Please login again"
);
} else {
setError(
"Unable to load profile"
);
}
} finally {
setLoading(false);
}
};
Login API
↓
JWT Token
↓
AsyncStorage
↓
Axios Request Interceptor
↓
Authorization: Bearer JWT
↓
PHP Protected API
↓
JWT Verification
↓
Protected Response
↓
Axios Response
↓
React Native UI
This architecture keeps authentication logic centralized and avoids manually adding the JWT to every protected request.
1. User logs in
2. PHP verifies credentials
3. PHP returns JWT
4. React Native stores the token
5. Axios instance is configured
6. Request interceptor reads the token
7. Authorization header is added
8. Protected API receives the request
9. PHP verifies the JWT
10. PHP returns protected data
11. React Native displays the data
12. 401 response can trigger authentication cleanup
Axios interceptors provide a clean way to connect JWT authentication with React Native REST APIs. TypeScript interfaces can be used to strongly type login responses, protected API responses, and user data.
string | null.Question: Which Axios feature can automatically add a JWT token before every request?