REST API Authentication Example in PHP – JWT Tutorial

Previously, we learned how to create a simple REST API in PHP. The create, read, update and delete database records (CRUD operations) has been useful for our projects.

Today, we will learn how to authenticate a user using REST API and JSON Web Tokens or JWT.

In this tutorial, we will cover a basic sign up or registration form, login, and logout operations, updating a user account and more.

1.0 Project overview
1.1 What is JWT?
1.2 JWT simple analogy
1.3 What does a JWT look like?
1.4 JWT vs OAuth

2.0 Final output
3.0 File structure

4.0 Setup the database
4.1 Create a database
4.2 Create a table
4.3 Create a directory for configuration
4.4 Create a database connection file

5.0 Create API for user registration
5.1 Create a file for creating a user
5.2 Connect to database and user table
5.3 Assign submitted data to object properties
5.4 Use the create() method
5.5 Create the user object class
5.6 Add a create() method
5.7 Output

6.0 Create API for user login
6.1 Create a file for user login
6.2 Connect to database and user table
6.3 Check if email exists
6.4 Add emailExists() method
6.5 Include files to encode JWT
6.6 Generate JSON web token
6.7 Tell the user login failed
6.8 Create core configuration file
6.9 Download PHP-JWT from GitHub
6.10 Output

7.0 Create API for JWT validation
7.1 Create a file to validate JWT
7.2 Include files to decode JWT
7.3 Retrieve given JWT
7.4 Decode JWT if it exists
7.5 Show error if decoding failed
7.6 Show error message if JWT is empty
7.7 Output

8.0 Create API for user account
8.1 Create a file for updating user account
8.2 Include files to decode JWT
8.3 Connect to database and user table
8.4 Retrieve given JWT
8.5 Decode JWT if it exists
8.6 Show error message if decoding fails
8.7 Set user property values
8.8 Use the update() method
8.9 Add update() method in user class
8.10 Re-generate JWT
8.11 Show error message if JWT is empty
8.12 Output

9.0 Create interface for user registration
9.1 Create index page
9.2 Add navigation bar
9.3 Add content section
9.4 Add Bootstrap 4 and custom CSS links
9.5 Create custom CSS file
9.6 Add jQuery and Bootstrap 4 script links
9.7 Show a sign up HTML form
9.8 Trigger when sign up form is submitted
9.9 Remove any prompt messages
9.10 Add serializeObject() function
9.11 Output

10.0 Create a login page
10.1 Trigger when login menu was clicked
10.2 Show login HTML form
10.3 Add setCookie() function
10.4 Change menu appearance
10.5 Output

11.0 Show login responses
11.1 Submitted form trigger
11.2 Create an HTTP request
11.3 Show home page HTML
11.4 Add home page HTML
11.5 Add getCookie() function
11.6 Show error for invalid login
11.7 Output

12.0 On click of Home menu
12.1 Add trigger to show home page
12.2 Set logged-in menu
12.3 Show login page if JWT is invalid
12.4 Output

13.0 Show user account page
13.1 Add trigger to show account form
13.2 Verify if JWT is valid
13.3 Show account form if JWT is valid
13.4 Show login page if JWT is invalid
13.5 Add a trigger for updating a user account
13.6 Get form data and JWT
13.7 Send data to API
13.8 Show error message
13.9 Output

14.0 Add JavaScript for user logout
14.1 Add a trigger to logout
14.2 Output

15.0 Download Source Codes
16.0 What's Next?
17.0 Related Tutorials
18.0 Notes

1.0 Project Overview

1.1 What is JWT?

In technical terms, JSON Web Token or JWT is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

For example, a server could generate a token that has the claim "logged in as admin" and provide that to a client. The client could then use that token to prove that it is logged in as admin. Please read more here, here, here and here.

1.2 JWT simple analogy

The following is my own simple analogy. If you think you have a better one, please let me know via email. My email address is [email protected]

John (server) owns a house (protected data). Michael (client) wants to rent this house. John and Michael agreed to the house rules and they signed a contract (valid username and password).

John gave Michael a key (token) with other related information (claims) so he has access to the house. Michael can now get inside or outside (HTTP requests) the house. It means access was granted.

If John (sever) and Michael (client) did not agree with the house rules, the contract won't be signed (invalid username and password) and John (server) won't give him a key (token).

Even if Michael (client) has another type of key (token), he still won't have access to the house because it is a wrong key (token). It is not the key (token) John gave. It means access was denied.

I've found another analogy that can be useful for you. Read it here.

The video below might help explain the analogy.

The following video about token-based authentication might help as well.

1.3 What does a JWT look like?

A JSON Web Token or JWT looks like a string with three parts separated by dots. The following is an example of JWT.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZyIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUuY29tIiwiaWF0IjoxMzU2OTk5NTI0LCJuYmYiOjEzNTcwMDAwMDAsImRhdGEiOnsiaWQiOiI5IiwiZmlyc3RuYW1lIjoiTWlrZSIsImxhc3RuYW1lIjoiRGFsaXNheSIsImVtYWlsIjoibWlrZUBjb2Rlb2ZhbmluamEuY29tIn19.h_Q4gJ3epcpwdwNCNCYxtiKdXsN34W9MEjxZ7sx21Vs

JWT in the serialized form represents a string of the following format:

header.payload.signature

The header component contains information about how the JWT signature should be computed. The payload component is the data that is stored inside the JWT. This can be the user information like user ID, name and email.

To create the signature component, you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that. Read more here.

In this tutorial, we won't have to worry about generating or encoding and decoding JWT because we will use a library called PHP-JWT.

1.4 JWT vs OAuth

We explained the JWT above. JWT is a token format and we can say it is a simple authentication protocol. OAuth is an authentication framework that can use JWT as a token.

OAuth is used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords.

Use JWT if:

  • You have a very simple use-case, like a single client application.
  • Your users access their resources only through your own application.
  • You want a quick-to-implement and simple stateless HTTP authentication to an API.

Use OAuth if:

  • Your users can access their resources through another application you don't own.
  • You want to provide API to browser-based apps, native mobile apps or desktop apps.
  • You want to use an Authentication Server that keeps track of tokens.

Please read more here and here.

2.0 Final output

2.1 LEVEL 1 source code output

It is important to visualize what we are trying to achieve in this tutorial. At the end of this tutorial, we will achieve the LEVEL 1 source code as seen on the screenshots below.

Please click a photo below to enlarge and use the arrow icons to navigate the slideshow.

2.2 LEVEL 2 source code output

Screenshots coming soon! See the list of features in section 14.2 below.

The LEVEL 2 source code shows more amazing features that you can learn once you completed studying the LEVEL 1 source code.

For now, let's proceed to the complete tutorial of our LEVEL 1 source code below. Let's code!

3.0 File Structure

At the end of this tutorial, we will have the following folders and files.

├─ rest-api-authentication-example/ - name of the project folder.
├─── api/ - main folder of the API.
├────── config/
├───────── core.php - file used for common settings or variables.
├───────── database.php - file used for connecting to the database.
├────── libs/
├───────── php-jwt-master/ - folder of the JWT library developed by Google.
├────── objects/
├───────── user.php - the class file that will handle the database queries.
├────── create_user.php - the file that will process the input of from "sign up" form.
├────── login.php - the file that will encode and generate a JSON web token.
├────── update_user.php - the file that will process the input of from "user account" form.
├────── validate_token.php - the file that will validate or decode the JSON web token.
├─── custom.css - contains any customization in the user interface.
├─── index.html - contains HTML and JavaScript that renders different user interfaces.

4.0 Setup the Database

4.1 Create a database

4.2 Create a table

  • On the api_db database, create a new table called users.
  • Put the following fields on the users table.

4.3 Create a directory for configuration

  • Create our main project folder and put rest-api-authentication-example as its name.
  • If you're using XAMPP, you must create it inside the htdocs folder. In my case, I created it inside C:\xampp\htdocs directory.
  • Open rest-api-authentication-example folder.
  • Create api folder.
  • Open api folder.
  • Create config folder.

4.4 Create a database connection file

  • Open config folder.
  • Create a new file called database.php.

Place the following code.

<?php
// used to get mysql database connection
class Database{

	// specify your own database credentials
	private $host = "localhost";
	private $db_name = "api_db";
	private $username = "root";
	private $password = "";
	public $conn;

	// get the database connection
	public function getConnection(){

		$this->conn = null;

		try{
			$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
		}catch(PDOException $exception){
			echo "Connection error: " . $exception->getMessage();
		}

		return $this->conn;
	}
}
?>

5.0 Create API for user registration

5.1 Create a file for creating a user

  • Open rest-api-authentication-example folder.
  • Create a folder called api.
  • Open the api folder.
  • Create a new file called create_user.php.

We need to set headers on this new file so that it will only accept JSON data. Place the following code.

<?php
// required headers
header("Access-Control-Allow-Origin: http://localhost/rest-api-authentication-example/");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// database connection will be here

5.2 Connect to database and user table

We are saving the user information on a database so we need the database connection. We need to instantiate the user table as well because this will make the insert query later.

Replace // database connection will be here comment of create_user.php file with the following code.

// files needed to connect to database
include_once 'config/database.php';
include_once 'objects/user.php';

// get database connection
$database = new Database();
$db = $database->getConnection();

// instantiate product object
$user = new User($db);

// submitted data will be here

5.3 Assign submitted data to object properties

The user information will be submitted through an HTML form and JavaScript code. We will see this code later.

We need to assign the submitted data on the object properties such as firstname, lastname, etc.

Replace // submitted data will be here comment of create_user.php file with the following code.

// get posted data
$data = json_decode(file_get_contents("php://input"));

// set product property values
$user->firstname = $data->firstname;
$user->lastname = $data->lastname;
$user->email = $data->email;
$user->password = $data->password;

// use the create() method here

5.4 Use the create() method

One the code below, we use the user object's create() method. It will tell the user if the user was created or not.

Replace // use the create() method here comment of create_user.php file with the following code.

// create the user
if(
    !empty($user->firstname) &&
    !empty($user->email) &&
    !empty($user->password) &&
    $user->create()
){

    // set response code
    http_response_code(200);

    // display message: user was created
    echo json_encode(array("message" => "User was created."));
}

// message if unable to create user
else{

    // set response code
    http_response_code(400);

    // display message: unable to create user
    echo json_encode(array("message" => "Unable to create user."));
}
?>

5.5 Create the user object class

The previous section will not work without the user object class. This is where we'll place all the user methods that contains database queries.

If you're not familiar with private or public scopes, please learn from this resource.

  • Open the api folder.
  • Open objects folder.
  • Create a new file called user.php.
  • Place the following code.
