Prije ovoga bacite oko na ovu objavu.

Kod je na github.

Cijeli postupak ide ovako:

  • registracija korisnika
    • korisnik pošalje email i šifru
    • ako email ne postoji napravi novi korisnički račun
  • prijava korisnika
    • korisnik pošalje email i šifru
    • ako email postoji – prijavi korisnika

Kod registracije unosimo email, šifru i user id (UUID) u bazu podataka.

Kad se korisnik prijavi šaljemo mu (http-only) browser-only cookie. Znači cookie do kojeg se ne može doći sa javascriptom u internet pregledniku. U sesiji (session) dodajmo ključ [“user-logged-on” => ‘f750204d-e73d-43e7-a5c2-cff967d233c6’].

Na ponovnoj posjeti, provjeravamo (preko cookie-ja) “user-logged-on” iz sesije.

Na odjavi, brišemo ključ sesije.

U phpMyAdmin (ili preko terminala) pravimo tablicu:

DROP DATABASE IF EXISTS ci_login_app;
CREATE DATABASE ci_login_app;
DROP USER IF EXISTS 'ci_login_app'@'localhost';
CREATE USER 'ci_login_app'@'localhost' IDENTIFIED BY 'ci_login_app';
GRANT ALL ON ci_login_app.* TO 'ci_login_app'@'localhost';
USE ci_login_app;

CREATE TABLE users (
    id int(11) NOT NULL AUTO_INCREMENT,
    email varchar(320) DEFAULT NULL,
    password varchar(256) DEFAULT NULL,
    uuid varchar(36) DEFAULT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Instaliramo CI4 i instaliramo libse:

composer create-project codeigniter4/appstarter ci_login_app
cd ./ci_login_app
composer require ramsey/uuid
composer require respect/validation

Kopiramo env u .env

cp ./env ./.env

…i mjenjamo pod “Database” i “Enviroment”:

#--------------------------------------------------------------------
# ENVIRONMENT
#--------------------------------------------------------------------

CI_ENVIRONMENT = development

#...

#--------------------------------------------------------------------
# DATABASE
#--------------------------------------------------------------------

 database.default.hostname = localhost
 database.default.database = ci_login_app
 database.default.username = ci_login_app
 database.default.password = ci_login_app
 database.default.DBDriver = MySQLi
 # database.default.DBPrefix =

Otvaramo dodatni tab u terminalu i startamo dev server:

php spark serve

Server je na:

http://localhost:8080

Pravimo “Users” controller i model:

php spark make:model Users --suffix
php spark make:controller Users --suffix 

Zatim pravimo view predloške:

touch ./app/Views/dashboard.php
touch ./app/Views/login.php
touch ./app/Views/logout.php
touch ./app/Views/register.php

Unosimo rute (putanje) “./app/Config/Routes.php”:

//ispod: $routes->get('/', 'Home::index');
$routes->get('/login', 'UsersController::login');
$routes->post('/login-post', 'UsersController::loginPost');
$routes->get('/register', 'UsersController::register');
$routes->post('/register-post', 'UsersController::registerPost');
$routes->get('/dashboard', 'UsersController::dashboard');
$routes->get('/logout', 'UsersController::logout');

U naš “./app/Models/UsersModel.php” unosimo promjene:

<?php

namespace App\Models;

use CodeIgniter\Model;

class UsersModel 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    = ['password','email','uuid'];

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



	// Validation
	protected $validationRules      = [
	  "password" => "required|min_length[8]|max_length[256]",
	  "email" => "required|valid_email|min_length[5]|is_unique[users.email]",
	];
	protected $validationMessages   = [
	  "password" => [
		"required" => "Password is required",
		"min_length" => "Minimum length of password should be 8 chars",
		"max_length" => "Maximum length of password should be 256 chars",
	  ],
	  "email" => [
		"required" => "Email needed",
		"valid_email" => "Please provide a valid email address"
	  ],
	];
    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    = [];
}

U predloške unosimo: “./app/Views/login.php”

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Login</title>
	<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
  </head>
  <body>
	  

<div class="w3-panel w3-blue-grey w3-animate-top info-text-conatiner" style="margin:0; display:none;">
  <h3>Information!</h3>
  <p class="info-text"></p>
</div> 
		
<div class="w3-card-4">

