PHP

Add 2FA to Your PHP App

A complete guide to integrating Google Authenticator using AuthenticatorAPI.com

Also available in: Python C# JavaScript Java

This guide walks through adding Google Authenticator-compatible two-factor authentication to any PHP application — from a simple login form to a full Laravel or Symfony project. No libraries to install. No Composer packages required. Just PHP and HTTP.

👉 Prerequisites: PHP 7.1 or later with the curl extension enabled (standard on most hosts). A MySQL or similar database to store user secret codes.
Step-by-Step Integration
1

Generate a Secret Code for Each User

When a user enables 2FA, generate a cryptographically random Base32-encoded secret and store it against their account in your database. The secret should be at least 16 characters of Base32 (A–Z and 2–7).

// Generate a random Base32 secret (160-bit)
function generate_2fa_secret(): string {
    $base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    $secret = '';
    for ($i = 0; $i < 32; $i++) {
        $secret .= $base32Chars[random_int(0, 31)];
    }
    return $secret;
}

$secret = generate_2fa_secret();
// Example: 'JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP'

// Store encrypted in your database
$encryptedSecret = openssl_encrypt($secret, 'AES-256-CBC', $_ENV['ENCRYPTION_KEY'], 0, $iv);
// Save $encryptedSecret and $iv to your users table
2

Display the QR Code to the User

Call the Pair endpoint and embed the returned QR code image in your 2FA setup page. The user will scan this with their Google Authenticator app.

function get_qr_code_html(string $appName, string $userEmail, string $secret): string {
    $url = 'https://www.authenticatorApi.com/pair.aspx?' . http_build_query([
        'AppName'    => $appName,
        'AppInfo'    => $userEmail,
        'SecretCode' => $secret,
    ]);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $html = curl_exec($ch);
    curl_close($ch);

    return $html; // Returns an <img> tag with the QR code
}

// In your setup page template:
echo get_qr_code_html('MyApp', $_SESSION['user_email'], $secret);
📷 Display the QR code alongside the raw secret text so users who cannot scan a QR code can enter the key manually in their authenticator app.
3

Verify the PIN During Login

After the user enters their password, prompt them for the 6-digit code from their app. Validate it against the stored secret using the Validate endpoint.

function validate_totp_pin(string $pin, string $secret): bool {
    // Basic input sanity check
    if (!ctype_digit($pin) || strlen($pin) !== 6) {
        return false;
    }

    $url = 'https://www.authenticatorApi.com/Validate.aspx?' . http_build_query([
        'Pin'        => $pin,
        'SecretCode' => $secret,
    ]);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $response = trim(curl_exec($ch));
    curl_close($ch);

    return strcasecmp($response, 'true') === 0;
}

// In your login handler:
$pin    = $_POST['totp_pin'] ?? '';
$secret = get_user_secret($_SESSION['user_id']); // fetch & decrypt from DB

if (validate_totp_pin($pin, $secret)) {
    $_SESSION['2fa_verified'] = true;
    header('Location: /dashboard');
} else {
    $error = 'Invalid code. Please try again.';
}
4

Protect Your Routes

Add a middleware-style check to all pages that require full authentication. A user must have passed both their password and their TOTP check.

// auth_check.php — include at the top of protected pages
session_start();

if (empty($_SESSION['user_id'])) {
    header('Location: /login');
    exit;
}

if (user_has_2fa_enabled($_SESSION['user_id']) && empty($_SESSION['2fa_verified'])) {
    header('Location: /login/verify');
    exit;
}
Complete Working Example

The following is a minimal but complete single-file PHP example demonstrating the full enrolment and login flow.

<?php
/**
 * minimal_2fa.php
 * A self-contained demo of 2FA enrolment and validation.
 * Not for production — no real session/DB persistence.
 */
session_start();

function http_get(string $url): string {
    $ch = curl_init($url);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>10]);
    $r = curl_exec($ch);
    curl_close($ch);
    return $r;
}

function random_base32(int $len = 32): string {
    $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    return implode('', array_map(fn() => $chars[random_int(0,31)], range(1,$len)));
}

// --- Enrol ---
if ($_SERVER['REQUEST_METHOD'] === 'GET' && !isset($_SESSION['secret'])) {
    $_SESSION['secret'] = random_base32();
}
$secret = $_SESSION['secret'];

// --- Validate ---
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $pin  = preg_replace('/\D/', '', $_POST['pin'] ?? '');
    $resp = trim(http_get("https://www.authenticatorApi.com/Validate.aspx?Pin=$pin&SecretCode=$secret"));
    $message = strcasecmp($resp, 'true') === 0
        ? '<div class="alert alert-success">✓ Code accepted!</div>'
        : '<div class="alert alert-danger">✗ Invalid code. Try again.</div>';
}

$qr = http_get("https://www.authenticatorApi.com/pair.aspx?AppName=Demo&AppInfo=user@example.com&SecretCode=$secret");
?>

<!-- HTML output -->
<h2>Scan this QR code with Google Authenticator</h2>
<?= $qr ?>
<p>Or enter this secret manually: <code><?= htmlspecialchars($secret) ?></code></p>
<form method="post">
    <input type="text" name="pin" placeholder="6-digit code" maxlength="6">
    <button type="submit">Verify</button>
</form>
<?= $message ?>
Using with Laravel

In a Laravel application the pattern is the same — generate, store, display, validate — but you use Laravel's HTTP client and session helpers instead of raw curl.

// In your TwoFactorController
use Illuminate\Support\Facades\Http;

public function enroll(Request $request)
{
    $secret = strtoupper(base_convert(bin2hex(random_bytes(20)), 16, 32));
    $secret = substr($secret, 0, 32);

    $request->user()->update(['totp_secret' => encrypt($secret)]);

    $qr = Http::get('https://www.authenticatorApi.com/pair.aspx', [
        'AppName'    => config('app.name'),
        'AppInfo'    => $request->user()->email,
        'SecretCode' => $secret,
    ])->body();

    return view('auth.2fa-setup', ['qr' => $qr, 'secret' => $secret]);
}

public function verify(Request $request)
{
    $secret = decrypt($request->user()->totp_secret);

    $result = Http::get('https://www.authenticatorApi.com/Validate.aspx', [
        'Pin'        => $request->input('pin'),
        'SecretCode' => $secret,
    ])->body();

    if (strtolower(trim($result)) === 'true') {
        $request->session()->put('2fa_verified', true);
        return redirect()->intended('/dashboard');
    }

    return back()->withErrors(['pin' => 'Invalid code, please try again.']);
}
Security Checklist for PHP