<?php
// 'user' object
class User{

	// database connection and table name
	private $conn;
	private $table_name = "users";

	// object properties
	public $id;
	public $firstname;
	public $lastname;
	public $email;
	public $password;

	// constructor
	public function __construct($db){
		$this->conn = $db;
	}

// create() method will be here
}

5.6 Add a create() method

The code below shows the INSERT query, data sanitation, and binding, and we used the built-in password_hash() method to secure the user's password on the database.

If the execution is a success, the user information will be saved on the database.

Replace the // create() method will be here comment of user.php file with the following code.

// create new user record
function create(){

	// insert query
	$query = "INSERT INTO " . $this->table_name . "
            SET
				firstname = :firstname,
				lastname = :lastname,
				email = :email,
				password = :password";

	// prepare the query
	$stmt = $this->conn->prepare($query);

	// sanitize
	$this->firstname=htmlspecialchars(strip_tags($this->firstname));
	$this->lastname=htmlspecialchars(strip_tags($this->lastname));
	$this->email=htmlspecialchars(strip_tags($this->email));
	$this->password=htmlspecialchars(strip_tags($this->password));

	// bind the values
	$stmt->bindParam(':firstname', $this->firstname);
	$stmt->bindParam(':lastname', $this->lastname);
	$stmt->bindParam(':email', $this->email);

	// hash the password before saving to database
	$password_hash = password_hash($this->password, PASSWORD_BCRYPT);
	$stmt->bindParam(':password', $password_hash);

	// execute the query, also check if query was successful
	if($stmt->execute()){
		return true;
	}

	return false;
}

// emailExists() method will be here

5.7 Output

You need to use POSTMAN to test our API. Download your version of POSTMAN here.

  • First, we will test for the successful creation of a user.
  • Launch POSTMAN.
  • Enter the following as the request URL
http://localhost/rest-api-authentication-example/api/create_user.php
  • Click "Body" tab.
  • Click "raw".
  • Enter the following JSON.
{
 "firstname" : "Mike",
 "lastname" : "Dalisay",
 "email" : "[email protected]",
 "password" : "555"
}
  • Click the blue "Send" button. The output will be:
{
    "message": "User was created."
}
  • On POSTMAN, it should look like this:
  • To test for a failed creation of a user, just remove the value of the password above.
  • Click the blue "Send" button.
  • It should look like this:

6.0 Create API for user login

6.1 Create a file for user login

On the code below, we set the file headers so that it will know where the request should come from and what type of data is accepted.

  • Open rest-api-authentication-example folder.
  • Create a new file called login.php.
  • Place the following code.
<?php
// required headers
header("Access-Control-Allow-Origin: http://localhost/rest-api-authentication-example/");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// database connection will be here

6.2 Connect to database and user table

We will compare the user email and password from the database so we need the database connection.

We need to instantiate the user table as well because this will allow us to verify if the email exists and read the hashed password.

Replace // database connection will be here comment of login.php file with the following code.

// files needed to connect to database
include_once 'config/database.php';
include_once 'objects/user.php';

// get database connection
$database = new Database();
$db = $database->getConnection();

// instantiate user object
$user = new User($db);

// check email existence here

6.3 Check if email exists

On the code below, we get the email submitted by the user through the login form. We check if the email exists on our database.

Replace // check email existence here comment of login.php file with the following code.

// get posted data
$data = json_decode(file_get_contents("php://input"));

// set product property values
$user->email = $data->email;
$email_exists = $user->emailExists();

// files for jwt will be here

6.4 Add emailExists() method

We will add an emailExists() method on our user object class. This method will return true if the submitted email exists, else it will return false.

Replace // emailExists() method will be here comment of /api/objects/user.php file with the following code.

// check if given email exist in the database
function emailExists(){

	// query to check if email exists
	$query = "SELECT id, firstname, lastname, password
			FROM " . $this->table_name . "
			WHERE email = ?
			LIMIT 0,1";

	// prepare the query
	$stmt = $this->conn->prepare( $query );

	// sanitize
	$this->email=htmlspecialchars(strip_tags($this->email));

	// bind given email value
	$stmt->bindParam(1, $this->email);

	// execute the query
	$stmt->execute();

	// get number of rows
	$num = $stmt->rowCount();

	// if email exists, assign values to object properties for easy access and use for php sessions
	if($num>0){

		// get record details / values
		$row = $stmt->fetch(PDO::FETCH_ASSOC);

		// assign values to object properties
		$this->id = $row['id'];
		$this->firstname = $row['firstname'];
		$this->lastname = $row['lastname'];
		$this->password = $row['password'];

		// return true because email exists in the database
		return true;
	}

	// return false if email does not exist in the database
	return false;
}

// update() method will be here

6.5 Include files to encode JWT

The code below shows the necessary files we needed to include to generate or encode a JSON web token.

Replace // files for jwt will be here comment of login.php file with the following code.

// generate json web token
include_once 'config/core.php';
include_once 'libs/php-jwt-master/src/BeforeValidException.php';
include_once 'libs/php-jwt-master/src/ExpiredException.php';
include_once 'libs/php-jwt-master/src/SignatureInvalidException.php';
include_once 'libs/php-jwt-master/src/JWT.php';
use \Firebase\JWT\JWT;

// generate jwt will be here

6.6 Generate JSON web token

The code below will check if email exists and if password match what is in the database. We used the built-in password_verify() function to do the matching.

If login is valid, it will generate the JSON Web Token.

Replace // generate jwt will be here comment of login.php file with the following code.

// check if email exists and if password is correct
if($email_exists && password_verify($data->password, $user->password)){

    $token = array(
       "iss" => $iss,
       "aud" => $aud,
       "iat" => $iat,
       "nbf" => $nbf,
       "data" => array(
           "id" => $user->id,
           "firstname" => $user->firstname,
           "lastname" => $user->lastname,
           "email" => $user->email
       )
    );

    // set response code
    http_response_code(200);

    // generate jwt
    $jwt = JWT::encode($token, $key);
    echo json_encode(
            array(
                "message" => "Successful login.",
                "jwt" => $jwt
            )
        );

}

// login failed will be here

6.7 Tell the user login failed

If the email does not exist or the password did not match, tell the user he cannot login.

Replace // login failed will be here comment of login.php file with the following code.

// login failed
else{

    // set response code
    http_response_code(401);

    // tell the user login failed
    echo json_encode(array("message" => "Login failed."));
}
?>

6.8 Create core configuration file

The login.php file will not work without the core.php file. This file contains common settings or variables of our application.

We have variables used by our JWT library to encode and decode a token. $key's value must be your own and unique secret key.

The rest is called the registered claim names. The iss (issuer) claim identifies the principal that issued the JWT.

The aud (audience) claim identifies the recipients that the JWT is intended for. The iat (issued at) claim identifies the time at which the JWT was issued.

