Yii2 Authentication & Authorization: RBAC & AccessControl

Phase 3 gave you a task manager backed by a real database. Anyone can create, edit, and delete any task. That's a problem. In production, you need to answer two questions for every request:
- Authentication — Who are you? (login, session, identity)
- Authorization — What are you allowed to do? (roles, permissions, rules)
Yii2 has a complete built-in system for both. You don't need third-party packages or manual session wrangling. The framework gives you an IdentityInterface for authentication, AccessControl for simple permission gates, and a full RBAC (Role-Based Access Control) component with database storage for complex permission hierarchies.
By the end of this post, your task manager will have user registration, login/logout, controller-level access rules, and a complete RBAC system where admins can manage all tasks while regular users can only manage their own.
What You'll Build
✅ User registration with password hashing
✅ Login/logout with session-based authentication
✅ AccessControl behavior to protect controller actions
✅ Database-backed RBAC with roles and permissions
✅ Custom RBAC rules for "own resource" checks
✅ Admin vs regular user permission hierarchy
1. The User Component
Every Yii2 app has a user application component that manages authentication state. It's configured in config/web.php:
// config/web.php
'components' => [
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
'loginUrl' => ['site/login'],
],
],| Property | Purpose |
|---|---|
identityClass | The model class that implements IdentityInterface |
enableAutoLogin | Remember-me cookie support |
loginUrl | Where to redirect unauthenticated users |
The user component is available everywhere via Yii::$app->user. Key methods:
Yii::$app->user->isGuest; // true if not logged in
Yii::$app->user->id; // current user's ID
Yii::$app->user->identity; // current User model instance
Yii::$app->user->login($user); // start session
Yii::$app->user->logout(); // destroy session2. The IdentityInterface
Your User model must implement yii\web\IdentityInterface. This interface has five methods that Yii2 calls during authentication:
// models/User.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';
}
// --- IdentityInterface methods ---
/**
* Find user by primary key (used by session to reload identity)
*/
public static function findIdentity($id): ?self
{
return static::findOne($id);
}
/**
* Find user by access token (used for stateless/API auth)
*/
public static function findIdentityByAccessToken($token, $type = null): ?self
{
return static::findOne(['access_token' => $token]);
}
/**
* Return the primary key value
*/
public function getId(): int
{
return $this->id;
}
/**
* Return a key for cookie-based login validation
*/
public function getAuthKey(): string
{
return $this->auth_key;
}
/**
* Validate the auth key (compare stored vs provided)
*/
public function validateAuthKey($authKey): bool
{
return $this->auth_key === $authKey;
}
}The Users Migration
Create a migration for the users table if you don't already have one:
php yii migrate/create create_users_tablepublic function safeUp(): void
{
$this->createTable('users', [
'id' => $this->primaryKey(),
'username' => $this->string(50)->notNull()->unique(),
'email' => $this->string(255)->notNull()->unique(),
'password_hash' => $this->string(255)->notNull(),
'auth_key' => $this->string(32)->notNull(),
'access_token' => $this->string(255)->unique(),
'status' => $this->smallInteger()->notNull()->defaultValue(10),
'created_at' => $this->dateTime()->notNull(),
'updated_at' => $this->dateTime()->notNull(),
]);
}Run it:
php yii migrate3. Password Hashing
Never store passwords in plain text. Yii2 wraps PHP's password_hash() and password_verify() in a security helper:
use Yii;
class User extends ActiveRecord implements IdentityInterface
{
// ... IdentityInterface methods from above ...
/**
* Hash and store the password
*/
public function setPassword(string $password): void
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
/**
* Verify a password against the stored hash
*/
public function validatePassword(string $password): bool
{
return Yii::$app->security->validatePassword($password, $this->password_hash);
}
/**
* Generate a random auth key for cookie-based login
*/
public function generateAuthKey(): void
{
$this->auth_key = Yii::$app->security->generateRandomString();
}
}generatePasswordHash() uses bcrypt by default with a cost factor of 13 — strong enough for most applications. You can adjust the cost:
Yii::$app->security->generatePasswordHash($password, 12); // cost = 124. Signup
Create a form model for registration:
// models/SignupForm.php
namespace app\models;
use yii\base\Model;
class SignupForm extends Model
{
public string $username = '';
public string $email = '';
public string $password = '';
public function rules(): array
{
return [
['username', 'required'],
['username', 'string', 'min' => 3, 'max' => 50],
['username', 'unique', 'targetClass' => User::class],
['email', 'required'],
['email', 'email'],
['email', 'unique', 'targetClass' => User::class],
['password', 'required'],
['password', 'string', 'min' => 8],
];
}
public function signup(): ?User
{
if (!$this->validate()) {
return null;
}
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->created_at = date('Y-m-d H:i:s');
$user->updated_at = date('Y-m-d H:i:s');
return $user->save() ? $user : null;
}
}Signup Controller Action
// controllers/SiteController.php
public function actionSignup(): string|\yii\web\Response
{
$model = new SignupForm();
if ($model->load(Yii::$app->request->post()) && $model->signup()) {
Yii::$app->session->setFlash('success', 'Account created. Please log in.');
return $this->redirect(['site/login']);
}
return $this->render('signup', ['model' => $model]);
}Signup View
<!-- views/site/signup.php -->
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
$this->title = 'Sign Up';
?>
<h1><?= Html::encode($this->title) ?></h1>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
<?= $form->field($model, 'email') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton('Sign Up', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>5. Login
Create a login form model:
// models/LoginForm.php
namespace app\models;
use Yii;
use yii\base\Model;
class LoginForm extends Model
{
public string $username = '';
public string $password = '';
public bool $rememberMe = true;
private ?User $_user = null;
public function rules(): array
{
return [
[['username', 'password'], 'required'],
['rememberMe', 'boolean'],
['password', 'validatePassword'],
];
}
/**
* Custom validator — checks password against database
*/
public function validatePassword(string $attribute): void
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
public function login(): bool
{
if ($this->validate()) {
return Yii::$app->user->login(
$this->getUser(),
$this->rememberMe ? 3600 * 24 * 30 : 0 // 30 days
);
}
return false;
}
protected function getUser(): ?User
{
if ($this->_user === null) {
$this->_user = User::findOne(['username' => $this->username]);
}
return $this->_user;
}
}Login Controller Action
public function actionLogin(): string|\yii\web\Response
{
if (!Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack(); // redirect to page they were trying to access
}
return $this->render('login', ['model' => $model]);
}
public function actionLogout(): \yii\web\Response
{
Yii::$app->user->logout();
return $this->goHome();
}Login View
<!-- views/site/login.php -->
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
$this->title = 'Login';
?>
<h1><?= Html::encode($this->title) ?></h1>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= $form->field($model, 'rememberMe')->checkbox() ?>
<div class="form-group">
<?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>6. AccessControl Filter
AccessControl is a behavior you attach to controllers. It checks access rules before any action runs — no RBAC setup required. Think of it as a bouncer at the door.
Basic Usage
// controllers/TaskController.php
use yii\filters\AccessControl;
public function behaviors(): array
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'actions' => ['index', 'view'],
'allow' => true,
'roles' => ['?'], // guests can view
],
[
'actions' => ['create', 'update', 'delete'],
'allow' => true,
'roles' => ['@'], // only authenticated users
],
],
],
];
}Role Symbols
| Symbol | Meaning |
|---|---|
? | Guest (not logged in) |
@ | Authenticated (any logged-in user) |
admin | RBAC role named "admin" (requires RBAC setup) |
Rule Properties
Each rule in the rules array is a yii\filters\AccessRule with these properties:
| Property | Type | Purpose |
|---|---|---|
actions | array | Action IDs this rule applies to (empty = all) |
allow | bool | true to allow, false to deny |
roles | array | ?, @, or RBAC role/permission names |
ips | array | IP whitelist (e.g., ['127.0.0.1']) |
verbs | array | HTTP methods (e.g., ['POST', 'DELETE']) |
matchCallback | callable | Custom matching logic |
Advanced AccessControl
'access' => [
'class' => AccessControl::class,
'only' => ['create', 'update', 'delete'], // only check these actions
'rules' => [
[
'allow' => true,
'roles' => ['@'],
'matchCallback' => function ($rule, $action) {
// Only allow during business hours
$hour = (int) date('H');
return $hour >= 9 && $hour < 17;
},
],
],
'denyCallback' => function ($rule, $action) {
throw new \yii\web\ForbiddenHttpException('Access denied.');
},
],Combining with VerbFilter
It's common to use AccessControl together with VerbFilter for REST-style controllers:
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
public function behaviors(): array
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
['actions' => ['index', 'view'], 'allow' => true, 'roles' => ['?']],
['actions' => ['create'], 'allow' => true, 'roles' => ['@']],
['actions' => ['update', 'delete'], 'allow' => true, 'roles' => ['admin']],
],
],
'verbs' => [
'class' => VerbFilter::class,
'actions' => [
'delete' => ['POST'],
],
],
];
}7. RBAC Overview
AccessControl is fine for simple cases (guest vs logged in). But real apps need more nuance:
- "Authors can edit their own posts, but not others'"
- "Moderators can delete any comment"
- "Admins can do everything"
This is where RBAC (Role-Based Access Control) comes in. Yii2's RBAC is a tree of three node types:
| Node Type | Purpose | Example |
|---|---|---|
| Role | A named group of permissions | admin, author, moderator |
| Permission | A specific capability | createTask, updateTask, deleteTask |
| Rule | Dynamic condition checked at runtime | "Is this user the author of this task?" |
Roles contain permissions. Permissions can have rules. Users are assigned roles.
8. Setting Up RBAC with DbManager
Yii2 provides two RBAC managers:
| Manager | Storage | Best For |
|---|---|---|
PhpManager | PHP files | Simple apps, few roles |
DbManager | Database tables | Production apps, dynamic roles |
We'll use DbManager — it's the right choice for any non-trivial app.
8.1 Configure the AuthManager
// config/web.php (and config/console.php — needed for migrations!)
'components' => [
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
],Important: Add
authManagertoconfig/console.phptoo — the migration command runs in console context.
8.2 Create the RBAC Tables
Yii2 ships with a built-in migration for RBAC tables:
php yii migrate --migrationPath=@yii/rbac/migrationsThis creates four tables:
| Table | Purpose |
|---|---|
auth_rule | Custom rules (PHP classes) |
auth_item | Roles and permissions |
auth_item_child | Parent-child relationships (role → permission) |
auth_assignment | User ↔ role assignments |
8.3 Initialize Roles and Permissions
Create a console command to set up your initial RBAC hierarchy:
// commands/RbacController.php
namespace app\commands;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit(): void
{
$auth = Yii::$app->authManager;
// Remove all previous data
$auth->removeAll();
// --- Permissions ---
$createTask = $auth->createPermission('createTask');
$createTask->description = 'Create a new task';
$auth->add($createTask);
$viewTask = $auth->createPermission('viewTask');
$viewTask->description = 'View tasks';
$auth->add($viewTask);
$updateOwnTask = $auth->createPermission('updateOwnTask');
$updateOwnTask->description = 'Update own tasks';
$auth->add($updateOwnTask);
$updateAnyTask = $auth->createPermission('updateAnyTask');
$updateAnyTask->description = 'Update any task';
$auth->add($updateAnyTask);
$deleteOwnTask = $auth->createPermission('deleteOwnTask');
$deleteOwnTask->description = 'Delete own tasks';
$auth->add($deleteOwnTask);
$deleteAnyTask = $auth->createPermission('deleteAnyTask');
$deleteAnyTask->description = 'Delete any task';
$auth->add($deleteAnyTask);
$manageUsers = $auth->createPermission('manageUsers');
$manageUsers->description = 'Manage user accounts';
$auth->add($manageUsers);
// --- Rules ---
$isAuthorRule = new \app\rbac\IsAuthorRule();
$auth->add($isAuthorRule);
// Attach rule to "own" permissions
$updateOwnTask->ruleName = $isAuthorRule->name;
$auth->update('updateOwnTask', $updateOwnTask);
$deleteOwnTask->ruleName = $isAuthorRule->name;
$auth->update('deleteOwnTask', $deleteOwnTask);
// --- Roles ---
// Author role
$author = $auth->createRole('author');
$author->description = 'Regular user who can manage their own tasks';
$auth->add($author);
$auth->addChild($author, $viewTask);
$auth->addChild($author, $createTask);
$auth->addChild($author, $updateOwnTask);
$auth->addChild($author, $deleteOwnTask);
// Admin role (inherits all author permissions + more)
$admin = $auth->createRole('admin');
$admin->description = 'Administrator with full access';
$auth->add($admin);
$auth->addChild($admin, $author); // inherits author permissions
$auth->addChild($admin, $updateAnyTask);
$auth->addChild($admin, $deleteAnyTask);
$auth->addChild($admin, $manageUsers);
echo "RBAC hierarchy created.\n";
}
}Run it:
php yii rbac/init8.4 Assign Roles to Users
After registration, assign the default role:
// In SignupForm::signup() after $user->save()
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $user->id);For promoting a user to admin:
$auth = Yii::$app->authManager;
$adminRole = $auth->getRole('admin');
$auth->assign($adminRole, $userId);9. Writing Custom Rules
Rules are PHP classes that evaluate dynamic conditions at runtime. The IsAuthorRule checks if the current user created the resource:
// rbac/IsAuthorRule.php
namespace app\rbac;
use yii\rbac\Rule;
class IsAuthorRule extends Rule
{
public $name = 'isAuthor';
/**
* @param int|string $userId The user ID
* @param \yii\rbac\Item $item The role or permission this rule is attached to
* @param array $params Extra params passed to can()
*/
public function execute($userId, $item, $params): bool
{
if (!isset($params['task'])) {
return false;
}
return $params['task']->user_id == $userId;
}
}Using the Rule in Controllers
// controllers/TaskController.php
public function actionUpdate(int $id): string|\yii\web\Response
{
$task = Task::findOne($id);
if ($task === null) {
throw new \yii\web\NotFoundHttpException('Task not found.');
}
// Check RBAC: can this user update THIS task?
if (!Yii::$app->user->can('updateOwnTask', ['task' => $task])
&& !Yii::$app->user->can('updateAnyTask')) {
throw new \yii\web\ForbiddenHttpException('You are not allowed to update this task.');
}
if ($task->load(Yii::$app->request->post()) && $task->save()) {
return $this->redirect(['view', 'id' => $task->id]);
}
return $this->render('update', ['model' => $task]);
}The can() method:
Yii::$app->user->can('permissionName'); // simple check
Yii::$app->user->can('updateOwnTask', ['task' => $task]); // with params for ruleWhen you call can('updateOwnTask', ['task' => $task]):
- RBAC finds the
updateOwnTaskpermission - Sees it has
ruleName = 'isAuthor' - Instantiates
IsAuthorRuleand callsexecute() execute()compares$task->user_idwith the current user ID- Returns
trueonly if the user owns the task
10. Protecting the TaskController
Let's put it all together. Here's the full TaskController with both AccessControl and RBAC checks:
// controllers/TaskController.php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\web\ForbiddenHttpException;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use app\models\Task;
class TaskController extends Controller
{
public function behaviors(): array
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
// Anyone can view
['actions' => ['index', 'view'], 'allow' => true, 'roles' => ['?', '@']],
// Only logged-in users can create/update/delete
['actions' => ['create', 'update', 'delete'], 'allow' => true, 'roles' => ['@']],
],
],
'verbs' => [
'class' => VerbFilter::class,
'actions' => [
'delete' => ['POST'],
],
],
];
}
public function actionIndex(): string
{
$tasks = Task::find()
->with('user')
->orderBy(['created_at' => SORT_DESC])
->all();
return $this->render('index', ['tasks' => $tasks]);
}
public function actionView(int $id): string
{
return $this->render('view', ['model' => $this->findModel($id)]);
}
public function actionCreate(): string|\yii\web\Response
{
if (!Yii::$app->user->can('createTask')) {
throw new ForbiddenHttpException('You are not allowed to create tasks.');
}
$model = new Task();
$model->user_id = Yii::$app->user->id;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
Yii::$app->session->setFlash('success', 'Task created.');
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('create', ['model' => $model]);
}
public function actionUpdate(int $id): string|\yii\web\Response
{
$model = $this->findModel($id);
// RBAC: check updateOwnTask (with rule) OR updateAnyTask
if (!Yii::$app->user->can('updateOwnTask', ['task' => $model])
&& !Yii::$app->user->can('updateAnyTask')) {
throw new ForbiddenHttpException('You are not allowed to update this task.');
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
Yii::$app->session->setFlash('success', 'Task updated.');
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('update', ['model' => $model]);
}
public function actionDelete(int $id): \yii\web\Response
{
$model = $this->findModel($id);
if (!Yii::$app->user->can('deleteOwnTask', ['task' => $model])
&& !Yii::$app->user->can('deleteAnyTask')) {
throw new ForbiddenHttpException('You are not allowed to delete this task.');
}
$model->delete();
Yii::$app->session->setFlash('success', 'Task deleted.');
return $this->redirect(['index']);
}
protected function findModel(int $id): Task
{
$model = Task::findOne($id);
if ($model === null) {
throw new NotFoundHttpException('Task not found.');
}
return $model;
}
}The pattern is:
- AccessControl acts as the first gate — are you logged in?
- RBAC checks inside actions act as the second gate — do you have the right permission for this specific resource?
11. Showing/Hiding UI Elements
In views, use can() to show or hide buttons based on permissions:
<!-- views/task/view.php -->
<?php if (Yii::$app->user->can('updateOwnTask', ['task' => $model])
|| Yii::$app->user->can('updateAnyTask')): ?>
<?= Html::a('Edit', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
<?php endif; ?>
<?php if (Yii::$app->user->can('deleteOwnTask', ['task' => $model])
|| Yii::$app->user->can('deleteAnyTask')): ?>
<?= Html::a('Delete', ['delete', 'id' => $model->id], [
'class' => 'btn btn-danger',
'data' => [
'confirm' => 'Are you sure you want to delete this task?',
'method' => 'post',
],
]) ?>
<?php endif; ?>For navigation items:
// views/layouts/main.php
$menuItems = [
['label' => 'Tasks', 'url' => ['/task/index']],
];
if (Yii::$app->user->can('manageUsers')) {
$menuItems[] = ['label' => 'Users', 'url' => ['/user/index']];
}
if (Yii::$app->user->isGuest) {
$menuItems[] = ['label' => 'Login', 'url' => ['/site/login']];
$menuItems[] = ['label' => 'Sign Up', 'url' => ['/site/signup']];
} else {
$menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username . ')',
'url' => ['/site/logout'],
'linkOptions' => ['data-method' => 'post']
];
}12. RBAC Hierarchy Visualization
Here's the complete permission tree for our task manager:
When can() is called, Yii2 walks the tree:
- Find the user's assigned role (e.g.,
author) - Walk all child permissions recursively
- If a matching permission has a rule, execute it
- Return
trueonly if the permission exists AND the rule passes
Since admin has author as a child, admins automatically inherit all author permissions — no duplication needed.
13. Common Patterns
Check Multiple Permissions
// Helper method in User model
public function canAny(array $permissions, array $params = []): bool
{
foreach ($permissions as $permission) {
if (Yii::$app->user->can($permission, $params)) {
return true;
}
}
return false;
}Default Role for New Users
Instead of assigning in signup, use defaultRoles:
// config/web.php
'authManager' => [
'class' => 'yii\rbac\DbManager',
'defaultRoles' => ['author'],
],This makes every authenticated user an author without needing an auth_assignment row. Useful when you have a single default role.
Revoking and Reassigning Roles
$auth = Yii::$app->authManager;
// Revoke a specific role
$role = $auth->getRole('author');
$auth->revoke($role, $userId);
// Revoke all roles
$auth->revokeAll($userId);
// Get all roles for a user
$roles = $auth->getRolesByUser($userId);
// Returns: ['author' => Role object, ...]Checking Roles in Console Commands
RBAC works in console context too — just make sure authManager is in config/console.php:
// commands/SomeController.php
$auth = Yii::$app->authManager;
$admins = $auth->getUserIdsByRole('admin');
// Returns array of user IDs with admin roleWhat's Next
Your task manager now has a complete security layer:
- Users can register, log in, and log out
AccessControlguards controller actions (guest vs authenticated)- RBAC provides granular permissions (author vs admin, own vs any)
- Custom rules handle dynamic checks (is this my task?)
- UI elements show/hide based on permissions
Deep Dive → Forms, Validation & Data Input — build complex forms with nested models, file uploads, AJAX validation, custom validators, and data formatting with Yii2's powerful form system.
📬 Subscribe to Newsletter
Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.
💬 Comments
Sign in to leave a comment
We'll never post without your permission.