📜 ⬆️ ⬇️

Secure login with PHPixie 3

image
Today the most awaited component of PHPixie 3 - Auth for authorization of users has been released. Authorization is the most critical part of any application, making it right is difficult, and mistakes can compromise many users, especially when it comes to the open-source. The use of outdated hash functions, cryptographically unsafe random number generators, and incorrect work with cookies are found too often. I once wrote about the old vulnerability in Laravel , which by the way was not completely fixed. Therefore, in PHPixie Auth, I was very careful about authentication, especially for long sessions and cookies.

By the way, at the end of the article I have very good news for you (spoiler: PHPixie is now a member of PHP-FIG)

What makes PHPixie Auth safe:


The last point is the most interesting and among PHP frameworks it has no analogues out of the box. The bottom line is a separate table for storing login tokens.
  1. When you login, a pair of random strings is created: a series identifier and a password, which are given to the user in the form of a cookie
  2. A hash of a series with a password is created and recorded in the database along with the user ID and expiration date.
  3. When you re-access the site, the cookie hash is compared with the hash in the database, and if they match, the login occurs, the old token is deleted and the user creates a new one, but with the same series
  4. If the hash does not match, it means someone stole cookies or trying to pick. In this case, all tokens with the same series are deleted.

This approach allows the user to be simultaneously logged on to multiple devices (one device - one series). For example, Laravel simply saves the token in the user table, and as a result, the user can have only one token for all devices.
')
Configuration

Like other components of PHPixie, Auth can be used without the framework, but for simplicity, I will describe just this case.

Firstly, you will need a repository of users, each bundle can provide its own repositories, from which we will select the necessary ones in the config file. If you use ORM to work with users, then Auth comes with a ready wrapper:

namespace Project\App\ORMWrappers\User; //   class Repository extends \PHPixie\AuthORM\Repositories\Type\Login { //      //      protected function loginFields() { return array('username', 'email'); } } 


 namespace Project\App\ORMWrappers\User; //   class Entity extends \PHPixie\AuthORM\Repositories\Type\Login\User { //      protected function passwordHash() { return $this->password; } } 


Do not forget to register them in ORMWrappers.php

 namespace Project\App; class ORMWrappers extends \PHPixie\ORM\Wrappers\Implementation { protected $databaseEntities = array('user'); protected $databaseRepositories = array('user'); public function userEntity($entity) { return new ORMWrappers\User\Entity($entity); } public function userRepisitory($repository) { return new ORMWrappers\User\Repository($repository); } } 


Now we will register this repository in the bundle:

 namespace Project\App; class AuthRepositories extends \PHPixie\Auth\Repositories\Registry\Builder { protected $builder; public function __construct($builder) { $this->builder = $builder; } protected function buildUserRepository() { $orm = $this->builder->components()->orm(); return $orm->repository('user'); } } 


 namespace Project\App; class Builder extends \PHPixie\DefaultBundle\Builder { protected function buildAuthRepositories() { return new AuthRepositories($this); } } 


You also need to create a table for storing tokens (when using MongoDB, everything will work immediately)

  CREATE TABLE `tokens` ( `series` varchar(50) NOT NULL, `userId` int(11) DEFAULT NULL, `challenge` varchar(50) DEFAULT NULL, `expires` bigint(20) DEFAULT NULL, PRIMARY KEY (`series`) ) 


Now the config file itself. The most popular approach would look like this:

 // /assets/auth.php return array( 'domains' => array( 'default' => array( //  user   app 'repository' => 'app.user', 'providers' => array( //    'session' => array( 'type' => 'http.session' ), //   ( "remember me") 'cookie' => array( 'type' => 'http.cookie', //     session //     'persistProviders' => array('session'), //    'tokens' => array( 'storage' => array( 'type' => 'database', 'table' => 'tokens', 'defaultLifetime' => 3600*24*14 //   ) ) ), //    'password' => array( 'type' => 'login.password', //    . //       'cookies' //     "remember me"  //  ,       'persistProviders' => array('session') ) ) ) ); 


All providers are independent of one another, which makes it easy to change functionality. For example, if you want to use only cookies, without sessions, and at the same time disable the token update on each request, you can do this:

 // /assets/auth.php return array( 'domains' => array( 'default' => array( 'cookie' => array( 'type' => 'http.cookie', //    'tokens' => array( 'storage' => array( 'type' => 'database', 'table' => 'tokens', 'defaultLifetime' => 3600*24*14, 'refresh' => false ) ) ), 'password' => array( 'type' => 'login.password', 'persistProviders' => array('cookie') ) ) ) ); 


Using

Let's create a simple processor to try how it all works together:

 namespace Project\App\HTTPProcessors; class Auth extends \PHPixie\DefaultBundle\Processor\HTTP\Actions { protected $builder; public function __construct($builder) { $this->builder = $builder; } //       public function defaultAction($request) { $user = $this->domain()->user(); return $user ? $user->username : 'not logged'; } //       public function addAction($request) { $query = $request->query(); $username = $query->get('username'); $password = $query->get('password'); $orm = $this->builder->components()->orm(); $provider = $this->domain()->provider('password'); $user = $orm->createEntity('user'); $user->username = $username; //     $user->passwordHash = $provider->hash($password); $user->save(); return 'added'; } //     public function loginAction($request) { $query = $request->query(); $username = $query->get('username'); $password = $query->get('password'); $provider = $this->domain()->provider('password'); $user = $provider->login($username, $password); if($user) { // generate persistent cookie $provider = $this->domain()->provider('cookie'); $provider->persist(); } return $user ? 'success' : 'wrong password'; } //  public function logoutAction($request) { $this->domain()->forgetUser(); return 'logged out'; } protected function domain() { $auth = $this->builder->components()->auth(); return $auth->domain(); } } 


Now we go to the urls and see the result:

  1. / auth - user is not logged in
  2. / auth / add? username = jigpuzzled & password = 5 - create user
  3. / auth / login? username = jigpuzzled & password = 5 - log in
  4. / auth - check login
  5. / auth / logout - logout


You have already noticed that, like in the config file, the concept of a domain is also found in the processor. Domain is a separate authentication instance with its own repository and providers. In most cases, you will have only one domain. Several domains will be needed when you have separate sections of the site with different logins, for example, the front-end allows users to log in via the Facebook login, but the backend is only through the login and password.

Its providers

Over time, you will probably need to add your login provider, for example, for authorization via social networks. To do this, you will need to make your own provider builder (for example, by analogy with Login ) and register it as an extension . For a detailed description, I will write a separate article, but these two links should be enough to start.

PHPixie is now a member in PHP-FIG


Thanks SamDark PHPixie will already be a member of PHP-FIG from tomorrow! Full thread vote can be seen here . And many thanks to all users of the framework, since it is the popularity and number of downloads that is one of the main selection criteria ^ __ ^

Source: https://habr.com/ru/post/267177/


All Articles