The nbf (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.

You can use another useful claim name called exp (expiration time) which identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.

Including these claims are optional. Please read more about registered claim names here.

  • Open the api folder.
  • Open the config folder.
  • Create a new file called core.php.
  • Place the following code.
<?php
// show error reporting
error_reporting(E_ALL);

// set your default time-zone
date_default_timezone_set('Asia/Manila');

// variables used for jwt
$key = "example_key";
$iss = "http://example.org";
$aud = "http://example.com";
$iat = 1356999524;
$nbf = 1357000000;
?>

6.9 Download PHP-JWT from GitHub

The files included in login.php file will not work without this library.

  • Download the library from this link.
  • Create libs folder.
  • Unzip the downloaded library there.
  • See the file structure above to see how it should look like.

6.10 Output

  • To test for successful login, enter the following as the request URL.
http://localhost/rest-api-authentication-example/api/login.php
  • Enter the following on the body.
{
 "email" : "[email protected]",
 "password" : "555"
}
  • We need to take note of the generated JWT because we will use it to access a resource later.
  • To test for failed login, change the value of the password to 111 because it is the wrong password.

7.0 Create API for JWT validation

7.1 Create a file to validate JWT

This file will return output in JSON format and will accept requests from the specified URL. We'll set the correct headers.

  • Open api folder.
  • Create validate_token.php file.
  • Place the following code.
<?php
// required headers
header("Access-Control-Allow-Origin: http://localhost/rest-api-authentication-example/");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// files for decoding jwt will be here

7.2 Include files to decode JWT

The code below shows the inclusion of the necessary files to decode the given JSON web token.

Replace // files for decoding jwt will be here comment of validate_token.php file with the following code.

// required to decode jwt
include_once 'config/core.php';
include_once 'libs/php-jwt-master/src/BeforeValidException.php';
include_once 'libs/php-jwt-master/src/ExpiredException.php';
include_once 'libs/php-jwt-master/src/SignatureInvalidException.php';
include_once 'libs/php-jwt-master/src/JWT.php';
use \Firebase\JWT\JWT;

// retrieve gieve jwt here

7.3 Retrieve given JWT

The code below shows how to get the value of JSON web token.

Replace // retrieve gieve jwt here comment of validate_token.php file with the following code.

// get posted data
$data = json_decode(file_get_contents("php://input"));

// get jwt
$jwt=isset($data->jwt) ? $data->jwt : "";

// decode jwt here

7.4 Decode JWT if it exists

Check if a JWT is given. If true, decode it. Return a response code of 200, tell the user access is granted and some user information.

Replace // decode jwt here comment of validate_token.php file with the following code.

// if jwt is not empty
if($jwt){

    // if decode succeed, show user details
    try {
        // decode jwt
        $decoded = JWT::decode($jwt, $key, array('HS256'));

        // set response code
        http_response_code(200);

        // show user details
        echo json_encode(array(
            "message" => "Access granted.",
            "data" => $decoded->data
        ));

    }

    // catch will be here
}

// error if jwt is empty will be here

7.5 Show error if decoding failed

If decoding JWT failed, it means access to the resource is denied. We need to return a response code of 401, tell the user access is denied and some information about the error.

Replace // catch will be here comment of validate_token.php file with the following code.

// if decode fails, it means jwt is invalid
catch (Exception $e){

    // set response code
    http_response_code(401);

    // tell the user access denied  & show error message
    echo json_encode(array(
        "message" => "Access denied.",
        "error" => $e->getMessage()
    ));
}

7.6 Show error message if JWT is empty

If JWT is empty, it means access is also denied. We need to return a response code of 401 and tell the user access is denied.

Replace // error if jwt is empty will be here comment of validate_token.php file with the following code.

// show error message if jwt is empty
else{

    // set response code
    http_response_code(401);

    // tell the user access denied
    echo json_encode(array("message" => "Access denied."));
}
?>

7.7 Output

  • To test for successful access, enter the following request URL.
http://localhost/rest-api-authentication-example/api/validate_token.php
  • Enter the JSON Web Token we retrieved earlier. The JSON web token below is different from yours. Make sure your JWT was generated in your machine.
{
    "jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZyIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUuY29tIiwiaWF0IjoxMzU2OTk5NTI0LCJuYmYiOjEzNTcwMDAwMDAsImRhdGEiOnsiaWQiOiI5IiwiZmlyc3RuYW1lIjoiTWlrZSIsImxhc3RuYW1lIjoiRGFsaXNheSIsImVtYWlsIjoibWlrZUBjb2Rlb2ZhbmluamEuY29tIn19.h_Q4gJ3epcpwdwNCNCYxtiKdXsN34W9MEjxZ7sx21Vs"
}
  • It should look like this on POSTMAN.
  • To test for failed access, just put the word "EDITED" on your JWT. This will make JWT wrong. It will result in denied access.
  • It should look like the following.

8.0 Create API for user account

8.1 Create a file for updating user account

This file will return output in JSON format and will accept requests from the specified URL. We'll set the correct headers.

  • Open api folder.
  • Create update_user.php file.
  • Place the following code.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// files for decoding jwt will be here

8.2 Include files to decode JWT

The code below shows the inclusion of the necessary files to decode the given JSON web token.

Replace // files for decoding jwt will be here comment of update_user.php file with the following code.

// required to encode json web token
include_once 'config/core.php';
include_once 'libs/php-jwt-master/src/BeforeValidException.php';
include_once 'libs/php-jwt-master/src/ExpiredException.php';
include_once 'libs/php-jwt-master/src/SignatureInvalidException.php';
include_once 'libs/php-jwt-master/src/JWT.php';
use \Firebase\JWT\JWT;

// database connection will be here

8.3 Connect to database and user table

We will need to update user information on the database. That's why we need to get a database connection.

Replace // database connection will be here comment of update_user.php file with the following code.

// files needed to connect to database
include_once 'config/database.php';
include_once 'objects/user.php';

// get database connection
$database = new Database();
$db = $database->getConnection();

// instantiate user object
$user = new User($db);

// retrieve given jwt here

8.4 Retrieve given JWT

The code below shows how to get the value of given JSON web token.

Replace // retrieve given jwt here comment of update_user.php file with the following code.

// get posted data
$data = json_decode(file_get_contents("php://input"));

// get jwt
$jwt=isset($data->jwt) ? $data->jwt : "";

// decode jwt here

8.5 Decode JWT if it exists

Check if a JWT is given. If true, decode it inside a try block.

Replace // decode jwt here comment of update_user.php file with the following code.

// if jwt is not empty
if($jwt){

    // if decode succeed, show user details
    try {

        // decode jwt
        $decoded = JWT::decode($jwt, $key, array('HS256'));

        // set user property values here
    }

    // catch failed decoding will be here
}

// error message if jwt is empty will be here

8.6 Show error message if decoding fails

If decoding JWT fails, we need to set a response code of 401, tell the user access is denied and show information about the error.

Replace // catch failed decoding will be here comment of update_user.php file with the following code.

// if decode fails, it means jwt is invalid
catch (Exception $e){

    // set response code
    http_response_code(401);

    // show error message
    echo json_encode(array(
        "message" => "Access denied.",
        "error" => $e->getMessage()
    ));
}

8.7 Set user property values

We need to set the submitted data (through the HTML form) to the user object properties.

Replace // set user property values here comment of update_user.php file with the following code.

// set user property values
$user->firstname = $data->firstname;
$user->lastname = $data->lastname;
$user->email = $data->email;
$user->password = $data->password;
$user->id = $decoded->data->id;

// update user will be here

8.8 Use the update() method

One the code below, we use the user object's create() method. If it returns true, it means the user was updated. If it returns false, the system is unable to update the user information.

Replace // update user will be here comment of update_user.php file with the following code.

// update the user record
if($user->update()){
    // regenerate jwt will be here
}

// message if unable to update user
else{
    // set response code
    http_response_code(401);

    // show error message
    echo json_encode(array("message" => "Unable to update user."));
}

8.9 Add update() method in user class

The code below shows the UPDATE query, data sanitation, and binding.

If a password was typed in the HTML form, we use the built-in password_hash() method to secure the user's password on the database.

If the execution is a success, the user information will be updated on the database.

Replace the // update() method will be here comment of api/objects/user.php file with the following code.

// update a user record
public function update(){

	// if password needs to be updated
	$password_set=!empty($this->password) ? ", password = :password" : "";

	// if no posted password, do not update the password
	$query = "UPDATE " . $this->table_name . "
			SET
				firstname = :firstname,
				lastname = :lastname,
				email = :email
				{$password_set}
			WHERE id = :id";

	// prepare the query
	$stmt = $this->conn->prepare($query);

	// sanitize
	$this->firstname=htmlspecialchars(strip_tags($this->firstname));
	$this->lastname=htmlspecialchars(strip_tags($this->lastname));
	$this->email=htmlspecialchars(strip_tags($this->email));

	// bind the values from the form
	$stmt->bindParam(':firstname', $this->firstname);
	$stmt->bindParam(':lastname', $this->lastname);
	$stmt->bindParam(':email', $this->email);

	// hash the password before saving to database
	if(!empty($this->password)){
		$this->password=htmlspecialchars(strip_tags($this->password));
		$password_hash = password_hash($this->password, PASSWORD_BCRYPT);
		$stmt->bindParam(':password', $password_hash);
	}

	// unique ID of record to be edited
	$stmt->bindParam(':id', $this->id);

	// execute the query
	if($stmt->execute()){
		return true;
	}

	return false;
}

8.10 Re-generate JWT

We need to re-generate or get a new JSON Web Token especially if user information was changed. The code below does that and it sets a response code of 200 and tells the user that the information was updated.

Replace the // regenerate jwt will be here comment of update_user.php file with the following code.

// we need to re-generate jwt because user details might be different
$token = array(
   "iss" => $iss,
   "aud" => $aud,
   "iat" => $iat,
   "nbf" => $nbf,
   "data" => array(
       "id" => $user->id,
       "firstname" => $user->firstname,
       "lastname" => $user->lastname,
       "email" => $user->email
   )
);
$jwt = JWT::encode($token, $key);

// set response code
http_response_code(200);

// response in json format
echo json_encode(
        array(
            "message" => "User was updated.",
            "jwt" => $jwt
        )
    );

8.11 Show error message if JWT is empty

We need to tell the user that access is denied if JWT does not exist. We set a response code of 401 as well.

Replace the // error message if JWT is empty will be here comment of update_user.php file with the following code.

// show error message if jwt is empty
else{

    // set response code
    http_response_code(401);

    // tell the user access denied
    echo json_encode(array("message" => "Access denied."));
}
?>

8.12 Output

  • To test for successful user update, enter the following as request URL on POSTMAN.
http://localhost/rest-api-authentication-example/api/update_user.php
  • On the body section, enter new user information with the JSON Web Token we retrieved earlier.
  • The JSON web token below is different from yours. Make sure your JWT was generated in your machine.
{
    "firstname" : "Mike",
    "lastname" : "Dalisay",
    "email" : "[email protected]",
    "password" : "555",
    "jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZyIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUuY29tIiwiaWF0IjoxMzU2OTk5NTI0LCJuYmYiOjEzNTcwMDAwMDAsImRhdGEiOnsiaWQiOiI5IiwiZmlyc3RuYW1lIjoiVmluY2UiLCJsYXN0bmFtZSI6IkRhbGlzYXkiLCJlbWFpbCI6Im1pa2VAY29kZW9mYW5pbmphLmNvbSJ9fQ.3Sv65TVYACkNPo4HMr4NvreyZY16wxG-nSorLi_jykI"
}
  • It should look like this on POSTMAN.
  • As you can see in the image above, it generates a new JWT and it will be stored in the client application. We can use the new information on the app interface later.
  • To test for failed user update, you can just add the word EDITED on the submitted JWT or just remove the JWT. It should look like the following.

9.0 Create interface for user registration

9.1 Create index page

We will use the APIs we created earlier on a simple Single-Page Application (SPA) created using HTML, CSS and JavaScript.

All of the essential codes will be in this single index.html file.

  • Open rest-api-authentication-example folder.
  • Create index.html file.
  • Place the following code.
<!doctype html>
<html lang="en">
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

        <title>Rest API Authentication Example</title>

        <!-- CSS links will be here -->

    </head>
<body>

<!-- navigation bar will be here -->

<!-- script links will be here -->

</body>
</html>

9.2 Add navigation bar

The navigation bar is where the menus like home page, account page, login page, logout and sign up page can be clicked or triggered.

Replace the <!-- navigation bar will be here --> comment of index.html file with the following code.

<!-- navbar -->
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
    <a class="navbar-brand" href="#">Navbar</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
        <div class="navbar-nav">
            <a class="nav-item nav-link" href="#" id='home'>Home</a>
            <a class="nav-item nav-link" href="#" id='update_account'>Account</a>
            <a class="nav-item nav-link" href="#" id='logout'>Logout</a>
            <a class="nav-item nav-link" href="#" id='login'>Login</a>
            <a class="nav-item nav-link" href="#" id='sign_up'>Sign Up</a>
        </div>
    </div>
</nav>
<!-- /navbar -->

<!-- content section will be here -->

9.3 Add content section

The content section is where the contents like HTML forms and message prompts will be rendered.

Replace the <!-- content section will be here --> comment of index.html file with the following code.

<!-- container -->
<main role="main" class="container starter-template">

	<div class="row">
		<div class="col">

			<!-- where prompt / messages will appear -->
			<div id="response"></div>

			<!-- where main content will appear -->
			<div id="content"></div>
		</div>
	</div>

</main>
<!-- /container -->

9.4 Add Bootstrap 4 and custom CSS links

We are using Bootstrap 4 to make the user interface look good. We will use the CDN link so that we won't have to download the whole library.

We will see the use of custom CSS file on the next section.

Replace the <!-- CSS links will be here --> comment of index.html file with the following code.

<!-- Bootstrap 4 CSS and custom CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
<link rel="stylesheet" type="text/css" href="custom.css" />

9.5 Create custom CSS file

We use the custom CSS for any look & feel customization we want to implement.

  • Open rest-api-authentication-example folder.
  • Create custom.css file.
  • Place the following code.
body { padding-top: 5rem; }
.starter-template { padding: 3rem 1.5rem; }
#logout{ display:none; }

9.6 Add jQuery and Bootstrap 4 script links

In this tutorial, we use the jQuery library to render the interface and make HTTP requests.

To make Bootstrap 4 work, we need to include its own JavaScript as well.

Replace the <!-- script links will be here --> comment of index.html file with the following code.

<!-- jQuery & Bootstrap 4 JavaScript libraries -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>

<!-- jquery scripts will be here -->

9.7 Show a sign up HTML form

When you click the Sign Up menu on the navigation bar, it will show a sign up or registration form.

The code below shows the click trigger and the HTML form.

Replace the <!-- jquery scripts will be here --> comment of index.html file with the following code.

<script>
// jQuery codes
$(document).ready(function(){
    // show sign up / registration form
    $(document).on('click', '#sign_up', function(){

        var html = `
            <h2>Sign Up</h2>
            <form id='sign_up_form'>
            	<div class="form-group">
            		<label for="firstname">Firstname</label>
            		<input type="text" class="form-control" name="firstname" id="firstname" required />
            	</div>

            	<div class="form-group">
            		<label for="lastname">Lastname</label>
            		<input type="text" class="form-control" name="lastname" id="lastname" required />
            	</div>

            	<div class="form-group">
            		<label for="email">Email</label>
            		<input type="email" class="form-control" name="email" id="email" required />
            	</div>

            	<div class="form-group">
            		<label for="password">Password</label>
            		<input type="password" class="form-control" name="password" id="password" required />
            	</div>

                <button type='submit' class='btn btn-primary'>Sign Up</button>
            </form>
            `;

        clearResponse();
        $('#content').html(html);
    });

    // trigger when registration form is submitted here

    // show login form trigger will be here

    // clearResponse() will be here
});
</script>

9.8 Trigger when sign up form is submitted

We need to process the form data when it is submitted.

Replace the trigger when the registration form is submitted here comment of index.html file with the following code.

// trigger when registration form is submitted
$(document).on('submit', '#sign_up_form', function(){

	// get form data
	var sign_up_form=$(this);
	var form_data=JSON.stringify(sign_up_form.serializeObject());

	// submit form data to api
	$.ajax({
		url: "api/create_user.php",
		type : "POST",
		contentType : 'application/json',
		data : form_data,
		success : function(result) {
			// if response is a success, tell the user it was a successful sign up & empty the input boxes
			$('#response').html("<div class='alert alert-success'>Successful sign up. Please login.</div>");
			sign_up_form.find('input').val('');
		},
		error: function(xhr, resp, text){
			// on error, tell the user sign up failed
			$('#response').html("<div class='alert alert-danger'>Unable to sign up. Please contact admin.</div>");
		}
	});

	return false;
});

9.9 Remove any prompt messages

The clearResponse() method was used in the previous section. Its only purpose is to remove any prompt messages that may have been displayed on the screen.

Replace the // clearResponse() will be here comment of index.html file with the following code.

// remove any prompt messages
function clearResponse(){
    $('#response').html('');
}

// showLoginPage() will be here

// serializeObject will be here

9.10 Add serializeObject function

The serializeObject function will convert form data to JSON format. We need this function to send values from an HTML form to the API.

Replace the // serializeObject will be here comment of index.html file with the following code.

// function to make form values to json format
$.fn.serializeObject = function(){

	var o = {};
	var a = this.serializeArray();
	$.each(a, function() {
		if (o[this.name] !== undefined) {
			if (!o[this.name].push) {
				o[this.name] = [o[this.name]];
			}
			o[this.name].push(this.value || '');
		} else {
			o[this.name] = this.value || '';
		}
	});
	return o;
};

9.11 Output

When the user clicks the Sign-Up link on the navigation bar.

After the user filled out and submitted the form.

10.0 Create a login page

10.1 Trigger when login menu was clicked

When you click the Login menu on the navigation bar, it will show a login form.

The code below shows the click trigger and showLoginPage(); function to show a login form.

Replace the // show login form trigger will be here comment of index.html file with the following code.

// show login form
$(document).on('click', '#login', function(){
    showLoginPage();
});

// login form submit trigger will be here

10.2 Show login HTML form

The function below shows the HTML form for users to login.

Replace the // showLoginPage() will be here comment of index.html file with the following code.

// show login page
function showLoginPage(){

    // remove jwt
    setCookie("jwt", "", 1);

    // login page html
    var html = `
        <h2>Login</h2>
        <form id='login_form'>
            <div class='form-group'>
                <label for='email'>Email address</label>
                <input type='email' class='form-control' id='email' name='email' placeholder='Enter email'>
            </div>

            <div class='form-group'>
                <label for='password'>Password</label>
                <input type='password' class='form-control' id='password' name='password' placeholder='Password'>
            </div>

            <button type='submit' class='btn btn-primary'>Login</button>
        </form>
        `;

    $('#content').html(html);
    clearResponse();
    showLoggedOutMenu();
}

// setCookie() will be here 

// showLoggedOutMenu() will be here

10.3 Add setCookie() function

The setCookie() function will help us store JWT on the cookie.

Replace the // setCookie() will be here comment of index.html file with the following code.

// function to set cookie
function setCookie(cname, cvalue, exdays) {
    var d = new Date();
    d.setTime(d.getTime() + (exdays*24*60*60*1000));
    var expires = "expires="+ d.toUTCString();
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

10.4 Change menu appearance

The showLoggedOutMenu() function was used in the previous section.

This function will make the menu look like the options for a logged-out user.

Replace the // showLoggedOutMenu() will be here comment of index.html file with the following code.

// if the user is logged out
function showLoggedOutMenu(){
    // show login and sign up from navbar & hide logout button
    $("#login, #sign_up").show();
    $("#logout").hide();
}

// showHomePage() function will be here

10.5 Output

  • If the user clicks the Login menu on the navigation bar.

11.0 Show login responses

11.1 Submitted form trigger

The code below shows a submit trigger for the login form.

It gets the data from the form and stores it in the form_data variable.

Replace the // login form submit trigger will be here comment of index.html file with the following code.

// trigger when login form is submitted
$(document).on('submit', '#login_form', function(){

    // get form data
    var login_form=$(this);
    var form_data=JSON.stringify(login_form.serializeObject());

    // http request will be here

    return false;
});

// trigger to show home page will be here

11.2 Create an HTTP request

The code below shows how we make an HTTP request, specifically an AJAX request to verify if the submitted email and password are valid.

If it is valid, we will save the JWT to localStorage, show the home page and tell the user it was a successful login.

Replace the // http request will be here comment of index.html file with the following code.

// submit form data to api
$.ajax({
    url: "api/login.php",
    type : "POST",
    contentType : 'application/json',
    data : form_data,
    success : function(result){

        // store jwt to cookie
        setCookie("jwt", result.jwt, 1);

        // show home page & tell the user it was a successful login
        showHomePage();
        $('#response').html("<div class='alert alert-success'>Successful login.</div>");

    },
    // error response will be here
});

11.3 Show home page HTML

On the showHomePage() function, we need to validate the stored JWT before showing the home page HTML.

Replace the // showHomePage() function will be here comment of index.html file with the following code.

// show home page
function showHomePage(){

    // validate jwt to verify access
    var jwt = getCookie('jwt');
    $.post("api/validate_token.php", JSON.stringify({ jwt:jwt })).done(function(result) {

        // home page html will be here
    })

    // show login page on error will be here
}

// getCookie() will be here

// showLoggedInMenu() will be here

11.4 Add home page HTML

If JWT is valid, we show the home page HTML and call the showLoggedInMenu() function.

Replace the // home page html will be here comment of index.html file with the following code.

// if valid, show homepage
var html = `
    <div class="card">
        <div class="card-header">Welcome to Home!</div>
        <div class="card-body">
            <h5 class="card-title">You are logged in.</h5>
            <p class="card-text">You won't be able to access the home and account pages if you are not logged in.</p>
        </div>
    </div>
    `;

$('#content').html(html);
showLoggedInMenu();

11.5 Add getCookie() function

The getCookie() function will help us read the JWT we stored earlier.

Replace the // getCookie() will be here comment of index.html file with the following code.

// get or read cookie
function getCookie(cname){
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for(var i = 0; i <ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' '){
            c = c.substring(1);
        }

        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

11.6 Show error for invalid login

If the submitted email and password are invalid, we tell the user login failed and empty the login form.

Replace the // error response will be here comment of index.html file with the following code.

error: function(xhr, resp, text){
    // on error, tell the user login has failed & empty the input boxes
    $('#response').html("<div class='alert alert-danger'>Login failed. Email or password is incorrect.</div>");
    login_form.find('input').val('');
}

11.7 Output

  • If the user entered a valid email and password.
  • If the user entered an invalid email or password.

12.0 On click of Home menu

12.1 Add trigger to show home page

The code below shows a click trigger with showHomePage(); function.

Replace the // trigger to show home page will be here comment of index.html file with the following code.

// show home page
$(document).on('click', '#home', function(){
    showHomePage();
    clearResponse();
});

// trigger to show account form will be here 

12.2 Set logged-in menu

The showLoggedInMenu() function will change the menu options to look like a menu for a logged-in user.

Replace the showLoggedInMenu() will be here comment of index.html file with the following code.

// if the user is logged in
function showLoggedInMenu(){
    // hide login and sign up from navbar & show logout button
    $("#login, #sign_up").hide();
    $("#logout").show();
}

// showUpdateAccountForm() will be here

12.3 Show login page if JWT is invalid

If JWT is invalid, we will show the login page and ask the user to login.

Replace the // show login page on error will be here comment of index.html file with the following code.

// show login page on error
.fail(function(result){
    showLoginPage();
    $('#response').html("<div class='alert alert-danger'>Please login to access the home page.</div>");
});

12.4 Output

  • If a logged-out user clicked the Home menu on the navigation bar.
  • If a logged-in user clicked the Home menu on the navigation bar.

13.0 Show user account page

13.1 Add trigger to show account form

The code below shows a click trigger with showUpdateAccountForm(); function.

Replace the // trigger to show account form will be here comment of index.html file with the following code.

// show update account form
$(document).on('click', '#update_account', function(){
    showUpdateAccountForm();
});

// trigger for updating user account will be here

13.2 Verify if JWT is valid

We need the showUpdateAccountForm() function to render to HTML form for updating a user account.

First, we need to verify if JWT is valid. We use the getCookie('jwt'); function to get the JWT and send it to validate_token.php via jQuery $.post method.

Replace the // showUpdateAccountForm() will be here comment of index.html file with the following code.

function showUpdateAccountForm(){
    // validate jwt to verify access
    var jwt = getCookie('jwt');
    $.post("api/validate_token.php", JSON.stringify({ jwt:jwt })).done(function(result) {

        // html form for updating user account will be here
    })

    // error message when jwt is invalid will be here
}

13.3 Show account form if JWT is valid

If JWT is valid, we will show the HTML form using the code below.

Replace the // html form for updating user account will be here comment of index.html file with the following code.

// if response is valid, put user details in the form
var html = `
        <h2>Update Account</h2>
        <form id='update_account_form'>
            <div class="form-group">
                <label for="firstname">Firstname</label>
                <input type="text" class="form-control" name="firstname" id="firstname" required value="` + result.data.firstname + `" />
            </div>

            <div class="form-group">
                <label for="lastname">Lastname</label>
                <input type="text" class="form-control" name="lastname" id="lastname" required value="` + result.data.lastname + `" />
            </div>

            <div class="form-group">
                <label for="email">Email</label>
                <input type="email" class="form-control" name="email" id="email" required value="` + result.data.email + `" />
            </div>

            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" class="form-control" name="password" id="password" />
            </div>

            <button type='submit' class='btn btn-primary'>
                Save Changes
            </button>
        </form>
    `;

clearResponse();
$('#content').html(html);

13.4 Show login page if JWT is invalid

If JWT is invalid, we will logout the user and ask him to login.

Replace the // error message when jwt is invalid will be here comment of index.html file with the following code.

// on error/fail, tell the user he needs to login to show the account page
.fail(function(result){
    showLoginPage();
    $('#response').html("<div class='alert alert-danger'>Please login to access the account page.</div>");
});

13.5 Add a trigger for updating a user account

If the submit button was clicked, we will use the code below to catch that trigger.

We will get the form handle and JWT as well.

Replace the // trigger for updating user account will be here comment of index.html file with the following code.

// trigger when 'update account' form is submitted
$(document).on('submit', '#update_account_form', function(){

    // handle for update_account_form
    var update_account_form=$(this);

    // validate jwt to verify access
    var jwt = getCookie('jwt');

    // get form data and jwt here

    return false;
});

// trigger to logout will be here

13.6 Get form data and JWT

On the code below, we get the form values and add the JWT to it. We convert the form values to JSON via stringify() function so that it can be sent to the API.

Replace the // get form data and jwt here comment of index.html file with the following code.

// get form data
var update_account_form_obj = update_account_form.serializeObject()

// add jwt on the object
update_account_form_obj.jwt = jwt;

// convert object to json string
var form_data=JSON.stringify(update_account_form_obj);

// send data to api here

13.7 Send data to API

We send the form values to update_user.php using jQuery AJAX method. If the response is successful, we tell the user his account was updated.

We store the new JWT to localStorage as well.

Replace the // send data to api here comment of index.html file with the following code.

// submit form data to api
$.ajax({
    url: "api/update_user.php",
    type : "POST",
    contentType : 'application/json',
    data : form_data,
    success : function(result) {

        // tell the user account was updated
        $('#response').html("<div class='alert alert-success'>Account was updated.</div>");

        // store new jwt to coookie
        setCookie("jwt", result.jwt, 1);
    },

    // errors will be handled here
});

13.8 Show error message

If the system is unable to update the user, we tell the user about that.

If JWT is invalid and access is denied, we logout the user and ask him to log in.

Replace the // errors will be handled here comment of index.html file with the following code.

// show error message to user
error: function(xhr, resp, text){
    if(xhr.responseJSON.message=="Unable to update user."){
        $('#response').html("<div class='alert alert-danger'>Unable to update account.</div>");
    }

    else if(xhr.responseJSON.message=="Access denied."){
        showLoginPage();
        $('#response').html("<div class='alert alert-success'>Access denied. Please login</div>");
    }
}

13.9 Output

  • If a user account was updated successfully.
  • If there was a problem when updating a user account.

14.0 Add JavaScript for user logout

14.1 Add a trigger to logout

The click trigger below is used when the user click the Logout link on the menu.

We use the showLoginPage(); method to logout the user. We tell he is logged out as well.

Replace the // trigger to logout will be here comment of index.html file with the following code.

// logout the user
$(document).on('click', '#logout', function(){
    showLoginPage();
    $('#response').html("<div class='alert alert-info'>You are logged out.</div>");
});

14.2 Output

  • If the user clicked the Logout link on the menu.

15.0 Download source codes

We highly recommend for you to follow and study our well-detailed, step-by-step tutorial above first. Nothing beats experience when it comes to learning.

But we believe you will learn faster if you’ll see the final source code as well. We consider it as your additional guide.

Imagine the value or skill upgrade it can bring you. The additional income you can get from your work, projects or business. The precious time you save. Isn’t that what you want?

15.1 Download LEVEL 1 source code

FEATURES LEVEL 1
API for user registration / sign up YES
API for user login YES
API for JWT validation YES
API for updating user account YES
Sign up page / registration form YES
HTML5 validation for registration form YES
Tell the user if sign up is successful YES
Login using email and password YES
Tell the user if login failed YES
Tell the user if successfully logged in YES
Restricted access to home page YES
Restricted access to account page YES
Show home page when logged in YES
Show accounts page when logged in YES
Update user information YES
Tell the user if updating the account failed YES
Logout user YES
FREE email support for 3 months YES
Source code updates via email YES
Download Now

15.2 Download LEVEL 2 source code

FEATURES LEVEL 2
All features of LEVEL 1 source code YES
User access level YES
Admin access level YES
Admin can create user YES
Admin can read different user information YES
Admin can update user information YES
Admin can delete user YES
Admin can read users with pagination YES
Admin can search users YES
API for creating a user YES
API for reading users list (with pagination) YES
API for reading user information YES
API for updating a user YES
API for deleting a user YES
Tell the user if a request fails or succeeds. YES
Validate JWT for every HTTP request YES
FREE email support for 6 months YES
Source code updates via email YES
Download Now

16.0 What's Next?

We will learn how to create, read, update and delete database records (with user interface) on our AJAX CRUD Tutorial.

If you want to receive new and updated high-quality tutorials, please subscribe for free.

17.0 Related Tutorials

18.0 Notes

#1 Found An Issue?

If you found a problem with this code, please write a comment below. Please be descriptive about your issue. Please provide the error messages, screenshots (or screencast) and your test URL. Thanks!

Before you write a comment, remember to read this guide and our code of conduct.

#2 Become a true Ninja!

We constantly improve CodeOfaNinja. We update our tutorials and source codes. Receive valuable web programming tutorials and updates to your email. Subscribe now!

Enjoy high-quality web programming tutorials.
Subscribe to CodeOfaNinja now for FREE!

#3 Thank You!

Please note that this post is not yet in its final form. We are going to update this post so it will be perfect in the future.

If you have a friend or know someone who needs this REST API Authentication Example in PHP & JWT, please share this page with them! I know you will help them a lot by doing it. Thanks!

create-simple-rest-api-in-php

How To Create A Simple REST API in PHP? Step By Step Guide!

Previously, we learned how to create, read, update and delete database records (CRUD operations) with our PHP, MySQL & OOP CRUD Tutorial.

Today, before we go to JavaScript programming, we will learn how to create a simple REST API in PHP. Enjoy our step-by-step tutorial below!

This post covers the following topics:

1.0 Project Overview
1.1 What is REST API?
1.2 Why do we need REST API?
1.3 Where REST API is used?
1.4 REST API in our tutorials

2.0 File structure

3.0 Setup the database
3.1 Create categories table
3.2 Dump data for categories table
3.3 Products table
3.4 Dump data for products table
3.5 Connect to database

4.0 Read products
4.1 Product object
4.2 Create a file to read products
4.3 Connect to database and products table
4.4 Read products from the database
4.5 Add Product "read()" method
4.6 Tell the user no products found
4.7 Output

5.0 Create Product
5.1 Create create.php file
5.2 Product create() method
5.3 Output

6.0 Read One Product
6.1 Create read_one.php file
6.2 Product readOne() method
6.3 Output

7.0 Update product
7.1 Create “update.php” file
7.2 Product update() method
7.3 Output

8.0 Delete Product
8.1 Create “delete.php” file
8.2 Product delete() method
8.3 Output

9.0 Search Products
9.1 Create "search.php" file
9.2 Create "search()" method
9.3 Output

10.0 Paginate Products
10.1 Create "read_paging.php" file
10.2 Create "core.php" file
10.3 Create "readPaging()" method
10.4 Create "count()" method
10.5 Get "paging" array
10.6 Output

11.0 Read Categories
11.1 Category object
11.2 Create "read.php" file
11.3 Category "read()" method
11.4 Output

12.0 Download Source Codes
13.0 What's Next?
14.0 Related Tutorials
15.0 Notes

1.0 Project Overview

1.1 What is REST API?

To define "REST API", we have to know what is "REST" and what is "API" first. I'll do my best to explain it in simple terms because REST has a lot of concepts inside of it that could mean a lot of things.

REST stands for "REpresentational State Transfer". It is a concept or architecture for managing information over the internet. REST concepts are referred to as resources. A representation of a resource must be stateless. It is usually represented by JSON. This post is worth reading: How I Explained REST to My Wife?

API stands for "Application Programming Interface". It is a set of rules that allows one piece of software application to talk to another. Those "rules" can include the create, read, update and delete operations.

REST API enables your application to cooperate with one or several different applications using REST concepts. If you want to learn more, watch the video below.

1.2 Why do we need REST API?

In many applications, REST API is a need because this is the lightest way to create, read, update or delete information between different applications over the internet or HTTP protocol. This information is presented to the user in an instant especially if you use JavaScript to render the data on a webpage.

1.3 Where REST API is used?

REST API can be used by any application that can connect to the internet. If data from an application can be created, read, updated or deleted using another application, it usually means a REST API is used.

1.4 REST API in our tutorials

A REST API is needed for our AJAX CRUD Tutorial. But don't mind it for now. We will do it one step at a time. You don't need to learn all of it as well. Just choose what you need to learn.

Also, please note that this PHP REST API is not yet in its final form. We still have some work to do with .htaccess for better URLs and more.

But one thing is for sure, this source code is good enough and works for our JavaScript tutorials.

2.0 File structure

At the end of this tutorial, we will have the following folders and files.
├─ api/
├─── config/
├────── core.php - file used for core configuration
├────── database.php - file used for connecting to the database.
├─── objects/
├────── product.php - contains properties and methods for "product" database queries.
├────── category.php - contains properties and methods for "category" database queries.
├─── product/
├────── create.php - a file that will accept posted product data to be saved to the database.
├────── delete.php - a file that will accept a product ID to delete a database record.
├────── read.php - a file that will output JSON data based on "products" database records.
├────── read_paging.php - a file that will output "products" JSON data with pagination.
├────── read_one.php - a file that will accept product ID to read a record from the database.
├────── update.php - a file that will accept a product ID to update a database record.
├────── search.php - a file that will accept keywords parameter to search "products" database.
├─── category/
├────── read.php - a file that will output JSON data based on "categories" database records.
├─── shared/
├────── utilities.php - a file that will return pagination array.

3.0 Setup the database

Using PhpMyAdmin, create a new api_db database. Yes, api_db is the database name. After that, run the following SQL queries to create new tables with sample data.

3.1 Create categories table

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(256) NOT NULL,
  `description` text NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=19 ;

3.2 Dump data for categories table

INSERT INTO `categories` (`id`, `name`, `description`, `created`, `modified`) VALUES
(1, 'Fashion', 'Category for anything related to fashion.', '2014-06-01 00:35:07', '2014-05-30 17:34:33'),
(2, 'Electronics', 'Gadgets, drones and more.', '2014-06-01 00:35:07', '2014-05-30 17:34:33'),
(3, 'Motors', 'Motor sports and more', '2014-06-01 00:35:07', '2014-05-30 17:34:54'),
(5, 'Movies', 'Movie products.', '0000-00-00 00:00:00', '2016-01-08 13:27:26'),
(6, 'Books', 'Kindle books, audio books and more.', '0000-00-00 00:00:00', '2016-01-08 13:27:47'),
(13, 'Sports', 'Drop into new winter gear.', '2016-01-09 02:24:24', '2016-01-09 01:24:24');

3.3 Products table

CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `description` text NOT NULL,
  `price` decimal(10,0) NOT NULL,
  `category_id` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=65 ;

3.4 Dump data for products table

INSERT INTO `products` (`id`, `name`, `description`, `price`, `category_id`, `created`, `modified`) VALUES
(1, 'LG P880 4X HD', 'My first awesome phone!', '336', 3, '2014-06-01 01:12:26', '2014-05-31 17:12:26'),
(2, 'Google Nexus 4', 'The most awesome phone of 2013!', '299', 2, '2014-06-01 01:12:26', '2014-05-31 17:12:26'),
(3, 'Samsung Galaxy S4', 'How about no?', '600', 3, '2014-06-01 01:12:26', '2014-05-31 17:12:26'),
(6, 'Bench Shirt', 'The best shirt!', '29', 1, '2014-06-01 01:12:26', '2014-05-31 02:12:21'),
(7, 'Lenovo Laptop', 'My business partner.', '399', 2, '2014-06-01 01:13:45', '2014-05-31 02:13:39'),
(8, 'Samsung Galaxy Tab 10.1', 'Good tablet.', '259', 2, '2014-06-01 01:14:13', '2014-05-31 02:14:08'),
(9, 'Spalding Watch', 'My sports watch.', '199', 1, '2014-06-01 01:18:36', '2014-05-31 02:18:31'),
(10, 'Sony Smart Watch', 'The coolest smart watch!', '300', 2, '2014-06-06 17:10:01', '2014-06-05 18:09:51'),
(11, 'Huawei Y300', 'For testing purposes.', '100', 2, '2014-06-06 17:11:04', '2014-06-05 18:10:54'),
(12, 'Abercrombie Lake Arnold Shirt', 'Perfect as gift!', '60', 1, '2014-06-06 17:12:21', '2014-06-05 18:12:11'),
(13, 'Abercrombie Allen Brook Shirt', 'Cool red shirt!', '70', 1, '2014-06-06 17:12:59', '2014-06-05 18:12:49'),
(26, 'Another product', 'Awesome product!', '555', 2, '2014-11-22 19:07:34', '2014-11-21 20:07:34'),
(28, 'Wallet', 'You can absolutely use this one!', '799', 6, '2014-12-04 21:12:03', '2014-12-03 22:12:03'),
(31, 'Amanda Waller Shirt', 'New awesome shirt!', '333', 1, '2014-12-13 00:52:54', '2014-12-12 01:52:54'),
(42, 'Nike Shoes for Men', 'Nike Shoes', '12999', 3, '2015-12-12 06:47:08', '2015-12-12 05:47:08'),
(48, 'Bristol Shoes', 'Awesome shoes.', '999', 5, '2016-01-08 06:36:37', '2016-01-08 05:36:37'),
(60, 'Rolex Watch', 'Luxury watch.', '25000', 1, '2016-01-11 15:46:02', '2016-01-11 14:46:02');

3.5 Connect to database

The code below shows the database credentials and a method to get a database connection using PDO. If you're not yet familiar with PDO, please learn from our PHP OOP CRUD Tutorial first.

  • Create api folder. Open api folder.
  • Create config folder. Open config folder.
  • Create a database.php file. Place the following code inside it.
<?php
class Database{
 
    // specify your own database credentials
    private $host = "localhost";
    private $db_name = "api_db";
    private $username = "root";
    private $password = "";
    public $conn;
 
    // get the database connection
    public function getConnection(){
 
        $this->conn = null;
 
        try{
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
            $this->conn->exec("set names utf8");
        }catch(PDOException $exception){
            echo "Connection error: " . $exception->getMessage();
        }
 
        return $this->conn;
    }
}
?>

4.0 Read products

4.1 Product object

The code below shows a class named Product with several of its properties. It also shows a constructor method that will accept the database connection.

We will use this class to read data from the database.

  • Open api folder.
  • Create objects folder.
  • Open objects folder.
  • Create product.php file.
  • Place the following code inside it.
<?php
class Product{
 
    // database connection and table name
    private $conn;
    private $table_name = "products";
 
    // object properties
    public $id;
    public $name;
    public $description;
    public $price;
    public $category_id;
    public $category_name;
    public $created;
 
    // constructor with $db as database connection
    public function __construct($db){
        $this->conn = $db;
    }
}
?>

4.2 Create file to read products

The code below shows headers about who can read this file and which type of content it will return.

In this case, our read.php the file can be read by anyone (asterisk * means all) and will return a data in JSON format.

  • Open api folder.
  • Create product folder.
  • Open product folder.
  • Create read.php file.
  • Place the following code inside it.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
 
// database connection will be here

4.3 Connect to database and products table

In the code below, we included the database.php and product.php files. These are the files we created earlier.

We need to use the getConnection() method of the Database class to get a database connection. We pass this connection to the Product class.

Replace of // database connection will be here comment of read.php file with the following code.

// include database and object files
include_once '../config/database.php';
include_once '../objects/product.php';
 
// instantiate database and product object
$database = new Database();
$db = $database->getConnection();
 
// initialize object
$product = new Product($db);
 
// read products will be here

4.4 Read products from the database

In the code below, we used the read() method of Product class to read data from the database. Through the $num variable, we check if there are records found.

If there are records found, we loop through it using the while loop, add each record to the $products_arr array, set a 200 OK response code and show it to the user in JSON format.

Replace of // read products will be here comment of read.php file with the following code.

// query products
$stmt = $product->read();
$num = $stmt->rowCount();
 
// check if more than 0 record found
if($num>0){
 
    // products array
    $products_arr=array();
    $products_arr["records"]=array();
 
    // retrieve our table contents
    // fetch() is faster than fetchAll()
    // http://stackoverflow.com/questions/2770630/pdofetchall-vs-pdofetch-in-a-loop
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
        // extract row
        // this will make $row['name'] to
        // just $name only
        extract($row);
 
        $product_item=array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
 
        array_push($products_arr["records"], $product_item);
    }
 
    // set response code - 200 OK
    http_response_code(200);
 
    // show products data in json format
    echo json_encode($products_arr);
}
 
// no products found will be here

4.5 Add product "read()" method

We used the read() method in the previous section but it does not exist yet in the Product class. We need to add this read() method. The code below shows the query to get records from the database.

  • Open objects folder.
  • Open product.php file.
  • Place the following code inside the Product class.
  • To make sure you added it correctly, place the code before the last closing curly brace.
// read products
function read(){
 
    // select all query
    $query = "SELECT
                c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
            FROM
                " . $this->table_name . " p
                LEFT JOIN
                    categories c
                        ON p.category_id = c.id
            ORDER BY
                p.created DESC";
 
    // prepare query statement
    $stmt = $this->conn->prepare($query);
 
    // execute query
    $stmt->execute();
 
    return $stmt;
}

4.6 Tell the user no products found

If the $num variable has a value of zero or negative, it means there are no records returned from the database. We need to tell the user about this.

On the code below, we set the response code to 404 - Not found and a message that says No products found.

Replace of // no products found will be here comment of read.php file with the following code.

else{
 
    // set response code - 404 Not found
    http_response_code(404);
 
    // tell the user no products found
    echo json_encode(
        array("message" => "No products found.")
    );
}

4.7 Output

You need to use POSTMAN to test our API. Download your version of POSTMAN here.

Launch POSTMAN. Enter the following as the request URL.

http://localhost/api/product/read.php

Click the blue "Send" button.

  • Output if there are product data.
  • Output if there are no product data.

5.0 Create Product

5.1 Create create.php file

  • Open product folder.
  • Create a new create.php file.
  • Open that file and put the following code inside it.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
 
// get database connection
include_once '../config/database.php';
 
// instantiate product object
include_once '../objects/product.php';
 
$database = new Database();
$db = $database->getConnection();
 
$product = new Product($db);
 
// get posted data
$data = json_decode(file_get_contents("php://input"));
 
// make sure data is not empty
if(
    !empty($data->name) &&
    !empty($data->price) &&
    !empty($data->description) &&
    !empty($data->category_id)
){
 
    // set product property values
    $product->name = $data->name;
    $product->price = $data->price;
    $product->description = $data->description;
    $product->category_id = $data->category_id;
    $product->created = date('Y-m-d H:i:s');
 
    // create the product
    if($product->create()){
 
        // set response code - 201 created
        http_response_code(201);
 
        // tell the user
        echo json_encode(array("message" => "Product was created."));
    }
 
    // if unable to create the product, tell the user
    else{
 
        // set response code - 503 service unavailable
        http_response_code(503);
 
        // tell the user
        echo json_encode(array("message" => "Unable to create product."));
    }
}
 
// tell the user data is incomplete
else{
 
    // set response code - 400 bad request
    http_response_code(400);
 
    // tell the user
    echo json_encode(array("message" => "Unable to create product. Data is incomplete."));
}
?>

5.2 Product create() method

  • Open objects folder.
  • Open product.php file.
  • The previous section will not work without the following code inside the Product (objects/product.php) class.
// create product
function create(){
 
    // query to insert record
    $query = "INSERT INTO
                " . $this->table_name . "
            SET
                name=:name, price=:price, description=:description, category_id=:category_id, created=:created";
 
    // prepare query
    $stmt = $this->conn->prepare($query);
 
    // sanitize
    $this->name=htmlspecialchars(strip_tags($this->name));
    $this->price=htmlspecialchars(strip_tags($this->price));
    $this->description=htmlspecialchars(strip_tags($this->description));
    $this->category_id=htmlspecialchars(strip_tags($this->category_id));
    $this->created=htmlspecialchars(strip_tags($this->created));
 
    // bind values
    $stmt->bindParam(":name", $this->name);
    $stmt->bindParam(":price", $this->price);
    $stmt->bindParam(":description", $this->description);
    $stmt->bindParam(":category_id", $this->category_id);
    $stmt->bindParam(":created", $this->created);
 
    // execute query
    if($stmt->execute()){
        return true;
    }
 
    return false;
     
}

5.3 Output

To test for the successful creation of a product, open POSTMAN. Enter the following as the request URL

http://localhost/api/product/create.php
  • Click the "Body" tab.
  • Click "raw".
  • Enter this JSON value:
{
    "name" : "Amazing Pillow 2.0",
    "price" : "199",
    "description" : "The best pillow for amazing programmers.",
    "category_id" : 2,
    "created" : "2018-06-01 00:35:07"
}
  • It should look like this:
  • If the system is unable to create the product, it should look like this:
  • If the sent data is incomplete, for example, it is missing the price data, the output should look like this:

6.0 Read One Product

6.1 Create read_one.php file

  • Open product folder.
  • Create a w read_one.php file.
  • Open that file and put the following code.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Allow-Credentials: true");
header('Content-Type: application/json');
 
// include database and object files
include_once '../config/database.php';
include_once '../objects/product.php';
 
// get database connection
$database = new Database();
$db = $database->getConnection();
 
// prepare product object
$product = new Product($db);
 
// set ID property of record to read
$product->id = isset($_GET['id']) ? $_GET['id'] : die();
 
// read the details of product to be edited
$product->readOne();
 
if($product->name!=null){
    // create array
    $product_arr = array(
        "id" =>  $product->id,
        "name" => $product->name,
        "description" => $product->description,
        "price" => $product->price,
        "category_id" => $product->category_id,
        "category_name" => $product->category_name
 
    );
 
    // set response code - 200 OK
    http_response_code(200);
 
    // make it json format
    echo json_encode($product_arr);
}
 
else{
    // set response code - 404 Not found
    http_response_code(404);
 
    // tell the user product does not exist
    echo json_encode(array("message" => "Product does not exist."));
}
?>

6.2 Product readOne() method

  • Open objects folder.
  • Open product.php file.
  • The previous section will not work without the following code inside the Product class.
// used when filling up the update product form
function readOne(){
 
    // query to read single record
    $query = "SELECT
                c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
            FROM
                " . $this->table_name . " p
                LEFT JOIN
                    categories c
                        ON p.category_id = c.id
            WHERE
                p.id = ?
            LIMIT
                0,1";
 
    // prepare query statement
    $stmt = $this->conn->prepare( $query );
 
    // bind id of product to be updated
    $stmt->bindParam(1, $this->id);
 
    // execute query
    $stmt->execute();
 
    // get retrieved row
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
 
    // set values to object properties
    $this->name = $row['name'];
    $this->price = $row['price'];
    $this->description = $row['description'];
    $this->category_id = $row['category_id'];
    $this->category_name = $row['category_name'];
}

6.3 Output

  • First, we will test for a product that exists. Open POSTMAN. Enter the following as the request URL. Click the blue "Send" button.
http://localhost/api/product/read_one.php?id=60
  • Next, we will test for a product that does not exist. Enter the following as the request URL. Click the blue "Send" button.
http://localhost/api/product/read_one.php?id=999

7.0 Update product

7.1 Create "update.php" file

  • Open product folder.
  • Create a new update.php file.
  • Open that file and put the following code inside it.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
 
// include database and object files
include_once '../config/database.php';
include_once '../objects/product.php';
 
// get database connection
$database = new Database();
$db = $database->getConnection();
 
// prepare product object
$product = new Product($db);
 
// get id of product to be edited
$data = json_decode(file_get_contents("php://input"));
 
// set ID property of product to be edited
$product->id = $data->id;
 
// set product property values
$product->name = $data->name;
$product->price = $data->price;
$product->description = $data->description;
$product->category_id = $data->category_id;
 
// update the product
if($product->update()){
 
    // set response code - 200 ok
    http_response_code(200);
 
    // tell the user
    echo json_encode(array("message" => "Product was updated."));
}
 
// if unable to update the product, tell the user
else{
 
    // set response code - 503 service unavailable
    http_response_code(503);
 
    // tell the user
    echo json_encode(array("message" => "Unable to update product."));
}
?>

7.2 Product update() method

  • Open objects folder.
  • Open product.php file.
  • The previous section will not work without the following code inside the Product class.
// update the product
function update(){
 
    // update query
    $query = "UPDATE
                " . $this->table_name . "
            SET
                name = :name,
                price = :price,
                description = :description,
                category_id = :category_id
            WHERE
                id = :id";
 
    // prepare query statement
    $stmt = $this->conn->prepare($query);
 
    // sanitize
    $this->name=htmlspecialchars(strip_tags($this->name));
    $this->price=htmlspecialchars(strip_tags($this->price));
    $this->description=htmlspecialchars(strip_tags($this->description));
    $this->category_id=htmlspecialchars(strip_tags($this->category_id));
    $this->id=htmlspecialchars(strip_tags($this->id));
 
    // bind new values
    $stmt->bindParam(':name', $this->name);
    $stmt->bindParam(':price', $this->price);
    $stmt->bindParam(':description', $this->description);
    $stmt->bindParam(':category_id', $this->category_id);
    $stmt->bindParam(':id', $this->id);
 
    // execute the query
    if($stmt->execute()){
        return true;
    }
 
    return false;
}

7.3 Output

Open POSTMAN. Enter the following as the request URL.

http://localhost/api/product/update.php
  • Click the "Body" tab.
  • Click "raw".
  • Enter the following JSON value.
  • Make sure the ID exists in your database.
  • Click the blue "Send" button.
{
    "id" : "106",
    "name" : "Amazing Pillow 3.0",
    "price" : "255",
    "description" : "The best pillow for amazing programmers.",
    "category_id" : 2,
    "created" : "2018-08-01 00:35:07"
}

The product ID 106, is just an example. You need to specify a product ID that exists in your database.

If you specify an ID that does not exist in the database, it might still say that the product was updated. It does not update anything on the database but the query was executed successfully without any syntax errors.

To prevent this, you need an extra validation where you check if an ID exists in the database. This feature is not yet part of our tutorial.

  • If updating a product is successful, it should look like this:
  • If the system fails to update the product, the output will look like this:

8.0 Delete Product

8.1 Create "delete.php" file

  • Open product folder.
  • Create new delete.php file.
  • Open that file and put the following code inside it.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
 
// include database and object file
include_once '../config/database.php';
include_once '../objects/product.php';
 
// get database connection
$database = new Database();
$db = $database->getConnection();
 
// prepare product object
$product = new Product($db);
 
// get product id
$data = json_decode(file_get_contents("php://input"));
 
// set product id to be deleted
$product->id = $data->id;
 
// delete the product
if($product->delete()){
 
    // set response code - 200 ok
    http_response_code(200);
 
    // tell the user
    echo json_encode(array("message" => "Product was deleted."));
}
 
// if unable to delete the product
else{
 
    // set response code - 503 service unavailable
    http_response_code(503);
 
    // tell the user
    echo json_encode(array("message" => "Unable to delete product."));
}
?>

8.2 Product delete() method

  • Open objects folder.
  • Open product.php file.
  • The previous section will not work without the following code inside the Product class.
// delete the product
function delete(){
 
    // delete query
    $query = "DELETE FROM " . $this->table_name . " WHERE id = ?";
 
    // prepare query
    $stmt = $this->conn->prepare($query);
 
    // sanitize
    $this->id=htmlspecialchars(strip_tags($this->id));
 
    // bind id of record to delete
    $stmt->bindParam(1, $this->id);
 
    // execute query
    if($stmt->execute()){
        return true;
    }
 
    return false;
}

8.3 Output

Open POSTMAN. Enter the following as the request URL.

http://localhost/api/product/delete.php
  • Click the "Body" tab.
  • Click "raw".
  • Enter the following JSON value.
  • Make sure the ID exists in your database.
  • Click the blue "Send" button.
{
    "id" : "106"
}
  • If a product was successfully deleted, it should look like this:
  • If the system fails to delete the product, the output will look like this:

9.0 Search Products

9.1 Create "search.php" file

  • Open product folder.
  • Create a search.php file.
  • Open that file and place the following code.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
 
// include database and object files
include_once '../config/core.php';
include_once '../config/database.php';
include_once '../objects/product.php';
 
// instantiate database and product object
$database = new Database();
$db = $database->getConnection();
 
// initialize object
$product = new Product($db);
 
// get keywords
$keywords=isset($_GET["s"]) ? $_GET["s"] : "";
 
// query products
$stmt = $product->search($keywords);
$num = $stmt->rowCount();
 
// check if more than 0 record found
if($num>0){
 
    // products array
    $products_arr=array();
    $products_arr["records"]=array();
 
    // retrieve our table contents
    // fetch() is faster than fetchAll()
    // http://stackoverflow.com/questions/2770630/pdofetchall-vs-pdofetch-in-a-loop
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
        // extract row
        // this will make $row['name'] to
        // just $name only
        extract($row);
 
        $product_item=array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
 
        array_push($products_arr["records"], $product_item);
    }
 
    // set response code - 200 OK
    http_response_code(200);
 
    // show products data
    echo json_encode($products_arr);
}
 
else{
    // set response code - 404 Not found
    http_response_code(404);
 
    // tell the user no products found
    echo json_encode(
        array("message" => "No products found.")
    );
}
?>

9.2 Create search() method

  • Open objects folder.
  • Open product.php file.
  • Add the following search() method.
// search products
function search($keywords){
 
    // select all query
    $query = "SELECT
                c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
            FROM
                " . $this->table_name . " p
                LEFT JOIN
                    categories c
                        ON p.category_id = c.id
            WHERE
                p.name LIKE ? OR p.description LIKE ? OR c.name LIKE ?
            ORDER BY
                p.created DESC";
 
    // prepare query statement
    $stmt = $this->conn->prepare($query);
 
    // sanitize
    $keywords=htmlspecialchars(strip_tags($keywords));
    $keywords = "%{$keywords}%";
 
    // bind
    $stmt->bindParam(1, $keywords);
    $stmt->bindParam(2, $keywords);
    $stmt->bindParam(3, $keywords);
 
    // execute query
    $stmt->execute();
 
    return $stmt;
}

9.3 Output

Open POSTMAN. Enter the following as the request URL.

http://localhost/api/product/search.php?s=shirt

Click the blue "Send" button.

  • If there was a product found, it should look like this:
  • If there are no products found, the output will look like this:

10.0 Paginate Products

10.1 Create "read_paging.php" file

  • Open product folder.
  • Create read_paging.php file.
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
 
// include database and object files
include_once '../config/core.php';
include_once '../shared/utilities.php';
include_once '../config/database.php';
include_once '../objects/product.php';
 
// utilities
$utilities = new Utilities();
 
// instantiate database and product object
$database = new Database();
$db = $database->getConnection();
 
// initialize object
$product = new Product($db);
 
// query products
$stmt = $product->readPaging($from_record_num, $records_per_page);
$num = $stmt->rowCount();
 
// check if more than 0 record found
if($num>0){
 
    // products array
    $products_arr=array();
    $products_arr["records"]=array();
    $products_arr["paging"]=array();
 
    // retrieve our table contents
    // fetch() is faster than fetchAll()
    // http://stackoverflow.com/questions/2770630/pdofetchall-vs-pdofetch-in-a-loop
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
        // extract row
        // this will make $row['name'] to
        // just $name only
        extract($row);
 
        $product_item=array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
 
        array_push($products_arr["records"], $product_item);
    }
 
 
    // include paging
    $total_rows=$product->count();
    $page_url="{$home_url}product/read_paging.php?";
    $paging=$utilities->getPaging($page, $total_rows, $records_per_page, $page_url);
    $products_arr["paging"]=$paging;
 
    // set response code - 200 OK
    http_response_code(200);
 
    // make it json format
    echo json_encode($products_arr);
}
 
