User Management
Rather than provide multiple scenarios for each type of user management, I feel it may be easier if I show how I have used php to generate/confirm the various user values required to replicate the kind of behaviour seen in Firebase Authentication.
To begin with, we need to create a table for users. (I do it this way, because if your database is on shared hosting, it is unlikely that you will be able to create additional users for the database.
I have a table called sodales (members in Latin), with the following fields:
- id
- password(hashed)
- uid
- access_token
- refresh_token
- timestamp
- verified
id
This is generated automatically by the database when a new record is created, and is the primary key (unique) for the table. If a record is deleted, the ids of other records remain the same. I just use the default incrementing integer.
This is provided by the user. I use php to check that the email address provided is actally an email address, and does not already exist in the table.
//email validation
if (!filter_var($_REQUEST['email'], FILTER_VALIDATE_EMAIL)) {
die('invalid email address format');
}
$email = $_REQUEST['email'];
$escaped_email = mysqli_real_escape_string($conn, $email);
$sql = "SELECT * FROM sodales WHERE email = '$escaped_email'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
die("email address already exists");
}
The email address can also be verified - see verified further down.
password (hashed)
The password is also provided by the user. We can then hash the password with a php function, so that the password is not stored in plain text, and there is another php function used to check the password provided against the hash
//hash the password
$password = $_REQUEST['password'];
$h_password = password_hash($password, PASSWORD_DEFAULT);
//check the hash against password when signing in
$email = $_REQUEST['email'];
$password = $_REQUEST['password'];
$sql = "SELECT id, password FROM sodales WHERE email = '$email'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
if (password_verify($password, $row['password'])) {
echo "signin successful";
} else {
echo "invalid password";
}
} else {
echo "user not found";
}
uid
We can generate a unique uid for the user, which they can user to carry out validated actions on the database. This also can allow for users to change their email/password, without losing their uid.
//generate uid
$uid = str_replace(array("+","/"), array("0","0"),strtoupper(base64_encode(random_bytes(intval(ceil(20 * 3 / 4))))));
access_token
An access_token, which is used in conjunction with the uid, can be generated using php, a length of 400 characters
//generate access_token
$access_token = str_replace(array("+","/"), array("0","0"),base64_encode(random_bytes(intval(ceil(400 * 3 / 4)))));
refresh_token
A refresh_token, which can be used to create a new access_token, can be generated using php, a length of 200 characters
//generate refresh_token
$refresh_token = str_replace(array("+","/"), array("0","0"),base64_encode(random_bytes(intval(ceil(200 * 3 / 4)))));
timestamp
A timestamp can be created (usually 1 hour ahead of current time) to create an expiry for the access_token, using php. We could just use this instead of an access_token...
//create timestamp
$timestamp = floor(microtime(true) * 1000) + 3600000;
insert all these values to the user record:
$sql = "INSERT INTO sodales (email, password, uid, access_token, refresh_token, timestamp) VALUES ('$email','$h_password', '$uid', '$access_token', '$refresh_token', '$timestamp');";
if ($conn->query($sql) === TRUE) {
echo "You are signed up successfully";
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
verified
This will be a boolean 0 (not verified), 1 (verified). What we are doing here is verifying the email address. On receipt of an email address from the propsective user, we send an email (using the phpMailer library) to the email address supplied, with a link (which contains a token) to a verfication page. The use must click the link to the page, then click the verfication button/link on the page, which will then set the verified field to 1 (verified). Future access to the database can then be controlled by checking this field.
(Note the email and verification page are very basic, they can be prettified with more html and css!)
//send the verification email (requires phpMailer in place)
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\SMTP;
// Load PHPMailer classes
require __DIR__ . '/../phpmailer/Exception.php';
require __DIR__ . '/../phpmailer/PHPMailer.php';
require __DIR__ . '/../phpmailer/SMTP.php';
$mail = new PHPMailer(true);
try {
// parameters
$email = $_REQUEST['email'];
$token = $_REQUEST['token'];
// Server settings
$mail->isSMTP();
$mail->Host = 'smtp.provider.com'; // For provider Email
$mail->SMTPAuth = true;
$mail->Username = 'verify@domain.co.uk'; // full email address (sending)
$mail->Password = '123abc';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // or 'tls'
$mail->Port = 587;
// Sender & recipient
$mail->setFrom('verify@domain.co.uk', 'Infernus');
$mail->addAddress($email);
$mail->isHTML(false);
$mail->Subject = 'Verify Your Email Address for Infernus';
$mail->Body = "Click here to verify your email for Infernus: https://domain.co.uk/infernus/verify.php?token=" .$token;
$mail->send();
return true;
} catch (Exception $e) {
return false;
}
?>
// the verification page
<?php
$token = $_REQUEST['token'];
require_once 'config.php';
$conn = new mysqli(servername, username, password, dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$sql = "SELECT id,uid,access_token FROM sodales WHERE token = $token;";
$result = $conn->query($sql);
$rows = array();
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$rows[] = [$row["id"],$row["uid"],$row["access_token"]];
}
$vid = $rows[0][0];
$vsql = "UPDATE sodales SET verified = 1 WHERE id = $vid;";
if ($conn->query($vsql) === TRUE) {
echo '{"verified":"true","uid":' . $rows[0][1] . ',"access_token":' . $rows[0][2] . '}';
}
}
else {
echo "Invalid verification link or token";
}
$conn->close();
?>
I have also created an OTP process (for phone number verfication) using a phone to send SMS. SEE HERE.
It is also possible to create anonymous users, just like Firebase. We simply create a user without email or password, and the user is provided with a uid, access_token and refresh_token to use in order to access the database. These users will definitely need to hang on to their refresh_token for continued access! (this would, of course, be managed by your app.)
Summary
Using a combination (one, some or all) of all the methods above, it is possible to replicate Firebase Authentication methods, controlling user access to the database, and how a user might use the database once they have access.
You might want to refer back to my guide on Firebase with the Web component for reference