<header class="w3-container w3-blue">
  <h1>Login</h1>
</header>

<div class="w3-container">

		<br>
	  	<form class="w3-container">

		<input class="w3-input email-input" type="text">
		<label>Email</label>

		<input class="w3-input password-input" type="text">
		<label>Password</label>

		</form> 

		<br>
		<button class="w3-button w3-blue" onclick="login()"> Submit </button>
		<br>
		<br>
</div> 		
		
</div> 
		
				
	
</body>
</html>


<script>

const login = async () => { 

	const email = document.querySelector(".email-input");
	const password = document.querySelector(".password-input");
	const messageContainer = document.querySelector(".info-text-conatiner");
	const messageContainerText = document.querySelector(".info-text");
		
		
	if (email.value < 4)	
	{
		console.log( "bad email" );
		return null;
	}
		
	if (password.value < 4)	
	{
		console.log( "bad password" );
		return null;
	}
				
	const postData = new FormData();
	postData.append("login-form-email", email.value);
	postData.append("login-form-password", password.value);

    try {
		const response = await fetch('/login-post', {
			method: 'POST', 
			body: postData,
		});

		const jsonUserData = await response.json();
		
		messageContainer.style.display = 'block';
		messageContainerText.innerHTML = jsonUserData.message;
		
		if (!jsonUserData.error)
		{
			window.location = '/dashboard';
		}

		//console.log( jsonUserData.name );
	}
	catch (error) {
		console.error(error);
	}
		
}

		
</script>

.app/Views/logout.php

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Logged off!</title>
	<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
  </head>
  <body>
	  

		
<div class="w3-card-4">

<header class="w3-container w3-blue">
  <h1>Logged off!</h1>
</header>
<div class="w3-margin w3-padding">
	<p>User logged off.</p>
  <div class="w3-button w3-blue" onclick="window.location='/login'"> Go to login page. </div>
  <br><br>
</div>
		

</div> 
	
  </body>
</html>

./app/Views/register.php

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Register</title>
	<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
  </head>
  <body>
	  

<div class="w3-panel w3-blue-grey w3-animate-top info-text-conatiner" style="margin:0; display:none;">
  <h3>Information!</h3>
  <p class="info-text"></p>
</div> 
		
<div class="w3-card-4">

<header class="w3-container w3-blue">
  <h1>Register</h1>
</header>

<div class="w3-container">

		<br>
	  	<form class="w3-container">

		<input class="w3-input email-input" type="text">
		<label>Email</label>

		<input class="w3-input password-input" type="text">
		<label>Password</label>

		</form> 

		<br>
		<button class="w3-button w3-blue" onclick="register()"> Submit </button>
		<br>
		<br>
</div> 		
		
</div> 
		
				
	
</body>
</html>


<script>

const register = async () => { 

	const email = document.querySelector(".email-input");
	const password = document.querySelector(".password-input");
	const messageContainer = document.querySelector(".info-text-conatiner");
	const messageContainerText = document.querySelector(".info-text");
		
		
	if (email.value < 4)	
	{
		console.log( "bad email" );
		return null;
	}
		
	if (password.value < 4)	
	{
		console.log( "bad password" );
		return null;
	}
				
	const postData = new FormData();
	postData.append("register-form-email", email.value);
	postData.append("register-form-password", password.value);

    try {
		const response = await fetch('/register-post', {
			method: 'POST', 
			body: postData,
		});

		const jsonUserData = await response.json();
		
		messageContainer.style.display = 'block';
		messageContainerText.innerHTML = jsonUserData.message;
		
		console.log( jsonUserData.name );
	}
	catch (error) {
		console.error(error);
	}
		
}



		
</script>

./app/Views/dashboard.php

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Dashboard</title>
	<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
  </head>
  <body>
	  		
<div class="w3-card-4">

<header class="w3-container w3-blue">
  <h1>Dashboard</h1>
</header>
<div class="w3-margin w3-padding">
	<p>Only logged user can see this.</p>
  <div class="w3-button w3-blue" onclick="window.location='/logout'"> Logout </div>
  <br><br>
</div>
		
</div> 
	
	
  </body>
</html>

I na kraju, unosimo u “./app/Controllers/UsersController.php”:

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\UsersModel;
use Ramsey\Uuid\Uuid;
use Respect\Validation\Validator as v;


