(updated) In today’s mobile-first world, building a robust and secure PHP API is crucial. This API should not only perform CRUD (Create, Read, Update, Delete) operations but also ensure API security, user authentication with JSON Web Tokens (JWT), and provide meaningful responses with status codes for mobile applications.
Setting Up Your Environment
Before we dive into PHP API development, ensure you have the necessary tools and environment set up:
- PHP Installed: Make sure you have PHP installed on your server.
- MySQL Database: Set up a MySQL database to interact with your API.
- Web Server: Configure a web server like Apache or Nginx to handle PHP requests.
Let’s create 6 files, you might have the following files:
- index.php: default file
login.php
: Contains the Login operation.create.php
: Contains thecreate
operation.read.php
: Contains the Read operation.update.php
: Contains the Update operation.delete.php
: Contains the Delete operation.
Each of these files would handle a specific API endpoint.
The full API URL for each function in a typical setup would depend on your web server configuration and URL structure. Assuming your API is hosted on http://example.com/api/, here are the full API URLs for each function:
create
Operation:
Full API URL: http://phpapis.test/api/create
.php
HTTP Method: POST
Read Operation:
Full API URL: http://phpapis.test/api/read.php
HTTP Method: GET
Update Operation:
Full API URL: http://phpapis.test/api/update.php
HTTP Method: POST
Delete Operation:
Full API URL: http://phpapis.test/api/delete.php
HTTP Method: POST
Login Operation:
Full API URL: http://phpapis.test/api/login.php
HTTP Method: POST
you can achieve clean URLs without .php
extensions using Apache’s .htaccess
file as an example:
# Enable URL rewriting
RewriteEngine On
# to send bearer token with request
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect URLs with .php extension to non-extension versions
RewriteCond %{THE_REQUEST} \s/+(.*?)\.php [NC]
RewriteRule ^ /%1 [R=301,L]
# Rewrite URLs without the .php extension to their corresponding .php files
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^([^\.]+)$ $1.php [NC,L]
# Define rules to map clean URLs to PHP scripts
RewriteRule ^api/create$ create.php [L]
RewriteRule ^api/read$ read.php [L]
RewriteRule ^api/update$ update.php [L]
RewriteRule ^api/delete$ delete.php [L]
RewriteRule ^api/login$ login.php [L]
Login Operation
Let’s add a Login operation to the PHP API with JWT authentication for user login. We’ll also provide responses suitable for mobile clients.
<?php
// Include JWT library
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
// Database configuration
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "phpapis";
// JWT secret key
$jwtSecretKey = "your_jwt_secret_key";
// Function to establish a database connection
function connectToDatabase() {
global $servername, $username, $password, $dbname;
$connection = new mysqli($servername, $username, $password, $dbname);
if ($connection->connect_error) {
die("Connection failed: " . $connection->connect_error);
}
return $connection;
}
// Function to authenticate a user and generate a JWT
function authenticateUser($username, $password) {
global $jwtSecretKey;
// Replace this with your actual user authentication logic
// For this example, we'll assume you have a user database
$user = getUserFromDatabase($username);
if ($user !== null && password_verify($password, $user['password'])) {
// User exists in the database, and the provided password matches the hashed password
// Create a payload for the JWT with user-related data
$payload = array(
"user_id" => $user['id'],
"username" => $user['username'],
"registration_date" => $user['date'],
// You can add more user-related data here
);
$algorithm = 'HS256';
// Encode the payload into a JWT using the secret key
$jwt = JWT::encode($payload, $jwtSecretKey,$algorithm);
return $jwt;
}
return false;
}
// Login Operation
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["username"]) && isset($_POST["password"])) {
$username = $_POST["username"];
$password = $_POST["password"];
// echo $username . $password;
$jwt = authenticateUser($username, $password);
if ($jwt) {
http_response_code(200);
echo json_encode(["status" => "success", "jwt" => $jwt]);
} else {
http_response_code(401);
echo json_encode(["status" => "error", "message" => "Authentication failed"]);
}
} else {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Invalid request"]);
}
// Function to retrieve user data from the database (dummy example)
function getUserFromDatabase($username) {
// Replace this with your actual database query to fetch user data by username
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('SELECT * FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$db = null;
return $user;
}
?>

Create (C) Operation
Creating a new record in your database is essential, and now it’s secure with JWT authentication:
<?php
// Include JWT library
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
// Database configuration
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "phpapis";
// JWT secret key
$jwtSecretKey = "your_jwt_secret_key";
// Function to establish a database connection
function connectToDatabase()
{
global $servername, $username, $password, $dbname;
$connection = new mysqli($servername, $username, $password, $dbname);
if ($connection->connect_error) {
die("Connection failed: " . $connection->connect_error);
}
return $connection;
}
// User Registration Operation
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["username"]) && isset($_POST["password"])) {
$newUsername = $_POST["username"];
$newPassword = $_POST["password"];
// Check if the username is available (not already taken)
if (isUsernameAvailable($newUsername)) {
// Hash the password before storing it in the database
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT);
// Store the new user in the database
if (createUserInDatabase($newUsername, $hashedPassword)) {
http_response_code(201); // HTTP status code for successful resource creation
echo json_encode(["status" => "success", "message" => "User created successfully"]);
} else {
http_response_code(500); // HTTP status code for server error
echo json_encode(["status" => "error", "message" => "User creation failed"]);
}
} else {
http_response_code(400); // HTTP status code for bad request
echo json_encode(["status" => "error", "message" => "Username already taken"]);
}
} else {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Invalid request"]);
}
// Function to check if a username is available
function isUsernameAvailable($username)
{
// Replace this with your actual database query to check if the username is available
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('SELECT * FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$db = null;
return $user === false; // If no user is found, the username is available
}
// Function to create a user in the database
function createUserInDatabase($username, $password)
{
// Create a timestamp for the current date and time
$currentDate = date('Y-m-d H:i:s');
// Replace this with your actual database insert query to create a new user
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('INSERT INTO
users (username, password, date)
VALUES (:username, :password, :registration_date)');
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->bindParam(':registration_date', $currentDate);
if ($stmt->execute()) {
return true;
}
return false;
}
// Function to retrieve user data from the database (dummy example)
function getUserFromDatabase($username)
{
// Replace this with your actual database query to fetch user data by username
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('SELECT * FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$db = null;
return $user;
}
?>

Read (R) Operation
Reading data from your database now requires JWT authentication:
<?php
// Include JWT library
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
// Database configuration
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "phpapis";
// JWT secret key
$jwtSecretKey = "your_jwt_secret_key";
// Function to establish a database connection
function connectToDatabase()
{
global $servername, $username, $password, $dbname;
$connection = new mysqli($servername, $username, $password, $dbname);
if ($connection->connect_error) {
die("Connection failed: " . $connection->connect_error);
}
return $connection;
}
if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET["username"])) {
// Read Operation: Retrieve user data
$usernameToRead = $_GET["username"];
// Check if the username exists
$user = getUserFromDatabase($usernameToRead);
if ($user !== false) {
http_response_code(200); // HTTP status code for success
echo json_encode(["status" => "success", "user" => $user]);
} else {
http_response_code(404); // HTTP status code for not found
echo json_encode(["status" => "error", "message" => "User not found"]);
}
} else {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Invalid request"]);
}
// Function to retrieve user data from the database (dummy example)
function getUserFromDatabase($username)
{
// Replace this with your actual database query to fetch user data by username
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('SELECT username, date FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$db = null;
return $user;
}
?>

Update (U) Operation
Updating an existing record is now more secure with JWT-based authentication:
<?php
// Include JWT library
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Database configuration
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "phpapis";
// JWT secret key
$jwtSecretKey = "your_jwt_secret_key";
// Function to establish a database connection
function connectToDatabase()
{
global $servername, $username, $password, $dbname;
$connection = new mysqli($servername, $username, $password, $dbname);
if ($connection->connect_error) {
die("Connection failed: " . $connection->connect_error);
}
return $connection;
}
// Function to validate and decode the JWT
function validateJWT($jwt)
{
global $jwtSecretKey;
$headers = null; // We're not interested in the headers in this example
try {
$decoded = JWT::decode($jwt, new Key($jwtSecretKey, 'HS256'));
return $decoded;
}catch (Firebase\JWT\ExpiredException $e) {
// Handle token expiration
echo $e;
return null;
} catch (Firebase\JWT\BeforeValidException $e) {
// Handle token not yet valid
echo $e;
return null;
} catch (Firebase\JWT\SignatureInvalidException $e) {
// Handle invalid signature
echo $e;
return null;
} catch (Exception $e) {
// Handle other exceptions
echo $e;
return null;
}
}
// Function to update the user's password
function updateUserPassword($username, $newPassword)
{
// Hash the new password before storing it in the database
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT);
// Replace this with your actual database update query to update the user's password
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('UPDATE users SET password = :password WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $hashedPassword);
if ($stmt->execute()) {
return true;
}
return false;
}
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["username"])) {
// Update Operation: Update user password
$requestedUsername = $_POST["username"];
// Check if the JWT is provided in the request header
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$authorizationHeader = $_SERVER['HTTP_AUTHORIZATION'];
$jwt = str_replace('Bearer ', '', $authorizationHeader);
// echo $jwt;
// Validate the JWT
$decodedJWT = validateJWT($jwt);
if ($decodedJWT !== null) {
// The JWT is valid, and you can access the claims, including the user's information
// Here, we assume that the "username" claim in the JWT is the authenticated user's username
$authenticatedUsername = $decodedJWT->username;
if ($authenticatedUsername === $requestedUsername) {
// The authenticated user is updating their own password
// Retrieve the new password from the request
$newPassword = $_POST["password"];
// Update the user's password in the database
if (updateUserPassword($requestedUsername, $newPassword)) {
http_response_code(200); // HTTP status code for success
echo json_encode(["status" => "success", "message" => "Password updated successfully"]);
} else {
http_response_code(500); // HTTP status code for server error
echo json_encode(["status" => "error", "message" => "Password update failed"]);
}
} else {
http_response_code(403); // HTTP status code for forbidden
echo json_encode(["status" => "error", "message" => "Access forbidden"]);
}
} else {
http_response_code(401); // HTTP status code for unauthorized
echo json_encode(["status" => "error", "message" => "Invalid JWT"]);
}
} else {
http_response_code(401); // HTTP status code for unauthorized
echo json_encode(["status" => "error", "message" => "JWT not provided"]);
}
} else {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Invalid request"]);
}
// Function to retrieve user data from the database (dummy example)
function getUserFromDatabase($username)
{
// Replace this with your actual database query to fetch user data by username
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('SELECT * FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$db = null;
return $user;
}
?>


Delete (D) Operation
Deleting a record is protected by JWT authentication:
<?php
// Include JWT library
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
// Database configuration
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "phpapis";
// JWT secret key
$jwtSecretKey = "your_jwt_secret_key";
// Function to establish a database connection
function connectToDatabase()
{
global $servername, $username, $password, $dbname;
$connection = new mysqli($servername, $username, $password, $dbname);
if ($connection->connect_error) {
die("Connection failed: " . $connection->connect_error);
}
return $connection;
}
if ($_SERVER["REQUEST_METHOD"] == "GET" && isset($_GET["username"])) {
// Read Operation: Retrieve user data
$usernameToRead = $_GET["username"];
// Check if the username exists
$user = getUserFromDatabase($usernameToRead);
if ($user !== false) {
http_response_code(200); // HTTP status code for success
echo json_encode(["status" => "success", "user" => $user]);
} else {
http_response_code(404); // HTTP status code for not found
echo json_encode(["status" => "error", "message" => "User not found"]);
}
} elseif ($_SERVER["REQUEST_METHOD"] == "DELETE" && isset($_GET["username"])) {
// Delete Operation: Delete a user
$usernameToDelete = $_GET["username"];
// Check if the user was successfully deleted
if (deleteUserFromDatabase($usernameToDelete)) {
// http_response_code(204); // no content will be displayed HTTP status code for successful deletion
http_response_code(200); // HTTP status code for successful deletion
echo json_encode(["status" => "success", "message" => "User deleted successfully"]);
} else {
http_response_code(404); // HTTP status code for not found
echo json_encode(["status" => "error", "message" => "User not found or deletion failed"]);
}
} else {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Invalid request"]);
}
// Function to retrieve user data from the database (dummy example)
function getUserFromDatabase($username)
{
// Replace this with your actual database query to fetch user data by username
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('SELECT username, date FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$db = null;
return $user;
}
// Function to delete a user from the database (dummy example)
function deleteUserFromDatabase($username)
{
// Replace this with your actual database delete query
// Example database query (using PDO):
$db = new PDO('mysql:host=localhost;dbname=phpapis', 'root', '');
$stmt = $db->prepare('DELETE FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
// Execute the delete query
return $stmt->execute();
}

API Security and Authorization
Our API now emphasizes security and user access control:
- Security Measures: We’ve implemented input validation, password hashing, and secured API communication with HTTPS.
- JWT Authentication: Users must provide a valid JWT to access protected API endpoints.
- Role-Based Access Control (RBAC): You can implement RBAC to control user access to different parts of your API.
- Middleware: Middleware checks the validity of JWTs and ensures users have the necessary permissions for specific operations.
Testing with Mobile Clients
Testing is crucial to ensure a seamless mobile app experience:
- Mobile App Integration: Test your API from your mobile app, ensuring that authentication and authorization work correctly.
- Error Handling: The mobile app should handle API errors gracefully and provide user-friendly messages.
API Response with Status Codes
We continue to use meaningful HTTP status codes and a standard response format:
- HTTP Status Codes: We use appropriate HTTP status codes (e.g., 200, 201, 400, 401, 404, 500) to indicate the outcome of each API operation.
- Standard Response Format: Responses follow a standard format, including a status indicator (e.g., “status”: “success” or “error”), a message explaining the result, and relevant data.
- JSON Responses: JSON remains the chosen format for mobile API responses due to its lightweight and easy parsing.
By integrating JWT-based authentication into our PHP API for all CRUD operations, we’ve significantly enhanced its security and user access control. Mobile clients can now authenticate and securely access protected resources, ensuring the integrity and confidentiality of user data.