Two-factor authentication (2FA) with Google Authenticator is a must-have for securing withdrawals in your PHP app—think crypto exchanges, payment systems, or any platform moving sensitive assets. In this guide, we’ll walk through the full setup: installing dependencies with Composer, enabling 2FA, and securing withdrawals. Whether you’re a cPanel user or a seasoned dev, I’ve got you covered with a step-by-step that’s secure and straightforward. Let’s dive in!
Step 1: Setting Up Composer and Dependencies
Composer is PHP’s go-to dependency manager—it’s like a magic wand for pulling in libraries without the hassle. We’ll use it to install the Google Authenticator package, but first, let’s set it up right, with a nod to security and cPanel users.
What You’ll Need
PHP: 7.4+ (modern and stable).
Composer: We’ll install this shortly.
MySQLi: For database queries (or use PDO if that’s your jam).
Hosting: A server with cPanel or SSH access.
Installing Composer
If you’re on a shared host with cPanel (like many of us), here’s how to get Composer running:
Log into cPanel:
- Head to your hosting dashboard and find the Terminal option (some hosts enable this under “Advanced”). No Terminal? Use SSH Access if available—check with your host.
Install Composer:
In the terminal, run:
bash
curl -sS https://getcomposer.org/installer | php
This downloads composer.phar. Move it to a global spot (if you have root access):
bash
sudo mv composer.phar /usr/local/bin/composer
On shared hosting without sudo, keep it local—we’ll use it from your project folder.
Why Outside public_html?
For better security, I created my project directory outside public_html—like /home/youruser/myproject—instead of inside /home/youruser/public_html. Why? Anything in public_html is web-accessible, so if a hacker stumbles on your vendor/ folder, they might exploit it. Keeping it outside means only your scripts can access it, not the public. Here’s how:
Create the Directory:
In cPanel’s File Manager or terminal:
bash
mkdir /home/youruser/myproject
Navigate to It:
In the terminal:
bash
cd /home/youruser/myproject
This is where we’ll live for the next steps.
Initialize Composer:
Run:
bash
php /home/youruser/composer.phar init
Or if Composer’s global:
bash
composer init
This creates a composer.json file—the blueprint for your dependencies.
The composer init Questions:
You’ll see prompts like:
Package name (<vendor>/<name>) []: Description []: Author []: Minimum Stability []: Package Type []: License []: Define your dependencies interactively? (yes/no) [yes]:
I just hit Enter for most—here’s what’s happening:
Package Name: Defaults to something like youruser/projectname based on your folder. Skip it for now—it’s optional for private projects.
Description: A blurb about your app. Blank is fine.
Author: Your name/email (e.g., “John Doe john@example.com (mailto:john@example.com)”). Enter skips it.
Minimum Stability: Defaults to stable. Good enough.
Package Type: Usually project—Enter works.
License: Skip or type MIT for open-source vibes.
Dependencies: Says “yes” to add packages interactively. Hit Enter here—we’ll add them manually next.
Result: A basic composer.json like:
json
{ "name": "youruser/myproject", "description": "", "require": {} }
Install Google Authenticator Library:
Add the Sonata package:
bash
composer require sonata-project/google-authenticator
This pulls the library into /home/youruser/myproject/vendor/ and updates composer.json:
json
"require": { "sonata-project/google-authenticator": "^2.3" }
Directory Structure:
Now you’ve got:
/home/youruser/myproject/ ├── composer.json ├── composer.lock └── vendor/ ├── autoload.php └── sonata-project/
In your PHP files (e.g., inside public_html/studio/), include it with:
php
require '/home/youruser/myproject/vendor/autoload.php';
cPanel Note: If your script’s in public_html/studio/tee.php, a relative path like require '../../myproject/vendor/autoload.php'; works too—just count the levels up.
.
Step 2: Enabling 2FA with Google Authenticator
Let’s build enable_2fa.php—users scan a QR code, verify a code, and enable 2FA.
Code: enable_2fa.php
php
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
include 'top.php'; // Assumes $xid, $email, $link (MySQLi)
require '/home/youruser/myproject/vendor/autoload.php';
use Sonata\GoogleAuthenticator\GoogleAuthenticator;
use Sonata\GoogleAuthenticator\GoogleQrUrl;
ini_set('display_errors', 1);
error_reporting(E_ALL);
$userId = $xid;
$userEmail = $email;
$stmt = $link->prepare("SELECT 2fa_enabled FROM users WHERE id = ?");
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && $row['2fa_enabled']) {
header("Location: factor2");
exit;
}
$g = new GoogleAuthenticator();
if (!isset($_SESSION['temp_2fa_secret'])) {
$_SESSION['temp_2fa_secret'] = $g->generateSecret();
}
$secret = $_SESSION['temp_2fa_secret'];
$qrCodeUrl = GoogleQrUrl::generate($userEmail, $secret, 'YourAppName');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$code = $_POST['code'] ?? '';
if ($g->checkCode($secret, $code)) {
$stmt = $link->prepare("UPDATE users SET 2fa_secret = ?, 2fa_enabled = TRUE WHERE id = ?");
$stmt->bind_param("si", $secret, $userId);
$stmt->execute();
unset($_SESSION['temp_2fa_secret']);
header("Location: factor2");
exit;
} else {
$error = "Invalid code. Try again.";
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Enable 2FA</title>
</head>
<body>
<h2>Enable Two-Factor Authentication</h2>
<p>Scan this QR code with Google Authenticator:</p>
<div class="qr1">
<img src="<?php echo $qrCodeUrl; ?>" alt="QR Code">
</div>
<p>Secret Key: <span><?php echo $secret; ?></span> <button onclick="navigator.clipboard.writeText('<?php echo $secret; ?>')">Copy</button></p>
<form method="post">
<input type="text" name="code" placeholder="Enter 6-digit code" maxlength="6" required>
<button type="submit">Verify</button>
</form>
<?php if (isset($error)) echo "<p style='color:red;'>$error</p>"; ?>
</body>
</html>
Notes
DB Schema: Add 2fa_secret VARCHAR(16) and 2fa_enabled BOOLEAN DEFAULT FALSE to your users table.
Security: Storing the secret in $_SESSION keeps it temporary until verified.
Step 3: Handling Withdrawals with 2FA
Withdrawal Initiation: withdraw.php
php
<?php
session_start();
include 'top.php';
require '/home/youruser/myproject/vendor/autoload.php';
$userId = $xid;
?>
<!DOCTYPE html>
<html>
<head>
<title>Withdraw Funds</title>
<script>
function confirmWithdrawal() {
const receiverAddress = document.querySelector('input[name="receiveraddress"]').value;
const amount = parseFloat(document.getElementById('myamount').value);
const finalAmount = amount;
const data = {
formId: 'withdraw',
coinname: coinIdss,
receiverAddress: receiverAddress,
finalAmount: finalAmount,
};
console.log("Data:", data);
fetch('check_2fa.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: '<?php echo $xid; ?>' })
})
.then(response => response.json())
.then(result => {
if (result.twoFactorEnabled) {
fetch('save_withdrawal.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(saveResult => {
if (saveResult.success) {
window.location.href = 'tee.php';
} else {
alert('Failed to save withdrawal.');
}
});
} else {
fetch('middle.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
window.location.href = `history.php?id=${coinIdss}`;
} else {
alert(result.message || 'Withdrawal failed.');
}
});
}
});
}
</script>
</head>
<body>
<h2>Withdraw</h2>
<form onsubmit="event.preventDefault(); confirmWithdrawal();">
<input type="text" name="receiveraddress" placeholder="Receiver Address" required>
<input type="number" id="myamount" placeholder="Amount" required>
<button type="submit">Withdraw</button>
</form>
</body>
</html>
Helper Files
- check_2fa.php:
php
<?php
session_start();
include 'top.php';
header('Content-Type: application/json');
$userId = $_POST['userId'];
$stmt = $link->prepare("SELECT 2fa_enabled FROM users WHERE id = ?");
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
echo json_encode(['twoFactorEnabled' => (bool)$row['2fa_enabled']]);
?>
- save_withdrawal.php:
php
<?php
session_start();
include 'top.php';
header('Content-Type: application/json');
$data = json_decode(file_get_contents('php://input'), true);
$_SESSION['pending_withdrawal'] = $data;
echo json_encode(['success' => true]);
?>
- middle.php:
php
<?php
header('Content-Type: application/json');
$data = json_decode(file_get_contents('php://input'), true);
// Add withdrawal logic (e.g., blockchain API)
echo json_encode(['success' => true, 'message' => 'Withdrawal processed']);
?>
Step 4: Verifying 2FA on Withdrawal (tee.php)
Code: tee.php
php
<?php
session_start();
include 'top.php';
require '/home/youruser/myproject/vendor/autoload.php';
use Sonata\GoogleAuthenticator\GoogleAuthenticator;
if (!isset($xid) || !isset($email) || !isset($link)) {
die("Error: Required variables not defined");
}
$userId = $xid;
$stmt = $link->prepare("SELECT 2fa_secret FROM users WHERE id = ?");
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$twoFactorSecret = $row['2fa_secret'] ?? null;
if (!$twoFactorSecret || !isset($_SESSION['pending_withdrawal'])) {
header("Location: home");
exit;
}
$g = new GoogleAuthenticator();
$data = $_SESSION['pending_withdrawal'];
$coinId = $data['coinname'] ?? '';
$receiverAddress = $data['receiverAddress'] ?? '';
$finalAmount = $data['finalAmount'] ?? 0;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$code = $_POST['code'] ?? '';
if ($g->checkCode($twoFactorSecret, $code)) {
$mesa = 1;
$cafy = "Success";
} else {
$error = "Invalid 2FA code. Please try again.";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Verify 2FA</title>
<script>
function confirmWithdrawal() {
const data = {
formId: 'withdraw',
coinname: "<?php echo htmlspecialchars($coinId); ?>",
receiverAddress: "<?php echo htmlspecialchars($receiverAddress); ?>",
finalAmount: <?php echo $finalAmount; ?>,
};
fetch('middle.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
document.getElementById('status').innerHTML = "<p style='color:green;'>Withdrawal successful! Redirecting...</p>";
setTimeout(() => {
window.location.href = `history.php?id=${data.coinname}`;
}, 3000);
} else {
document.getElementById('status').innerHTML = "<p style='color:red;'>" + (result.message || 'Withdrawal failed.') + "</p>";
}
})
.catch(error => {
document.getElementById('status').innerHTML = "<p style='color:red;'>Error occurred.</p>";
});
}
<?php if (isset($mesa) && $mesa == 1): ?>
window.onload = function() { confirmWithdrawal(); };
<?php endif; ?>
</script>
</head>
<body>
<form method="post">
<div>
<p>You are withdrawing <?php echo number_format($finalAmount, 2); ?> <?php echo htmlspecialchars($coinId); ?> to <?php echo htmlspecialchars($receiverAddress); ?></p>
<input type="text" name="code" placeholder="Enter 6-digit code" maxlength="6" required>
<button type="submit">Verify</button>
<div id="status">
<?php if (isset($error)) echo "<p style='color:red;'>$error</p>"; ?>
<?php if (isset($cafy)) echo "<p style='color:green;'>$cafy</p>"; ?>
</div>
</div>
</form>
</body>
</html>
Wrapping Up
You’ve built a secure withdrawal system with Google 2FA:
Composer: Set up outside public_html for safety, initialized with composer init, and loaded the Google Authenticator library.
2FA Enabling: Users activate 2FA via QR code in enable_2fa.php.
Withdrawal Flow: withdraw.php checks 2FA status, redirects to tee.php if enabled, or processes directly if not.
Test It: Enable 2FA, initiate a withdrawal, and watch it flow seamlessly.
Need This on Your Site or App?
Looking to secure your website or app with Google 2FA? I’m here to help individuals and companies alike—custom solutions for fintech platforms, e-commerce, or anything in between. Reach out at ruemuogunuyo@yahoo.com or ping me on X @iruemublog