I looking for great php framework and great choice for me is Codeigniter 4. Framework have a small footprint, simple for use over complexity and very fast.

Question:

How to make simple Authentication with CodeIgniter 4 ?

Answer:

You can to use official packages Shield or build own. I will fast build own implementation of Authentication.

 

Installation of ci4 framework with "app starter":

composer create-project codeigniter4/appstarter ci4-example-auth --no-dev

Configure env file (set database driver and app key):

cd ci4-example-auth
cp env .env
sed -i "/# database.default.hostname = localhost/i database.default.DBDriver = SQLite3" .env
sed -i "/database.default.DBDriver = SQLite3/i database.default.database = database.sqlite" .env
php spark key:generate
php spark migrate

Create database migration for users:

php spark make:migration CreateUserTable

Now edit new created file app/Database/Migrations/2022-11-28-131710_CreateUserTable.php

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;
use CodeIgniter\Database\RawSql;

class CreateUserTable extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type'           => 'INT',
                'constraint'     => 5,
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'name' => [
                'type'       => 'VARCHAR',
                'constraint' => '150',
                'unique'     => true,
            ],
            'email' => [
                'type'       => 'VARCHAR',
                'constraint' => '150',
                'unique'     => true,
            ],
            'password' => [
                'type'       => 'VARCHAR',
                'constraint' => '150',
            ],
            'created_at' => [
                'type'    => 'TIMESTAMP',
                'default' => new RawSql('CURRENT_TIMESTAMP'),
            ],

        ]);

        $this->forge->addPrimaryKey('id');
        $this->forge->addUniqueKey('name');
        $this->forge->addUniqueKey('email');
        $this->forge->createTable('users');
    }

    public function down()
    {
        $this->forge->dropTable('users');
    }
}

Update database with new table:

php spark migrate

Create model UserModel:

php spark make:model UserModel

app/Models/UserModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $DBGroup          = 'default';
    protected $table            = 'users';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $insertID         = 0;
    protected $returnType       = 'array';
    protected $useSoftDeletes   = false;
    protected $protectFields    = true;
    protected $allowedFields    = [
        'name',
        'email',
        'password',
        'created_at'
    ];

    // Dates
    protected $useTimestamps = false;
    protected $dateFormat    = 'datetime';
    protected $createdField  = 'created_at';
    protected $updatedField  = 'updated_at';
    protected $deletedField  = 'deleted_at';

    // Validation
    protected $validationRules      = [];
    protected $validationMessages   = [];
    protected $skipValidation       = false;
    protected $cleanValidationRules = true;

    // Callbacks
    protected $allowCallbacks = true;
    protected $beforeInsert   = [];
    protected $afterInsert    = [];
    protected $beforeUpdate   = [];
    protected $afterUpdate    = [];
    protected $beforeFind     = [];
    protected $afterFind      = [];
    protected $beforeDelete   = [];
    protected $afterDelete    = [];
}

Create controller LoginController:

php spark make:controller Auth\\LoginController

app/Controllers/Auth/LoginController.php

<?php

namespace App\Controllers\Auth;

use App\Controllers\BaseController;
use App\Models\UserModel;

class LoginController extends BaseController
{
    public function showLoginForm()
    {
        helper(['form']);
        echo view('auth/login');
    }

    public function login()
    {
        $email = $this->request->getVar('email');
        $password = $this->request->getVar('password');

        $user = new UserModel();
        $data = $user->where('email', $email)->first();

        if ($data) {
            $pass = $data['password'];
            $authenticatePassword = password_verify($password, $pass);
            if ($authenticatePassword) {
                $authSession = new SessionConroller();
                $authSession->authorised($data);
                return redirect()->route('/');
            } else {
                $session->setFlashdata('msg', 'Password is incorrect.');
                return redirect()->back()->withInput();
            }
        } else {
            $session->setFlashdata('msg', 'Email does not exist.');
            return redirect()->back()->withInput();
        }
    }

    public function logout()
    {
        $authSession = new SessionConroller();
        $authSession->unauthorised();
        return redirect()->route('/');
    }
}

Create controller RegisterController:

php spark make:controller Auth\\RegisterController

app/Controllers/Auth/RegisterController.php

<?php

namespace App\Controllers\Auth;

use App\Controllers\BaseController;
use App\Models\UserModel;

class RegisterController extends BaseController
{
    public function showRegistrationForm()
    {
        helper(['form']);
        $data = [];
        echo view('auth/register', $data);
    }

