DEV Community

Cover image for PHP - Create your own PHP Authentication
F.R Michel
F.R Michel

Posted on • Edited on

PHP - Create your own PHP Authentication

How to Build a Secure Authentication with PHP

PHP version required 7.3

The interfaces

UserInterface

<?php

namespace DevCoder\Authentication;

/**
 * Interface UserInterface
 * @package DevCoder\Authentication
 */
interface UserInterface
{
    public function getUsername() :?string;

    public function getPassword() :?string;

    public function getRoles() : array;

    public function isEnabled(): bool;
}
Enter fullscreen mode Exit fullscreen mode

UserTokenInterface

<?php

namespace DevCoder\Authentication\Token;

use DevCoder\Authentication\UserInterface;

/**
 * Interface UserTokenInterface
 * @package DevCoder\Authentication\Token
 */
interface UserTokenInterface
{
    const DEFAULT_PREFIX_KEY = 'user_security';

    public function getUser(): UserInterface;

    public function serialize(): string;
}
Enter fullscreen mode Exit fullscreen mode

UserManagerInterface

<?php

namespace DevCoder\Authentication\Core;

use DevCoder\Authentication\Token\UserTokenInterface;
use DevCoder\Authentication\UserInterface;

interface UserManagerInterface
{
    public function getUserToken(): ?UserTokenInterface;

    public function hasUserToken(): bool;

    public function createUserToken(UserInterface $user): UserTokenInterface;

    public function logout(): void;

    public function cryptPassword(string $plainPassword): string;

    public function isPasswordValid(UserInterface $user, string $plainPassword): bool;
}
Enter fullscreen mode Exit fullscreen mode

Now let's create the classes which will implement the above interfaces

PasswordTrait to Manage Passwords

<?php


namespace DevCoder\Authentication\Core;

use DevCoder\Authentication\UserInterface;

/**
 * Trait PasswordTrait
 * @package DevCoder\Authentication\Core
 */
trait PasswordTrait
{
    private $cost = 10;

    public function cryptPassword(string $plainPassword): string
    {
        return password_hash($plainPassword, PASSWORD_BCRYPT, ['cost' => $this->cost]);
    }

    public function isPasswordValid(UserInterface $user, string $plainPassword): bool
    {
        return password_verify($plainPassword, $user->getPassword());
    }

    public function setCost(int $cost): void
    {
        if ($cost < 4 || $cost > 12) {
            throw new \InvalidArgumentException('Cost must be in the range of 4-31.');
        }
        $this->cost = $cost;
    }
}
Enter fullscreen mode Exit fullscreen mode

UserManager to Manage User

<?php

namespace DevCoder\Authentication\Core;

use DevCoder\Authentication\Token\UserToken;
use DevCoder\Authentication\Token\UserTokenInterface;
use DevCoder\Authentication\UserInterface;

/**
 * Class UserManager
 * @package DevCoder\Authentication\Core
 */
class UserManager implements UserManagerInterface
{

    use PasswordTrait;

