今回は、CodeIgniterでログイン機能の実装を解説していきたいと思います。
0.概要
本記事は下記の構成になっております。
1.事前準備
CodeIgniterのインストールや実行環境の整備をします。
2.ユーザー登録機能の実装
CodeIgniter4で用意されているフォームヘルパーのバリデーション機能を使用してユーザーを新規登録する機能を実装します。
3.ログイン画面の実装
フォームヘルパーのバリデーションルールを追加し、セッションを使用してログイン機能を実装します。
4.ログアウト機能の実装
セッションデストロイ関数やリダイレクト関数を使用してログアウト機能を実装します。
5.ユーザーページの実装
ユーザー名をセッションから取り出して、ユーザーページへ反映させてユーザーページを実装します。
6.ユーザー情報を更新する
更新前に実行する関数を設定し、セッションのフラッシュデータを使用してユーザー情報を更新します。
7.ログイン認証の実装
フィルタークラスを使用してログイン認証の作成を行います。
それでは行ってみましょう。
1.事前準備
まずは、事前準備です。事前準備としてはCodeIgniterのインストールと実行環境の構築になります。
それらの手順については下記記事に記載されていますので、そちらを参考にしていただけたらと思います。
2.ユーザー登録機能を実装する
まずは、ユーザーの登録機能の実装からです。
1.ルーティングの追加
まずはルーティングの追加から行います。
$routes->match(['get', 'post'], 'register', 'Users::register');
getメソッド、postメソッドそれぞれを用いてURIにアクセする場合、上のようにmatch()を使ってルーティングを設定します。
今回は/registerへアクセスした場合、Usersコントローラーのregisterメソッドへアクセスします。
2.コントローラーの作成
続いてコントローラーの作成です。
App\Controllers\にUsers.phpを作成し、registerメソッドを作成、編集していきましょう。
public function register()
{
$data = [];
helper(['form']);
if($this->request->getMethod() == "post")
{
$rules = [
'firstname' => 'required|min_length[3]|max_length[20]',
'lastname' => 'required|min_length[3]|max_length[20]',
'email' => 'required|min_length[6]|max_length[50]|valid_email|is_unique[users.email]',
'password' => 'required|min_length[8]|max_length[255]',
'password_confirm' => 'matches[password]'
];
if(!$this->validate($rules))
{
$data['validation'] = $this->validator;
}
else
{
$model = new UsersModel();
$newData = [
'firstname' =>$this->request->getVar('firstname'),
'lastname' =>$this->request->getVar('lastname'),
'email' =>$this->request->getVar('email'),
'password' =>$this->request->getVar('password'),
];
$model->save($newData);
$session = session();
$session->setFlashdata('success', 'Successful Registration');
return redirect()->to('/');
}
}
echo view('templates/header', $data);
echo view('register');
echo view('templates/footer');
}
最初にフォームヘルパーを設定します。これによってformの補助的なメソッドを使用可能になります。
フォームヘルパーの参考サイトは下に貼っておくので詳しく知りたいという方はそちらをご覧ください。
次のif文では、リクエストメソッドがPostであるかを判定し、もしPostメソッドであった場合はバリデーションルールを設定、Postメソッドでない場合は登録フォームを表示します。
次はPostデータに対して実際にバリデーションをかけ、falseが返ってきた場合エラーメッセージを$data[‘validation’]に格納、trueが返ってきた場合はユーザー情報をUsersModelを用いて登録します。
登録が完了したら、セッションへ登録成功のメッセージを格納し、ホーム画面へ遷移します。
3.モデルの作成
次にApp\ModelsにUsersModel.phpを作成します。
<?php
namespace App\Models;
use CodeIgniter\Model;
class UsersModel extends Model
{
protected $table = "users";
protected $allowedFields = ['firstname', 'lastname', 'email', 'password', 'updated_at'];
protected $beforeInsert = ['beforeInsert'];
protected $beforeUpdate = ['beforeUpdate'];
protected function beforeInsert(array $data)
{
$data = $this->passwordHash($data);
return $data;
}
protected function beforeUpdate(array $data)
{
$data = $this->passwordHash($data);
return $data;
}
protected function passwordHash(array $data)
{
if(isset($data['data']['password']))
$data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
return $data;
}
}
最初の2行ではModelの型を呼び出したり名前空間を作成したりしています。
クラスを宣言した後の初めの2行ではそれぞれ使うテーブル、またデータの挿入先のカラムを指定します。
次2行ですが、それぞれデータの挿入前に実行されるbeforeInsert、データの更新前に実行されるbeforeUpdateを宣言し、それ以降それぞれのメソッドの定義を行っています。今回はそれぞれのメソッド内でパスワードのハッシュ化を行っています。
4.ビューの作成
最後にViewを作成します。App\Views\にregister.phpを作成します。
<div class="container">
<div class="row">
<div class="col-12 col-sm8- offset-sm-2 col-md-6 offset-md-3 mt-5 pt-3 pb-3 bg-white from-wrapper">
<div class="container">
<h3>Register</h3>
<hr>
<form class="" action="/register" method="post">
<div class="row">
<div class="col-12 col-sm-6">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" name="firstname" id="firstname" value="<?=set_value('firstname') ?>">
</div>
</div>
<div class="col-12 col-sm-6">
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" name="lastname" id="lastname" value="<?=set_value('lastname') ?>">
</div>
</div>
<div class="col-12">
<div class="form-group">
<label for="email">Email address</label>
<input type="email" class="form-control" name="email" id="email" value="<?=set_value('email') ?>">
</div>
</div>
<div class="col-12 col-sm-6">
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" id="password" value="">
</div>
</div>
<div class="col-12 col-sm-6">
<div class="form-group">
<label for="password_confirm">Confirm Password</label>
<input type="password" class="form-control" name="password_confirm" id="password_confirm" value="">
</div>
</div>
<?php if(isset($validation)): ?>
<div class="col-12">
<div class="alert alert-danger" role="alert">
<?= $validation->listErrors() ?>
</div>
</div>
<?php endif ?>
</div>
<div class="row">
<div class="col-12 col-sm-4">
<button type="submit" class="btn btn-primary">Register</button>
</div>
<div class="col-12 col-sm-8 text-right">
<a href="/">Already have an acount?</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
今回は名前、苗字、メールアドレス、パスワード、確認用パスワードを入力します。
そして、もし$validationにエラーメッセージが格納されていた場合、表示するようにします。登録完了のメッセージはログイン画面にて表示するため今回はその部分のコードは書きません。
これで登録機能の実装は完了です。
3.ログイン画面の実装
続いてログイン画面の実装です。
1.ルーティングの追加
まずは、ルーティングを追加します。
$routes->get('/', 'Users::index');
今回はログイン画面がホーム画面になるので、’/’でUsersコントローラーのIndexメソッドを呼び出します。
2.コントローラーの作成
次にApp\Controllers\にコントローラーのUsers.phpを作成します。
public function index()
{
$data = [];
helper(['form']);
if($this->request->getMethod() == "post")
{
$rules = [
'email' => 'required|min_length[6]|max_length[50]|valid_email',
'password' => 'required|min_length[8]|max_length[255]1|validateUser[email, password]',
];
$errors = [
'password' => [
'validateUser' => 'Email or Password don\'t match. '
]
];
if(!$this->validate($rules, $errors))
{
$data['validation'] = $this->validator;
}
else
{
$model = new UsersModel();
$user = $model->where('email', $this->request->getVar('email'))
->first();
$this->setUserSession($user);
return redirect()->to('dashboard');
}
}
echo view('templates/header', $data);
echo view('login');
echo view('templates/footer');
}
private function setUserSession($user)
{
$data = [
'id' => $user['id'],
'firstname' => $user['firstname'],
'lastname' => $user['lastname'],
'email' => $user['email'],
'isLoggedIn' => true
];
session()->set($data);
return true;
}
まずはフォームヘルパーを呼び出します。これにより、formタグに対してCodeIgniterが用意している様々な関数が使えるようになります。
次に、もしPostメソッドを用いてアクセスしている場合はPost内容に対してバリデーションのルールとエラーメッセージを設定しバリデーションを行います。
もし、エラーの場合はエラーメッセージの表示、そうでない場合は、ユーザー情報をUsersModelから取得し、setUserSession()でセッション内にセットします。
このページにPostメソッド以外でアクセスしている場合はViewを表示します。
ここで出てくる疑問がどこでパスワード認証をしているのかという点だと思います。
それは、バリデーションルール内にある’validateUser[email, password]’がこれを担っています。つぎはこのvalidateUser()というメソッドを作成していきましょう。
3.validateメソッドの追加
\App\にValidationというフォルダーを作り、Validationフォルダ内にUsersRule.phpというファイルを作ります。
<?php
namespace App\Validation;
use App\Models\UsersModel;
class UserRules
{
public function validateUser(string $str, string $fields, array $data)
{
$model = new UsersModel();
$user = $model->where('email', $data['email'])
->first();
if(!$user){
return false;
}
else
{
return password_verify($data['password'], $user['password']);
}
}
}
ここではPostされた内容を元にユーザーが存在するか、ユーザーのパスワードと入力したパスワードが一致しているかを認証します。
しかしながら、このメソッドを作っただけでは、機能してくれません。
App\Config\Validation.phpの編集を行います。
public $ruleSets = [
Rules::class,
FormatRules::class,
FileRules::class,
CreditCardRules::class,
\App\Validation\UserRules::class //この一文を追加
];
これでコントローラーのバリデーション時にユーザー認証のルールが加わりました。
4.ビューの編集
今回は特にモデルで特別なところはないので、割愛してViewを見ていきます。
<div class="container">
<div class="row">
<div class="col-12 col-sm-8 offset-sm-2 col-md-6 offset-md-3 mt-5 pt-3 pb-3 bg-white from-wrapper">
<div class="container">
<h3>Login</h3>
<hr>
<?php if(session()->get('success')): ?>
<div class="alert alert-success" role ="alert">
<?= session()->get('success'); ?>
</div>
<?php endif; ?>
<form class="" action="/" method="post">
<div class="form-group">
<label for="email">Email address</label>
<input type="email" class="form-control" name="email" id="email" value="<?=set_value('email') ?>">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" id="password" value="">
</div>
<?php if(isset($validation)): ?>
<div class="col-12">
<div class="alert alert-danger" role="alert">
<?= $validation->listErrors() ?>
</div>
</div>
<?php endif ?>
<div class="row">
<div class="col-12 col-sm-4">
<button type="submit" class="btn btn-primary">Login</button>
</div>
<div class="col-12 col-sm-8 text-right">
<a href="/register">Don't have an account yet?</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
今回のビューでは、emailとpasswordだけをPostできるようにしてあります。
もし、セッション内に登録成功メッセージがある場合もここで表示します。
これで、ログイン画面の完成です。
4.ログアウト機能を作成する
続いてログアウト機能を実装します。
1.ルーティングの追加
まずはルーティング設定の追加を行います。
$routes->get('logout', 'Users::logout');
今回は’logout’にアクセスした時、Usersコントローラーのlogoutメソッドにアクセスします。
2.コントローラーの設定
次にUsersコントローラーのlogoutメソッドを実装します。
public function logout()
{
session()->destroy();
return redirect()->to('/');
}
引数などはなく、セッションにあるユーザー情報を初期化し、ホーム画面へと遷移します。
これでログアウト機能の実装は完了です。
ログアウト機能では、データベースの利用や用意したビューはないので、これで完成となります。
5.ユーザーページの作成
続いて、ユーザーページを作成していきます。
1.ルーティングの設定
まずはルーティングを設定します。
$routes->get('dashboard', 'Dashboard::index');
今回は’dashboard’にアクセスした時Dashboardコントローラーのindexメソッドを呼び出します。
2.Dashboardコントローラーindexメソッドの作成
次にDashboardコントローラーのindexメソッドを作成します。
<?php
namespace App\Controllers;
class Dashboard extends BaseController
{
public function index()
{
$data = [];
echo view('templates/header', $data);
echo view('dashboard');
echo view('templates/footer');
}
}
今回はユーザー情報を表示するだけなので、特別な処理をここでは行いません。
また、今回はセッション内のデータを用いてユーザー情報を表示するため、モデルも使用しません。
次にViewを作成します。
3.Viewの作成
今回はヘッダーやフッターのテンプレートは割愛し、dashboard.phpだけ作成していきます。
<div class="container">
<div class="row">
<div class="col-12">
<h1>Hello, <?=session()->get('firstname') ?></h1>
</div>
</div>
</div>
ここでは、セッションの’firstname’に格納されたデータを取得し、表示します。
これでユーザーページの実装は完了です。
6.ユーザー情報を更新する
続いてユーザ情報の更新機能を実装します。
1.ルーティングの設定の追加
まずはルーティングの設定の追加から行います。
$routes->match(['get', 'post'], 'profile', 'Users::profile');
今回は’profile’にget、postどちらかでアクセスした場合Usersコントローラーのprofileメソッドを呼び出します。
2.コントローラーの設定
次にコントローラーの作成です。
App\Controllers\にあるコントローラーであるUsersにprofileメソッドを追加します。
public function profile(){
$data = [];
helper(['form']);
$model = new UsersModel();
if($this->request->getMethod() == "post")
{
$rules = [
'firstname' => 'required|min_length[3]|max_length[20]',
'lastname' => 'required|min_length[3]|max_length[20]',
'password' => 'required|min_length[8]|max_length[255]',
'password_confirm' => 'matches[password]'
];
if(!$this->validate($rules))
{
$data['validation'] = $this->validator;
}
else
{
$newData = [
'id' => session()->get('id'),
'firstname' =>$this->request->getVar('firstname'),
'lastname' =>$this->request->getVar('lastname'),
];
if($this->request->getPost('password') != '')
{
$newData['password'] = $this->request->getPost('password');
}
$model->save($newData);
session()->setFlashdata('success', 'Successfully Updated');
return redirect()->to('/profile');
}
}
$data['user'] = $model->where('id', session()->get('id'))->first();
echo view('templates/header', $data);
echo view('profile');
echo view('templates/footer');
}
まずはフォームヘルパーを設定した後、UsersModelを宣言します。
次にメソッドの判定です。
もしPostメソッドだった場合はバリデーション、Getメソッドである場合は、セッションのデータを元に、データベースからデータを取得し、profile.phpを表示します。
バリデーションについてはバリデーションルールを設定した後、バリデーションの結果に対して条件分岐をします。もし、バリデーションエラーがあった場合は、エラーメッセージの格納、バリデーションに問題がない場合は、更新内容を$newDataに格納。パスワードに関しては変更があった場合のみ$newDataに格納します。
その後データベースに保存し、変更成功の文章をセッションに格納した後、再度’profile’にアクセスします。
3.モデルの設定
次にモデルであるApp\Models\UsersModel.phpを編集します。
protected $beforeUpdate = ['beforeUpdate'];
//・・・
protected function beforeUpdate(array $data)
{
$data = $this->passwordHash($data);
return $data;
}
//・・・
protected function passwordHash(array $data)
{
if(isset($data['data']['password']))
$data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
return $data;
}
今回もパスワードをデータベースに格納する前にハッシュ化したいのでデータベースの更新前に呼び出す関数beforeUpdateにてパスワードをハッシュ化します。
4.ビューの作成
最後にビューを作成します。
<h3><?=$user['firstname'].' '.$user['lastname'] ?></h3>
<?php if(session()->get('success')): ?>
<?= session()->get('success'); ?>
<?php endif; ?>
<form class="" action="/profile" method="post">
<label for="firstname">First Name</label>
<input type="text" class="form-control" name="firstname" id="firstname" value="<?=set_value('firstname', $user['firstname']) ?>">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" name="lastname" id="lastname" value="<?=set_value('lastname', $user['lastname']) ?>">
<label for="email">Email address</label>
<input type="email" class="form-control" readonly name="email" id="email" value="<?=set_value('email', $user['email']) ?>">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" id="password" value="">
<label for="password_confirm">Confirm Password</label>
<input type="password" class="form-control" name="password_confirm" id="password_confirm" value="">
<?php if(isset($validation)): ?>
<?= $validation->listErrors() ?>
<?php endif ?>
<button type="submit" class="btn btn-primary">Update</button>
</form>
今回はメールアドレスの変更はできないようにしてあります。
また、変更成功のメッセージがあった場合、もしくはバリデーションエラーがあった場合は表示できるようにしてあります。
そのほかはフォームを用意して、データベースから取得したユーザー情報をデフォルトでセットしておきます。
これでユーザー情報の更新の実装は完了です。
7.ログイン認証の実装
最後にログイン状態か否かの認証機能の実装を行います。
1.Routes.phpの設定
まずはルーティングから実装します。
$routes->get('/', 'Users::index', ['filter' => 'noauth']);
$routes->get('logout', 'Users::logout');
$routes->match(['get', 'post'], 'register', 'Users::register', ['filter' => 'noauth']);
$routes->match(['get', 'post'], 'profile', 'Users::profile', ['filter' => 'auth']);
$routes->get('dashboard', 'Dashboard::index', ['filter' => 'auth']);
それぞれのページに対して、フィルターをかけます。
ログイン状態でないとアクセスできないページには[‘filter’ => ‘auth’]を追加し、ログイン状態ではアクセスできないページに対しては[‘filter’ => ‘noauth’]を追加します。
ユーザー情報を更新するための’profile’やユーザーページの’dashboard’には、ログインしていないとアクセスできないようにしなければいけませんし、反対にログインフォームである’/’、登録画面である’register’はログインしていない状態でアクセスしなければいけません。
なのでそれぞれのルーティングに先程の[‘filter’ => ‘auth’]、[‘filter’ => ‘noauth’]を加えます。
2.Filters.phpの設定
上記のフィルターをルーティングに追加した、次はそれぞれのフィルターを定義しなければいけません。
まずは、app\config\filters.phpの$aliasesに’auth’と’noauth’のクラスを追加します。
public $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'auth' => \App\Filters\Auth::class,
'noauth' => \App\Filters\Noauth::class,
];
これによりルーティングで設定したフィルターでよってそれぞれのフィルタークラスを呼び出すことができます。
3.フィルタークラスの作成。
次にapp\Filters内にAuth.php、およびNoauth.phpを作成し、それぞれ実装をしていきます。
<?php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class Auth implements FilterInterface
{
public function before(RequestInterface $request, $arguments=null)
{
if(!session()->get('isLoggedIn'))
{
return redirect()->to('/');
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = NULL)
{
}
}
<?php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class Noauth implements FilterInterface
{
public function before(RequestInterface $request, $arguments=null)
{
if(session()->get('isLoggedIn'))
{
return redirect()->to('/dashboard');
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = NULL)
{
}
}
それぞれセッション内にログイン情報があるなしを判定し、それぞれ所定のページへと遷移します。
4.グローバルフィルターを設定
これでルーティングを用いた認証は完了です。しかし、CodeIgniterの仕様上、ルートを用いなくても、クラス名メソッドを記述するとそのページへとアクセスすることができます。
なので例えばUsers/indexとURIに書き込むとルーティングなしで、ログインフォームへとアクセスすることが可能でそうなるとルーティングで実装したフィルターを通らなくなってしまい、ログイン認証が意味をなさなくなってしまいます。
そのためにリクエストするたびに実行されるグローバルフィルターを実装し、必ずルーティングで設定したフィルターを通すようにします。
まず、App\Config\Filters.phpの$aliasesを以下の記述を追加します。
'userscheck' => \App\Filters\UsersCheck::class,
続いて、その下に用意されている$globalを以下のように変更します。
public $globals = [
'before' => [
'userscheck'
// 'honeypot',
// 'csrf',
],
'after' => [
'toolbar',
// 'honeypot',
],
];
これで全てのリクエストにおいてUsersCheckクラスを呼び出します。
それでは、app\Filters\UsersCheck.phpの実装をしていきます。
<?php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class UsersCheck implements FilterInterface
{
//added $arguments = null
public function before(RequestInterface $request, $arguments=null)
{
$uri = service('uri');
if($uri->getSegment(1) === 'users')
{
if($uri->getSegment(2) == '')
{
$segment = '/';
}else{
$segment = $uri->getSegment(2);
}
return redirect()->to($segment);
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = NULL)
{
}
}
ここでは、Usersクラスに対してルーティングを経由せずアクセスした場合、redirectによってルーティングを介してそれぞれのページにアクセスするようにします。これによって全てのアクセスにおいてユーザー認証を行うことができます。
これでログイン機能の実装が完了します。
最後まで読んでくださった皆様ありがとうございました。