A user login API allows a mobile application to send a user's email and password to the server. The PHP API checks the user in the database, verifies the password hash, and returns a suitable JSON response. In later lessons, we will use this login process with token and JWT authentication.
User login is the process of verifying the identity of a registered user.
Mobile App
↓
Login Form
↓
PHP Login API
↓
Find User
↓
Verify Password
↓
Login Result
A typical login process follows these steps:
A login API can use a POST endpoint.
POST /api/login.php
POST is used because the client is sending login credentials to the server.
The mobile application can send the email and password as JSON.
{
"email": "rahul@example.com",
"password": "MyPassword123"
}
header("Content-Type: application/json");
The API will use JSON for its response.
$pdo = new PDO(
"mysql:host=localhost;dbname=schooldb",
"root",
""
);
$pdo->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION
);
$input = file_get_contents(
"php://input"
);
$data = json_decode(
$input,
true
);
The second argument true converts the JSON object into an associative PHP array.
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode([
"success" => false,
"message" => "Invalid JSON data"
]);
exit;
}
$email = trim(
$data['email'] ?? ''
);
$password =
$data['password'] ?? '';
The null coalescing operator prevents errors when a field is missing.
if (
$email === '' ||
$password === ''
) {
http_response_code(400);
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 address"
]);
exit;
}
The API can search for the registered user using a prepared statement.
$stmt = $pdo->prepare(
"SELECT *
FROM users
WHERE email = ?"
);
$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 authentication message avoids revealing whether a particular email address is registered.
The stored password is a hash. Use password_verify() to compare the entered 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 database stores a password hash, not the original password.
Entered Password
↓
password_verify()
↓
Stored Password Hash
↓
Match?
┌────┴────┐
Yes No
↓ ↓
Continue Reject
The password does not need to be decrypted.
After successful password verification, the API can return a JSON response.
http_response_code(200);
echo json_encode([
"success" => true,
"message" => "Login successful"
]);
In later authentication lessons, this response will be extended with a token.
If user information is returned, only send the fields the client needs.
echo json_encode([
"success" => true,
"message" => "Login successful",
"user" => [
"id" => $user['id'],
"name" => $user['name'],
"email" => $user['email']
]
]);
Do not include the password hash in the response.
Avoid returning the complete database user record if it contains the password hash.
// Avoid
echo json_encode([
"user" => $user
]);
Instead, select or construct only the safe fields that should be returned.
function sendError(
int $status,
string $message
) {
http_response_code($status);
echo json_encode([
"success" => false,
"message" => $message
]);
exit;
}
if (
!$user ||
!password_verify(
$password,
$user['password']
)
) {
sendError(
401,
"Invalid email or password"
);
}
Using one general message for both cases avoids unnecessarily revealing whether an account exists.
try {
$stmt = $pdo->prepare(
"SELECT *
FROM users
WHERE email = ?"
);
$stmt->execute([$email]);
$user = $stmt->fetch(
PDO::FETCH_ASSOC
);
} catch (PDOException $e) {
error_log(
$e->getMessage()
);
sendError(
500,
"Login service unavailable"
);
}
$stmt = $pdo->prepare(
"SELECT *
FROM users
WHERE email = ?"
);
$stmt->execute([$email]);
$user = $stmt->fetch(
PDO::FETCH_ASSOC
);
if (
!$user ||
!password_verify(
$password,
$user['password']
)
) {
sendError(
401,
"Invalid email or password"
);
}
http_response_code(200);
echo json_encode([
"success" => true,
"message" => "Login successful",
"user" => [
"id" => $user['id'],
"name" => $user['name'],
"email" => $user['email']
]
]);
You can test the login API using Postman.
{
"email": "rahul@example.com",
"password": "MyPassword123"
}
React Native can send the login information using fetch().
fetch(
"https://example.com/api/login.php",
{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: email,
password: password
})
}
)
.then(response => response.json())
.then(data => {
console.log(data);
});
fetch(url, options)
.then(async response => {
const data =
await response.json();
if (!response.ok) {
throw new Error(
data.message
);
}
return data;
})
.then(data => {
console.log(
"Login successful"
);
})
.catch(error => {
console.log(
error.message
);
});
Login Screen
↓
Email + Password
↓
Fetch / Axios
↓
PHP Login API
↓
Find User
↓
password_verify()
↓
Authentication Result
↓
JSON Response
↓
React Native
Later, the successful response can contain a JWT token for protected API requests.
| Situation | Status | Response |
|---|---|---|
| Login successful | 200 | Login successful |
| Missing data | 400 | Required fields message |
| Invalid email format | 422 | Invalid email |
| Invalid credentials | 401 | Invalid email or password |
| Database problem | 500 | Server error message |
<?php
header("Content-Type: application/json");
function sendError(
int $status,
string $message
) {
http_response_code($status);
echo json_encode([
"success" => false,
"message" => $message
]);
exit;
}
try {
$pdo = new PDO(
"mysql:host=localhost;dbname=schooldb",
"root",
""
);
$pdo->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION
);
$input = file_get_contents(
"php://input"
);
$data = json_decode(
$input,
true
);
if (
json_last_error() !==
JSON_ERROR_NONE
) {
sendError(
400,
"Invalid JSON data"
);
}
$email = trim(
$data['email'] ?? ''
);
$password =
$data['password'] ?? '';
if (
$email === '' ||
$password === ''
) {
sendError(
400,
"Email and password are required"
);
}
if (!filter_var(
$email,
FILTER_VALIDATE_EMAIL
)) {
sendError(
422,
"Invalid email address"
);
}
$stmt = $pdo->prepare(
"SELECT *
FROM users
WHERE email = ?"
);
$stmt->execute([$email]);
$user = $stmt->fetch(
PDO::FETCH_ASSOC
);
if (
!$user ||
!password_verify(
$password,
$user['password']
)
) {
sendError(
401,
"Invalid email or password"
);
}
http_response_code(200);
echo json_encode([
"success" => true,
"message" => "Login successful",
"user" => [
"id" => $user['id'],
"name" => $user['name'],
"email" => $user['email']
]
]);
} catch (PDOException $e) {
error_log(
$e->getMessage()
);
sendError(
500,
"Login service unavailable"
);
}
?>
The user login API receives email and password from the mobile application. PHP validates the request, searches for the user, verifies the password hash using password_verify(), and returns a JSON response. In the next lessons, this successful login process will be extended with authentication tokens.
React Native
↓
POST Login JSON
↓
PHP Login API
↓
Validate Input
↓
Find User
↓
password_verify()
↓
Success / Failure
↓
JSON Response
↓
Next: Token Authentication
Question: Which PHP function should be used to verify a user's entered password against the password hash stored in the database?