    public function __construct()
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
    }

    public function getUserToken(): ?UserTokenInterface
    {
        $userToken = null;
        if ($this->hasUserToken()) {
            $userToken = unserialize($_SESSION[UserTokenInterface::DEFAULT_PREFIX_KEY]);
        }

        return $userToken;
    }

    public function hasUserToken(): bool
    {
        $key = UserTokenInterface::DEFAULT_PREFIX_KEY;
        return (array_key_exists($key, $_SESSION) && unserialize($_SESSION[$key]) !== false);
    }

    public function isGranted(array $roles): bool
    {
        if (!is_null($userToken = $this->getUserToken())) {
            return false;
        }

        if ($userToken->getUser() instanceof UserInterface) {
            return (!empty(array_intersect($roles, $userToken->getUser()->getRoles())));
        }

        return false;
    }

    public function createUserToken(UserInterface $user): UserTokenInterface
    {
        $userToken = new UserToken($user);
        $_SESSION[UserTokenInterface::DEFAULT_PREFIX_KEY] = $userToken->serialize();

        return $userToken;
    }

    public function logout(): void
    {
        if ($this->hasUserToken()) {
            unset($_SESSION[UserTokenInterface::DEFAULT_PREFIX_KEY]);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

UserToken to get current User

<?php


namespace DevCoder\Authentication\Token;


use DevCoder\Authentication\UserInterface;

/**
 * Class UserToken
 * @package DevCoder\Authentication\Token
 */
class UserToken implements UserTokenInterface
{
    /**
     * @var UserInterface
     */
    private $user;

    public function __construct(UserInterface $user)
    {
        $this->user = $user;
    }

    public function getUser(): UserInterface
    {
        return $this->user;
    }

    public function serialize(): string
    {
        return serialize($this);
    }
}

Enter fullscreen mode Exit fullscreen mode

User class

<?php


namespace DevCoder\Authentication;

class User implements UserInterface
{

    /**
     * @var string
     */
    private $userName;

    /**
     * @var string
     */
    private $password;

    /**
     * @var array
     */
    private $roles = [];

    /**
     * @var bool
     */
    private $enabled = true;

    /**
     * @return null|string
     */
    public function getUsername(): ?string
    {
        return $this->userName;
    }

    /**
     * @return null|string
     */
    public function getPassword(): ?string
    {
        return $this->password;
    }

    /**
     * @return array
     */
    public function getRoles(): array
    {
        return $this->roles;
    }

    /**
     * @return bool
     */
    public function isEnabled(): bool
    {
        return $this->enabled;
    }

    /**
     * @param string $userName
     * @return User
     */
    public function setUserName(string $userName): self
    {
        $this->userName = $userName;
        return $this;
    }

    /**
     * @param string $password
     * @return User
     */
    public function setPassword(string $password): self
    {
        $this->password = $password;
        return $this;
    }

    /**
     * @param array $roles
     * @return User
     */
    public function setRoles(array $roles): self
    {
        $this->roles = $roles;
        return $this;
    }

    /**
     * @param bool $enabled
     * @return User
     */
    public function setEnabled(bool $enabled): self
    {
        $this->enabled = $enabled;
        return $this;
    }
}

Enter fullscreen mode Exit fullscreen mode

How to use ?

Registration

<?php

use DevCoder\Authentication\Core\UserManager;
use DevCoder\Authentication\User;

// register
$userManager = new UserManager();

$password = $userManager->cryptPassword($_POST['password']);
$user = (new User())
    ->setUserName($_POST['username'])
    ->setPassword($password)
    ->setRoles(['ROLE_USER']);

$userManager->createUserToken($user);

// check Token in Session
var_dump($userManager->getUserToken());
// object(DevCoder\Authentication\Token\UserToken)[4]
//  private 'user' => 
//    object(DevCoder\Authentication\User)[5]
//      private 'userName' => string 'username' (length=8)
//      private 'password' => string '$2y$10$iWdcmebmikUFlgKMqW7/rOmUp1DjFAuWKqdUHBhL08FZ7LL6bwRey' (length=60)
//      private 'roles' => 
//        array (size=1)
//          0 => string 'ROLE_USER' (length=9)
//      private 'enabled' => boolean true

Enter fullscreen mode Exit fullscreen mode

Connected or not

<?php

use DevCoder\Authentication\Core\UserManager;
use DevCoder\Authentication\User;

$userManager = new UserManager();
if ($userManager->hasUserToken()) {
    // connected

    $token = $userManager->getUserToken();
    $user = $token->getUser();
    var_dump($user);
// object(DevCoder\Authentication\User)[5]
//  private 'userName' => string 'username' (length=8)
//  private 'password' => string '$2y$10$OBobeLhdvdiftuedlv1a6e4.qF6sCG/usq5WEV4E3uB.UiS1egv/m' (length=60)
//  private 'roles' => 
//    array (size=1)
//      0 => string 'ROLE_USER' (length=9)
//  private 'enabled' => boolean true

}else {
    // not connected
}
Enter fullscreen mode Exit fullscreen mode

Access management

$userManager = new UserManager();
if ($userManager->isGranted(['ROLE_ADMIN'])) {
    //is admin
    // return Response 200
}else {
    //is not admin
    // return Response 403
}
Enter fullscreen mode Exit fullscreen mode

Logout

$userManager = new UserManager();
$userManager->logout();
Enter fullscreen mode Exit fullscreen mode

Login

<?php

use DevCoder\Authentication\Core\UserManager;

$stmt = $pdo->prepare("SELECT * FROM users WHERE username=?");
$stmt->execute([$_POST['username']]);
$userFromDataBase = $stmt->fetch();
/**
 * Hydration
 */
$user = (new \Test\DevCoder\Authentication\User())
    ->setUserName($userFromDataBase['username'])
    ->setPassword($userFromDataBase['password'])
    ->setRoles(json_decode($userFromDataBase['roles']))
    ->setEnabled($userFromDataBase['active']);

$userManager = new UserManager();
if ($userManager->isPasswordValid($user, $_POST['password'])) {

    // login OK, set Token in session
    $userManager->createUserToken($user);

}else {
 // login failed , return error
}
Enter fullscreen mode Exit fullscreen mode

Ideal for small project
Simple and easy!
https://github.com/devcoder-xyz/php-user-authentication

Top comments (4)

Collapse
 
beatt108 profile image
František Heča

This is awesome. Thanks a lot for sharing. I am not sure how to validate submited username and password with a User database columns. Can you please provide an example of this password validation, please?

Collapse
 
fadymr profile image
F.R Michel

i add login exemple with password validation

Collapse
 
bogkonstantin profile image
Konstantin Bogomolov • Edited

Why you decided to use trait here?

Collapse
 
fadymr profile image
F.R Michel

It Can be use anywhere to check password or crypte password ( example In a Controller ) without call UserManager if you want and I don't want to have a class with a lot of lines so that it is easily maintainable.