melanjutkan dari praktek sebelumnya, praktek yang kedua ini saya akan membuat fitur login dan register agar nantinya halaman produk tidak bisa diakses begitu saja
berikut spesifikasi kode dari fitur login dan register yang akan dibuat
- Menggunakan Ajax
- Validasi Data
- Menggunakan Filter sebagai Middleware
- Fitur Remember me (Login otomatis)
- Membuat 2 Halaman yaitu login dan register
Persiapan
karena fitur login dan register ini menggunakan file dari tutorial sebelumnya, maka pastikan kalian sudah membaca dan mempraktekannya
baca membuat crud codeigniter 4
Membuat Table
Langkah 1 : buat table user menggunakan migration
ketikan perintah spark
php spark make:migration user
Langkah 2 : membangun struktur table user
edit file migration user yang telah dibuat terletak di 'app/Database/Migrations/DATETIME_User.php' ganti dengan kode dibawah ini
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class User extends Migration
{
public function up()
{
$this->forge->addField([
'id' => [
'type' => 'INT',
'constraint' => 100,
'unsigned' => true,
'auto_increment' => true,
],
'username' => [
'type' => 'VARCHAR',
'constraint' => '30',
],
'password' => [
'type' => 'VARCHAR',
'constraint' => '200',
],
'email' => [
'type' => 'VARCHAR',
'constraint' => '100',
],
'created_at' => [
'type' => 'DATETIME',
],
'updated_at' => [
'type' => 'DATETIME',
]
]);
$this->forge->addKey('id', true);
$this->forge->createTable('user');
}
public function down()
{
$this->forge->dropTable('user');
}
}
Langkah 3 : menjalankan migration menggunakan spark ketikan perintah spark berikut
php spark migrate
Membuat Controller
ketikan perintah spark
php spark make:controller auth
edit kode controller auth yang terletak di 'app/Controllers/Auth.php' dengan kode dibawah ini
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
class Auth extends BaseController
{
public function index_login()
{
$data['title'] = 'Login';
return view('auth/login', $data);
}
public function login()
{
// validate input text
$validationRule = [
'identity' => [
'rules' => 'required'
],
'password' => [
'rules' => 'required'
]
];
if (!$this->validate($validationRule)) {
$error = $this->validator->getErrors();
$error_val = array_values($error);
die(json_encode([
'status' => false,
'response' => $error_val[0]
]));
}
// input data
$identity = $this->request->getPost('identity');
$password = $this->request->getPost('password');
// load model
$userModel = new \App\Models\User();
// find user
$user = $userModel->select('id,username,password')->where('username', $identity)->orWhere('email', $identity)->first();
// user not found.
if (!$user) {
return $this->response->setJSON([
'status' => false,
'response' => 'Account not found'
]);
}
// validate password
if (!password_verify($password, $user['password'])) {
// invalid password
return $this->response->setJSON([
'status' => false,
'response' => 'Password Invalid'
]);
}
// build data
$data = [
'id' => $user['id'],
'username' => $user['username'],
];
// set session
session()->set('auth', $data);
// check if remember exist
if ($this->request->getPost('remember')) {
// load helper
helper('aeshash');
// set cookie
$auth_hash = aeshash('enc', json_encode($_SESSION['auth']) , config('Encryption')->key);
setcookie('auth', $auth_hash, time() + (86400 * 30), '/');
}
// send response
return $this->response->setJSON([
'status' => true,
'response' => 'Success Login',
'redirect' => base_url('product')
]);
}
public function index_register()
{
$data['title'] = 'Register';
return view('auth/register', $data);
}
public function register()
{
// validate input text
$validationRule = [
'email' => [
'rules' => 'required|max_length[100]|valid_email|is_unique[user.email]'
],
'username' => [
'rules' => 'required|min_length[4]|max_length[30]|is_unique[user.username]'
],
'password' => [
'rules' => 'required|min_length[4]|max_length[50]'
],
'password_confirm' => [
'rules' => 'matches[password]'
]
];
if (!$this->validate($validationRule)) {
$error = $this->validator->getErrors();
$error_val = array_values($error);
die(json_encode([
'status' => false,
'response' => $error_val[0]
]));
}
// input data
$data['email'] = $this->request->getPost('email');
$data['username'] = $this->request->getPost('username');
$data['password'] = password_hash($this->request->getPost('password'), PASSWORD_DEFAULT);
// load model
$userModel = new \App\Models\User();
// insert data
$register = $userModel->insert($data);
// build data
$data = [
'id' => $register,
'username' => $data['username'],
];
// set session
session()->set('auth', $data);
// send response
return $this->response->setJSON([
'status' => true,
'response' => 'Success Register',
'redirect' => base_url('product')
]);
}
public function logout()
{
session()->remove('auth');
setcookie('auth', null, -1, '/');
return redirect()->to(base_url('login'));
}
}
Membuat Model
ketikan perintah spark
php spark make:model user
edit kode model user terletak di 'app/Models/User.php' dengan kode dibawah ini
<?php
namespace App\Models;
use CodeIgniter\Model;
class User extends Model
{
protected $table = 'user';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $returnType = 'array';
protected $protectFields = false;
// Dates
protected $useTimestamps = true;
protected $dateFormat = 'datetime';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
}
Membuat Filter
ketikan perintah spark
php spark make:filter Auth
edit filter auth dengan kode dibawah ini
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class Auth implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
if (is_array($arguments) AND $arguments[0] == 'page') {
if (session()->auth) {
return redirect()->to(base_url('product'));
}
}else{
if (!session()->auth) {
// if ajax request
if ($request->isAJAX()) {
http_response_code(400);
header('Content-Type: application/json; charset=utf-8');
die(json_encode([
'status' => false,
'response' => 'no authorize'
]));
}else{
// if direct request
session()->setFlashdata('message', 'please login first');
return redirect()->to(base_url('login'));
}
}
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
//
}
}
dilanjut dengan membuat alias filter authnya
silahkan edit file 'app/Config/Filters.php'
tambahkan kode dibawah ini pada propery $aliases
'auth' => \App\Filters\Auth::class,
sehingga property $aliases
menjadi seperti
public $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'auth' => \App\Filters\Auth::class,
];
Membuat dan Mengedit Route
disini saya akan membuat route untuk login dan registernya serta menerapkan filter yang telah dibuat sebelumnya
edit file 'app/Config/Routes.php'
silahkan diedit route product yang sudah ada menjadi seperti kode dibawah ini
// route product with filter auth
$routes->group('', ['filter' => 'auth'] ,function ($routes) {
$routes->get('/product', 'product::index');
$routes->get('/product/read', 'product::read');
$routes->post('/product/create', 'product::create');
$routes->post('/product/edit', 'product::edit');
$routes->post('/product/update', 'product::update');
$routes->post('/product/delete', 'product::delete');
$routes->post('/product/delete_batch', 'product::delete_batch');
});
kemudian tambahkan route login, register, dan logout
// route auth with filter auth:page
$routes->group('', ['filter' => 'auth:page'] ,function ($routes) {
$routes->get('/login', 'auth::index_login');
$routes->post('/login', 'auth::login');
$routes->get('/register', 'auth::index_register');
$routes->post('/register', 'auth::register');
});
$routes->get('/logout', 'auth::logout');
Membuat dan Mengedit View
edit file view nav.php yang terletak di 'app/Views/_layout/nav.php' dengan kode dibawah ini
<nav class="navbar navbar-expand-md mb-4">
<div class="container">
<span class="navbar-brand">Store</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="<?= base_url() ?>">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?= base_url('product') ?>">Product</a>
</li>
</ul>
<ul class="navbar-nav mb-2 mb-md-0 d-flex">
<?php if (!session()->auth): ?>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="<?= base_url('login') ?>">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?= base_url('register') ?>">Register</a>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link btn btn-outline-danger me-2" aria-current="page" href="<?= base_url('logout') ?>">Logout</a>
</li>
<?php endif ?>
</ul>
</div>
</div>
</nav>
dilanjut dengan membuat folder view baru dengan nama auth kemudian buat 2 file didalamnya dengan nama login.php dan register.php
sehingga nanti akan ada folder dan file baru seperti gambaran dibawah ini
.
└── app/
└── Views/
└── auth/
├── login.php
└── register.php
buka file auth/login.php dan isi dengan kode dibawah ini
<?= $this->extend('_layout') ?>
<?= $this->section('content') ?>
<main class="py-5 my-auto">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-md-4">
<form id="form-login" method="POST">
<h1 class="h3 mb-3 fw-normal">Login</h1>
<div id="form-message">
<?php if (session()->getFlashdata('message')): ?>
<div class="alert alert-warning">
<?= session()->getFlashdata('message') ?>
</div>
<?php endif ?>
</div>
<div class="form-floating mb-3">
<input name="identity" type="text" class="form-control" id="input-identity" placeholder="foo...">
<label for="input-identity">Username / Email</label>
</div>
<div class="form-floating mb-3">
<input name="password" type="password" class="form-control" id="input-password" placeholder="Password">
<label for="input-password">Password</label>
</div>
<div class="checkbox mb-3">
<label>
<input name="remember" type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-outline-dark submit-button" type="submit">Sign in</button>
</form>
</div>
</div>
</div>
</main>
<?= $this->endSection() ?>
<?= $this->section('js') ?>
<script>
$("#form-login").on("submit", function(e){
e.preventDefault();
let form = $(this);
// animation
$("input", form).prop("readonly",true);
$(".submit-button").prop("disabled",true);
$(".submit-button",form).html($(".submit-button",form).html() + xsetting.spinner);
let buttonspinner = $(".button-spinner");
$.post(base_url + `/login`, form.serialize() , {}, 'json')
.done(function(data){
if (data.status) {
$("#form-message").html(`<div class="alert alert-info text-break">${data.response}</div>`);
setTimeout(() => {
window.location.href = data.redirect
},1000);
}else{
// animation
$("input", form).prop("readonly",false);
$(".submit-button").prop("disabled",false);
buttonspinner.remove();
$("#form-message").html(`<div class="alert alert-warning text-break">${data.response}</div>`);
}
})
.fail(function(xhr, statusText, errorThrown) {
alert(xhr.responseText);
// animation
$("input", form).prop("readonly",false);
$(".submit-button").prop("disabled",false);
buttonspinner.remove();
})
})
</script>
<?= $this->endSection() ?>
buka file auth/register.php dan isi dengan kode dibawah ini
<?= $this->extend('_layout') ?>
<?= $this->section('content') ?>
<main class="py-5 my-auto">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-md-4">
<form id="form-register" method="POST">
<h1 class="h3 mb-3 fw-normal">Register</h1>
<div id="form-message"></div>
<div class="form-floating mb-3">
<input name="email" type="email" class="form-control" id="input-email" placeholder="[email protected]">
<label for="input-email">Email</label>
</div>
<div class="form-floating mb-3">
<input name="username" type="text" class="form-control" id="input-username" placeholder="foo...">
<label for="input-username">Username</label>
</div>
<div class="form-floating mb-3">
<input name="password" type="password" class="form-control" id="input-password" placeholder="Password">
<label for="input-password">Password</label>
</div>
<div class="form-floating mb-3">
<input name="password_confirm" type="password" class="form-control" id="input-password-confirm" placeholder="Password">
<label for="input-password-confirm">Confirm Password</label>
</div>
<button class="btn btn-lg btn-outline-dark submit-button" type="submit">Sign Up</button>
</form>
</div>
</div>
</div>
</main>
<?= $this->endSection() ?>
<?= $this->section('js') ?>
<script>
$("#form-register").on("submit", function(e){
e.preventDefault();
let form = $(this);
// animation
$("input", form).prop("readonly",true);
$(".submit-button").prop("disabled",true);
$(".submit-button",form).html($(".submit-button",form).html() + xsetting.spinner);
let buttonspinner = $(".button-spinner");
$.post(base_url + `/register`, form.serialize() , {}, 'json')
.done(function(data){
if (data.status) {
$("#form-message").html(`<div class="alert alert-info text-break">${data.response}</div>`);
setTimeout(() => {
window.location.href = data.redirect
},1000);
}else{
// animation
$("input", form).prop("readonly",false);
$(".submit-button").prop("disabled",false);
buttonspinner.remove();
$("#form-message").html(`<div class="alert alert-danger text-break">${data.response}</div>`);
}
})
.fail(function(xhr, statusText, errorThrown) {
alert(xhr.responseText);
// animation
$("input", form).prop("readonly",false);
$(".submit-button").prop("disabled",false);
buttonspinner.remove();
})
})
</script>
<?= $this->endSection() ?>
Membuat Fitur Login Remember me
fitur remember me pada login berguna agar ketika user masih dalam keadaan login lalu menutup browsernya, maka ketika browser dibuka kembali dan mengunjungi web akan login otomatis tanpa harus login lagi
kalian juga harus tau fitur remember me ini menggunakan cookie untuk menyimpan data user yang nanti akan kita deteksi ketika user keluar browser
karena fitur remember me menyimpan data user dengan cookie maka untuk mengamankannya agar tidak dihijack (membuat cookie tiruan untuk masuk) maka kita harus mengencrypt data cookienya agar tidak mudah dibaca dan dipecahkan
Langkah 1 (Membuat Helper Encrypt & Decyrpt)
buat file helper dengan nama aeshash_helper.php didalam folder 'app/Helpers/'
tambahkan kode dibawah ini pada file aeshash_helper.php terletak di 'app/Helpers/aeshash_helper.php'
<?php
/**
* https://gist.github.com/joashp/a1ae9cb30fa533f4ad94
*/
function aeshash($action, $string, $aeskey) {
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = $aeskey;
$secret_iv = $aeskey;
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
if ( $action == 'enc' ) {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
} else if( $action == 'dec' ) {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
Langkah 2 (Mengatur Key Encription)
edit file pengaturan encryption terletak di 'app/Config/Encryption.php'
kemudian isi property $key dengan kode rahasia kalian
misalnya seperti
public $key = 'STRONG_PASSWORD_!@#!!((##_GREAT___+!#_';
Mendeteksi User Kehilangan session auth dan masih memiliki cookie auth
saya akan menggunakan fitur filter untuk mendeteksinya, mengingat filter ini bisa diakses secara global maka setiap mengakses url maka akan melalui filter ini terlebih dahulu
buat file filter menggunakan spark dengan mengetikan perintah
php spark make:filter AuthCookie
edit kode filter AuthCookie yang terletka di 'app/Filters/AuthCookie.php' dengan kode dibawah ini
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class AuthCookie implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
if (!session()->auth AND isset($_COOKIE['auth'])) {
// load helper
helper('aeshash');
// decode cookie
$hash = aeshash('dec', $_COOKIE['auth'] , config('Encryption')->key);
// validate hash
if (!$hash) {
// hash invalid > remove cookie
setcookie('auth', null, -1, '/');
}else{
$read = json_decode($hash,true);
session()->set('auth', $read);
}
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
//
}
}
dilanjut dengan mengaktifkan filternya dengan mengaturnya didalam file 'app/Config/Filters.php'
tambahkan kode dibawah ini pada propery $aliases
'authcookie' => \App\Filters\AuthCookie::class,
sehingga property $aliases
menjadi seperti
public $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'auth' => \App\Filters\Auth::class,
'authcookie' => \App\Filters\AuthCookie::class,
];
kemudian untuk mengaktifkannya filternya kita tambahkan aliases dari filter tersebut pada property $globals
tambahkan filternya dinilai before agar filter diekseskusi sebelum controller
nantinya property $globals
akan menjadi seperti ini
public $globals = [
'before' => [
// 'honeypot',
// 'csrf',
// 'invalidchars',
'authcookie'
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
];
Uji Coba Aplikasi
jalankan web server dengan spark
php spark serve
buka url http://localhost:8080 dibrowser kalian
dan berikut hasilnya