class UsersController extends BaseController
{
    public function index()
    {
        //
    }
	
	
	public function login()	
	{
		$session = session();
		if ($session->has('loginId'))
		{
			return redirect()->to('/dashboard');
		}

		return view("login");
	}

	public function loginPost()
	{	
		header('Content-type:application/json;charset=utf-8');

		$session = session();

		$email = $this->request->getVar("login-form-email");
        $password = $this->request->getVar("login-form-password"); 

		if (!v::email()->validate($email))
		{
			http_response_code(500);
			echo json_encode([
				'error' => true,
				'message' => 'Bad email format!',
				'data' => null,
			]);		
			die();
		}

		if (!v::alnum()->noWhitespace()->length(8, 40)->validate($password))
		{
			http_response_code(500);
			echo json_encode([
				'error' => true,
				'message' => 'Password must be alphanumeric 8-40 chars!',
				'data' => null,
			]);		
			die();
		}

		$usersModel = new UsersModel();
		$emailExists = $usersModel->getWhere(["email" => $email]);
		$emailExistsResult = $emailExists->getResult();

		if (!$emailExistsResult)
		{
			http_response_code(500);
			echo json_encode([
				'error' => true,
				'message' => 'User email not found!',
				'data' => null,
			]);		
			die();
		}


		$passHash = $emailExistsResult[0]->password;
		$uuid = $emailExistsResult[0]->uuid;
		if (!password_verify($password, $passHash))
		{
			http_response_code(500);
			echo json_encode([
				'error' => true,
				'message' => 'Bad login password!',
				'data' => null,
			]);		
			die();
		}
		else
		{
			$session->set('loginId', $uuid);

			echo json_encode([
				'error' => false,
				'message' => 'User login valid!',
				'data' => $uuid,
			]);		
			die();
		}
	
		http_response_code(500);
		echo json_encode([
			'error' => true,
			'message' => 'Fail, internal server error.',
			'data' => null,
		]);		
		die();

	}

	public function register()
	{
		$session = session();
		if ($session->has('loginId'))
		{
			return redirect()->to('/dashboard');
		}

		return view("register");
	}


	public function registerPost()
	{
		header('Content-type:application/json;charset=utf-8');


		$session = session();

		$email = $this->request->getVar("register-form-email");
        $password = $this->request->getVar("register-form-password"); 
		$uuid = Uuid::uuid4();

		if (!v::email()->validate($email))
		{
			http_response_code(500);
			echo json_encode([
				'error' => true,
				'message' => 'Bad email format!',
				'data' => null,
			]);		
			die();
		}

		if (!v::alnum()->noWhitespace()->length(8, 40)->validate($password))
		{
			http_response_code(500);
			echo json_encode([				
				'error' => true,
				'message' => 'Password must be alphanumeric 8-40 chars!',
				'data' => null,
			]);		
			die();
		}

		$passwordHash = password_hash($password, PASSWORD_BCRYPT, ['cost'=>12]);

		$usersModel = new UsersModel();
		$emailExists = $usersModel->getWhere(["email" => $email]);
		$emailExistsResult = $emailExists->getResult();



		if ($emailExistsResult)
		{
			http_response_code(500);
			echo json_encode([
				'error' => true,
				'message' => 'Email already exists!',
				'data' => null,
			]);		
			die();
		}

		$insertUserId = $usersModel->insert([
			'email' => $email,
			'password' => $passwordHash,
			'uuid' => $uuid,
		]);

		if ($insertUserId > 0)
		{
			echo json_encode([
				'error' => false,
				'message' => 'User added.',
				'data' => $uuid,
			]);		
			die();
		}

		http_response_code(500);
		echo json_encode([
			'error' => true,
			'message' => 'Fail, internal server error.',
			'data' => null,
		]);		
		die();
	}
	

	public function dashboard()
	{
		return view("dashboard");
	}
	
	
	public function logout()
	{
		$session = session();
		$session->remove('loginId');
		return view('logout');	
	}
	
	
	
}

Testiranje

Password polje sam namjerno ostavio vidljivo. Kada odemo na “/register” putanju dobijemo registracijsku formu:

I vidimo unos u bazi podataka:

Na “/login” imamo naravno login formu:

I kada smo spojeni možemo na “/dashboard”.

I na kraju da odjavimo korisnika idemo na “/logout” rutu.