else{
 
    // set response code - 404 Not found
    http_response_code(404);
 
    // tell the user products does not exist
    echo json_encode(
        array("message" => "No products found.")
    );
}
?>

10.2 Create "core.php" file

This file holds our core configuration like the home URL and pagination variables.

  • Open the config folder.
  • Create core.php file.
  • Open core.php file.
  • Place the following code.
<?php
// show error reporting
ini_set('display_errors', 1);
error_reporting(E_ALL);
 
// home page url
$home_url="http://localhost/api/";
 
// page given in URL parameter, default page is one
$page = isset($_GET['page']) ? $_GET['page'] : 1;
 
// set number of records per page
$records_per_page = 5;
 
// calculate for the query LIMIT clause
$from_record_num = ($records_per_page * $page) - $records_per_page;
?>

10.3 Create "readPaging()" method

  • Open objects folder.
  • Open product.php file.
  • Add the following method inside the product class.
  • This method will return a list of records limited to what we set in $records_per_page of the previous section.
// read products with pagination
public function readPaging($from_record_num, $records_per_page){
 
    // select query
    $query = "SELECT
                c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
            FROM
                " . $this->table_name . " p
                LEFT JOIN
                    categories c
                        ON p.category_id = c.id
            ORDER BY p.created DESC
            LIMIT ?, ?";
 
    // prepare query statement
    $stmt = $this->conn->prepare( $query );
 
    // bind variable values
    $stmt->bindParam(1, $from_record_num, PDO::PARAM_INT);
    $stmt->bindParam(2, $records_per_page, PDO::PARAM_INT);
 
    // execute query
    $stmt->execute();
 
    // return values from database
    return $stmt;
}

