Membangun autentikasi dan otorisasi di Yii: login dan signup dengan password hashing yang benar lewat Security component, komponen yii\web\User dengan IdentityInterface, access control filter per action, serta RBAC berbasis DbManager untuk memetakan role, permission, dan assignment pengguna.

Aplikasi yang tidak bisa membedakan siapa penggunanya tidak bisa membedakan hak aksesnya. Di episode 9 ini kita membangun dua lapisan keamanan yang saling terkait: autentikasi (membuktikan siapa kalian) dan otorisasi (apa yang boleh kalian lakukan).
Yii menyediakan keduanya sebagai komponen built-in: yii\web\User untuk menangani login/session, dan RBAC (Role-Based Access Control) untuk memetakan hak akses. Di episode ini kita implementasikan login, signup, hashing password yang benar, lalu membangun sistem role admin + permission yang bisa langsung dipakai di aplikasi enterprise.
Yii tidak mengunci model user tertentu — ia hanya butuh class yang mengimplementasikan yii\web\IdentityInterface. Inilah kontrak yang menghubungkan yii\web\User dengan data kalian (bisa tabel users, API eksternal, atau LDAP):
<?php
namespace app\models;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
public static function tableName(): string
{
return 'users';
}
public static function findIdentity($id): ?User
{
return static::findOne($id);
}
public static function findIdentityByAccessToken($token, $type = null): ?User
{
return static::findOne(['access_token' => $token]);
}
public function getId(): int
{
return $this->id;
}
public function getAuthKey(): ?string
{
return $this->auth_key;
}
public function validateAuthKey($authKey): bool
{
return $this->auth_key === $authKey;
}
}Empat method ini adalah kontraknya: findIdentity (muat user dari session), findIdentityByAccessToken (dipakai API di episode 10), getAuthKey/validateAuthKey (validasi "remember me" via cookie). Kolom auth_key di tabel users menyimpan string acak — bukan password.
Jangan pernah menyimpan password mentah atau hash dengan MD5/SHA1. Yii menyediakan Security component yang membungkus password_hash() dengan algoritma bcrypt (secara default):
use Yii;
// saat signup atau ganti password
$user->password_hash = Yii::$app->security->generatePasswordHash($password);
// saat login
if (!Yii::$app->security->validatePassword($password, $user->password_hash)) {
// password salah
}generatePasswordHash menghasilkan string yang berisi salt + cost + hash sekaligus, jadi tidak perlu menyimpan salt terpisah. Validasi password selalu lewat fungsi ini agar tahan timing attack.
Template basic sudah menyediakan LoginForm — mari kita periksa logikanya:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
class LoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;
private ?User $_user = null;
public function rules(): array
{
return [
[['username', 'password'], 'required'],
['rememberMe', 'boolean'],
['password', 'validatePassword'],
];
}
public function validatePassword($attribute, $params): void
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Username atau password salah.');
}
}
}
public function login(): bool
{
if (!$this->validate()) {
return false;
}
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
}
private function getUser(): ?User
{
if ($this->_user === null) {
$this->_user = User::findByUsername($this->username);
}
return $this->_user;
}
}Yii::$app->user->login() menulis identitas ke session (dan cookie remember me jika durasi diisi). Di controller:
public function actionLogin()
{
if (!Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
}
$model->password = '';
return $this->render('login', ['model' => $model]);
}Pola signup sama: SignupForm mengumpulkan input, menghasilkan User baru, dan langsung login:
public function signup(): ?User
{
if (!$this->validate()) {
return null;
}
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password); // generatePasswordHash
$user->generateAuthKey(); // string acak untuk remember me
$user->status = User::STATUS_ACTIVE;
return $user->save() ? $user : null;
}Lapisan pertama otorisasi: yii\filters\AccessControl di controller membatasi akses per action berdasarkan role:
use yii\filters\AccessControl;
public function behaviors(): array
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'actions' => ['index', 'view'],
'roles' => ['?'], // guest (belum login)
],
[
'allow' => true,
'actions' => ['create', 'update', 'delete'],
'roles' => ['@'], // sudah login
],
[
'allow' => true,
'actions' => ['delete'],
'roles' => ['admin'],
],
[
'allow' => false,
],
],
],
];
}Makna simbol role: ? = guest, @ = semua user yang login, nama role lain (misal admin) = role RBAC. Aturan dievaluasi dari atas ke bawah — aturan pertama yang cocok menentukan hasil.
AccessControl saja cukup untuk app kecil, tapi untuk aplikasi enterprise kalian butuh RBAC: pemetaan role → permission → assignment user yang disimpan di database. Yii menyediakan yii\rbac\DbManager.
php yii migrate/up --migrationPath=@yii/rbac/migrationsPerintah ini membuat 4 tabel: auth_assignment, auth_item, auth_item_child, auth_rule.
'components' => [
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
],Jangan lupa mendaftarkan juga di config/console.php — konsol butuh authManager saat kita menulis perintah untuk seeding role.
RBAC memisahkan role (misal admin, editor) dan permission (misal createPost, deletePost). Cara terbaik membuatnya adalah lewat console command yang idempotent — bisa dijalankan berulang:
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit(): int
{
$auth = Yii::$app->authManager;
// permissions
$createPost = $auth->createPermission('createPost');
$createPost->description = 'Membuat post baru';
$auth->add($createPost);
$updatePost = $auth->createPermission('updatePost');
$updatePost->description = 'Mengubah post';
$auth->add($updatePost);
$deletePost = $auth->createPermission('deletePost');
$deletePost->description = 'Menghapus post';
$auth->add($deletePost);
// roles
$editor = $auth->createRole('editor');
$auth->add($editor);
$auth->addChild($editor, $createPost);
$auth->addChild($editor, $updatePost);
$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $editor);
$auth->addChild($admin, $deletePost);
$this->stdout("RBAC selesai diinisialisasi\n");
return self::EXIT_CODE_NORMAL;
}
public function actionAssign(string $username, string $role): int
{
$auth = Yii::$app->authManager;
$user = \app\models\User::findByUsername($username);
if (!$user) {
$this->stderr("User tidak ditemukan: $username\n");
return self::EXIT_CODE_ERROR;
}
$auth->assign($auth->getRole($role), $user->id);
$this->stdout("Role '$role' ditugaskan ke $username\n");
return self::EXIT_CODE_NORMAL;
}
}Perhatikan struktur hierarki: admin adalah anak dari editor (lewat addChild), jadi admin mewarisi semua permission editor ditambah deletePost. Hierarki inilah yang membuat RBAC skala besar tetap mudah dikelola.
php yii rbac/init
php yii rbac/assign admin admin-roleDi controller atau view, cek hak akses dengan can():
use Yii;
if (!Yii::$app->user->can('deletePost')) {
throw new ForbiddenHttpException('Tidak punya akses.');
}
// di view: tampilkan tombol hanya jika berhak
<?php if (Yii::$app->user->can('updatePost')): ?>
<a href="<?= Url::to(['post/update', 'id' => $model->id]) ?>">Edit</a>
<?php endif; ?>Dan di AccessControl, nama role/permission bisa langsung dipakai:
[
'allow' => true,
'actions' => ['delete'],
'roles' => ['deletePost'], // permission, bukan role
],Important
Bedakan kedua istilah: permission = kemampuan atomik ("dapat menghapus post"), role = kumpulan permission + hierarki ("editor", "admin"). Berikan role kepada user; berikan permission kepada role. Jangan menugaskan permission langsung ke user — hierarki akan kacau saat tim membesar.
Sekarang satukan semuanya: tabel user sudah ada, RBAC aktif, role sudah di-seed. Verifikasi alurnya:
php yii migrate/up --migrationPath=@yii/rbac/migrations --interactive=0
php yii rbac/init
php yii rbac/assign budi adminLalu di PostController, tambahkan aturan RBAC agar hanya admin yang bisa menghapus. Login sebagai user tanpa role — tombol delete tidak muncul dan akses langsung ditolak 403. Login sebagai budi — semuanya berfungsi.
Inti yang harus dibawa pulang:
IdentityInterface adalah kontrak antara yii\web\User dan data user kalian.Security::generatePasswordHash (bcrypt) — jangan pernah mentah.AccessControl memfilter akses per action dengan role ?, @, atau nama role RBAC.DbManager: permission atomik, role hierarkis, assignment via konsol.Yii::$app->user->can('permission') dipakai untuk cek akses di kode dan view.Di episode 10 selanjutnya, kita membangun sisi lain aplikasi: REST API & Behaviors — yii\rest\ActiveController yang menghasilkan endpoint CRUD JSON secara otomatis, serializers, autentikasi token, rate limiting, serta behaviors seperti timestamp, soft delete, dan blameable yang menempel ke siklus hidup model. Sampai jumpa di episode 10!