Part 3: Building Admin Login Functionality With Yii2.

Photo by Andrew Neel on Unsplash

Part 3: Building Admin Login Functionality With Yii2.

This is the third article in my blog series "Building A Simple Web Application With YII2" and in this article we'll be working on the admin login.

In the last article we worked on user login, this is going to be very similar to that except that we will create some new methods specifically for admins and I explained why in the article.

The full code for this article can be found on my github: https://github.com/mrfola/yii-result-portal-tutorial/tree/article-3

To check the previous article kindly go here: Part 2: Building Login Functionality With Yii2.

Requirements

In order to get the best from this article, you need to have knowledge of certain things. The details of the requirements can be found here: Building A Simple Web Application With YII2

Create User Roles and Permission

In order to separate admin actions from user actions, we need to use roles and permission.

YII has made it simple through something called "RBAC"

So let's implement it.

Step 1: Create "UserTypeRule" Class

Since we already have a "user_type" field in our users table, we just need to connect the user_type field to our rbac roles.

To do that we need to create a class that automatically assigns each user an RBAC role.

In your project root directory, create a folder "rbac".

Inside it, create a file "UserTypeRule.php"

Type the following inside that file:

<?php   

namespace app\rbac;

use Yii;
use yii\rbac\Rule;

/**
 * Checks if user type matches
 */
class UserTypeRule extends Rule
{
    public $name = 'userType';

    public function execute($user, $item, $params)
    {
        if (!Yii::$app->user->isGuest)
        {
            $user_type = Yii::$app->user->identity->user_type;
            if ($item->name === 'admin')
            {
                return $user_type == 'admin';

            } elseif ($item->name === 'user')
            {
                return $user_type == 'user';
            }
        }
        return false;
    }
}

Now let's move on to step 2.

Step 2: Create rbac migration

In your CLI, type the following:

yii migrate/create init_rbac

You get a prompt like this:

Create new migration 'path-to-your-project/migrations\m220309_131448_init_rbac.php? (yes|no) [no]:

Enter "yes" and you should see a success message like this:

New migration created successfully.

Navigate to your migration include this at the top:

use yii\rbac\PhpManager;

Next, modify your "safeUp" method to look like this:

     public function SafeUp()
    {
        $auth = new PhpManager;//Yii::$app->authManager;

        $rule = new \app\rbac\UserTypeRule;
        $auth->add($rule);

        //add "updateResult" permission
        $updateResult = $auth->createPermission('updateResult');
        $updateResult->description = 'Update result';
        $auth->add($updateResult);

        //add "viewResultDashboard" permission
        $viewResultDashboard = $auth->createPermission('viewResultDashboard');
        $viewResultDashboard->description = 'View Result Dashboard';
        $auth->add($viewResultDashboard);

        // add "admin" role and give this role the "viewResultDashboard" and "updateResult" permission
        $admin = $auth->createRole('admin');
        $admin->ruleName = $rule->name;
        $auth->add($admin);
        $auth->addChild($admin, $updateResult);
        $auth->addChild($admin, $viewResultDashboard);

        // add "user" role
        $user = $auth->createRole('user');
        $user->ruleName = $rule->name;
        $auth->add($user);

    }

Now modify your safe down method to look like this:

   public function safeDown()
    {
        $auth = Yii::$app->authManager;
        $auth->removeAll();
    }

Run your migration to setup the appropriate roles.

Type the following in your CLI:

yii migrate

You should receive a confirmation prompt. Type "yes" and your migration should be successful.

With that done, we are going to configure our auth manager in web configurations.

Step 3: Configure Auth Manager

Navigate to config/web.php, add the following to the components array:

'authManager' => [
            'class' => 'yii\rbac\PhpManager',
            'defaultRoles' => ['admin', 'user'],
        ],

Now that we have setup our roles and permission, lets implement admin login.

Creating New Methods In User Model

Here, we are going to create some methods in the User model that we will need for admin login.

We could have re-factored the previous login methods to allow admins but I want to separate the admin entry point from the user login point, so I am creating different login methods to handle that.

Navigate to models/User.php.

Add the following methods before the last curly braces in that file.

    public static function findByAdminEmail($email)
    {
        return self::findOne([
            "email" => $email,
            "user_type" => "admin"
        ]);
    }

    public function adminLogin()
    {
        $user = $this->findByAdminEmail($this->email);
        if ($user && $this->validatePassword($user->password))
        {
            return Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0);
        }else
        {
            $this->addError('password', 'Incorrect username or password.');
        }
    }

Creating Database Seeders