10.4 Create "count()" method

Still in the product class (product.php file), add the following method. The total rows are needed to build the pagination array. It is included in the 'paging' computation.

// used for paging products
public function count(){
    $query = "SELECT COUNT(*) as total_rows FROM " . $this->table_name . "";
 
    $stmt = $this->conn->prepare( $query );
    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
 
    return $row['total_rows'];
}

10.5 Get "paging" array

  • Create shared folder.
  • Open shared folder.
  • Create utilities.php file.
  • Open utilities.php file and place the following code.
<?php
class Utilities{
 
    public function getPaging($page, $total_rows, $records_per_page, $page_url){
 
        // paging array
        $paging_arr=array();
 
        // button for first page
        $paging_arr["first"] = $page>1 ? "{$page_url}page=1" : "";
 
        // count all products in the database to calculate total pages
        $total_pages = ceil($total_rows / $records_per_page);
 
        // range of links to show
        $range = 2;
 
        // display links to 'range of pages' around 'current page'
        $initial_num = $page - $range;
        $condition_limit_num = ($page + $range)  + 1;
 
        $paging_arr['pages']=array();
        $page_count=0;
         
        for($x=$initial_num; $x<$condition_limit_num; $x++){
            // be sure '$x is greater than 0' AND 'less than or equal to the $total_pages'
            if(($x > 0) && ($x <= $total_pages)){
                $paging_arr['pages'][$page_count]["page"]=$x;
                $paging_arr['pages'][$page_count]["url"]="{$page_url}page={$x}";
                $paging_arr['pages'][$page_count]["current_page"] = $x==$page ? "yes" : "no";
 
                $page_count++;
            }
        }
 
        // button for last page
        $paging_arr["last"] = $page<$total_pages ? "{$page_url}page={$total_pages}" : "";
 
        // json format
        return $paging_arr;
    }
 
}
?>

