Multi-Factor Authentication (MFA/2FA) Setup in CodeIgniter 4 with Google Authenticator
Security is no longer optional for modern web applications. Password-only authentication is vulnerable to phishing, credential stuffing, and brute-force attacks. This is why Multi-Factor Authentication (MFA)—also known as Two-Factor Authentication (2FA)—has become a standard security requirement.MFA/2FA significantly enhances security by combining something the user knows (e.g., a password) with something they have (e.g., a time-based one-time password, or TOTP, from Google Authenticator). Implementing MFA in CodeIgniter 4 protects sensitive user data and reduces the risk of unauthorized access, making it essential for modern web applications.
In this tutorial, we will build a complete MFA system from scratch — including user registration, login, QR code generation, TOTP verification, and the ability to enable or disable MFA per user. By the end, you will have a production-ready MFA flow integrated into your CodeIgniter 4 project.

Table Of Content
1 Prerequisites
- PHP ≥ 8.1 (CodeIgniter 4 requirement)
- Composer
- MySQL Database
- Google Authenticator App: Available on the user’s mobile device (iOS or Android).
- Basic Knowledge: Familiarity with CodeIgniter 4, PHP, and MySQL.
2 Introduction
In this guide, you will learn how to implement MFA in CodeIgniter 4 using Google Authenticator, step by step, with working sample code and explanations.
What is TOTP? TOTP stands for Time-based One-Time Password. It is an algorithm that generates a short numeric code (usually 6 digits) that changes every 30 seconds. Google Authenticator uses this algorithm to generate codes on the user's mobile device. The server and the app share a secret key, and both independently calculate the same code based on the current time — no internet connection is needed on the mobile device.
How does the MFA flow work?
The typical flow is: the user logs in with their email and password. If MFA is enabled, they are redirected to a verification screen where they must enter the 6-digit code from their Google Authenticator app. Only after successful verification are they granted full access to the application. This adds a strong second layer of defense even if the password is compromised.
3 Create / Install a Codeigniter 4 Project
3.1 Install Codeigniter 4 Project
Use the following command to install new Codeigniter Project.
composer create-project codeigniter4/appstarter ci4-mfa-app
Then, navigate to your project directory:
cd ci4-mfa-app
3.2 Configure Environment and MySQL Database
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
database.default.hostname = localhost
database.default.database = ci4_mfa_app
database.default.username = your_db_user
database.default.password = your_db_pass
database.default.DBDriver = MySQLi
Create the database ci4_mfa_app in your MySQL server using phpMyAdmin or the MySQL command line.
Next, create a users table to store user credentials and their MFA secrets. Below is the SQL schema:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
mfa_secret VARCHAR(32) DEFAULT NULL,
mfa_enabled TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
- mfa_secret: Stores the user's unique secret key used by the TOTP algorithm to generate time-based codes. This is shared between the server and the Google Authenticator app.
- mfa_enabled: A boolean flag (0 or 1) that indicates whether MFA is currently active for the user. The login flow checks this field to decide if TOTP verification is required.
4 Create User Model
php spark make:model UserModel
Edit app/Models/UserModel.php to configure the model:
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserModel extends Model
{
protected $table = 'users';
protected $primaryKey = 'id';
protected $allowedFields = [
'username', 'email', 'password',
'mfa_secret', 'mfa_enabled'
];
// Optional: Hash passwords automatically
protected $beforeInsert = ['hashPassword'];
protected $beforeUpdate = ['hashPassword'];
protected function hashPassword(array $data)
{
if (!isset($data['data']['password'])) {
return $data;
}
$data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
return $data;
}
}
5 Install Google Authenticator Library
We will use PHPGangsta/GoogleAuthenticator, a lightweight and trusted PHP library for TOTP-based two-factor authentication. It generates secret keys, creates QR code URLs (via Google Charts API), and verifies TOTP codes — all without external dependencies.
Run this command in your CodeIgniter 4 project root:
composer require phpgangsta/googleauthenticator
After installation, the library is autoloaded by Composer. You can use the class PHPGangsta_GoogleAuthenticator directly in your controllers without any additional configuration.
Why this library? It is simple, has no framework-specific dependencies, works with any TOTP-compatible authenticator app (Google Authenticator, Authy, Microsoft Authenticator, etc.), and has been widely adopted in PHP projects.
6 Auth Controller (Basic Registration & Login)
Important: CodeIgniter 4 has built-in CSRF protection. Make sure it is enabled in app/Config/Filters.php by adding csrf to the $globals['before'] array. All forms using POST method should include <?= csrf_field() ?> to generate the hidden CSRF token field automatically.
<?php
namespace App\Controllers;
use App\Models\UserModel;
use PHPGangsta_GoogleAuthenticator;
class AuthController extends BaseController
{
protected $userModel;
protected $ga;
public function __construct()
{
$this->userModel = new UserModel();
$this->ga = new PHPGangsta_GoogleAuthenticator();
}
// Register (simple example - add validation as needed)
public function register()
{
if ($this->request->getMethod() === 'post') {
// Basic validation
$rules = [
'username' => 'required|min_length[3]|max_length[100]',
'email' => 'required|valid_email|is_unique[users.email]',
'password' => 'required|min_length[8]',
];
if (!$this->validate($rules)) {
return view('register', ['validation' => $this->validator]);
}
$data = [
'username' => $this->request->getPost('username'),
'email' => $this->request->getPost('email'),
'password' => $this->request->getPost('password'),
];
if ($this->userModel->insert($data)) {
return redirect()->to('/login')->with('success', 'Registration successful. Please login.');
}
}
return view('register');
}
// Login view
public function login()
{
return view('login');
}
// Process login
public function processLogin()
{
$email = $this->request->getPost('email');
$password = $this->request->getPost('password');
$user = $this->userModel->where('email', $email)->first();
if ($user && password_verify($password, $user['password'])) {
// Store user in session temporarily
session()->set('user_id', $user['id']);
session()->set('user_email', $user['email']);
// If MFA enabled, redirect to verify
if ($user['mfa_enabled']) {
return redirect()->to('/mfa/verify');
}
// Otherwise, full login
session()->set('logged_in', true);
return redirect()->to('/dashboard');
}
return redirect()->to('/login')->with('error', 'Invalid credentials');
}
// Logout
public function logout()
{
session()->destroy();
return redirect()->to('/login');
}
}
?>
Note: For production applications, you should add more robust validation, rate limiting on login attempts, and account lockout policies to further strengthen security.
7 MFA Controller
Important Note: In CodeIgniter 4, returning a redirect() from a constructor does not halt execution of the controller method. For production use, consider using a Controller Filter instead to protect MFA routes. The constructor check below is shown for simplicity, but a filter-based approach (registered in app/Config/Filters.php) is the recommended pattern in CI4.
<?php
namespace App\Controllers;
use App\Models\UserModel;
use PHPGangsta_GoogleAuthenticator;
class MfaController extends BaseController
{
protected $userModel;
protected $ga;
public function __construct()
{
$this->userModel = new UserModel();
$this->ga = new PHPGangsta_GoogleAuthenticator();
// Protect all MFA routes - user must be partially logged in
if (!session()->has('user_id')) {
return redirect()->to('/login');
}
}
// Show MFA setup page (enable 2FA)
public function setup()
{
$userId = session()->get('user_id');
$user = $this->userModel->find($userId);
// Generate secret if not exists
if (empty($user['mfa_secret'])) {
$secret = $this->ga->createSecret();
$this->userModel->update($userId, ['mfa_secret' => $secret]);
$user['mfa_secret'] = $secret;
}
$qrCodeUrl = $this->ga->getQRCodeGoogleUrl(
'MyApp:' . $user['email'], // Label shown in app
$user['mfa_secret']
);
return view('mfa_setup', [
'qrCodeUrl' => $qrCodeUrl,
'secret' => $user['mfa_secret']
]);
}
// Verify code and enable MFA
public function enable()
{
$userId = session()->get('user_id');
$user = $this->userModel->find($userId);
$code = $this->request->getPost('code');
if ($this->ga->verifyCode($user['mfa_secret'], $code, 2)) { // 2 = tolerance of 60 seconds
$this->userModel->update($userId, ['mfa_enabled' => 1]);
// Full login after enabling
session()->set('logged_in', true);
return redirect()->to('/dashboard')->with('success', 'MFA enabled successfully!');
}
return redirect()->back()->with('error', 'Invalid code. Try again.');
}
// MFA verification after login (if already enabled)
public function verify()
{
if ($this->request->getMethod() === 'post') {
$userId = session()->get('user_id');
$user = $this->userModel->find($userId);
$code = $this->request->getPost('code');
if ($this->ga->verifyCode($user['mfa_secret'], $code, 2)) {
session()->set('logged_in', true);
return redirect()->to('/dashboard');
}
return redirect()->back()->with('error', 'Invalid MFA code');
}
return view('mfa_verify');
}
// Optional: Disable MFA (protected route)
public function disable()
{
if (!session()->get('logged_in')) {
return redirect()->to('/login');
}
$userId = session()->get('user_id');
$this->userModel->update($userId, [
'mfa_secret' => null,
'mfa_enabled' => 0
]);
return redirect()->to('/dashboard')->with('success', 'MFA disabled');
}
}
?>
8 Create Views
<!DOCTYPE html>
<html>
<head>
<title>User Register</title>
</head>
<body>
<h2>Register</h2>
<?php if (session()->getFlashdata('success')): ?>
<p style="color:green"><?= session()->getFlashdata('success') ?></p>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<p style="color:red"><?= session()->getFlashdata('error') ?></p>
<?php endif; ?>
<form method="post">
<?= csrf_field() ?>
<input type="text" name="username" placeholder="Username" required><br>
<input type="email" name="email" placeholder="Email" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<button type="submit">Register</button>
</form>
</body>
</html>
Login Form app/Views/login.php
<!DOCTYPE html>
<html>
<head>
<title>User Login</title>
</head>
<body>
<h2>Login</h2>
<?php if (session()->getFlashdata('success')): ?>
<p style="color:green"><?= session()->getFlashdata('success') ?></p>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<p style="color:red"><?= session()->getFlashdata('error') ?></p>
<?php endif; ?>
<form method="post" action="<?= base_url('/login') ?>">
<?= csrf_field() ?>
<input type="email" name="email" placeholder="Email" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<button type="submit">Login</button>
</form>
</body>
</html>
MFA Setup app/Views/mfa_setup.php
<!DOCTYPE html>
<html>
<head>
<title>MFA Setup</title>
</head>
<body>
<h2>Setup Two-Factor Authentication</h2>
<?php if (session()->getFlashdata('error')): ?>
<p style="color:red"><?= session()->getFlashdata('error') ?></p>
<?php endif; ?>
<p>Scan this QR code with Google Authenticator:</p>
<img src="<?= $qrCodeUrl ?>" alt="QR Code"><br><br>
<p>Or manually enter secret: <strong><?= $secret ?></strong></p>
<form method="post" action="<?= base_url('/mfa/enable') ?>">
<?= csrf_field() ?>
<label>Enter code from app:</label>
<input type="text" name="code" required>
<button type="submit">Verify & Enable MFA</button>
</form>
</body>
</html>
MFA Verify app/Views/mfa_verify.php
<!DOCTYPE html>
<html>
<head>
<title>MFA Verify</title>
</head>
<body>
<h2>Enter MFA Code</h2>
<?php if (session()->getFlashdata('error')): ?>
<p style="color:red"><?= session()->getFlashdata('error') ?></p>
<?php endif; ?>
<form method="post">
<?= csrf_field() ?>
<label>Google Authenticator Code:</label>
<input type="text" name="code" required>
<button type="submit">Verify</button>
</form>
</body>
</html>
Dashboard app/Views/dashboard.php
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
</head>
<body>
<h1>Welcome to Dashboard!</h1>
<?php if (session()->getFlashdata('success')): ?>
<p style="color:green"><?= session()->getFlashdata('success') ?></p>
<?php endif; ?>
<p>You are logged in with MFA protection.</p>
<a href="/mfa/setup">Setup MFA</a> |
<a href="/mfa/disable">Disable MFA</a> | <a href="/logout">Logout</a>
</body>
</html>
9 Define a Route
$routes->get('/register', 'AuthController::register');
$routes->post('/register', 'AuthController::register');
$routes->get('/login', 'AuthController::login');
$routes->post('/login', 'AuthController::processLogin');
$routes->get('/logout', 'AuthController::logout');
$routes->get('/mfa/setup', 'MfaController::setup');
$routes->post('/mfa/enable', 'MfaController::enable');
$routes->match(['get', 'post'], '/mfa/verify', 'MfaController::verify');
$routes->get('/mfa/disable', 'MfaController::disable');
$routes->get('/dashboard', function () {
if (!session()->get('logged_in')) {
return redirect()->to('/login');
}
return view('dashboard');
});
Tip: For a cleaner approach, you can group the MFA routes and apply a filter to check if the user is partially authenticated (has user_id in session but not logged_in).
10 Folder Structure
Related CodeIgniter 4 Tutorials
If you found this MFA tutorial helpful, explore these related CodeIgniter 4 guides on our site to further strengthen and extend your application:
- CodeIgniter 4 Authentication Tutorial: Build Login & Registration System from Scratch — Learn the official way to implement authentication in CI4 using CodeIgniter Shield, the recommended auth library.
- Role-Based Login System in CodeIgniter 4: Admin & User Roles — Implement role-based access control (RBAC) to manage different permission levels for admins and regular users.
- How to Secure Your CodeIgniter 4 Application from SQL Injection — Essential security practices to protect your CI4 application from SQL injection vulnerabilities.
- CodeIgniter 4 REST API CRUD Example with Postman — Build a complete RESTful API in CI4, which pairs well with token-based authentication and MFA for API security.
- How to Build a Simple Blog in CodeIgniter 4 with MySQL and Bootstrap — A practical CRUD project tutorial to understand MVC fundamentals in CodeIgniter 4.
- Boost CodeIgniter 4 Performance and Page Speed — Optimize your CI4 application for faster load times and better user experience.
11 Run and Test
php spark serve
Visit http://localhost:8080/register in your browser and follow these steps to test the complete MFA flow:
- Step 1: Register a new user at /register with a username, email, and password.
- Step 2: Log in at /login with the registered credentials. Since MFA is not yet enabled, you will be redirected directly to the dashboard.
- Step 3: Navigate to /mfa/setup from the dashboard to enable MFA. A QR code will be displayed.
- Step 4: Open Google Authenticator on your phone, tap the "+" button, and scan the QR code. The app will start generating 6-digit codes that refresh every 30 seconds.
- Step 5: Enter the current code from the app into the verification field and submit. MFA is now enabled for your account.
- Step 6: Log out and log in again. This time, after entering your password, you will be redirected to /mfa/verify where you must enter the TOTP code from Google Authenticator to complete the login.
If the code is entered incorrectly or has expired, the user will see an error message and can try again with the latest code from the app.
12 Conclusion
Implementing MFA in CodeIgniter 4 using Google Authenticator dramatically improves application security with minimal overhead. The TOTP-based approach is scalable, works offline on the user's device, and aligns with modern security standards recommended by OWASP and NIST.
If your application handles sensitive data — whether it is user accounts, payment information, or admin panels — MFA is no longer optional. It is essential.
Next Steps to Consider:
- Add backup/recovery codes so users can regain access if they lose their phone.
- Implement rate limiting on the verification endpoint to prevent brute-force attacks on TOTP codes.
- Use CI4 Controller Filters instead of constructor-based authentication checks for a cleaner architecture.
- Store session data securely using database-backed sessions in production.
- Consider using CodeIgniter Shield for a more comprehensive authentication solution — read our guide: CodeIgniter 4 Authentication Tutorial with Shield.
For more security best practices, check out our article on How to Secure Your CodeIgniter 4 Application from SQL Injection.
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
If a user loses access to their authenticator app, they will be locked out of their account unless you implement backup recovery codes. It is a best practice to generate a set of one-time backup codes during MFA setup that the user can store securely. You can also allow admin-level MFA reset as a fallback.
Yes, any TOTP-compatible authenticator app will work with this implementation. Popular alternatives include Authy, Microsoft Authenticator, 1Password, and FreeOTP. The TOTP standard (RFC 6238) ensures interoperability across all these apps.
The PHPGangsta/GoogleAuthenticator library is widely used and suitable for most projects. However, for high-security production environments, consider using more actively maintained alternatives like the pragmarx/google2fa or robthree/twofactorauth packages which offer additional features like custom code lengths and algorithm support.
MFA (Multi-Factor Authentication) in CodeIgniter 4 adds an extra layer of security beyond just a password. After entering their credentials, users must also provide a time-based one-time password (TOTP) generated by an app like Google Authenticator. This is implemented using the PHPGangsta/GoogleAuthenticator library which handles secret generation, QR code creation, and code verification.
Yes, Google Authenticator is completely free to download and use on both iOS and Android devices. It generates TOTP codes offline without requiring an internet connection, making it reliable and secure. Other compatible apps include Authy, Microsoft Authenticator, and 1Password.
Yes, MFA can be disabled at any time. In this tutorial, the disable function resets the mfa_secret to NULL and sets mfa_enabled to 0 in the database. For security, you should require the user to verify their identity (password or current TOTP code) before allowing MFA to be disabled.
MFA adds only a few seconds to the login process — the user simply opens their authenticator app and enters the 6-digit code. This minimal delay provides a massive security improvement by protecting against password theft, phishing, and brute-force attacks.
No, MFA can be made optional or mandatory depending on your application security policy. In this tutorial, users can choose to enable MFA from their dashboard. For applications handling sensitive data (banking, healthcare, admin panels), making MFA mandatory for all users is strongly recommended.
