Why Use JWT Authentication in CodeIgniter 4? Benefits for Modern APIs
In today's API-driven world, securing your backend is non-negotiable—especially for mobile apps, single-page applications (SPAs), and microservices that rely on RESTful endpoints. Traditional session-based authentication falls short here because it’s stateful, server-heavy, and tricky to scale across distributed systems. That's where JWT (JSON Web Token) authentication shines in CodeIgniter 4.
JWT is a compact, self-contained standard (RFC 7519) that securely transmits information between parties as a JSON object. It’s digitally signed (using HS256 or RS256) to guarantee integrity and authenticity—no database lookups needed on every request. This makes it stateless, lightweight, and perfect for high-performance APIs. With CodeIgniter 4 JWT authentication, you eliminate server-side session storage, reduce latency, and enable seamless cross-origin requests over HTTPS.
Key advantages include:
- Scalability: Tokens are verified independently—no shared session store required.
- Security: Built-in expiration (
exp), issued-at (iat), and custom claims prevent replay attacks and unauthorized access. - Flexibility: Ideal for CodeIgniter 4 login and register flows in APIs, supporting role-based access, refresh tokens, and integration with modern frontends.
- Simplicity: Libraries like
firebase/php-jwtordaycry/jwtmake implementation straightforward in CodeIgniter 4’s lightweight architecture.
Whether you're building a mobile backend or a headless CMS, mastering JWT authentication in CodeIgniter 4 ensures your API remains secure, efficient, and future-proof. In this updated 2026 tutorial, we'll walk through every step—from setup to protected routes—so you can implement a robust, production-ready system today.