10.6 Output

Open POSTMAN. Enter the following as the request URL.

http://localhost/api/product/read_paging.php

Click the blue "Send" button.

  • If there are products found, scroll down to see the paging node. It should look like this:
  • If there are no products found, the output will look like this:

11.0 Read Categories

11.1 Create "category.php" file

  • Open objects folder.
  • Create new category.php file.
  • Place the following code inside the category.php file.
<?php
class Category{
 
    // database connection and table name
    private $conn;
    private $table_name = "categories";
 
    // object properties
    public $id;
    public $name;
    public $description;
    public $created;
 
    public function __construct($db){
        $this->conn = $db;
    }
 
    // used by select drop-down list
    public function readAll(){
        //select all data
        $query = "SELECT
                    id, name, description
                FROM
                    " . $this->table_name . "
                ORDER BY
                    name";
 
        $stmt = $this->conn->prepare( $query );
        $stmt->execute();
 
        return $stmt;
    }
}
?>

11.2 Create "read.php" file

  • Create new category folder.
  • Open that folder and create new read.php file inside it.
  • Open read.php file and place the following code.
<?php
// required header
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
 
// include database and object files
include_once '../config/database.php';
include_once '../objects/category.php';
 
// instantiate database and category object
$database = new Database();
$db = $database->getConnection();
 