If you don't know what database seeding is, here is a simple explanation from Wikipedia:

"Database seeding is populating a database with an initial set of data. It's common to load seed data such as initial user accounts or dummy data upon initial setup of an application."

However, we have 1 tiny teeny problem when it comes to seeding data.

Yii doesn't come with Seeders by default, so we are going to create our own work around.

To do that we will create a Yii console command to insert data into our database.

Navigate to commands folder, create a file called "SeedController.php".

Inside that file, type the following code:

<?php

namespace app\commands;

use yii\console\Controller;
use app\models\User;
use Yii;

class SeedController extends Controller
{
    public function actionAdmin()
    {
        $admin = new User();
        $admin->name = "admin";
        $admin->email = "admin@gmail.com";
        $admin->password = Yii::$app->getSecurity()->generatePasswordHash("password");
        $admin->user_type = "admin";

        if($admin->save())
        {
            echo "Admins successfully seeded";
        }else
        {
            foreach($admin->getErrors() as $error)
            {
                echo $error[0]." ";
            }
        }   
    }


 }

In the code above, "actionAdmin" method seeds our admins table with the a default admin and YII allows us to access console commands by typing the following in the CLI:

yii controllerID/actionID

This will run whatever code is in that controller action.

So to seed our admin table, we will run

yii seed/admin

You should get a success message like this: Admins successfully seeded

Now that we're done with that, let's move on to creating an admin layout file.

Admin Layout

We would like to have a different navigation and title for all admin pages, so we would create a custom admin layout for that.

YII comes with a default layout found in views/layouts/main.php.

We will be overriding the default layout for all admin views.

Navigate to views/layouts. Create a file "admin.php".

Type the following in that file:

<?php

/* @var $this \yii\web\View */
/* @var $content string */

use app\assets\AppAsset;
use app\widgets\Alert;
use yii\bootstrap4\Breadcrumbs;
use yii\bootstrap4\Html;
use yii\bootstrap4\Nav;
use yii\bootstrap4\NavBar;

AppAsset::register($this);
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>" class="h-100">
<head>
    <meta charset="<?= Yii::$app->charset ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <?php $this->registerCsrfMetaTags() ?>
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body class="d-flex flex-column h-100">
<?php $this->beginBody() ?>

<header>
    <?php
    NavBar::begin([
        'brandLabel' => "ResultPortal Admin",
        'brandUrl' => "ResultPortal Admin",
        'options' => [
            'class' => 'navbar navbar-expand-md navbar-dark bg-dark fixed-top',
        ],
    ]);

         echo Nav::widget([
             'options' => ['class' => 'navbar-nav'],
             'items' => [
                 ['label' => 'Home', 'url' => ['/admin/index']],
                 Yii::$app->user->isGuest ? '' : (['label' => 'Dashboard', 'url' => ['/admin/dashboard']]),
                 Yii::$app->user->isGuest ? (
                     ['label' => 'Login', 'url' => ['/admin/login']]
                 ) : (
                     '<li>'
                     . Html::beginForm(['/admin/logout'], 'post', ['class' => 'form-inline'])
                     . Html::submitButton(
                         'Logout (' . Yii::$app->user->identity->email . ')',
                         ['class' => 'btn btn-link logout']
                     )
                     . Html::endForm()
                     . '</li>'
                 )
             ],
         ]);

    NavBar::end();
    ?>
</header>

<main role="main" class="flex-shrink-0">
    <div class="container">
        <?= Breadcrumbs::widget([
            'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
        ]) ?>
        <?= Alert::widget() ?>
        <?= $content ?>
    </div>
</main>

<footer class="footer mt-auto py-3 text-muted">
    <div class="container">
        <p class="float-left">&copy; Result Portal <?= date('Y') ?></p>
        <p class="float-right"><a href="https://mrfola.hashnode.dev/">Powered By Mr Fola</a></p>
    </div>
</footer>

<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

Now let's move on to creating our login view.

Admin Login View.

Navigate to views folder create a new folder called "admin".

Inside that folder, create a "login.php" file.

Type the following code into that file:

<?php

/* @var $this yii\web\View */
/* @var $form yii\bootstrap4\ActiveForm */
/* @var $model app\models\LoginForm */

use yii\bootstrap4\ActiveForm;
use yii\bootstrap4\Html;

