A protected API is an API endpoint that requires valid authentication before allowing access to protected data or operations. In this lesson, we will protect a PHP REST API using JWT authentication and connect it with a React Native application.
A protected API is an endpoint that cannot be used successfully without valid authentication.
Client
↓
Protected API
↓
Authentication Check
↓
Valid?
┌───┴───┐
Yes No
↓ ↓
Data 401
Many applications contain private information that should only be available to authenticated users.
| Public API | Protected API |
|---|---|
| May be accessed without login | Requires authentication |
| No JWT may be required | JWT is commonly required |
| Example: Public courses | Example: Student profile |
| Limited sensitive data | Private data |
React Native
↓
JWT Token
↓
Authorization Header
↓
Protected PHP API
↓
Verify JWT
↓
Identify User
↓
Return Protected Data
The React Native application can send the JWT using the Authorization header.
Authorization:
Bearer YOUR_JWT_TOKEN
$authorization =
$_SERVER['HTTP_AUTHORIZATION'] ?? '';
if ($authorization === '') {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Token required"
]);
exit;
}
if (
!preg_match(
'/Bearer\s+(.+)/i',
$authorization,
$matches
)
) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid authorization header"
]);
exit;
}
$token = trim($matches[1]);
Use a maintained JWT library for creating and verifying JWTs.
composer require firebase/php-jwt
The library provides the classes required for JWT verification.
require_once
__DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
The protected API needs the same verification key that corresponds to the signing method used when the JWT was created.
$secretKey =
"YOUR_SECURE_SERVER_SECRET";
try {
$decoded = JWT::decode(
$token,
new Key($secretKey, 'HS256')
);
} catch (Throwable $e) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Invalid token"
]);
exit;
}
Suppose the login API placed the user ID in the sub claim.
$userId = $decoded->sub ?? null;
if (!$userId) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid token payload"
]);
exit;
}
$stmt = $pdo->prepare(
"SELECT id, name, email
FROM users
WHERE id = ?
LIMIT 1"
);
$stmt->execute([$userId]);
$user = $stmt->fetch(
PDO::FETCH_ASSOC
);
if (!$user) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "User not found"
]);
exit;
}
This prevents the API from continuing when the JWT refers to a user that does not exist.
After successful authentication, the API can return the user's protected profile information.
echo json_encode([
"success" => true,
"message" =>
"Profile loaded",
"user" => $user
]);
<?php
header(
"Content-Type: application/json"
);
require_once '../db.php';
require_once
__DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secretKey =
"YOUR_SECURE_SERVER_SECRET";
try {
$authorization =
$_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (
!preg_match(
'/Bearer\s+(.+)/i',
$authorization,
$matches
)
) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Token required"
]);
exit;
}
$token = trim($matches[1]);
$decoded = JWT::decode(
$token,
new Key($secretKey, 'HS256')
);
$userId =
$decoded->sub ?? null;
if (!$userId) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid token payload"
]);
exit;
}
$stmt = $pdo->prepare(
"SELECT id, name, email
FROM users
WHERE id = ?
LIMIT 1"
);
$stmt->execute([$userId]);
$user =
$stmt->fetch(
PDO::FETCH_ASSOC
);
if (!$user) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "User not found"
]);
exit;
}
http_response_code(200);
echo json_encode([
"success" => true,
"message" =>
"Profile loaded successfully",
"user" => $user
]);
} catch (Throwable $e) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid or expired token"
]);
}
?>
The same authentication process can protect a student API.
GET /api/student.php
Authorization:
Bearer JWT
After verification, the API can use the authenticated user's ID to retrieve the appropriate student information.
JWT
↓
User ID
↓
Student Table
↓
Find Student
↓
Return Student Data
For example, the API could return a student's name, course, attendance, or other information that the authenticated user is permitted to access.
If the token is missing, invalid, malformed, or expired, the API should normally return HTTP 401.
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Authentication required"
]);
A valid JWT proves that the request is authenticated, but the user may still not have permission to perform every operation.
JWT Valid
↓
Identify User
↓
Check Permission
↓
Allowed?
┌───┴───┐
Yes No
↓ ↓
Allow 403
Suppose the JWT contains a role claim.
{
"sub": "101",
"role": "student"
}
An admin-only endpoint can check the user's role after authentication.
if ($decoded->role !== 'admin') {
http_response_code(403);
echo json_encode([
"success" => false,
"message" =>
"Access denied"
]);
exit;
}
const response = await fetch(
"https://example.com/api/profile.php",
{
method: "GET",
headers: {
"Authorization":
"Bearer " + token,
"Accept":
"application/json"
}
}
);
const data =
await response.json();
console.log(data);
if (response.ok) {
console.log(
"Protected data:",
data
);
} else if (
response.status === 401
) {
console.log(
"Login required"
);
} else if (
response.status === 403
) {
console.log(
"Access denied"
);
}
The same JWT can be sent using Axios.
axios.get(
"https://example.com/api/profile.php",
{
headers: {
Authorization:
"Bearer " + token
}
}
)
.then(response => {
console.log(
response.data
);
});
Authentication can protect not only GET APIs but also POST, PUT, PATCH, and DELETE operations.
POST /api/student.php
Authorization:
Bearer JWT
Content-Type:
application/json
The API should verify authentication before processing the operation.
A DELETE API should verify the authenticated user and authorization before deleting data.
DELETE /api/student.php?id=15
Authorization:
Bearer JWT
A valid JWT alone does not necessarily mean the user has permission to delete the selected record.
React Native
↓
Login API
↓
JWT
↓
Token Storage
↓
Protected Request
↓
Authorization Header
↓
PHP API
↓
Verify JWT
↓
Identify User
↓
Check Permission
↓
MySQL Query
↓
JSON Response
Step 1: Login first.
POST
http://localhost/api/jwt_login.php
Step 2: Copy the JWT from the response.
Step 3: Open the protected endpoint.
GET
http://localhost/api/profile.php
Step 4: Add:
Authorization:
Bearer YOUR_JWT_TOKEN
Step 5: Send the request.
A valid token should allow the API to return protected data. Removing the token or changing it should result in an authentication error.
A protected API verifies the user's JWT before providing private resources. The React Native application sends the JWT in the Authorization header. The PHP server verifies the token, identifies the user, checks authorization when necessary, performs the database operation, and returns the protected JSON response.
React Native
↓
Bearer JWT
↓
Protected API
↓
Verify JWT
↓
Identify User
↓
Check Permission
↓
Database
↓
Protected JSON Response
Question: What should a protected API do before returning private user data?