// initialize object
$category = new Category($db);
 
// query categorys
$stmt = $category->read();
$num = $stmt->rowCount();
 
// check if more than 0 record found
if($num>0){
 
    // products array
    $categories_arr=array();
    $categories_arr["records"]=array();
 
    // retrieve our table contents
    // fetch() is faster than fetchAll()
    // http://stackoverflow.com/questions/2770630/pdofetchall-vs-pdofetch-in-a-loop
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
        // extract row
        // this will make $row['name'] to
        // just $name only
        extract($row);
 
        $category_item=array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description)
        );
 
        array_push($categories_arr["records"], $category_item);
    }
 
    // set response code - 200 OK
    http_response_code(200);
 
    // show categories data in json format
    echo json_encode($categories_arr);
}
 
else{
 
    // set response code - 404 Not found
    http_response_code(404);
 
    // tell the user no categories found
    echo json_encode(
        array("message" => "No categories found.")
    );
}
?>

11.3 Add Category "read()" method

  • Open objects folder.
  • Open category.php file.
  • The previous section's code will not work without the following code inside the category.php file.
  • Add the following method inside the Category class.
// used by select drop-down list
public function read(){
 
    //select all data
    $query = "SELECT
                id, name, description
            FROM
                " . $this->table_name . "
            ORDER BY
                name";
 
    $stmt = $this->conn->prepare( $query );
    $stmt->execute();
 
    return $stmt;
}

