JWT verification is the process of checking whether a JSON Web Token is valid, correctly signed, and acceptable for the requested API operation. After a user logs in and receives a JWT, the React Native application sends that token with protected API requests. The server must verify the token before returning protected data.
JWT verification checks whether a received JWT is valid and can be trusted by the API.
React Native
↓
Protected API
↓
Receive JWT
↓
Verify JWT
↓
Valid?
┌───┴───┐
Yes No
↓ ↓
Allow 401
A client can send any string as a token. The API must not assume that the token is valid.
Authorization Header
↓
Extract JWT
↓
Decode and Verify
↓
Verify Signature
↓
Check Claims
↓
Valid JWT?
┌────┴────┐
Yes No
↓ ↓
Identify HTTP 401
User
↓
Protected Data
The mobile application commonly sends the JWT through the Authorization header using the Bearer scheme.
Authorization: Bearer YOUR_JWT_TOKEN
The protected PHP API reads this header before attempting verification.
$authorization =
$_SERVER['HTTP_AUTHORIZATION'] ?? '';
if ($authorization === '') {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Token required"
]);
exit;
}
The API needs to separate the token from the word Bearer.
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]);
PHP applications should normally use a maintained JWT library instead of implementing cryptographic JWT operations manually.
For this lesson, we use:
firebase/php-jwt
Install it with Composer:
composer require firebase/php-jwt
After installing the package, load Composer's autoloader.
require_once
__DIR__ . '/vendor/autoload.php';
The exact relative path depends on your project structure.
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
The JWT class is used for JWT operations and
Key is used to specify the verification key and algorithm.
For an HS256 JWT, the same secret used to sign the token must be available to the server that verifies the token.
$secretKey =
"YOUR_SECURE_SERVER_SECRET";
With the Firebase PHP JWT library, a token can be decoded and its signature can be verified using the secret key.
$decoded = JWT::decode(
$token,
new Key($secretKey, 'HS256')
);
If verification succeeds, the decoded token data can be used according to the application's authentication rules.
If the JWT is malformed, has an invalid signature, or otherwise fails verification, the API should reject the request.
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Invalid token"
]);
exit;
JWT decoding and verification can throw exceptions when the token cannot be accepted.
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;
}
JWT libraries can validate registered claims such as the exp claim when decoding a token.
$payload = [
"sub" => "101",
"iat" => time(),
"exp" => time() + 3600
];
If the token is used after its expiration time, it should not be accepted.
Suppose the login API places the user ID inside the sub claim.
$decoded = JWT::decode(
$token,
new Key($secretKey, 'HS256')
);
$userId = $decoded->sub;
The user ID can then be used to retrieve permitted information from the database.
$stmt = $pdo->prepare(
"SELECT id, name, email
FROM users
WHERE id = ?
LIMIT 1"
);
$stmt->execute([$userId]);
$user = $stmt->fetch(
PDO::FETCH_ASSOC
);
Prepared statements should be used when querying the database.
A token may be correctly signed but the referenced user may no longer exist. The API should handle this situation.
if (!$user) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "User not found"
]);
exit;
}
After successful JWT verification, the API can return protected data.
echo json_encode([
"success" => true,
"message" => "Authenticated",
"user" => $user
]);
When the request does not contain valid authentication credentials, HTTP 401 Unauthorized is commonly used.
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Authentication required"
]);
| Decoding | Verification |
|---|---|
| Reads token contents | Checks token authenticity |
| Does not prove the token is trustworthy | Checks the cryptographic signature |
| Payload can be decoded | Signature must be valid |
| Not enough for authentication | Required for authentication |
A protected API should authenticate the request before using token claims to identify the user or access protected resources.
Request
↓
Extract Token
↓
Verify JWT
↓
Read User ID
↓
Database Query
↓
Return Protected Data
React Native does not normally verify the server's JWT signature for ordinary API authentication. It sends the token to the server, and the server verifies it.
React Native
↓
Authorization Header
↓
PHP API
↓
JWT Verification
↓
Protected Data
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 the server returns 401, the mobile application can treat the user as unauthenticated and redirect to the login screen when appropriate.
if (response.status === 401) {
console.log(
"Token invalid or expired"
);
// Navigate to Login
}
Verifying a JWT proves that the request is authenticated. It does not automatically mean that the user can perform every operation.
JWT Verification
↓
Identify User
↓
Check Permission
↓
Allowed?
┌─────┴─────┐
Yes No
↓ ↓
Allow 403
React Native
↓
Protected API Request
↓
Authorization: Bearer JWT
↓
PHP API
↓
Extract JWT
↓
JWT::decode()
↓
Verify Signature
↓
Check exp
↓
Read sub
↓
Find User
↓
Check Authorization
↓
Return Protected Data
<?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" =>
"JWT verified successfully",
"user" => $user
]);
} catch (Throwable $e) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" => "Invalid or expired token"
]);
}
?>
Step 1: Login using the JWT login API.
POST
http://localhost/api/jwt_login.php
Step 2: Copy the JWT from the response.
Step 3: Open the protected API.
GET
http://localhost/api/profile.php
Step 4: Add the header:
Authorization:
Bearer YOUR_JWT_TOKEN
Step 5: Send the request.
If the JWT is valid, the API should return the protected data. If the JWT is invalid or expired, the API should return an authentication error.
JWT verification is a critical part of protecting REST APIs. The server receives a JWT from the client, extracts it from the Authorization header, verifies its signature using the expected key and algorithm, checks relevant claims such as expiration, identifies the user, and then allows the protected operation when authentication is successful.
JWT
↓
Extract
↓
Verify Signature
↓
Check Expiration
↓
Read User ID
↓
Find User
↓
Check Permission
↓
Return Protected Data
Question: What is the main purpose of JWT verification?