In this lesson, we will create a JWT-based login API using PHP and MySQL. The user will send an email and password to the API. The API will verify the credentials and, if they are correct, generate a JWT and return it to the React Native application.
A JWT login API is an API endpoint that authenticates a user and returns a JSON Web Token after successful login.
POST /api/jwt_login.php
Email + Password
↓
PHP Login API
↓
Verify User
↓
Generate JWT
↓
Return JWT
React Native
↓
POST Login Request
↓
PHP JWT Login API
↓
Find User
↓
Verify Password
↓
Create JWT
↓
JSON Response
↓
React Native
↓
Store Token
Suppose our login API is located at:
http://localhost/api/jwt_login.php
For a real mobile application, the API should be hosted on a server using HTTPS.
Login data should be sent using the HTTP POST method.
POST /api/jwt_login.php
The email and password are sent inside the request body as JSON.
{
"email": "student@example.com",
"password": "123456"
}
The PHP API will read this JSON data and validate the supplied values.
The API should tell the client that its response is JSON.
header(
"Content-Type: application/json"
);
PHP can read the raw request body using
php://input.
$input = file_get_contents(
"php://input"
);
$data = json_decode(
$input,
true
);
The second argument true converts the JSON object into a
PHP associative array.
$email =
trim($data['email'] ?? '');
$password =
$data['password'] ?? '';
Using the null coalescing operator prevents errors when a field is missing.
The API should check that both email and password have been provided.
if (
$email === '' ||
$password === ''
) {
http_response_code(422);
echo json_encode([
"success" => false,
"message" =>
"Email and password are required"
]);
exit;
}
PHP provides filter_var() for validating an email address.
if (
!filter_var(
$email,
FILTER_VALIDATE_EMAIL
)
) {
http_response_code(422);
echo json_encode([
"success" => false,
"message" => "Invalid email"
]);
exit;
}
After validation, the API searches for the user using the supplied email address.
$stmt = $pdo->prepare(
"SELECT id, name, email, password
FROM users
WHERE email = ?
LIMIT 1"
);
$stmt->execute([$email]);
$user = $stmt->fetch(
PDO::FETCH_ASSOC
);
if (!$user) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid email or password"
]);
exit;
}
A generic error message avoids revealing whether a particular email exists in the database.
The stored password should be a password hash. Use
password_verify() to compare the submitted password with
the stored hash.
if (
!password_verify(
$password,
$user['password']
)
) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid email or password"
]);
exit;
}
The password and password hash should never be returned to the mobile application.
Return only the information the client actually needs.
{
"id": 101,
"name": "Rahul",
"email": "student@example.com"
}
After successful password verification, we can prepare the JWT payload.
$payload = [
"sub" => (string)$user['id'],
"email" => $user['email'],
"iat" => time(),
"exp" => time() + 3600
];
Here the token is configured to expire after one hour for demonstration.
| Field | Purpose |
|---|---|
| sub | User identifier |
| User email | |
| iat | Token issued time |
| exp | Token expiration time |
For an HMAC-based JWT such as HS256, the server needs a secret key.
$secretKey =
"YOUR_SECURE_SERVER_SECRET";
A PHP project should normally use a well-maintained JWT library rather than implementing all JWT cryptographic operations manually.
One commonly used PHP package is:
firebase/php-jwt
If Composer is available, the package can be installed with:
composer require firebase/php-jwt
After installing the JWT package with Composer, include the Composer autoload file.
require_once
__DIR__ . '/vendor/autoload.php';
The exact path depends on the location of your PHP API file.
use Firebase\JWT\JWT;
The JWT class can then be used to encode the payload and create the signed token.
After creating the payload and selecting the secret key, the JWT can be encoded.
$token = JWT::encode(
$payload,
$secretKey,
'HS256'
);
The result is the JWT that can be returned to the mobile application.
http_response_code(200);
echo json_encode([
"success" => true,
"message" => "Login successful",
"token" => $token,
"user" => [
"id" => $user['id'],
"name" => $user['name'],
"email" => $user['email']
]
]);
A successful response can look like this:
{
"success": true,
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 101,
"name": "Rahul",
"email": "student@example.com"
}
}
const response = await fetch(
"https://example.com/api/jwt_login.php",
{
method: "POST",
headers: {
"Content-Type":
"application/json",
"Accept":
"application/json"
},
body: JSON.stringify({
email: email,
password: password
})
}
);
const data =
await response.json();
console.log(data);
After a successful login, the application can read the returned token.
if (data.success) {
const token = data.token;
console.log(token);
}
The token should then be stored using an appropriate secure storage strategy for the mobile application.
const response = await fetch(
"https://example.com/api/profile.php",
{
method: "GET",
headers: {
"Authorization":
"Bearer " + token,
"Accept":
"application/json"
}
}
);
const data =
await response.json();
The protected API will verify the JWT before returning private data.
If the credentials are incorrect, the API should return an appropriate authentication error.
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid email or password"
]);
exit;
The React Native application can display a suitable message to the user.
<?php
header(
"Content-Type: application/json"
);
require_once '../db.php';
require_once
__DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
$secretKey =
"YOUR_SECURE_SERVER_SECRET";
try {
$input =
file_get_contents(
"php://input"
);
$data =
json_decode(
$input,
true
);
$email =
trim($data['email'] ?? '');
$password =
$data['password'] ?? '';
if (
$email === '' ||
$password === ''
) {
http_response_code(422);
echo json_encode([
"success" => false,
"message" =>
"Email and password are required"
]);
exit;
}
if (
!filter_var(
$email,
FILTER_VALIDATE_EMAIL
)
) {
http_response_code(422);
echo json_encode([
"success" => false,
"message" =>
"Invalid email"
]);
exit;
}
$stmt = $pdo->prepare(
"SELECT id, name, email, password
FROM users
WHERE email = ?
LIMIT 1"
);
$stmt->execute([$email]);
$user =
$stmt->fetch(
PDO::FETCH_ASSOC
);
if (
!$user ||
!password_verify(
$password,
$user['password']
)
) {
http_response_code(401);
echo json_encode([
"success" => false,
"message" =>
"Invalid email or password"
]);
exit;
}
$now = time();
$payload = [
"sub" => (string)$user['id'],
"email" => $user['email'],
"iat" => $now,
"exp" => $now + 3600
];
$token = JWT::encode(
$payload,
$secretKey,
'HS256'
);
http_response_code(200);
echo json_encode([
"success" => true,
"message" =>
"Login successful",
"token" => $token,
"user" => [
"id" => $user['id'],
"name" => $user['name'],
"email" => $user['email']
]
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode([
"success" => false,
"message" =>
"Database error"
]);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
"success" => false,
"message" =>
"Authentication error"
]);
}
?>
Step 1: Select POST.
POST
http://localhost/api/jwt_login.php
Step 2: Select:
Body
→ raw
→ JSON
Step 3: Send:
{
"email": "student@example.com",
"password": "123456"
}
Step 4: A successful response should contain:
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIs..."
}
Copy the token and use it in the Authorization header when testing a protected API.
The JWT login API receives the user's email and password, validates the input, finds the user in MySQL, verifies the password hash, creates a JWT after successful authentication, and returns the token as JSON. React Native can then use that token when accessing protected APIs.
React Native
↓
POST /jwt_login.php
↓
JSON Email + Password
↓
Validate Input
↓
Find User
↓
password_verify()
↓
Create JWT
↓
Return JSON
↓
React Native
↓
Store JWT
↓
Protected API
Question: What should a successful JWT login API normally return to the mobile application?