Table Of Content
1 Prerequisites
- PHP 8.2 or higher
- Composer
- MySQL or compatible database
- Basic knowledge of CodeIgniter 4
2 Introduction
In this comprehensive tutorial, you'll learn how to implement CodeIgniter 4 JWT authentication to create a secure, stateless REST API. We'll guide you step-by-step through building CodeIgniter 4 login and register functionality using JWT (JSON Web Tokens), covering token generation, validation with filters, and protecting API endpoints.
Whether you're developing mobile backends, SPAs, or microservices, mastering CodeIgniter 4 JSON Web Token authentication is essential for modern API security. JWT provides a compact, signed, and self-contained token that eliminates server-side sessions, improves scalability, and supports secure cross-origin requests over HTTPS. In 2026, the recommended production approach leverages CodeIgniter Shield's built-in JWT authenticator (an official extension) for robust, framework-native handling—though we'll also demonstrate a lightweight manual implementation using firebase/php-jwt or daycry/jwt for flexibility.
By the end, you'll have a fully functional, production-ready authentication system that enhances API integrity, prevents unauthorized access, and follows current best practices for token expiration, claims validation, and refresh mechanisms.
3 Create / Install a Codeigniter 4 Project
3.1 Install Codeigniter 4 Project
composer create-project codeigniter4/appstarter ci-4-jwt-app
Navigate to your project directory:
cd ci-4-jwt-app
3.2 Configure Environment (.env)
sudo cp env .env
Next, set up development mode by editing the .env file:
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
Configure the database settings in .env:
app.baseURL = 'http://localhost:8080/'
database.default.hostname = localhost
database.default.database = your_db
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi
JWT_SECRET = your_very_long_random_secret_key_here_32_chars_min
4 Create A Model and Migration
Create a migration file for the users table:
php spark make:migration AddUser
Edit the migration file to define the table structure:
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateUsersTable extends Migration
{
public function up()
{
$this->forge->addField([
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 255],
'email' => ['type' => 'VARCHAR', 'constraint' => 255, 'unique' => true],
'password' => ['type' => 'VARCHAR', 'constraint' => 255],
'created_at' => ['type' => 'DATETIME', 'null' => true],
'updated_at' => ['type' => 'DATETIME', 'null' => true],
]);
$this->forge->addKey('id', true);
$this->forge->addUniqueKey('email');
$this->forge->createTable('users');
}
public function down()
{
$this->forge->dropTable('users');
}
}
Run the migration:
php spark migrate
php spark make:model UserModel
Edit app/Models/UserModel.php to configure the model for user management:
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserModel extends Model
{
protected $table = 'users';
protected $primaryKey = 'id';
protected $allowedFields = ['name', 'email', 'password'];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $validationRules = [
'name' => 'required|min_length[3]',
'email' => 'required|valid_email|is_unique[users.email]',
'password' => 'required|min_length[6]'
];
}
5 Install JWT Library
composer require firebase/php-jwt
After installing the JWT package, add the JWT_SECRET in the .env file:
#--------------------------------------------------------------------
# JWT
#--------------------------------------------------------------------
JWT_SECRET = 'JWT SECRET KEY SAMPLE HERE'
6 Create a Helper for JWT
Location: app/Helpers/jwt_Helper.php
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function generateJWT(array $userData): string
{
$key = getenv('JWT_SECRET');
$expiration = time() + (int)getenv('JWT_EXPIRATION');
$payload = [
'iat' => time(),
'exp' => $expiration,
'data' => $userData
];
return JWT::encode($payload, $key, 'HS256');
}
function validateJWT(string $token)
{
try {
$key = getenv('JWT_SECRET');
return JWT::decode($token, new Key($key, 'HS256'));
} catch (\Exception $e) {
return null;
}
}
Autoload Helper → Edit app/Config/Autoload.php:
public $helpers = ['url', 'jwt'];
7 Create Controller ( Auth Controller - Register + Login )
php spark make:controller Auth
php spark make:controller User
Edit app/Controllers/Auth.php to handle CodeIgniter 4 JWT Login and Register with JWT (JSON Web Token) functionality.
<?php
namespace App\Controllers;
use App\Models\UserModel;
use CodeIgniter\RESTful\ResourceController;
class AuthController extends ResourceController
{
public function register()
{
$rules = [
'name' => 'required|min_length[3]',
'email' => 'required|valid_email|is_unique[users.email]',
'password' => 'required|min_length[6]'
];
if (!$this->validate($rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
$userModel = new UserModel();
$data = [
'name' => $this->request->getVar('name'),
'email' => $this->request->getVar('email'),
'password' => password_hash($this->request->getVar('password'), PASSWORD_DEFAULT),
];
$userModel->insert($data);
return $this->respondCreated(['message' => 'User registered successfully']);
}
public function login()
{
$rules = [
'email' => 'required|valid_email',
'password' => 'required'
];
if (!$this->validate($rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
$userModel = new UserModel();
$user = $userModel->where('email', $this->request->getVar('email'))->first();
if (!$user || !password_verify($this->request->getVar('password'), $user['password'])) {
return $this->failUnauthorized('Invalid email or password');
}
$payload = [
'id' => $user['id'],
'email' => $user['email'],
'name' => $user['name']
];
$token = generateJWT($payload);
return $this->respond([
'message' => 'Login successful',
'token' => $token
]);
}
}
Location: app/Controllers/User.php
<?php
namespace App\Controllers;
use CodeIgniter\RESTful\ResourceController;
class UserController extends ResourceController
{
public function profile()
{
return $this->respond([
'user' => $this->request->user
]);
}
}
8 Create Auth Filter
Use the following spark command to Create Controller Filter.
php spark make:filter AuthFilter
Location: app/Filters/AuthFilter.php
<?php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class AuthFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
$authHeader = $request->getHeaderLine('Authorization');
if (!$authHeader) {
return Services::response()
->setJSON(['error' => 'Authorization token required'])
->setStatusCode(401);
}
$token = str_replace('Bearer ', '', $authHeader);
$decoded = validateJWT($token);
if (!$decoded) {
return Services::response()
->setJSON(['error' => 'Invalid or expired token'])
->setStatusCode(401);
}
$request->user = $decoded->data;
return $request;
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// No post-processing needed
}
}
After creating the filter, we must add it to filters config located at app/Config/Filters.php. We will creating an alias for our filter.
Location: app/Config/Filters.php
<?php
namespace Config;
use CodeIgniter\Config\Filters as BaseFilters;
use CodeIgniter\Filters\Cors;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\ForceHTTPS;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\PageCache;
use CodeIgniter\Filters\PerformanceMetrics;
use CodeIgniter\Filters\SecureHeaders;
class Filters extends BaseFilters
{
/**
* Configures aliases for Filter classes to
* make reading things nicer and simpler.
*
* @var array>
*
* [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
*/
public array $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'cors' => Cors::class,
'forcehttps' => ForceHTTPS::class,
'pagecache' => PageCache::class,
'performance' => PerformanceMetrics::class,
'authFilter' => \App\Filters\AuthFilter::class,
];
/**
* List of special required filters.
*
* The filters listed here are special. They are applied before and after
* other kinds of filters, and always applied even if a route does not exist.
*
* Filters set by default provide framework functionality. If removed,
* those functions will no longer work.
*
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
*
* @var array{before: list, after: list}
*/
public array $required = [
'before' => [
'forcehttps', // Force Global Secure Requests
'pagecache', // Web Page Caching
],
'after' => [
'pagecache', // Web Page Caching
'performance', // Performance Metrics
'toolbar', // Debug Toolbar
],
];
/**
* List of filter aliases that are always
* applied before and after every request.
*
* @var array>>|array>
*/
public array $globals = [
'before' => [
// 'honeypot',
// 'csrf',
// 'invalidchars',
],
'after' => [
// 'honeypot',
// 'secureheaders',
],
];
/**
* List of filter aliases that works on a
* particular HTTP method (GET, POST, etc.).
*
* Example:
* 'POST' => ['foo', 'bar']
*
* If you use this, you should disable auto-routing because auto-routing
* permits any HTTP method to access a controller. Accessing the controller
* with a method you don't expect could bypass the filter.
*
* @var array>
*/
public array $methods = [];
/**
* List of filter aliases that should run on any
* before or after URI patterns.
*
* Example:
* 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
*
* @var array>>
*/
public array $filters = [];
}
9 Define a Route
<?php
use CodeIgniter\Router\RouteCollection;
/**
* @var RouteCollection $routes
*/
$routes->get('/', 'Home::index');
$routes->group('api', function ($routes) {
$routes->post('register', 'AuthController::register');
$routes->post('login', 'AuthController::login');
// Protected routes
$routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('profile', 'UserController::profile');
});
});
10 Folder Structure
11 Run Web Server to Test the App
To test the Codeigniter 4 API Authentication Using JWT, set the request header to Accept: application/json and include the JWT token as a Bearer token for authenticated routes. Start the server using:
Use the following spark command to Test the App.
php spark serve
Test with Postman
1. Register:
- POST → http://localhost:8080/api/register
- Body (JSON):
{
"name": "John Doe",
"email": "john@example.com",
"password": "password123"
}
2. Login:
- POST → http://localhost:8080/api/login
- Get token from response
3. Get Profile:
- GET → http://localhost:8080/api/profile
- Headers: Authorization: Bearer YOUR_TOKEN_HERE
11 Conclusion
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
The tutorial uses firebase/php-jwt, installed via composer require firebase/php-jwt.
Run composer require firebase/php-jwt, then add JWT_SECRET='your-secret-key' to your .env file. Use this secret for encoding and decoding tokens.
In AuthController, register() validates and saves user with password_hash(). login() verifies credentials and generates a JWT access token (valid for 10 minutes) using the helper function.
Create an AuthFilter that checks the Authorization: Bearer
Create a 'users' migration with id, name, email, created_at, and updated_at fields. Use a UserModel with allowedFields for safe insertion.
Access tokens expire after 10 minutes (iat + 600 seconds). Use the refresh-token endpoint to generate a new token from a valid existing one.
Include it in the header: Authorization: Bearer
Common issues: Missing or invalid Bearer token, expired token, incorrect JWT_SECRET, or filter not applied to the route. Check headers and token validity.
Yes, a /api/refresh-token endpoint validates the current access token and issues a new one (no separate long-lived refresh token).
It uses HS256 signing, password hashing, and short-lived tokens. For production, use HTTPS, longer secrets, and consider adding blacklisting for revoked tokens.
