Implement Role-Based Access Control with PHP and MySQL on Ubuntu 20.04 Server
Introduction
When designing an internal application, you must provide a form of access control through authentication and authorization. You use authentication to confirm whether users have the right to access your system by validating their usernames and passwords against their account values. On the other hand, authorization allows you to verify whether an authenticated user has the correct permission to access a particular resource. For instance, to delete or update a product.
While the MySQL database provides roles-based permissions, it is not practically possible to create database usernames for all users accessing your system unless they're connecting directly via the command-line interface. In an optimal PHP application, your scripts connect to the database server through a single MySQL user account. Then, it is your job to code an access control functionality using the different features of PHP and MySQL.
In this guide, you'll set up a role-based permission feature for your application with PHP and MySQL on your Ubuntu 20.04 server. Instead of assigning permissions to users directly, you'll group the permissions and assign them to a role. Then, once you tie up users to a role, they will automatically inherit the privileges assigned to that role.
Prerequisites
To complete this guide, you require:
1. Set Up a Database and User Account
SSH to your server to complete the following steps.
Log in to your MySQL database server as
root
.$ sudo mysql -u root -p
Enter your
root
password when prompted and press Enter to proceed. Then, set up a sampleerp_db
database and user account. ReplaceEXAMPLE_PASSWORD
with a strong value.mysql> CREATE DATABASE erp_db; CREATE USER 'erp_db_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD'; GRANT ALL PRIVILEGES ON erp_db.* TO 'erp_db_user'@'localhost'; FLUSH PRIVILEGES;
Switch to the new
erp_db
database.mysql> USE erp_db;
2. Create a products Table
In this sample application, you'll create a products
table to store items available for sale in your business. This is a simple resource that you want to protect in your application. While only authenticated users will be able to access it, you don't want anyone to mess around with the price list or probably edit the product names unless they have the right permissions.
To achieve this form of security, you'll later create roles and permissions and define what a specific user can do in regards to the products
resource. For instance, you can allow all users to list the items in the products resource and only restrict product updates and deletion to a few administrators.
Create the
products
table.mysql> CREATE TABLE products ( product_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, product_name VARCHAR(50), retail_price DOUBLE ) ENGINE = InnoDB;
Populate the
products
table with some records.mysql> INSERT INTO products(product_name, retail_price) VALUES ('VIRTUAL PRIVATE SERVER', 6); INSERT INTO products(product_name, retail_price) VALUES ('BARE METAL', 185); INSERT INTO products(product_name, retail_price) VALUES ('DEDICATED CLOUD', 60);
Confirm the entries in the
products
table.mysql> SELECT product_id, product_name, retail_price FROM products;
Output.
+------------+------------------------+--------------+ | product_id | product_name | retail_price | +------------+------------------------+--------------+ | 1 | VIRTUAL PRIVATE SERVER | 6 | | 2 | BARE METAL | 185 | | 3 | DEDICATED CLOUD | 60 | +------------+------------------------+--------------+ 3 rows in set (0.00 sec)
3. Create the Role-based Access Control Tables
You require 3 MySQL tables to create an access control module for your PHP application.
Create the
system_permissions
table. This table stores your application's permissions in a human-readable format.mysql> CREATE TABLE system_permissions ( permission_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, permission_name VARCHAR(50) ) ENGINE = InnoDB;
Populate the
system_permissions
table. Since you only have 1 resource for demonstration purposes, you'll create 4 permissions. In a real-life application with lots of resources, you might require hundreds or even thousands of permissions depending on the complexity of the software.mysql> INSERT INTO system_permissions(permission_name) VALUES ('CREATE PRODUCT'); INSERT INTO system_permissions(permission_name) VALUES ('EDIT PRODUCT'); INSERT INTO system_permissions(permission_name) VALUES ('DELETE PRODUCT'); INSERT INTO system_permissions(permission_name) VALUES ('LIST PRODUCTS');
Verify the new permissions.
mysql> SELECT permission_id, permission_name FROM system_permissions;
Output.
+---------------+-----------------+ | permission_id | permission_name | +---------------+-----------------+ | 1 | CREATE PRODUCT | | 2 | EDIT PRODUCT | | 3 | DELETE PRODUCT | | 4 | LIST PRODUCTS | +---------------+-----------------+ 4 rows in set (0.00 sec)
Create a
roles
table. A role allows you to group multiple permissions together.mysql> CREATE TABLE roles ( role_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, role_name VARCHAR(50) ) ENGINE = InnoDB;
Populate the
roles
table with 3 records. You'll later assign permissions to these roles.mysql> INSERT INTO roles(role_name) VALUES ('ERP MANAGER'); INSERT INTO roles(role_name) VALUES ('LEVEL 1 ADMIN'); INSERT INTO roles(role_name) VALUES ('ERP CLERKS');
Confirm the records from the
roles
table.mysql> SELECT role_id, role_name FROM roles;
Output.
+---------+---------------+ | role_id | role_name | +---------+---------------+ | 1 | ERP MANAGER | | 2 | LEVEL 1 ADMIN | | 3 | ERP CLERKS | +---------+---------------+ 3 rows in set (0.00 sec)
Please note: Although you've defined 3 roles in this sample application, there is no limit to the number of system users that you can later tie to them. For instance, in a real-life scenario, you can have 5
ERP MANAGERs
, 12LEVEL 1 ADMINs
, and 125ERP clerks
.Next, create a
system_permission_to_roles
table. You'll use this table to assign permissions to each role. Here, don't repeat the names of the permission and roles. You'll simply use therole_id
andpermission_id
to optimize the table and avoid bloating your database. In simple terms, you're creating a many-to-many relationship between theroles
andsystem_permissions
tables. This is because a single role can have many different permissions and you can assign single permission to many different roles.mysql> CREATE TABLE system_permission_to_roles ( ref_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, role_id BIGINT, permission_id BIGINT ) ENGINE = InnoDB;
For now, you'll create the following roles and permissions for your sample application.
- ERP MANAGER: Has a right to
CREATE PRODUCT
,EDIT PRODUCT
,LIST PRODUCTS
, andDELETE PRODUCT
. - LEVEL 1 ADMIN: Has a right to
CREATE PRODUCT
andLIST PRODUCTS
. - ERP CLERKS: Has a right to
LIST PRODUCTS
only.
Add those roles and their respective permissions into the
system_permission_to_roles
table.The
ERP MANAGER's
permissions:mysql> INSERT INTO system_permission_to_roles (role_id, permission_id) VALUES (1, 1); INSERT INTO system_permission_to_roles (role_id, permission_id) VALUES (1, 2); INSERT INTO system_permission_to_roles (role_id, permission_id) VALUES (1, 3); INSERT INTO system_permission_to_roles (role_id, permission_id) VALUES (1, 4);
The
LEVEL 1 ADMIN's
permissions:mysql> INSERT INTO system_permission_to_roles (role_id, permission_id) VALUES (2, 1); INSERT INTO system_permission_to_roles (role_id, permission_id) VALUES (2, 4);
The
ERP CLERKS'
permissions:mysql> INSERT INTO system_permission_to_roles (role_id, permission_id) VALUES (3, 4);
- ERP MANAGER: Has a right to
4. Create the Users Data Tables
To log in(authenticate) to your system, a system user will require a username and a password. To provide the access control functionality, you'll later in this guide assign such users some roles depending on the privileges that you want to assign to them(authorization).
Create a
system_users
table. This table stores the real names, usernames, and hashed passwords for the system users.mysql> CREATE TABLE system_users ( user_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50), username VARCHAR(50), pwd VARCHAR(255) ) ENGINE = InnoDB;
Add some records into the
system_users
table. For demonstration purposes, all the 3 users are using the same password(EXAMPLE_PASSWORD
) hashed with thebcrypt
library. In a production environment, you should design a register script to allow users to sign up to your application and this is where you should hash the passwords before posting them in thesystem_users
table using the syntax `password_hash(SAMPLE_PASSWORD, PASSWORD_BCRYPT).For now, populate the
system_users
table with the pre-hashedEXAMPLE_PASSWORD
password.mysql> INSERT INTO system_users (first_name, last_name, username, pwd) VALUES ('JOHN', 'DOE', 'john_doe','$2a$12$JOe5OFLD9dFkI.KJ1k9TP.ixWX/YtYArB/Yv.A8XSeIcCBkIlPvoi'); INSERT INTO system_users (first_name, last_name, username, pwd) VALUES ('MARY', 'SMITH', 'mary_smith','$2a$12$JOe5OFLD9dFkI.KJ1k9TP.ixWX/YtYArB/Yv.A8XSeIcCBkIlPvoi'); INSERT INTO system_users (first_name, last_name, username, pwd) VALUES ('STEVE', 'RIA', 'steve_ria','$2a$12$JOe5OFLD9dFkI.KJ1k9TP.ixWX/YtYArB/Yv.A8XSeIcCBkIlPvoi');
Confirm the records in the
system_users
table.mysql> SELECT user_id , first_name, last_name, username, pwd FROM system_users;
Output.
+---------+------------+-----------+------------+--------------------------------------------------------------+ | user_id | first_name | last_name | username | pwd | +---------+------------+-----------+------------+--------------------------------------------------------------+ | 1 | JOHN | DOE | john_doe | $2a$12$JOe5OFLD9dFkI.KJ1k9TP.ixWX/YtYArB/Yv.A8XSeIcCBkIlPvoi | | 2 | MARY | SMITH | mary_smith | $2a$12$JOe5OFLD9dFkI.KJ1k9TP.ixWX/YtYArB/Yv.A8XSeIcCBkIlPvoi | | 3 | STEVE | RIA | steve_ria | $2a$12$JOe5OFLD9dFkI.KJ1k9TP.ixWX/YtYArB/Yv.A8XSeIcCBkIlPvoi | +---------+------------+-----------+------------+--------------------------------------------------------------+ 3 rows in set (0.00 sec)
Create a
system_users_to_roles
table. You'll use this table to tie up users to the roles you've created in the previous steps.mysql> CREATE TABLE system_users_to_roles ( ref_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id BIGINT, role_id BIGINT ) ENGINE = InnoDB;
Assign users to the respective roles depending on the permissions that you want to inherit..
Assign
JOHN DOE
to theERP MANAGER's
role:mysql> INSERT INTO system_users_to_roles (user_id,role_id) VALUES (1, 1);
Assign
MARY SMITH
to theLEVEL 1 ADMIN
role:mysql> INSERT INTO system_users_to_roles (user_id,role_id) VALUES (2, 2);
Assign
STEVE RIA
to theERP CLERKs
role:mysql> INSERT INTO system_users_to_roles (user_id,role_id) VALUES (3, 3);
Verify if you've assigned the roles correctly by running a
JOIN
statement against the two tables.mysql> SELECT system_users.user_id, system_users.username, system_users.first_name, system_users.last_name, system_users_to_roles.role_id, roles.role_name FROM system_users LEFT JOIN system_users_to_roles ON system_users.user_id = system_users_to_roles.user_id LEFT JOIN roles ON system_users_to_roles.role_id = roles.role_id;
As you can see from the following output, you were able to match the users to the respective roles correctly.
+---------+------------+------------+-----------+---------+---------------+ | user_id | username | first_name | last_name | role_id | role_name | +---------+------------+------------+-----------+---------+---------------+ | 1 | john_doe | JOHN | DOE | 1 | ERP MANAGER | | 2 | mary_smith | MARY | SMITH | 2 | LEVEL 1 ADMIN | | 3 | steve_ria | STEVE | RIA | 3 | ERP CLERKS | +---------+------------+------------+-----------+---------+---------------+ 3 rows in set (0.00 sec)
Log out from the MySQL server.
mysql> EXIT;
5. Create the PHP Scripts
You've set up the appropriate data tables for managing access control in your MySQL database. You'll now create a web resource using PHP. When users access your sample application, you'll only allow them to perform tasks depending on the permissions tied to their assigned roles.
Create a new
db_function.php
file under the root directory of your web server.$ sudo nano /var/www/html/db_function.php
Paste the information below into the file. In this file, you're defining variables to connect to your database via the PHP Data Object(PDO). This file has a single
pdoConnection()
function that you'll use later in this guide. ChangeEXAMPLE_PASSWORD
with the correct password.<?php function pdoConnection() { try { $db_name = 'erp_db'; $db_user = 'erp_db_user'; $db_password = 'EXAMPLE_PASSWORD'; $db_host = 'localhost'; $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $pdo; } catch (PDOException $e) { echo $e->getMessage(); } }
Save and close the file.
Next, create an
authentications.php
script. This script handles the authentication part of your application. It checks whether a user has a valid username and password.$ sudo nano /var/www/html/authentications.php
Paste the following information into the file. In this file, you'll retrieve the
user_id
from the MySQL database and assign it to the variable$user_id
which you'll later use to check the permissions that a user has in the system. You're accomplishing this using the statement$user_id = $user_details['user_id'];
.<?php if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) { header("HTTP/1.0 401 Unauthorized"); echo '"{"error": "Authentication failed."}'; exit(); } else { $username = $_SERVER['PHP_AUTH_USER']; $password = $_SERVER['PHP_AUTH_PW']; } $sql_authenticate = 'select * from system_users where username = :username'; $data = [ 'username' => $username ]; $stmt = $pdo->prepare($sql_authenticate); $stmt->execute($data); $user_details = []; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $user_details = $row; } if (password_verify($password, $user_details['pwd']) == true) { $user_id = $user_details['user_id']; } else { header("HTTP/1.0 401 Unauthorized"); echo '{"error": "Invalid username or password."}'; exit(); }
Save and close the file.
Create a
system_permissions.php
script.$ sudo nano /var/www/html/system_permissions.php
Paste the information below into the file. In this file, you're checking if a user has the right permissions to perform a certain task by running a
JOIN
statement against thesystem_users_to_roles
andsystem_permission_to_roles
tables. This function accepts two variables. To check if authenticated users have permissions to perform a certain task, you simply send theiruser_id
that you've retrieved from theauthentications.php
script and thepermission_id
of a resource to this function. The function then returns eithertrue
orfalse
and allows you to either restrict or grant access to the resource.<?php function checkPermissions($user_id, $permission_id) { require_once('db_function.php'); $pdo = pdoConnection(); try { $sql = 'select count(*) as total_permissions from system_permission_to_roles left join system_users_to_roles on system_permission_to_roles.role_id = system_users_to_roles.role_id where system_users_to_roles.user_id = :user_id and permission_id = :permission_id '; $data = [ 'user_id' => $user_id, 'permission_id' => $permission_id ]; $stmt = $pdo->prepare($sql); $stmt->execute($data); $row = $stmt->fetch(); $authorized = ''; if ($row['total_permissions'] > 0) { $authorized = "true"; } else { $authorized = "false"; } return $authorized; } catch (Exception $e) { echo $e->getMessage(); } }
Save and close the file.
Create the
products.php
resource file. This is the main endpoint that you want to secure with authentication and authorization features. Users accessing your application will request this page through a client application(For example, a mobile app or desktop software). For this guide, you'll later usecurl
to retrieve the page.$ sudo nano /var/www/html/products.php
Paste the information below into the file. Please note, this guide doesn't address data validation in your PHP files. When working in a production environment, consider a form of data validation. To understand the CRUD (Create, Read, Update, or Delete)function the user wants to perform in your application, you're using the statement
$http_verb = $_SERVER['REQUEST_METHOD'];
.Then, you're checking if a user is authorized to perform the task using the statement
if (checkPermissions($user_id, 1) == "false") {...}
.<?php require_once('db_function.php'); require_once('system_permissions.php'); $pdo = pdoConnection(); $http_verb = $_SERVER['REQUEST_METHOD']; $params = json_decode(file_get_contents('php://input')); require_once('authentications.php'); if ($http_verb == 'POST') { if (checkPermissions($user_id, 1) == "false") { header("HTTP/1.0 403 Forbidden"); echo '{"error": "You do not have permissions to create a product."}' . '\n'; exit(); } $sql = 'insert into products ( product_name, retail_price ) values ( :product_name, :retail_price )'; $data = [ 'product_name' => $params->product_name, 'retail_price' => $params->retail_price ]; $stmt = $pdo->prepare($sql); $stmt->execute($data); echo '{"message": "Product created successfully."}' . '\n'; } elseif ($http_verb == 'GET') { $me = checkPermissions($user_id, 4); if (checkPermissions($user_id, 4) == "false") { header("HTTP/1.0 403 Forbidden"); echo '{"error": "You do not have permissions to list products."}' . '\n'; exit(); } $sql = 'select * from products'; $stmt = $pdo->prepare($sql); $stmt->execute(); $data = []; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $data[] = $row; } $reponse = []; $response['data'] = $data; echo json_encode($response, JSON_PRETTY_PRINT) . "\n"; } elseif ($http_verb == 'PUT') { if (checkPermissions($user_id, 2) == "false") { header("HTTP/1.0 403 Forbidden"); echo '{"error": "You do not have permissions to update a product."}' . '\n'; exit(); } $sql = 'update products set product_name = :product_name, retail_price = :retail_price where product_id = :product_id '; $data = [ 'product_id' => $params->product_id, 'product_name' => $params->product_name, 'retail_price' => $params->retail_price ]; $stmt = $pdo->prepare($sql); $stmt->execute($data); echo '{"message": "Product updated successfully"}' . '\n'; } elseif ($http_verb == 'DELETE') { if (checkPermissions($user_id, 3) == "false") { header("HTTP/1.0 403 Forbidden"); echo '{"error": "You do not have permissions to delete a product."}'; exit(); } $sql = 'delete from products where product_id = :product_id limit 1 '; $data = [ 'product_id' => $params->product_id ]; $stmt = $pdo->prepare($sql); $stmt->execute($data); echo '{"message": "Product deleted successfully."}' . '\n'; }
Save and close the file.
6. Test the Application
Your database tables and PHP scripts are now in place.
Use
curl
to access the URL http://localhost/products.php. Begin by entering the wrong credentials to check whether the authentication logic is working.$ curl -u john_doe:WRONG_PASSWORD http://localhost/products.php
Since you've entered the
WRONG_PASSWORD
for userjohn_doe
, you should receive the following error.{"error": "Invalid username or password."}
Next, attempt to list products using the 3 sample users. This time around, use the correct password. In this case,
EXAMPLE_PASSWORD
.$ curl -u john_doe:EXAMPLE_PASSWORD http://localhost/products.php $ curl -u mary_smith:EXAMPLE_PASSWORD http://localhost/products.php $ curl -u steve_ria:EXAMPLE_PASSWORD http://localhost/products.php
Since all the 3 users have the 'LIST PRODUCTS' permission, you should get the following output after running the above
curl
commands.{ "data": [ { "product_id": "1", "product_name": "VIRTUAL PRIVATE SERVER", "retail_price": "6" }, { "product_id": "2", "product_name": "BARE METAL", "retail_price": "185" }, { "product_id": "3", "product_name": "DEDICATED CLOUD", "retail_price": "60" } ] }
Next, you've only granted the
CREATE PRODUCT
permission tojohn_doe
andmary_smith
. Execute the followingcurl
command using those users' credentials to add more products into your database.$ curl -u john_doe:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request POST --data '{"product_name":"SAMPLE_PRODUCT_4","retail_price":"99.99"}' http://localhost/products.php $ curl -u mary_smith:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request POST --data '{"product_name":"SAMPLE_PRODUCT_5","retail_price":"299.20"}' http://localhost/products.php
Since the 2 users have the right permission, you should get the output below.
... {"message": "Product created successfully."}
Verify if you've successfully inserted the products into the database by listing them one more time.
$ curl -u john_doe:EXAMPLE_PASSWORD http://localhost/products.php
As you can see from the output below, you've added the
SAMPLE_PRODUCT_4
andSAMPLE_PRODUCT_5
into theproducts
table.{ "data": [ { "product_id": "1", "product_name": "VIRTUAL PRIVATE SERVER", "retail_price": "6" }, { "product_id": "2", "product_name": "BARE METAL", "retail_price": "185" }, { "product_id": "3", "product_name": "DEDICATED CLOUD", "retail_price": "60" }, { "product_id": "4", "product_name": "SAMPLE_PRODUCT_4", "retail_price": "99.99" }, { "product_id": "5", "product_name": "SAMPLE_PRODUCT_5", "retail_price": "299.2" } ] }
Attempt to create a product with
steve_ria's
credentials.$ curl -u steve_ria:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request POST --data '{"product_name":"SAMPLE_PRODUCT_6","retail_price":"340.63"}' http://localhost/products.php
Since you've not granted
steve_ria's
theCREATE PRODUCT
permission, the operation should fail with the following error.{"error": "You do not have permissions to create a product."}
Next, only
john_doe
( TheERP MANAGER
) can update or delete products. Execute the commands below with his credentials.$ curl -u john_doe:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request PUT --data '{"product_id":"1","product_name":"VPS MACHINE","retail_price":"6.00"}' http://localhost/products.php $ curl -u john_doe:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request DELETE --data '{"product_id":"5"}' http://localhost/products.php
You should be able to update and delete the product and get the following responses.
{"message": "Product updated successfully"} {"message": "Product deleted successfully."}
Verify whether you've updated and deleted the products by listing the products.
$ curl -u john_doe:EXAMPLE_PASSWORD http://localhost/products.php
You've indeed updated the first product from
VIRTUAL PRIVATE SERVER
toVPS MACHINE
and you've successfully deletedSAMPLE_PRODUCT_5
."data": [ { "product_id": "1", "product_name": "VPS MACHINE", "retail_price": "6" }, { "product_id": "2", "product_name": "BARE METAL", "retail_price": "185" }, { "product_id": "3", "product_name": "DEDICATED CLOUD", "retail_price": "60" }, { "product_id": "4", "product_name": "SAMPLE_PRODUCT_4", "retail_price": "99.99" } ] }
However, if you attempt to either update or delete a product with the
mary_smith's
orsteve_ria's
credentials, the operations should fail.$ curl -u mary_smith:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request PUT --data '{"product_ID":"1","product_name":"VPS MACHINE","retail_price":"6.00"}' http://localhost/products.php $ curl -u mary_smith:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request DELETE --data '{"product_ID":"5"}' http://localhost/products.php $ curl -u steve_ria:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request PUT --data '{"product_ID":"1","product_name":"VPS MACHINE","retail_price":"6.00"}' http://localhost/products.php $ curl -u steve_ria:EXAMPLE_PASSWORD --header "Content-Type: application/json" --request DELETE --data '{"product_ID":"5"}' http://localhost/products.php
Output.
{"error": "You do not have permissions to update a product."} {"error": "You do not have permissions to delete a product."} {"error": "You do not have permissions to update a product."} {"error": "You do not have permissions to update a product."}
Your permission settings are now working as expected.
7. Use Cases for Role-Based Access Control
You've seen the power of role-based access control in your application. Here are a few real-life scenarios where the technology is used.
In a banking application, cashiers have the right to deposit and withdrawal money in the system. However, the reversal right for erroneously entered data may be preserved to supervisors only. Also, only a loan officer can disburse loans to avoid fraud.
In a content management system where users are paid to submit articles to a blog, an editor may be assigned the right to approve the articles while an accountant may be the only authorized person to make payments for the completed articles.
In a grocery store software, cashiers can ring sales and check their end-of-day totals but they can't delete a sales record unless it is done by the manager(For instance, during a return).
Conclusion
In this guide, you've implemented role-based access control with PHP and MySQL on your Ubuntu 20.04 server. Implement the knowledge in this guide to offer tight authentication and authorization when designing your next PHP project.
Refer to the documents below for more information on using PHP and PDO.