    public function register()
    {
        helper(['form']);
        $rules = [
            'name'          => 'required|min_length[2]|max_length[50]',
            'email'         => 'required|min_length[4]|max_length[100]|valid_email|is_unique[users.email]',
            'password'      => 'required|min_length[4]|max_length[50]',
            'confirmpassword'  => 'matches[password]'
        ];

        if ($this->validate($rules)) {
            $user = new userModel();
            $data = [
                'name'     => $this->request->getVar('name'),
                'email'    => $this->request->getVar('email'),
                'password' => password_hash($this->request->getVar('password'), PASSWORD_DEFAULT)
            ];

            $user->save($data);
            $authSession = new SessionConroller();
            $authSession->authorised($data);

            return redirect()->to('/');
        } else {
            $data['validation'] = $this->validator;
            echo view('auth/register', $data);
        }
    }
}

Create controller SessionController:

php spark make:controller Auth\\SessionController

app/Controllers/Auth/SessionConroller.php

<?php

namespace App\Controllers\Auth;

use App\Controllers\BaseController;

class SessionConroller extends BaseController
{
    public function authorised($data)
    {
        $session = session();
        $authData = [
            'name' => $data['name'],
            'email' => $data['email'],
            'isLoggedIn' => true
        ];

        $session->set($authData);
    }

    public function unauthorised()
    {
        $session = session();
        $authData = ['isLoggedIn' => false];

        $session->set($authData);
    }
}

Create controller ProfileController:

php spark make:controller Auth\\ProfileController

app/Controllers/Auth/ProfileController.php

<?php

namespace App\Controllers\Auth;

use App\Controllers\BaseController;

class ProfileController extends BaseController
{
    public function index()
    {
        echo view('auth/profile');
    }
}

Create filter AuthGuard:

php spark make:filter AuthGuard

app/Filters/AuthGuard.php

<?php

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

class AuthGuard 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 mixed
     */
    public function before(RequestInterface $request, $arguments = null)
    {
        if (!session()->get('isLoggedIn')) {
            return redirect()->to('/login');
        }
    }

    /**
     * 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 mixed
     */
    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        //
    }
}

Register filter authGuard, edit file app/Config/Filters.php like this example:

        public $aliases = [
        'csrf'          => CSRF::class,
        'toolbar'       => DebugToolbar::class,
        'honeypot'      => Honeypot::class,
        'invalidchars'  => InvalidChars::class,
        'secureheaders' => SecureHeaders::class,
        'authGuard' => \App\Filters\AuthGuard::class,
    ];

 

Create view files:

app/Views/home.php

<?= $this->extend('layouts/default') ?>

<?= $this->section('content') ?>

<div class="has-text-centered">
    <?php if ($authorised): ?>

    <h3>
        Wellcome <?= esc($name) ?>
        (<?= esc($email) ?>) to ci4-auth
    </h3>

    <?php else : ?>

    <h3>Feel free to try ci4-auth</h3>

    <?php endif ?>

</div>

<?= $this->endSection() ?>

app/Views/layouts/default.php

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Welcome to ci4-auth</title>
    <meta charset="UTF-8">
    <meta name="description" content="Example of simple auth in CodeIgniter 4 ">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
    <link href="/css/app.css" rel="stylesheet">
    <?= $this->renderSection('head') ?>
</head>

<body>

    <section class="section">
        <nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
            <div class="navbar-brand">
                <a class="navbar-item" href="https://codeigniter.com">
                    <img src="/img/ci4.png">
                </a>
                <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false"
                    data-target="navbarBasic">
                    <span aria-hidden="true"></span>
                    <span aria-hidden="true"></span>
                    <span aria-hidden="true"></span>
                </a>
            </div>

            <div id="navbarBasic" class="navbar-menu">
                <div class="navbar-start">
                    <a class="navbar-item" href="<?= base_url() ?>">
                        Home
                    </a>

                    <a class="navbar-item" href="/profile">
                        Profile
                    </a>
                </div>

                <div class="navbar-end">
                    <div class="navbar-item">
                        <div class="buttons">
                            <?php if (session()->get('isLoggedIn')): ?>
                            <a class="button is-light" onclick="event.preventDefault();document.getElementById('logout-form').submit();">
                                Sign out
                            </a>
                            <form id="logout-form" action="/logout" method="POST">
                            </form>
                            <?php else : ?>
                            <a class="button is-primary" href="/register">
                                <strong>Sign up</strong>
                            </a>
                            <a class="button is-light" href="/login">
                                Sign in
                            </a>
                            <?php endif ?>
                        </div>
                    </div>
                </div>
            </div>
        </nav>
    </section>

    <div class="content">
        <?= $this->renderSection('content') ?>
    </div>

    <script src="/js/app.js"></script>
    <?= $this->renderSection('javascript') ?>