11.4 Output

Open POSTMAN. Enter the following as the request URL.

http://localhost/api/category/read.php

Click the blue "Send" button.

  • If there are categories found, it should look like this:
  • If there are no categories found, the output will look like this:

12.0 Download Source Codes

We highly recommend for you to follow and study our well-detailed, step-by-step tutorial above first. Nothing beats experience when it comes to learning.

But we believe you will learn faster if you’ll see the final source code as well. We consider it as your additional guide.

Imagine the value or skill upgrade it can bring you. The additional income you can get from your work, projects or business. The precious time you save. Isn’t that what you want?

12.1 Download LEVEL 1 source code

FEATURES LEVEL 1
Create product YES
Read products YES
Read one product YES
Update product YES
Delete product YES
Search products YES
Paginate products YES
Read categories YES
FREE email support for 3 months YES
Source code updates via email YES
Download Now

12.2 Download LEVEL 2 source code

Before downloading the LEVEL 2 source code, I highly recommend learning the LEVEL 1 source code first. Use our tutorial above. LEVEL 2 might be a bit challenging to understand if you don't fully understand LEVEL 1.

FEATURES LEVEL 2
All features of LEVEL 1 source code YES
Delete selected product YES
Export product CSV YES
Read products by category YES
Search products with pagination YES
Create category YES
Read categories YES
Read one category YES
Update category YES
Delete category YES
Search categories YES
Paginate categories YES
Delete selected categories YES
Export categories CSV YES
Search categories with pagination YES
FREE email support for 6 months YES
Source code updates via email YES
Download Now

13.0 What's Next?

You have two options:

a. Take your skills to the next level by learning how to do REST API authentication (with user interface) on our REST API Authentication Example in PHP – JWT Tutorial.

b. The tutorial above focuses on the API side, without any user interface. We will learn how to create, read, update and delete database records (with user interface) on our AJAX CRUD Tutorial.

If you want to receive new and updated high-quality tutorials, please subscribe for free.

14.0 Related Tutorials

15.0 Notes

#1 Found An Issue?

If you found a problem with this code, please write a comment below. Please be descriptive about your issue. Please provide the error messages, screenshots (or screencast) and your test URL. Thanks!

Before you write a comment, remember to read this guide and our code of conduct.

#2 Become a true Ninja!

We constantly improve CodeOfaNinja. We update our tutorials and source codes. Receive valuable web programming tutorials and updates to your email. Subscribe now!

Enjoy high-quality web programming tutorials.
Subscribe to CodeOfaNinja now for FREE!

#3 Thank You!

Please note that this post is not yet in its final form. We are going to update this post so it will be perfect in the future.

If you have a friend or know someone who needs this PHP REST API Tutorial, please share this page to them! I know you will help them a lot by doing it. Thanks!