A protected API is an API endpoint that can only be accessed after the user has been authenticated. In a React Native application, the JWT token is normally sent with the request, and the PHP REST API verifies the token before returning protected data.
A protected API requires authentication before returning protected information or performing a protected operation.
GET /api/profile.php
The API may require a valid JWT in the Authorization header.
React Native
↓
JWT Token
↓
Authorization Header
↓
PHP Protected API
↓
Verify JWT
↓
Identify User
↓
Check Permission
↓
Return Data
Public API:
GET /api/courses.php
Protected API:
GET /api/profile.php
Authorization: Bearer JWT_TOKEN
A protected endpoint requires authentication before processing the request.
React Native can send the JWT using the Authorization header.
Authorization: Bearer YOUR_JWT_TOKEN
The server extracts the token and verifies it.
const response =
await api.get(
"/profile.php",
{
headers: {
Authorization:
`Bearer ${token}`
}
}
);
Instead of manually adding the token to every request, an Axios request interceptor can add it automatically.
api.interceptors.request.use(
async config => {
const token =
await AsyncStorage.getItem(
"authToken"
);
if (token) {
config.headers.Authorization =
`Bearer ${token}`;
}
return config;
}
);
interface User {
id: number;
name: string;
email: string;
role: "admin" | "teacher" | "student";
}
interface UserResponse {
success: boolean;
message: string;
user: User;
}
TypeScript can use this interface to describe the response from the protected profile API.
const response =
await api.get<UserResponse>(
"/profile.php"
);
console.log(
response.data.user.name
);
$headers = getallheaders();
$authorization =
$headers["Authorization"] ?? "";
if ($authorization === "") {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Authentication required"
]);
exit;
}
if (
!preg_match(
'/Bearer\s(\S+)/',
$authorization,
$matches
)
) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Invalid authorization header"
]);
exit;
}
$token = $matches[1];
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
try {
$decoded = JWT::decode(
$token,
new Key(
$secretKey,
"HS256"
)
);
} catch (Exception $e) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Invalid or expired token"
]);
exit;
}
The JWT can contain the user ID in the sub claim.
$userId = $decoded->sub;
The API can use this authenticated identity when retrieving user-specific data.
$stmt = $pdo->prepare(
"SELECT id, name, email, role
FROM users
WHERE id = ?"
);
$stmt->execute([
$userId
]);
$user = $stmt->fetch(
PDO::FETCH_ASSOC
);
Prepared statements should be used for database queries.
if (!$user) {
http_response_code(404);
echo json_encode([
"success" => false,
"message" => "User not found"
]);
exit;
}
A valid token does not guarantee that the related database record still exists.
echo json_encode([
"success" => true,
"message" => "Profile loaded",
"user" => [
"id" => (int)$user["id"],
"name" => $user["name"],
"email" => $user["email"],
"role" => $user["role"]
]
]);
Never return the user's password or password hash in the API response.
const loadProfile = async () => {
try {
const response =
await api.get<UserResponse>(
"/profile.php"
);
setUser(
response.data.user
);
} catch (error) {
console.log(
"Unable to load profile"
);
}
};
const [loading, setLoading] =
useState(false);
const loadProfile = async () => {
try {
setLoading(true);
const response =
await api.get<UserResponse>(
"/profile.php"
);
setUser(
response.data.user
);
} finally {
setLoading(false);
}
};
The API can return HTTP 401 when authentication is missing, invalid, or expired.
if (
axios.isAxiosError(error) &&
error.response?.status === 401
) {
console.log(
"Please login again"
);
}
HTTP 403 can be used when the user is authenticated but does not have permission to perform an operation.
if (
axios.isAxiosError(error) &&
error.response?.status === 403
) {
console.log(
"Access denied"
);
}
| Concept | Question | Example |
|---|---|---|
| Authentication | Who are you? | Valid JWT |
| Authorization | What can you do? | Admin permission |
A protected API may require both authentication and authorization.
The server can check the authenticated user's role before allowing access to an admin-only endpoint.
if ($decoded->role !== "admin") {
http_response_code(403);
echo json_encode([
"success" => false,
"message" => "Access denied"
]);
exit;
}
Authorization must be enforced by the server, not only by the mobile UI.
A protected student endpoint can use the authenticated user's ID to return only data belonging to that user.
SELECT id, name, course
FROM students
WHERE user_id = ?
This prevents the client from simply choosing another user's ID and accessing their private data.
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 UserResponse {
success: boolean;
message: string;
user: User;
}
export const getProfile =
async (): Promise<UserResponse> => {
const response =
await api.get<UserResponse>(
"/profile.php"
);
return response.data;
};
The Axios interceptor can add the token automatically.
const [user, setUser] =
useState<User | null>(null);
const loadProfile = async () => {
try {
const result =
await getProfile();
if (result.success) {
setUser(result.user);
}
} catch (error) {
if (
axios.isAxiosError(error) &&
error.response?.status === 401
) {
console.log(
"Login required"
);
} else {
console.log(
"Unable to load profile"
);
}
}
};
useEffect(() => {
loadProfile();
}, []);
interface ApiResponse<T> {
success: boolean;
message: string;
data: T;
}
interface Student {
id: number;
name: string;
course: string;
}
type StudentResponse =
ApiResponse<Student>;
type StudentListResponse =
ApiResponse<Student[]>;
Generic response types can be reused across protected API endpoints.
React Native
↓
Axios
↓
Request Interceptor
↓
Bearer JWT
↓
PHP API
↓
Extract Token
↓
Verify JWT
↓
Identify User
↓
Check Authorization
↓
Query MySQL
↓
JSON Response
↓
React Native
<?php
header(
"Content-Type: application/json"
);
require_once "../db.php";
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secretKey =
"YOUR_LONG_SECRET_KEY";
$headers = getallheaders();
$authorization =
$headers["Authorization"] ?? "";
if (
!preg_match(
'/Bearer\s(\S+)/',
$authorization,
$matches
)
) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Token required"
]);
exit;
}
$token = $matches[1];
try {
$decoded = JWT::decode(
$token,
new Key(
$secretKey,
"HS256"
)
);
$stmt = $pdo->prepare(
"SELECT id, name, email, role
FROM users
WHERE id = ?"
);
$stmt->execute([
$decoded->sub
]);
$user =
$stmt->fetch(
PDO::FETCH_ASSOC
);
if (!$user) {
http_response_code(404);
echo json_encode([
"success" => false,
"message" => "User not found"
]);
exit;
}
echo json_encode([
"success" => true,
"message" => "Profile loaded",
"user" => [
"id" => (int)$user["id"],
"name" => $user["name"],
"email" => $user["email"],
"role" => $user["role"]
]
]);
} catch (Exception $e) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Invalid or expired token"
]);
}
1. User logs in
2. PHP verifies credentials
3. PHP creates JWT
4. React Native stores JWT
5. Axios reads JWT
6. Request interceptor adds Bearer token
7. Protected API receives request
8. PHP extracts JWT
9. PHP verifies signature and expiration
10. PHP identifies authenticated user
11. PHP checks authorization
12. PHP queries the database
13. PHP returns JSON
14. React Native receives typed response
15. UI displays protected data
Protected APIs are the foundation of authenticated mobile applications. React Native handles the user experience and sends the token, while the PHP backend must perform the actual authentication and authorization checks before returning protected information.
Question: Who must enforce authentication and authorization for a protected REST API?