Php Web Authentication

Obullo Auth package is designed to ease the management of authorization in the medium and large-scale applications caching the user identities according to their session numbers with the help of cache drivers. Auth package aims to be a scalable solution using the authentication adapters written for various common scenarios and supports multifactor authentication.

Installing with Composer

composer require obullo/auth

Installing Demo Application

git clone git@github.com:obullo/Auth-Demo.git auth-demo

To look over the real examples try to install demo application.

Features

MFA Feature

In login operations, if authorizing the users includes more than one step, it is called multiple authorization. The method Multi-Factor Authentication consists of a multi-layered structure. It provides a shield which the attackers cannot get through with several authentication methods. These methods may be like below ones:

MFA, multiple authorization method, has a second step which get users required to authenticate their identities unlike standard login functions. Even if an attacker has a user password, he cannot pass the authentication since he does not have a secure device authorized for MFA.

Flow Chart

The below diagram will give you a prior knowledge about how a user pass the authorization verification steps and how the server works:

Authentication

As seen on schema, two users are at the issue as Guest and User. Guest is unauthorized and user is authorized on the service side.

According to the schema, as soon as the Guest clicks the login button, firstly the cache is queried and checked if the user has already had a permanent identity. If there are permanent authorization on the memory block, the user identity is read from here. If not, the database is queried and retrieved identity card is re-written into cache.

Configuration

Authentication class works with Php League Container package by default.

require_once "vendor/autoload.php";

session_start();

$container = new League\Container\Container;
$request = Zend\Diactoros\ServerRequestFactory::fromGlobals();
$container->share('request', $request);

$container->addServiceProvider('ServiceProvider\Redis');
$container->addServiceProvider('ServiceProvider\Database');
$container->addServiceProvider('ServiceProvider\Authentication');

All the configuration of the auth can be edited in the register method in the classes/ServiceProvider/Authentication class.

$container->share('Auth.PASSWORD_COST', 6);
$container->share('Auth.PASSWORD_ALGORITHM', PASSWORD_BCRYPT);

$container->share('Auth:Storage', 'Obullo\Auth\Storage\Redis')
    ->withArgument($container->get('redis:default'))
    ->withArgument($container->get('request'))
    ->withMethodCall('setPermanentBlockLifetime', [3600]) // Should be same with app session lifetime.
    ->withMethodCall('setTemporaryBlockLifetime', [300]);

Adapters

Identity verification adapters are interfaces adding flexibility to applications, which specify identity is verified with either a database or over a different protocol. The default interface is Database (It is used commonly for both RDBMS and NoSQL databases).

It is possible that different adapters have different behaviors and options, however, some basic procedures are common among the identity verification adapters such as performing the queries for identity verification service and results returned by these queries.

Storages

Storage caches the user identity while verifying the identity and prevents the application from losing performance not connecting to the database when user logs in again and again.

Supported Drivers

Storages can be changed in the service configuration.

$container->share('Auth:Storage', 'Obullo\Auth\Storage\Memcached')
    ->withArgument($container->get('memcached:default'))

Also, you need to call your service provider from the mainpage.

$container->addServiceProvider('ServiceProvider\Memcached');
$container->addServiceProvider('ServiceProvider\Database');
$container->addServiceProvider('ServiceProvider\Authentication');

Database

If you use a relational database like MySQL, run the below SQL code to create a table.

CREATE DATABASE IF NOT EXISTS test;