</body>

</html>

app/Views/auth/login.php

<?= $this->extend('layouts/default') ?>

<?= $this->section('content') ?>

<div class="columns is-mobile is-centered">
    <div class="column is-narrow">
        <form class="box" action="<?= url_to('login') ?>" method="post">

            <h3 class="has-text-centered">Login Form</h3>

            <div class="field">
                <label class="label">Email</label>
                <div class="control">
                    <input class="input" type="text" name="email" placeholder="Email"
                        value="<?= old('email') ?>">
                </div>
            </div>

            <div class="field">
                <label class="label">Password</label>
                <div class="control">
                    <input class="input" type="password" name="password" placeholder="Password"
                        value="<?= old('password') ?>">
                </div>
            </div>

            <div class="field is-grouped">
                <div class="control">
                    <button class="button is-link">Sign in</button>
                </div>
            </div>

            <?php if(session()->getFlashdata('msg')):?>
            <div class="notification is-danger">
                <?= session()->getFlashdata('msg') ?>
            </div>
            <?php endif;?>

        </form>

    </div>
</div>

<?= $this->endSection() ?>

app/Views/auth/register.php

<?= $this->extend('layouts/default') ?>

<?= $this->section('content') ?>

<div class="columns is-mobile is-centered">
    <div class="column is-narrow">
        <form class="box" action="<?= url_to('register') ?>" method="post">

            <h3 class="has-text-centered">Register User</h3>

            <div class="field">
                <label class="label">Name</label>
                <div class="control">
                    <input class="input" type="text" name="name" placeholder="Name"
                        value="<?= set_value('name') ?>"
                        autofocus>
                </div>
            </div>

            <div class="field">
                <label class="label">Email</label>
                <div class="control">
                    <input class="input" type="text" name="email" placeholder="Email"
                        value="<?= set_value('email') ?>">
                </div>
            </div>

            <div class="field">
                <label class="label">Password</label>
                <div class="control">
                    <input class="input" type="password" name="password" placeholder="Password">
                </div>
            </div>

            <div class="field">
                <label class="label">Password</label>
                <div class="control">
                    <input class="input" type="password" name="confirmpassword" placeholder="Confirm Password">
                </div>
            </div>

            <div class="field is-grouped">
                <div class="control">
                    <button class="button is-link">Sign Up</button>
                </div>
            </div>

        </form>

        <?php if(isset($validation)):?>
        <div class="notification is-danger">
            <?= $validation->listErrors() ?>
        </div>
        <?php endif;?>


    </div>
</div>

<?= $this->endSection() ?>

app/Views/auth/profile.php

<?= $this->extend('layouts/default') ?>

<?= $this->section('content') ?>

<div class="columns is-mobile is-centered">
    <div class="column is-narrow box">
        <h2>Profile</h2>
        <p>
            <?= esc(session()->get('name')) ?>
        </p>
        <p>
            <?= esc(session()->get('email')) ?>
        </p>
    </div>
</div>

<?= $this->endSection() ?>

Create asset files:

public/css/app.css

@import url('/css/bulma.min.css');

public/css/bulma.min.css

Download from https://bulma.io/

public/js/app.js

/* Bluma toggles the class is-active on both the navbar-burger
   and the targeted navbar-menu, in Vanilla Javascript.
*/

document.addEventListener('DOMContentLoaded', () => {

    // Get all "navbar-burger" elements
    const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
  
    // Add a click event on each of them
    $navbarBurgers.forEach(el => {
      el.addEventListener('click', () => {
  
        // Get the target from the "data-target" attribute
        const target = el.dataset.target;
        const $target = document.getElementById(target);
  
        // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
        el.classList.toggle('is-active');
        $target.classList.toggle('is-active');
  
      });
    });
  
  });
  
  /* Bluma close
     notification button.
  */
  document.addEventListener('DOMContentLoaded', () => {
    (document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
      const $notification = $delete.parentNode;
  
      $delete.addEventListener('click', () => {
        $notification.parentNode.removeChild($notification);
      });
    });
  });

public/img/ci4.png

Download from ci4-example-auth.

Now is all ready to run this project with:

php spark serve

Open web browser and hit url http://localhost:8080

Full project can download from link ci4-example-auth