If you're asking, "What's Yii?" check out Introduction to the Yii Framework, which reviews the benefits of Yii and includes an overview of Yii 2.0.
In this Programming With Yii2 series, I'm guiding readers in use of the Yii2 Framework for PHP. If you're planning to share your application with the public, you'll need it to be secure, and it's best to plan this from the beginning. Fortunately, starting with a framework such as Yii makes this a lot easier than it otherwise would be. As stated in Features of Yii:
Yii is equipped with many security measures to help prevent your Web applications from attacks such as SQL injection, cross-site scripting (XSS), cross-site request forgery (CSRF), and cookie tampering.
In this tutorial, I'll walk you through the basic security concepts within the Yii application framework. And, if you're interested, future episodes will work to secure the application, Meeting Planner, featured in our startup series, as it approaches alpha release.
Before we get started, please remember, I do try to participate in the discussions below. If you have a question or topic suggestion, please post a comment below or contact me on Twitter @reifman.
Note: If you noticed the gap in between the Programming Yii series episodes, it's because I had to have brain surgery last year. Thank you for your patience and support—it's nice to be writing again regularly, and I'm looking forward to continuing coverage of Yii2.
The Basics of Security With Yii
If you're new to web application security, there's a lot to understand about Yii's offerings. I'll do my best to offer an overview based on the best of the Yii 2.0 documentation. The Yii team divides security into seven key areas:
- Authentication
- Authorization
- Working with Passwords
- Cryptography
- Views security
- Auth Clients
- Best Practices
Let's begin diving into these one by one.
1. Authentication
Ilko Kacharov's Yii Framework Security presentation offers a few helpful slides that summarize the goal of authentication (and the following sub-topic, authorization). Essentially, here are the questions these topics need to answer:
- Who is the user?
- Is the user who they say they are?
- Is the user authorized to access a resource?
- Is the user authorized to perform an action?
- Is the user authorized to perform an action on a resource?
The User Model and Identity Interface
Yii's yii/web/User class integrates with the yii\web\IdentityInterface to manage the user's authentication status within your application.
Last November, I wrote a tutorial about Yii's Advanced Application Template. One of the advanced template's advantages is that it provides pre-built integration of the User model with ActiveRecord and your database. So your application offers database-driven authentication right out of the box.
The User model allows you to programmatically log in and log out users:
- login() sets the specified identity and remembers the authentication status in session and cookie.
- logout() marks the user as a guest and clears the relevant information from session and cookie.
- setIdentity(): changes the user identity without touching session or cookie, best for API functionality.
The $isGuest property determines whether the current user has signed in or not. When the user is signed out, it's null, but otherwise returns an instance of the IdentityInterface.
Essentially, you need a User class which extends ActiveRecord and implements methods to support the IdentityInterface, like this:
<?php use yii\db\ActiveRecord; use yii\web\IdentityInterface; class User extends ActiveRecord implements IdentityInterface { public static function tableName() { return 'user'; } /** * Finds an identity by the given ID. * * @param string|integer $id the ID to be looked for * @return IdentityInterface|null the identity object that matches the given ID. */ public static function findIdentity($id) { return static::findOne($id); } /** * Finds an identity by the given token. * * @param string $token the token to be looked for * @return IdentityInterface|null the identity object that matches the given token. */ public static function findIdentityByAccessToken($token, $type = null) { return static::findOne(['access_token' => $token]); } /** * @return int|string current user ID */ public function getId() { return $this->id; } /** * @return string current user auth key */ public function getAuthKey() { return $this->auth_key; } /** * @param string $authKey * @return boolean if auth key is valid for current user */ public function validateAuthKey($authKey) { return $this->getAuthKey() === $authKey; } }
Also, before a user is created, the application generates a random string as an authorization key. This can be used in "forgot your password" emails or other email-based login links:
class User extends ActiveRecord implements IdentityInterface { ...... public function beforeSave($insert) { if (parent::beforeSave($insert)) { if ($this->isNewRecord) { $this->auth_key = \Yii::$app->security->generateRandomString(); } return true; } return false; } }
Authorization
Yii provides two built-in approaches to authorization. The simpler Access Control List (ACL) determines which users or processes are allowed to perform actions on a resource, and the more intensive Role-Based Access Control (RBAC) helps you manage access by defining roles. In RBAC, only users or system tasks with specific roles can perform specific actions.
Access Control List
The ACL is sometimes also called an Access Control Filter (ACF). Yii provides ACL support within yii\filters\AccessControl. It's ideal for applications that only need simple access control. It's what I've used so far in Meeting Planner.
Here's an example of the common SiteController configuring an access behavior to filter access to available actions, often pages. In this case, ACL is acting on signup, login, and logout. The '?'
indicates that any user can access login and signup pages, whereas the '@'
indicates that only logged in or authenticated users are allowed access. In the example below, only logged in users can log out:
use yii\web\Controller; use yii\filters\AccessControl; class SiteController extends Controller { public function behaviors() { return [ 'access' => [ 'class' => AccessControl::className(), 'only' => ['login', 'logout', 'signup'], 'rules' => [ [ 'allow' => true, 'actions' => ['login', 'signup'], 'roles' => ['?'], ], [ 'allow' => true, 'actions' => ['logout'], 'roles' => ['@'], ], ], ], ]; } // ... }
As the controller grows, each new action needs to be defined within the AccessControl rules. And, as your application grows, each controller and all of its actions need to integrate ACL filtering for security.
Role-Based Access Control
Role-Based Access Control (RBAC) provides a more robust authentication system but also requires a lot more upfront design and implementation.
With RBAC, you define authentication through roles which can be inherited (or not), and you apply roles to users. You can also define rules for roles. RBAC implementations can get quite complex.
In the figure below, admins can perform any task, and authors can create a post and update their own posts. Jane is an admin so she can perform the tasks of administrators, and John is just an author:
Yii implements what it calls "a General Hierarchical RBAC, following the NIST RBAC model." RBAC functionality is provided by its authManager application component.
I won't go into too much depth with RBAC here, but I hope to in a future tutorial. Again, it's up to the editorial goddesses—speaking with them is never easy:
Basically, to implement RBAC thoroughly, you must:
- define roles and permissions
- establish relations between your roles and permissions
- define any rules that exist
- associate rules to your roles and permissions
- and finally, assign roles to users
You can see the code required to enable the beginnings of an RBAC system below:
<?php namespace app\commands; use Yii; use yii\console\Controller; class RbacController extends Controller { public function actionInit() { $auth = Yii::$app->authManager; // add "createPost" permission $createPost = $auth->createPermission('createPost'); $createPost->description = 'Create a post'; $auth->add($createPost); // add "updatePost" permission $updatePost = $auth->createPermission('updatePost'); $updatePost->description = 'Update post'; $auth->add($updatePost); // add "author" role and give this role the "createPost" permission $author = $auth->createRole('author'); $auth->add($author); $auth->addChild($author, $createPost); // add "admin" role and give this role the "updatePost" permission // as well as the permissions of the "author" role $admin = $auth->createRole('admin'); $auth->add($admin); $auth->addChild($admin, $updatePost); $auth->addChild($admin, $author); // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId() // usually implemented in your User model. $auth->assign($author, 2); $auth->assign($admin, 1); } }
To implement RBAC, you must be prepared to write a lot of code up front, or as your application grows. And, if you do, Yii will manage the authentication according to the authentication framework you define. In other words, design and coding up front provide solid, detailed authentication.
Working With Passwords
As Mark Zuckerberg learned in June, some websites store user passwords in plain text, but yours shouldn't; to be fair to Zuckerberg, my Facebook account was once hacked due to PHPList having done the same—before the days of password managers. Anyway, Yii makes it easy to encrypt and securely verify passwords.
Yii's crypt function uses bcrypt to generate hashes for your password. When people register, a hash is created:
$hash = Yii::$app->getSecurity()->generatePasswordHash($password);
Then, when the user attempts to log in, it's hashed and compared to the hash in the database:
if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { // all good, logging user in } else { // wrong password }
But you can also use Yii for protecting data with cryptography.
Cryptography
The Yii Framework provides a number of built-in features to support data protection:
- Password and key generation functions such as generateRandomKey, generateRandomString, and generateSalt.
- Password validation: generatePasswordHash() and validatePassword().
- Encryption/decryption: encryptByKey(), decryptByKey(), encryptByPassword() and decryptByPassword().
- Key derivation using standard algorithms: pbkdf2() and hkdf().
- Data tampering prevention: hashData() and validateData().
Views Security
Any data that comes from users is possibly infected with attacks such as SQL injection or cross-browser scripts. It's important that any data you output to users in views should be cleansed. Yii offers a couple of methods for this.
First, there's Html::encode
, which essentially breaks any SQL or scripting:
<?php use yii\helpers\Html; ?> <div class="username"> <?= Html::encode($user->name) ?> </div>
And there's integration with the HtmlPurifier library for larger text blocks:
<?php use yii\helpers\HtmlPurifier; ?> <div class="post"> <?= HtmlPurifier::process($post->text) ?> </div>
Login via Auth Client
Yii also provides the ability for third-party authentication, which is especially useful for supporting social login via Google, Facebook, Twitter, etc.
I've written several tutorials for Envato Tuts+ on using AuthClient within the Yii Framework with social logins:
- Building Your Startup: Simplifying Onramp With AuthClient (pending publication)
- How to Program With Yii2: Google Authentication
-
How to Program With Yii2: AuthClient Integration With Twitter, Google and Other Networks
I've found social login works super well for Meeting Planner. New users can get started scheduling a meeting without a password.
Best Practices
Yii also recommends a handful of best practices when it comes to web application security. And its documentation provides a good primer on these topics for anyone.
- Filtering Input and Output
- Avoiding SQL injections
- Avoiding Cross-Site Scripting (XSS)
- Avoiding Cross-Site Request Forgery (CSRF)
- Avoiding file exposure
- Avoiding debug info and tools at production
- Using secure connection over TLS
The first three topics above are managed well by encoding discussed above in Views Security.
Yii also provides built-in CSRF protection for common activities—and it can be turned off when needed. In Meeting Planner, I had to turn off CSRF to accept messages posted from Mailgun's API services.
As for file exposure, the framework helps minimize this by funneling all input requests into the web/index.php request file. This greatly limits the need to write application code that filters requests. It's managed well in one place.
Finally, using HTTPS can help safeguard your connections and work with Yii to protect users. Earlier this year, I wrote about Let's Encrypt—you can use this tutorial to install HTTPS for Yii apps as well.
Want to Read More?
If you're interested in reading more detailed material on these topics, the Yii 1.x Framework offers these posts. Certainly, they're older and less specific to Yii 2, but they remain useful.
In Closing
I hope you've enjoyed my security overview for Yii2. If you integrate aspects of most or all of the concepts above into your application, you should have a basically secure web service. You may want to check out our Building Your Startup With PHP series for real-world implementation of some of these security practices.
Watch for upcoming tutorials in our Programming With Yii2 series as we continue diving into different aspects of the framework. I welcome feature and topic requests. You can post them in the comments below or email me at my Lookahead Consulting website.
If you'd like to know when the next Yii2 tutorial arrives, follow me @reifman on Twitter or check my instructor page. My instructor page will include all the articles from this series as soon as they are published.
Let's work together to keep the editorial goddesses happy.
Comments