composer create-project codeigniter4/appstarter ci-4-jwt-app
Navigate to your project directory:
cd ci-4-jwt-app
sudo cp env .env
Next, set up development mode by editing the .env file:
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ci4_jwt
DB_USERNAME=root
DB_PASSWORD=
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', 'created_at'];
}
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 AddUser extends Migration
{
public function up()
{
$this->forge->addField([
'id' => [
'type' => 'BIGINT',
'constraint' => 255,
'unsigned' => true,
'auto_increment' => true
],
'name' => [
'type' => 'VARCHAR',
'constraint' => '255',
],
'email' => [
'type' => 'longtext'
],
'created_at' => [
'type' => 'TIMESTAMP',
'null' => true
],
'updated_at' => [
'type' => 'TIMESTAMP',
'null' => true
],
]);
$this->forge->addPrimaryKey('id');
$this->forge->createTable('users');
}
public function down()
{
$this->forge->dropTable('users');
}
}
Run the migration:
php spark migrate
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'
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function generateAccessToken($userData) {
$key = getenv('JWT_SECRET');
$issuedAt = time();
$expirationTime = $issuedAt + 600; // 10 minutes validity for access token
$payload = [
'iat' => $issuedAt,
'exp' => $expirationTime,
'data' => $userData
];
return JWT::encode($payload, $key, 'HS256');
}
function validateToken($token) {
$key = getenv('JWT_SECRET');
try {
return JWT::decode($token, new Key($key, 'HS256'));
} catch (Exception $e) {
return null; // Invalid or expired token
}
}
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\Controllers\BaseController;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\API\ResponseTrait;
use App\Models\UserModel;
class Auth extends BaseController
{
use ResponseTrait;
public function register()
{
$rules = [
'email' => ['rules' => 'required|min_length[4]|valid_email|is_unique[users.email]'],
'password' => ['rules' => 'required|min_length[6]'],
'confirm_password' => [ 'label' => 'confirm password', 'rules' => 'matches[password]']
];
if($this->validate($rules)){
$model = new UserModel();
$data = [
'email' => $this->request->getVar('email'),
'password' => password_hash($this->request->getVar('password'), PASSWORD_DEFAULT)
];
$model->save($data);
return $this->respond(['message' => 'Registered Successfully'], 200);
}else{
$response = [
'errors' => $this->validator->getErrors(),
'message' => 'Invalid Inputs'
];
return $this->fail($response , 409);
}
}
public function login()
{
$userModel = new UserModel();
$email = $this->request->getVar('email');
$password = $this->request->getVar('password');
$user = $userModel->where('email', $email)->first();
if(is_null($user)) {
return $this->respond(['error' => 'Invalid Email Id.'], 401);
}
$pwd_verify = password_verify($password, $user['password']);
if(!$pwd_verify) {
return $this->respond(['error' => 'Wrong password.'], 401);
}
helper('Jwt');
$token = generateAccessToken($user);
$response = [
'message' => 'Login Succesful',
'token' => $token
];
return $this->respond($response, 200);
}
}
Location: app/Controllers/User.php
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\API\ResponseTrait;
use App\Models\UserModel;
class User extends BaseController
{
use ResponseTrait;
public function index()
{
$users = new UserModel;
return $this->respond(['users' => $users->findAll()], 200);
}
public function getuser()
{
$header = $this->request->getHeader("Authorization");
// extract the token from the header
if(!empty($header)) {
if (preg_match('/Bearer\s(\S+)/', $header, $matches)) {
$token = $matches[1];
}
}
if(is_null($token) || empty($token)) {
return $this->respond(['error' => 'Invalid Token'], 401);
}
helper('Jwt');
$decoded = validateToken($token);
$response = [
'id' => $decoded->data->id,
'email' => $decoded->data->email
];
return $this->respond($response, 200);
}
public function get_refresh_token()
{
$header = $this->request->getHeader("Authorization");
// extract the token from the header
if(!empty($header)) {
if (preg_match('/Bearer\s(\S+)/', $header, $matches)) {
$token = $matches[1];
}
}
if(is_null($token) || empty($token)) {
return $this->respond(['error' => 'Invalid Token'], 401);
}
helper('Jwt');
$decoded = validateToken($token);
$token = generateAccessToken($decoded->data);
$response = [
'message' => 'Refresh Token Generated Succesful',
'token' => $token
];
return $this->respond($response, 200);
}
}
php spark make:filter AuthFilter
Location: app/Filters/AuthFilter.php
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class AuthFilter implements FilterInterface
{
/**
* Do whatever processing this filter needs to do.
* By default it should not return anything during
* normal execution. However, when an abnormal state
* is found, it should return an instance of
* CodeIgniter\HTTP\Response. If it does, script
* execution will end and that Response will be
* sent back to the client, allowing for error pages,
* redirects, etc.
*
* @param RequestInterface $request
* @param array|null $arguments
*
* @return RequestInterface|ResponseInterface|string|void
*/
public function before(RequestInterface $request, $arguments = null)
{
$key = getenv('JWT_SECRET');
$header = $request->getHeader("Authorization");
$token = null;
// extract the token from the header
if(!empty($header)) {
if (preg_match('/Bearer\s(\S+)/', $header, $matches)) {
$token = $matches[1];
}
}
// check if token is null or empty
if(is_null($token) || empty($token)) {
$response = service('response');
$response->setBody('Access denied');
$response->setStatusCode(401);
return $response;
}
try {
// $decoded = JWT::decode($token, $key, array("HS256"));
$decoded = JWT::decode($token, new Key($key, 'HS256'));
} catch (\Exception $ex) {
$response = service('response');
$response->setBody('Access denied');
$response->setStatusCode(401);
return $response;
}
}
/**
* Allows After filters to inspect and modify the response
* object as needed. This method does not allow any way
* to stop execution of other after filters, short of
* throwing an Exception or Error.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param array|null $arguments
*
* @return ResponseInterface|void
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
//
}
}
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 = [];
}
<?php
use CodeIgniter\Router\RouteCollection;
/**
* @var RouteCollection $routes
*/
$routes->get('/', 'Home::index');
$routes->group("api", function ($routes) {
$routes->post("register", "Auth::register");
$routes->post("login", "Auth::login");
$routes->get("users", "User::index", ['filter' => 'authFilter']);
$routes->get("get-logger-user", "User::getuser", ['filter' => 'authFilter']);
$routes->get("refresh-token", "User::get_refresh_token", ['filter' => 'authFilter']);
});
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:
php spark serve