$this->title = 'Login';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-login">

    <?php
        $session = Yii::$app->session;
        $errors = $session->getFlash('errorMessages');
        $success = $session->getFlash('successMessage');
        if(isset($errors) && (count($errors) > 0))
        {
            foreach($session->getFlash('errorMessages') as $error)
            {
                echo "<div class='alert alert-danger' role='alert'>$error[0]</div>";
            }
        }

        if(isset($success))
        {
            echo "<div class='alert alert-success' role='alert'>$success</div>";
        }
    ?>

    <h1><?= Html::encode($this->title) ?></h1>

    <p>Please fill out the following fields to login:</p>

    <?php $form = ActiveForm::begin([
        'id' => 'login-form',
        'layout' => 'horizontal',
        'fieldConfig' => [
            'template' => "{label}\n{input}\n{error}",
            'labelOptions' => ['class' => 'col-lg-1 col-form-label mr-lg-3'],
            'inputOptions' => ['class' => 'col-lg-3 form-control'],
            'errorOptions' => ['class' => 'col-lg-7 invalid-feedback'],
        ],
    ]); ?>

        <?= $form->field($user, 'email')->input('email') ?>

        <?= $form->field($user, 'password')->passwordInput() ?>

        <?= $form->field($user, 'rememberMe')->checkbox([
            'template' => "<div class=\"offset-lg-1 col-lg-3 custom-control custom-checkbox\">{input} {label}</div>\n<div class=\"col-lg-8\">{error}</div>",
        ]) ?>

        <div class="form-group">
            <div class="offset-lg-1 col-lg-11">
                <?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
            </div>
        </div>

    <?php ActiveForm::end(); ?>

    <div class="offset-lg-1" style="color:#999;">
        You may login with <strong>admin@gmail.com/password</strong><br>
    </div>
</div>

This form basically creates the input fields and sends the request to the login method in our AdminController.

If the user successfully logs in, they are redirected to the admin dashboard.

Let's create that dashboard page.

Admin Dashboard View.

Navigate to views/admin and create a file "dashboard.php".

Put the following placeholder code inside that file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Admin Dashboard</title>
</head>
<body>
    <h1>Admin Dashboard</h1>
</body>
</html>

Let's also create a placeholder admin home page.

Admin Home View.

Navigate to views/admin and create a file "index.php".

Put the following placeholder code inside that file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Admin Home Page</title>
</head>
<body>

<div class="container">
    <h1>Admin Home Page</h1>
</div>

</body>
</html>

Now let's move unto the controller to handle the actual login functionality.

Admin Controller.

Navigate to controllers folder and create "AdminController.php" controller.

Insert the following code into that controller.

<?php

namespace app\controllers;

use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\web\Controller;
use yii\web\Response;
use app\models\User;
use Yii;


class AdminController extends Controller
{

    //override default layout and use admin.php instead
    public $layout = 'admin';

    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['logout', 'dashboard'],
                'rules' => [
                    [
                        'actions' => ['dashboard'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                    [
                        'actions' => ['logout'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'logout' => ['post'],
                ],
            ],
        ];
    }


    /**
     * {@inheritdoc}
     */
    public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'captcha' => [
                'class' => 'yii\captcha\CaptchaAction',
                'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
            ],
        ];
    }

    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionDashboard()
    {
        if (\Yii::$app->user->can('viewResultDashboard'))
         {
                return $this->render('dashboard');
          }else
        {
            return "You are not authorized to view this page.";
        }
    }

    /**
     * Login action.
     *
     * @return Response|string
     */

      public function actionLogin()
    {
        if (!Yii::$app->user->isGuest)
        {
            return $this->redirect(["admin/dashboard"]);
        }

        $request = Yii::$app->request->post();
        $admin = new User();
        if($request)
        {
            if ($admin->load($request) && $admin->adminLogin())
            {
                return $this->redirect(["admin/dashboard"]);
            }

            $session = Yii::$app->session;
            $session->setFlash('errorMessages', $admin->getErrors());
        }


        $admin->password = '';
        return $this->render('login', [
            'user' => $admin,
        ]);
    }

    /**
     * Logout action.
     *
     * @return Response
     */
    public function actionLogout()
    {
        Yii::$app->user->logout();

         return $this->redirect(["admin/index"]);
    }

}

In this code we check if the credentials are correct for an admin and then we redirect the user to the admin dashboard.

Test it out ...

That will bring us to the end of this article.

Serve your application:

Navigate to localhost:8080/index.php?r=admin%2Flogin.

Try to login with the default credentials and it should work perfectly🎉

If you have any questions or comments kindly leave them below. Thanks.

Don't forget that the full code for this article can be found on my github: https://github.com/mrfola/yii-result-portal-tutorial/tree/article-3