use test;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) NOT NULL,
  `password` varchar(80) NOT NULL,
  `remember_token` varchar(64) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `username` (`username`),
  KEY `remember_token` (`remember_token`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `users` (`id`, `username`, `password`, `remember_token`) VALUES 
(1, 'user@example.com', '$2y$06$6k9aYbbOiVnqgvksFR4zXO.kNBTXFt3cl8xhvZLWj4Qi/IpkYXeP.', '');

The name of the test user is user@example.com and the password is 123456.

Auth Table

If you want to change the queries or want to use a NoSQL solution, you can replace the value Obullo\Auth\Adapter\Table\Db of the key Auth:Table with your table class from the Authentication service provider.

$container->share('Auth:Table', 'My\Table\Db')
    ->withArgument($container->get('database:default'))
    ->withMethodCall('setColumns', [array('username', 'password', 'email', 'remember_token')])
    ->withMethodCall('setTableName', ['users'])
    ->withMethodCall('setIdentityColumn', ['email'])
    ->withMethodCall('setPasswordColumn', ['password'])
    ->withMethodCall('setRememberTokenColumn', ['remember_token']);

An example for Mongo Db.

$container->share('Auth:Table', 'Obullo\Auth\Adapter\Database\Table\Mongo');

Login

The login operation is performed over the login method and this method returns an AuthResult object checking the results of the login.

$credentials = new Obullo\Auth\Credentials;
$credentials->setIdentityValue('user@example.com');
$credentials->setPasswordValue('123456');
$credentials->setRememberMeValue(false);

$authAdapter = new Obullo\Auth\Adapter\Table($container);
$authResult  = $authAdapter->authenticate($credentials);
$authAdapter->regenerateSessionId(true);

if (false == $authResult->isValid()) {
    print_r($authResult->getMessages());
} else {
    $user = new Obullo\Auth\User\User($credentials);
    $user->setResultRow($authResult->getResultRow());

    $identity = $authAdapter->authorize($user); // Authorize user;

    header("Location: /example/Restricted.php");
}

The login success is checked with the method AuthResult->isValid() and if the login fails, all the returning error messages can be reached with the method getArray().

if ($auhtResult->isValid()) {

    // Success

} else {

    // Fail

    print_r($auhtResult->getArray());
}

Note: Remember take a look at the example created in the example folder.

Error Table

Code Constant Description
0 AuthResult::FAILURE Failed general authorization verification
-1 AuthResult::FAILURE_IDENTITY_AMBIGUOUS Failed authorization verification due to indefinite identity(Shows that the query results includes more than one identity).
-2 AuthResult::FAILURE_CREDENTIAL_INVALID Shows that invalid credentials are entered
1 AuthResult::SUCCESS Successful authorization verification

Identities

Identity class executes read and write operations of the user identity. The set method is used to save a value to the identity:

$identity->set('test', 'my_value');

The get method is used to retrieve the value from the credentials:

echo $identity->get('test');  // my_value

The below method is used to get all the credentials:

print_r($identity->getArray());

/*
Array
(
    [__isAuthenticated] => 1
    [__isTemporary] => 0
    [__rememberMe] => 0
    [__time] => 1470858670.5284
    [__ip] => 127.0.0.1
    [__agent] => Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0
    [__lastActivity] => 1470419173
    [id] => 1
    [password] => $2y$10$0ICQkMUZBEAUMuyRYDlXe.PaOT4LGlbj6lUWXg6w3GCOMbZLzM7bm
    [remember_token] => bqhiKfIWETlSRo7wB2UByb1Oyo2fpb86
    [username] => user@example.com
)
*/

Identity Keys

Key Description
__isAuthenticated If the user is authorized this key contains the value 1, otherwise 0.
__isTemporary It is used for the feature of authorization verification.
__rememberMe If the user has used this feature when login, this contains 1, otherwise 0.
__time Time when the identity is created. It is saved in the format of Unix microtime().
__ip The IP address which the user logins lastly.
__agent The browser and operation system information that the user has
__lastActivity The time of the last activity.

Password Rehash

If login has failed, the password rehash is decided with the method $authAdapter->passwordNeedsRehash(). This method returns the new hash value of the password using the password_needs_rehash() and password_hash() methods of php.

if ($hash = $authAdapter->passwordNeedsRehash()) {
    // UPDATE `users` WHERE email = `$email` SET password = "$hash";
}

If method does not return false, the user password should be replace in db with the returned hash.

Adapter


$authAdapter->authenticate(Credentials $credentials);

Returns to the AuthResult object after verifying the authorization with the user information.

$authAdapter->regenerateSessionId(true);

Specifies if the session id will be re-created or not after login.

$authAdapter->validateCredentials(Credentials $credentials);

Verifies the credentials without authorizing the user and returns true or false accordingly.

$authAdapter->authorize(User $user);

Used to authorize guest user whose credentials has already been verified with user object.

Identity


$identity->check();

Returns true if the user passes authorization verification, otherwise false.

$identity->guest();

Checks if the user is a guest, whose authorization has not been verified. If guest, returns true, otherwise false.

$identity->set($key, $value);

Sets a value to the key entered to the identity array.

$identity->get($key);

Returns the value of the key entered from the identity array. Returns fakse if no key is found.

$identity->remove($key);

Removes the existent key from the identity array.

$identity->expire($ttl);

Sets the expiring time to the __expire key in order for user identity to be expired when the time passes.

$identity->isExpired();

Returns true if the time set by the method expire() is expired and returns false otherwise. This method can be used on the Http Auth Layer as below:

if ($identity->isExpired()) {
    $identity->destroy();    
}

$identity->makeTemporary($expire = 300);

Makes the user identity which has logged in successfully temporary according to the time specified for the multifactor authentication. When expired, identity is removed from the storage.

$identity->makePermanent();

Makes the temporary identity of the user who has passed the multifactor authentication permanent. When the permanent identity time(3600 seconds by default) expires, the database is re-queried and the identity saves into memory.

$identity->isTemporary();

Shows either user identity is temporary or permanent in multifactor authentication, returns 1 if temporary, otherwise 0.

$identity->updateTemporary(string $key, mixed $val);

Enables to update the temporary credentials in multifactor authentication.

$identity->logout();

Logs out while updating the isAuthenticated key in the cache with 0. This method does not completely removes the user identity from the cache, it just saves the user as if he has ended the session. Thanks to caching, when the user logs in again within 3600 seconds, isAuthenticated value is updated to 1 and the database query is prevented.

$identity->destroy();

Destroys the user identity completely.

$identity->forgetMe();

Removes the cookie 'remember me' from the browser.

$identity->refreshRememberToken();

Refreshes the cookie 'rememeber me' and saves into database and cookie again.

$identity->getIdentifier();

Returns the identifier of the user. It is generally username or email.

$identity->getPassword();

Returns the hashed password of the user.

$identity->getRememberMe();

Returns 1 if the user uses the feature 'remember me', otherwise returns 0.

$identity->getTime();

Returns the first creation time of the identity (Unix microtime).

$identity->getRememberMe();

If the user has used the feature 'remember me' results 1, otherwise returns 0.

$identity->getRememberToken();

Returns the value of the cookie 'rememeber me'.

$identity->getLoginId();

One or more sessions are numbered and returns returns the session number of the user logged in, otherwise returns false.

$identity->getArray()

Returns all the credentials within an array.

Storage


$storage->getUserSessions();

If user has one or more sessions, returns these sessions within an array.

$sessions = $storage->getUserSessions();

If a user logs in with two different browsers, the output of this method is similar to below.

print_r($sesssion);

Array
(
    [048f7b509a22800088f1cd8c1cc04b96] => Array
        (
            [__isAuthenticated] => 1
            [__time] => 1470858670.5284
            [__id] => user@example.com
            [__key] => Auth:user@example.com:048f7b509a22800088f1cd8c1cc04b96
            [__agent] => Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,..
            [__ip] => 212.124.16.1,
            [__lastActivity] => 1470419674
        )

    [1dd468dbea32e8ed6f58cb00b40af76c] => Array
        (
            [__isAuthenticated] => 1
            [__time] => 1470858670.6000
            [__id] => user@example.com
            [__key] => Auth:user@example.com:1dd468dbea32e8ed6f58cb00b40af76c
            [__agent] => Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0
            [__ip] => 88.169.1.7,
            [__lastActivity] => 1470419665
        )
);

$storage->killSession($loginID);

Terminates the user session according to session id.

$storage->killSession("1dd468dbea32e8ed6f58cb00b40af76c");

In the previous example, when login ID belonging to Firefox browser value is sent to this method, the session on the Firefox browser is terminated.

AuthResult


$authResult->isValid();

Returns true if the error code returning from the method login attempt greater than 0, otherwise false. Successfull login operations returns the error code 1 and the other situations returns negative values.

$authResult->getCode();

Returns the error code after login attempt.

$authResult->getIdentifier();

Returns the credentials like id, username, email after login attempt.

$authResult->getMessages();

Returns the error messages after the login attempt.

$authResult->setCode(int $code);

Sets an error code to default result from the login attempt.

$authResult->setMessage(string $message);

Sets error messages to results returning from the login attempt.

$authResult->getArray();

Returns all the results in an array from the login attempt.

$authResult->getResultRow();

Returns the valid database adapter after the login attempt according to query result either from database or cache if it exists.

Recall (Recaller)

If the user has already had a cookie 'remember me', the user can be authorized without entering the user information using the recaller function.

if ($token = $identity->hasRecallerCookie()) {

    $recaller = new Obullo\Auth\Recaller($container);

    if ($resultRowArray = $recaller->recallUser($token)) {

        $credentials = new Obullo\Auth\User\Credentials;
        $credentials->setIdentityValue($resultRowArray['email']);
        $credentials->setPasswordValue($resultRowArray['password']);
        $credentials->setRememberMeValue(true);

        $user = new Obullo\Auth\User\User($credentials);
        $user->setResultRow($resultRowArray);

        $authAdapter = new Obullo\Auth\Adapter\Table($container);
        $authAdapter->authorize($user);
        $authAdapter->regenerateSessionId(true);
    }
}

Multifactor Authentication

Multifactor authentication makes the identity confirmation easier with the methods OTP, Call, Sms or QRCode after a user logs in.

After a successful login, the user identity is cached permanently(3600 seconds by default). If the user is wanted to be approved, permanent identities must be become temporary with the method $identity->makeTemporary() (300 seconds by default). A temporary identity expires within 300 seconds itself.

In multifactor authentication, after user logs in,

$identity->makeTemporary(300);

using the method above, user identity is made temporary and user cannot log in. The user must be sent verification code to approve his temporary identity.

if ($authResult->isValid()) {

    $identity->makeTemporary();

    // Send verification code to user

    header("Location: /example/Verify.php");
}

If verified, the temporary identity must be set as permanent with the method $identity->makePermanent(). A permanent identity means the user has successfully logged in.

$identity->makePermanent();

If multifactor authentication is not used, system saves all identities as permanent.

Mongo Table Driver

If you want to use Mongo table driver, add the Mongo service provider from the commong file. Remember updating the connection information in the service provider.

// $container->addServiceProvider('ServiceProvider\Database');
$container->addServiceProvider('ServiceProvider\Mongo');

Send the first argument in the authentication service as below.

$this->container->get('mongo:default')->selectDB('test');

The part needing to be changed in the service provider should be like below.

$container->share('Auth:Table', 'Obullo\Auth\Adapter\Table\Mongo')
    ->withArgument($this->container->get('mongo:default')->selectDB('test'))
    ->withMethodCall('setColumns', [array('username', 'password', 'email', 'remember_token')])
    ->withMethodCall('setTableName', ['users'])
    ->withMethodCall('setIdentityColumn', ['email'])
    ->withMethodCall('setPasswordColumn', ['password'])
    ->withMethodCall('setRememberTokenColumn', ['remember_token']);