DEV Community

loading...
Cover image for PHP - Create your own PHP Authentication

PHP - Create your own PHP Authentication

fadymr profile image F.R Michel Updated on ・3 min read

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

Discussion (4)

pic
Editor guide
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 Author

i add login exemple with password validation

Collapse
bogkonstantin profile image
Konstantin Bogomolov

Why you decided to use trait here?

Collapse
fadymr profile image
F.R Michel Author

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.