Skip to content

Phalcon Auth - Authentication based guards and providers adapters

License

Notifications You must be signed in to change notification settings

sinbadxiii/phalcon-auth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Phalcon Auth

Banner

You can see an example of an application with authentication here sinbadxiii/phalcon-auth-example

Software License Packagist Downloads Latest Version

Extended guards

Phalcon version

Unfortunately version 2 of the library no longer supports Phalcon 4.

Phalcon 3 Phalcon 4 Phalcon 5 Phalcon 6
✔️

PHP are supported

^7.4-8.1.

Install

Require the project using composer:

composer require "sinbadxiii/phalcon-auth:^v2.0.0"

Introduction

Banner

Phalcon Auth позволит вам создать систему аутентификации в вашем веб-приложении.

Система аутентификации имеет такие понятия как «Охранники» (Guard) и «Поставщики» (Provider), охранники определяют, как пользователи будут аутентифицироваться, например, используя стандартные Хранилища Сессии и файлов куки.

Провайдеры определяют, какие данные будут браться в качестве пользователей, и так же откуда будут извлекаться эти пользователи. Откуда будут извлекаться данные пользователей определяют Адаптеры (Adapter). Обычно это Phalcon\Mvc\Model и построитель запросов к базе данных.

Кроме того есть другие варианты адаптеров: файл или массив с данными. Можно создать свой адаптер, реализуя интерфейс адаптера. Об этом поговорим чуть позже.

Guards и Providers не следует путать с «roles» и «permissions» ACL. Auth и ACL следует использовать вместе, если требуется более точная надстройка доступа к узлам приложения. Например использовать роль manager со специфическими правами.

Быстрый старт

Полностью пример готового приложения с аутентификацией доступен по адресу sinbadxiii/phalcon-auth-example. Это типовой проект на Phalcon, который можно использовать как старт нового приложения, либо же просто ознакомиться с возможностями аутентификации на примере данного приложения.

Логика работы

Общий принцип работы аутентификации заключается в том, что пользователь вводит свое имя пользователя и пароль через форму входа. Если эти учетные данные верны, приложение сохранит информацию об аутентифицированном пользователе в сессии пользователя и будет считать пользователя "аутентифицированным". В случае использования "Запомнить меня" может быть создан файл cookie, который содержит идентификатор сессии, чтобы последующие запросы к приложению могли быть связаны с нужным пользователем. После получения идентификатора сессии из файла cookie приложение извлечет данные пользователя.

Возьмем другой случай, когда удаленному сервису необходимо пройти аутентификацию для доступа к API, обычно файлы cookie не используются для аутентификации, поскольку веб-браузер отсутствует. Вместо этого удаленная служба отправляет токен API при каждом запросе. Приложение может проверить входящий токен по таблице действительных токенов API и "аутентифицировать" запрос как выполненный пользователем, связанным с этим токеном API.

Подготовка базы данных

Для использования данных из бд, понадобится создать таблицу users.

Если необходимо будет использовать функцию "Запомнить меня" - RememberMe, которая позволяет хранить сеанс аутентификации пользователя длительное время, то так же понадобится таблица users_remember_tokens, ну и соответственно ее модель в виде App\Models\RememberToken.

Для быстрого создания таблиц вы можете импортировать файлы из папки db/users.sql, db/users_remember_tokens.sql, а так же db/create_auth_token_users.sql, если будете использовать в качестве Guard - Token, которому необходимо поле auth_token для корректной работы.

Managers

При создании аутентификации вы можете воспользоваться одним из двух менеджеров: Sinbadxiii\PhalconAuth\Manager или Sinbadxiii\PhalconAuth\ManagerFactory.

Manager

Если вы строго придерживаетесь философии фреймворка Phalcon и хотите вручную настроить все компоненты аутентификации, то вам понадобится класс Sinbadxiii\PhalconAuth\Manager - с помощью данного менеджера можно настроить охранника, адаптер поставщиков и распределить доступы пользователям.

use Sinbadxiii\PhalconAuth\Manager;
use App\Models\User;
use Sinbadxiii\PhalconAuth\Adapter\Model;
use Sinbadxiii\PhalconAuth\Guard\Session;

$auth = new Manager();

$configAdapter = [
    'model' => User::class,
];

$adapter = new Model($this->getSecurity(), $configAdapter);
$guard   = new Session(
    $adapter,
    $this->getSession(),
    $this->getCookies(),
    $this->getRequest(),
    $this->getEventsManager()
);

$auth->addGuard("web", $guard, true);

return $auth;

В результате получился менеджер, который будет искать пользователей через модель User в таблице базе данных users. Результат аутентификации будет храниться в сессии, и куках, если выбрать "Запомнить меня". В качестве других аргументов нужно передать сервис провайдеры $this->security, $this->session, $this->cookies, $this->request, $this->eventsManager, которые будут необходимы при дальнейшем использовании охранника и адаптера поставщиков.

  • public addGuard(string $nameGuard, GuardInterface $guard, bool $isDefault = false) - добавить охранника
  • public guard(?string $name = null) - получить конкретного охранника или по заданного по дефолту
  • public setDefaultGuard(GuardInterface $guard) - задать охранника по дефолту
  • public getDefaultGuard() - получить охранника по дефолту
  • public access(string $accessName) - назначить контроллеру определенный доступ
  • public getAccess(string $accessName) - назначить требуемый доступ
  • public setAccess(AccessInterface $access) - получить требуемый доступ
  • public setAccessList(array $accessList) - зарегистрировать список доступов
  • public addAccessList(array $accessList) - добавить список доступов
  • public except(...$actions) - исключенные экшны из проверки доступа
  • public only(...$actions) - обязательные экшны для проверки доступа
  • public __call() - magic __call

Guards

На данный момент существует два вида Охранников, которые покроют 90% типовых задач создания аутентификации веб-приложений. Это Sinbadxiii\PhalconAuth\Guard\Session и Sinbadxiii\PhalconAuth\Guard\Token, указывая одного из этих охранников вы выбираете, что будете использовать в своем приложении, аутентификацию на основе сессий или токена.

Предположительно Сессии вы будете использовать в веб-приложениях после логина в личный кабинет,а Токен, например, в микро приложениях в качестве api сервисов. Но ничего вам не мешает применять или комбинировать охранников в нестандартных приложениях.

Session Guard

use Sinbadxiii\PhalconAuth\Manager;
use App\Models\User;
use Sinbadxiii\PhalconAuth\Adapter\Model;
use Sinbadxiii\PhalconAuth\Guard\Session;

$auth = new Manager();

$configAdapter = [
    'model' => User::class,
];

$adapter = new Model($this->getSecurity(), $configAdapter);
$guard   = new Session(
    $adapter,
    $this->getSession(),
    $this->getCookies(),
    $this->getRequest(),
    $this->getEventsManager()
);

$auth->addGuard("web", $guard, true);

return $auth;
  • public function __construct(AdapterInterface $adapter, SessionManagerInterface $session, Cookies $cookies, Request $request, EventsManagerInterface $eventsManager)
  • public function attempt(array $credentials = [], $remember = false) - попытка аутентификации
  • public function user() - получить аутентифицированного пользователя
  • public function validate(array $credentials = []) - валидация входных данных
  • public function getName() - получение имени сессии
  • public function getRememberName() - имя куки при запомнить меня
  • public function login(AuthenticatableInterface $user, bool $remember = false) - логин экземпляра пользователя
  • public function loginById($id, bool $remember = false) - логин по Id пользователя
  • public function once(array $credentials = []) - логин без сохранения пользователя в сессию
  • public function logout() - выход
  • public function getLastUserAttempted() - получение последнего попытавшегося залогиниться пользователя
  • public function viaRemember() - проверка что пользователь был вытащен из Запомнить меня
  • public function getUser() - получить пользователя
  • public function setRequest(Request $request)
  • public function setSession(SessionManagerInterface $session)
  • public function setCookies(Cookies $cookies)
  • public function getAdapter() - получить адаптер поставщика
  • public function setAdapter(AdapterInterface $adapter) - назначить адаптера поставшика

Basic

  • public function basic(string $field = 'email', array $extraConditions = []) - аутентификация через Basic Auth
  • public function onceBasic(string $field = 'email', array $extraConditions = []) - аутентификация через Basic Auth без сохранения в сессию

Token Guard

Чтобы воспользоваться Sinbadxiii\PhalconAuth\Guard\Token, необходимо в качестве второго аргумента передать конфиг с названиями имя параметра запроса и поля в хранилище данных пользователей, например, поле таблицы users в бд:

[
    ... 
    'inputKey'   => 'auth_token', //имя параметра с токеном
    'storageKey' => 'auth_token', //имя поля в хранилище пользователей
    ...
]
use Sinbadxiii\PhalconAuth\Manager;
use App\Models\User;
use Sinbadxiii\PhalconAuth\Adapter\Model;
use Sinbadxiii\PhalconAuth\Guard\Token;

$auth = new Manager();

$configAdapter = [
    'model' => User::class,
];

$configGuard = [
    'inputKey'   => 'auth_token',
    'storageKey' => 'auth_token',
];

$adapter = new Model($this->getSecurity(), $configAdapter);
$guard   = new Token(
    $adapter,
    $configGuard
    $this->getRequest()
);

$auth->addGuard("api", $guard, true);

return $auth;

Соответствено GET запрос должен будет иметь вид:

//GET
https://yourapidomain/api/v2/users?auth_token=fGaYgdGPSfEgT41r3F4fg33

POST запрос:

//POST
//params POST request
[
  "auth_token": "fGaYgdGPSfEgT41r3F4fg33"
]

https://yourapidomain/api/v2/users

или заголовок Authorization:

Authorization: Bearer fGaYgdGPSfEgT41r3F4fg33

https://yourapidomain/api/v2/users

Помните, что каждый ваш запрос к приложению, должен сопровождаться параметром auth_token с токеном доступа.

  • public function __construct(AdapterInterface $adapter, array $config, Request $request)
  • public function user() - аутентифицированный пользователь
  • public function validate(array $credentials = []) - валидация
  • public function getTokenForRequest() - поулчить токен из запросов (GET, POST, Headers)
  • public function setRequest(Request $request)
  • public function getRequest()
  • public function getAdapter()
  • public function setAdapter(AdapterInterface $adapter)

Создание своего Охранника

<?php

declare(strict_types=1);

namespace Sinbadxiii\PhalconAuth\Guard;

use Sinbadxiii\PhalconAuth\AuthenticatableInterface;

interface GuardInterface
{
    public function check(): bool;
    public function user();
    public function setUser(AuthenticatableInterface $user);
    public function id();
    public function guest(): bool;
    public function validate(array $credentials = []): bool;
}

Реализуя интерфейс Sinbadxiii\PhalconAuth\Guard\GuardInterface вы можете создать своего Guard.

Access

С помощью Доступов (Access) вы можете задавать и проверять требуемый доступ к тем или иным областям приложения, например в контроллер профиля пользователя разрешен доступ только аутентифицированным пользователям.

<?php

declare(strict_types=1);

namespace App\Controllers;

class ProfileController extends ControllerBase
{
    public function onConstruct()
    {
        $this->auth->access("auth");
    }

    public function indexAction()
    {
    }
}

В то время как к контроллеру регистрации, например, нужен доступ только неаутентифицированным пользователям - гостям:

<?php

declare(strict_types=1);

namespace App\Controllers;

class RegisterController extends ControllerBase
{
    public function onConstruct()
    {
        $this->auth->access("guest");
    }

    public function indexAction()
    {
    }
}

Задается доступ в конструкторе контроллера onConstruct().

Из коробки есть два основных вида доступа - аутентифицированный и гостевой:

  • Sinbadxiii\PhalconAuth\Access\Auth
  • Sinbadxiii\PhalconAuth\Access\Guest

Если доступ удовлетворяет условию в методе allowIf, то дается разрешение на дальнейшее использование контроллера, например в дефолтном auth условием является:

class Auth extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->check()) {
            return true;
        }

        return false;
    }
}

$this->auth->check() - проверка на аутентификацию пользователя, т.е. чтобы получить доступ к $this->auth->access('auth') нужно быть аутентифицированным, а вот условие у $this->auth->access('guest') прямо противоположно:

<?php

namespace Sinbadxiii\PhalconAuth\Access;

/**
 * Class Guest
 * @package Sinbadxiii\PhalconAuth\Access
 */
class Guest extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->guest()) {
            return true;
        }

        return false;
    }
}

В случае если метод allowedIf() вернет true, то пользователь сможет идти дальше, если же результат будет равен false, то сработает метод неудачи redirectTo(), и приложение перенаправит пользователя, т.к. у каждого приложение логика перенаправлений может быть разная, то вам следует создать свои классы Access auth и guest, наследовав от дефолтных классов и переопределить метод redirectTo():

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\Auth as AuthAccess;;

class Auth extends AuthAccess
{
    public function redirectTo()
    {
        if (isset($this->response)) {
            return $this->response->redirect("/login")->send();
        }
    }
}

и

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\Guest as GuestAccess;

class Guest extends GuestAccess
{
    public function redirectTo()
    {
        if (isset($this->response)) {
            return $this->response->redirect("/profile")->send();
        }
    }
}

Чтобы создать свой Access, можно имплементировать интерфейс Sinbadxiii\PhalconAuth\Access\AccessInterface:

<?php

namespace Sinbadxiii\PhalconAuth\Access;

/**
 * Interface for Sinbadxiii\PhalconAuth\Access
 */
interface AccessInterface
{
    public function setExceptActions(...$actions): void;
    public function setOnlyActions(...$actions): void;
    public function isAllowed(): bool;
    public function redirectTo();
    public function allowedIf(): bool;
}

либо просто наследовав абстрактный класс Sinbadxiii\PhalconAuth\Access\AbstractAccess для более быстрого и гибкого использования кастомных доступов, например, давайте создадим доступ для пользователей, имеющих роль админа:

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;

class Admin extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($user = $this->auth->user() and $user->getRole() === "admin") {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        if (isset($this->response)) {
            return $this->response->redirect("/admin-login")->send();
        }
    }
}

или пример проверки доступа для Http Basic Auth:

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;
use Sinbadxiii\PhalconAuth\Exception;

class AuthWithBasic extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->basic("email")) {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        throw new Exception("Basic: Invalid credentials.");
    }
}

Регистрация доступов

Доступы должны быть зарегистрированы в системе аутентификации, если этого не сделать, то при запросе доступа $this->auth->access("auth") будет выдаваться ошибка, типа: Access with 'auth' name is not included in the access list.

Чтобы зарегистрировать доступы в системе, необходимо создать некоторое промежуточное программное обеспечение, подтипа middleware и прикрепить его к dispatcher приложения.

Минимальный вид класса App\Security\Authenticate будет таков:

<?php

declare(strict_types=1);

namespace App\Security;

use App\Security\Access\Auth;
use App\Security\Access\Guest;
use Sinbadxiii\PhalconAuth\Access\Authenticate as AuthMiddleware;

/**
 * Class Authenticate
 * @package App\Security
 */
class Authenticate extends AuthMiddleware
{
    protected array $accessList = [
        'auth'   => Auth::class,
        'guest'  => Guest::class
    ];
}

А затем необходимо прикрепить его к сервиc-провайдеру dispatcher:

$di->setShared('dispatcher', function () use ($di) {

    $dispatcher = new Phalcon\Mvc\Dispatcher();
    $eventsManager = $di->getShared('eventsManager');
    $eventsManager->attach('dispatch', new App\Security\Authenticate());
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;
});

Свойство $accessList позволяет быстро добавлять новые уровни доступа в приложении, например, чтобы добавить новый доступ admin, достаточно создать класс с условием и добавить его в список $accessList:

<?php

declare(strict_types=1);

namespace App\Security;

use App\Security\Access\Auth;
use App\Security\Access\Admin;
use App\Security\Access\Guest;
use Sinbadxiii\PhalconAuth\Access\Authenticate as AuthMiddleware;

/**
 * Class Authenticate
 * @package App\Security
 */
class Authenticate extends AuthMiddleware
{
    protected array $accessList = [
        'auth'   => Auth::class,
        'guest'  => Guest::class,
        'admin'  => Admin::class,
    ];
}

Так же список доступов можно зарегистрировать непосредственно в Manager при создании сервис провайдера с помощью метода setAccessList():

$authManager =  new Sinbadxiii\PhalconAuth\Manager();

$authManager->setAccessList(
    [
        'auth'   => App\Security\Access\Auth::class,
        'guest'  => App\Security\Access\Guest::class,
        'admin'  => App\Security\Access\Admin::class,
    ];
);
    
return $authManager;

Поставщики (Providers) и Адаптеры (Adapters)

Как уже было сказано ранее поставщики определяют какие сущности будут являться пользователями, например users или contacts, все зависит от контекста вашего приложения.

В настоящий момент существуют три вида адаптера:

  • Sinbadxiii\PhalconAuth\Adapter\Model
  • Sinbadxiii\PhalconAuth\Adapter\Stream
  • Sinbadxiii\PhalconAuth\Adapter\Memory

Модель, файл и массив с данными в приложении. Все адаптеры наследуются от абстрактного класса Sinbadxiii\PhalconAuth\Adapter\AbstractAdapter, который имеет:

  • public setModel(AuthenticatableInterface $model)` - назначить модель поставщика
  • public getModel()` - получить модель поставщика
  • public setConfig(array $config)` - установить конфиг
  • public getConfig()` - получить конфиг адаптера

Адаптер поставщика Model

Для использования адаптера Model нам понадобится модель пользователя, например App\Models\User::class вида:

<?php

namespace App\Models;

use Phalcon\Mvc\Model;

class User extends Model
{
    public $id;
    public $username;
    public $name;
    public $email;
    public $password;
    public $published;
    public $created_at;
    public $updated_at;

    public function initialize()
    {
        $this->setSource("users");
    }
}

Чтобы при использовании в нашем приложении, не выдавалась ошибка:

PHP Fatal error: Uncaught TypeError: Sinbadxiii\PhalconAuth\Adapter\Model::validateCredentials(): Argument #1 ($user) must be of type Sinbadxiii\PhalconAuth\AuthenticatableInterface.

Т.е. модель User надо имплементировать от Sinbadxiii\PhalconAuth\AuthenticatableInterface, а если хочется использовать возможности функции RememberMe (Запомнить меня), то так же надо будет наследовать интерфейс Sinbadxiii\PhalconAuth\RememberingInterface: В конечном счете, ваша модель должна иметь класс следующего вида:

<?php

namespace App\Models;

use Phalcon\Encryption\Security\Random;
use Phalcon\Mvc\Model;
use Sinbadxiii\PhalconAuth\RememberingInterface;
use Sinbadxiii\PhalconAuth\AuthenticatableInterface;
use Sinbadxiii\PhalconAuth\RememberTokenInterface;

class User extends Model implements AuthenticatableInterface, RememberingInterface
{
    public $id;
    public $username;
    public $name;
    public $email;
    public $password;
    public $published;
    public $created_at;
    public $updated_at;

    public function initialize()
    {
        $this->setSource("users");

        $this->hasOne(
            'id',
            RememberToken::class,
            'user_id',
            [
                'alias' => "remember_token"
            ]
        );
    }

    public function setPassword(string $password)
    {
        $this->password = $this->getDI()->get("security")->hash($password);
        return $this;
    }

    public function getAuthIdentifier()
    {
        return $this->id;
    }

    public function getAuthPassword()
    {
        return $this->password;
    }

    public function getRememberToken(string $token = null): ?RememberTokenInterface
    {
        return $this->getRelated('remember_token', [
            'token=:token:',
            'bind' => ['token' => $token]
        ]);
    }

    public function setRememberToken(RememberTokenInterface $value)
    {
        $this->remember_token = $value;
    }

    public function createRememberToken(): RememberTokenInterface
    {
        $random = new Random();

        $token = $random->base64(60);

        $rememberToken = new RememberToken();
        $rememberToken->token = $token;
        $rememberToken->user_agent = $this->getDI()->get("request")->getUserAgent();
        $rememberToken->ip =  $this->getDI()->get("request")->getClientAddress();

        $this->setRememberToken($rememberToken);
        $this->save();

        return $rememberToken;
    }
}

Модель App\Models\RememberToken будет иметь вид:

<?php

declare(strict_types=1);

namespace App\Models;

use Phalcon\Mvc\Model;
use Sinbadxiii\PhalconAuth\RememberTokenInterface;

class RememberToken extends Model implements  RememberTokenInterface
{
    /**
     * @var integer
     */
    public $id;

    /**
     * @var integer
     */
    public $user_id;

    /**
     * @var string
     */
    public $token;

    /**
     * @var string
     */
    public $ip;

    /**
     * @var string
     */
    public $user_agent;

    /**
     * @var integer
     */
    public $created_at;

    /**
     * @var integer
     */
    public $updated_at;

    /**
     * @var integer
     */
    public $expired_at;

    public function initialize()
    {
        $this->setSource("users_remember_tokens");
    }

    public function getToken(): string
    {
        return $this->token;
    }

    public function getUserAgent(): string
    {
        return $this->user_agent;
    }
    
      public function beforeValidationOnCreate()
    {
        $this->created_at = date(DATE_ATOM);
        $this->updated_at = date(DATE_ATOM);
        if (!$this->expired_at) {
            $this->expired_at = date(DATE_ATOM);
        }
    }

    public function beforeValidationOnSave()
    {
        if (!$this->created_at) {
            $this->created_at = date(DATE_ATOM);
        }
        if (!$this->expired_at) {
            $this->expired_at = date(DATE_ATOM);
        }
        $this->updated_at = date(DATE_ATOM);
    }

    public function beforeValidationOnUpdate()
    {
        $this->updated_at = date(DATE_ATOM);
    }
}

Интерфейс Sinbadxiii\PhalconAuth\AuthenticatableInterface имеет следущий вид:

<?php

namespace Sinbadxiii\PhalconAuth;

interface AuthenticatableInterface
{
    public function getAuthIdentifier();
    public function getAuthPassword();
}

а реализация "Запомнить меня" - Sinbadxiii\PhalconAuth\RememberingInterface:

<?php

namespace Sinbadxiii\PhalconAuth;

interface RememberingInterface
{
    public function getRememberToken(): ?RememberTokenInterface;
    public function createRememberToken(): RememberTokenInterface;
}

Теперь можно использовать модель при создании менеджера:

    use Sinbadxiii\PhalconAuth\Adapter\Model;
    use Sinbadxiii\PhalconAuth\Guard\Session;
    use Sinbadxiii\PhalconAuth\Manager;

    $security = $this->getSecurity();

    $adapter = new Model($security);
    $adapter->setModel(App\Models\User::class);
    $guard   = new Session(
        $adapter,
        $this->getSession(),
        $this->getCookies(),
        $this->getRequest(),
        $this->getEventsManager()
    );


    $manager = new Manager();
    $manager->addGuard("web", $guard);
    
    $manager->setDefaultGuard($guard);

    return $manager;

Адаптер поставщика memory

Используя setData() можно задать массив данных с пользователями, который имеет вид:

[
    ["username" =>"admin", "name" => "admin", 'password' => 'admin', "email" => "[email protected]"],
    ["username" => "user", "name" => "user", 'password' => 'user', "email" => "[email protected]"],
]
$di->setShared("auth", function () {

    $security = $this->getSecurity();

    $data = [
        ["auth_token" => '1', "name" => "admin", "username" => "admin", 'password' => 'admin', "email" => "[email protected]"],
        ["auth_token" => '2',  "name" => "admin1", "username" => "admin", 'password' => 'admin1', "email" => "[email protected]"],
    ];

    $adapter     = new \Sinbadxiii\PhalconAuth\Adapter\Memory($security);
    $adapter->setModel(App\Models\UserSimple::class);
    $adapter->setData($data);
    
    $configGuard = [
        'inputKey'   => 'auth_token',
        'storageKey' => 'auth_token',
    ];

    $guard = new \Sinbadxiii\PhalconAuth\Guard\Token(
        $adapter,
        $configGuard,
        $this->getRequest()
    );
    
    $manager = new Manager();
    $manager->addGuard("api", $guard, true);

    return $manager;
});
  • public setData(array $data) - массив с данными
  • public getData() - получить массив с данными

Адаптер поставщика Stream

Если взять в качестве адаптера поставщиков users не Sinbadxiii\PhalconAuth\Adapter\Model, а файл Sinbadxiii\PhalconAuth\Adapter\Stream: то необходимо будет задать источник файла формата json, например, users.json, который имеет вид:

[
  {"name":"admin", "username":"admin", "password": "admin","email": "[email protected]"},
  {"name":"user", "username":"user", "password": "user","email": "[email protected]"}
]

Возвращаемый пользователь в виде модели App\Models\UserSimple должен будет реализовывать интерфейс Sinbadxiii\PhalconAuth\AuthenticatableInterface, например:

<?php

namespace App\Models;

use Phalcon\Auth\AuthenticatableInterface;

class UserSimple implements AuthenticatableInterface
{
    /**
     *
     * @var integer
     */
    public $id;

    /**
     *
     * @var string
     */
    public string $username;

    /**
     *
     * @var string
     */
    public string $name;

    /**
     *
     * @var string
     */
    public string $email;

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

    /**
     *
     * @var integer
     */
    public $published;

    public function __construct($data)
    {
        foreach ($data as $field => $value) {
            $this->$field = $value;
        }
    }

    /**
     * @return int
     */
    public function getAuthIdentifier(): mixed
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getAuthPassword(): string
    {
        return $this->password;
    }
}

но следует учитывать, что нельзя будет использовать функцию RememberMe (Запомнить меня), т.к. Stream не имплементирует интерфейс Sinbadxiii\PhalconAuth\RememberingInterface ввиду отсутствия возможности сохранить токен сессии в файлом хранилище пользователей (что не мешает вам реализовать эту функцию в своем охраннике на основе хранилища в файле).

    $security = $this->getSecurity();

    $adapter  = new \Sinbadxiii\PhalconAuth\Adapter\Stream($security);
    $adapter->setModel(App\Models\UserSimple::class);
    $adapter->setFileSource(__DIR__ . "/users.json");

    $guard = new \Sinbadxiii\PhalconAuth\Guard\Session(
        $adapter,
        $this->getSession(),
        $this->getCookies(),
        $this->getRequest(),
        $this->getEventsManager()
    );

    $manager = new Manager();
    $manager->addGuard("web", $guard, true); //третий аргумент - назначить охранника по дефолту

    return $manager;
  • public setFileSource(string $pathSrcFile) - указать путь к файлу
  • public getFileSource() - получить путь к файлу
  • public setData(array $data) - массив с данными пользователей
  • public getData() - получить массив с данными пользователей

Не рекомендуется использовать адаптеры stream и memory в реальных приложениях из-за их функциональной ограниченности и сложности управления пользователями. Это может быть полезно в прототипах приложений и для ограниченных приложений, которые не хранят пользователей в базах данных.

Создание своего адаптера поставщика

Интерфейс адаптера поставщика Sinbadxiii\PhalconAuth\Adapter\AdapterInterface имеет вид:

<?php

namespace Sinbadxiii\PhalconAuth\Adapter;

use Sinbadxiii\PhalconAuth\AuthenticatableInterface;

interface AdapterInterface
{
    public function findFirstByCredentials(array $credentials);
    public function findFirstById($id);
    public function validateCredentials(AuthenticatableInterface $user, array $credentials): bool;
}

Так же для создания функционала "Запомнить меня" нужна реализация интерфейса Sinbadxiii\PhalconAuth\Adapter\AdapterWithRememberTokenInterface:

<?php

declare(strict_types=1);

namespace Sinbadxiii\PhalconAuth\Adapter;

use Sinbadxiii\PhalconAuth\AuthenticatableInterface;
use Sinbadxiii\PhalconAuth\RememberingInterface;
use Sinbadxiii\PhalconAuth\RememberTokenInterface;

interface AdapterWithRememberTokenInterface
{
    public function findFirstByToken($identifier, $token, $user_agent): ?AuthenticatableInterface;
    public function createRememberToken(RememberingInterface $user): RememberTokenInterface;
}

Manager Factory

Sinbadxiii\PhalconAuth\MangerFactory - это создание менеджера аутентификации с минимальными усилиями, если вы не хотите настраивать вручную менеджер аутентификации, а хотите быстро запустить сервис провайдер аутентификации, вы можете сделать это так:

$di->setShared('auth', function () { 
   
    $manager = new \Sinbadxiii\PhalconAuth\ManagerFactory();
    
    return $manager;
});

Все, дальше ManagerFactory сделает все за вас, на основе вашего конфигурационного файла. По умолчанию используется конфигурация из $this->config->auth, если вы хотите использовать другую конфигурацию, отличную от $this->config->auth то можно передать в качестве первого аргумента другой конфиг:

$di->setShared("auth", function () {
    $config = $this->getConfig()->auth_config_other;

    $manager = new \Sinbadxiii\PhalconAuth\ManagerFactory($config->toArray());

    return $manager;
});

Пример конфигурационного файла для использования Сессий

Итак, типичный пример конфигурационного файла приложения на основе Сессий. Файл может находится в папке конфигов config/auth.php или в глобальном файле config.php с доступом по ключу auth ($this->config->auth).

<?php
[
    'auth' => [
        'defaults' => [ //дефолтные значения
            'guard' => 'web'  //дефолтный охранник
        ],
        'guards' => [   //список охранников
            'web' => [          
                'driver' => 'session',   //драйвер сессия
                'provider' => 'users',   //поставщики users
            ],
        ],
        'providers' => [
            'users' => [
                'adapter' => 'model',  //адаптер поставщика users - model
                'model'  => App\Models\User::class,   //модель
            ]
        ]
    ],
];

Т.е. приложение будет использовать guard = web. В свою очередь Охранник web основан на драйвере session и использует поставщика пользователей users, которые извлекаются через Адаптера model - App\Models\Users. Данный конфигурационный файл позволяет создавать различные комбинации охранников и поставщиков, разделяя доступы в вашем приложений.

Пример конфигурационного файла для использования Токена

<?php
[
    'auth' => [
        'defaults' => [
            'guard' => 'api'
        ],
        'guards' => [
            'api' => [
                'driver' => 'token',
                'provider' => 'users',
                'inputKey' => 'auth_token', //опционально, по дефолту auth_token
                'storageKey' => 'auth_token', //опционально, по дефолту auth_token
            ],
        ],
        'providers' => [
            'users' => [
                'adapter' => 'model',
                'model'  => App\Models\User::class,
            ]
        ]
    ],
];

Расширить охранников можно с помощью extendGuard и передать в качестве аргумента имя охранника, используемого в конфиге jwt, а так же Closure с передачей аргументов в новый класс охранника, например:

$di->setShared('auth', function () {
    $auth = new \Sinbadxiii\PhalconAuth\ManagerFactory();    
    
    $request = $this->getRequest();

    $manager->extendGuard("jwt", function ($adapter, $config) use ($request) {
        return new JwtGuard($adapter, $config, $request);
    });
    
    return $auth;
});

Вы можете расширить список адаптеров с помощью метода extendProviderAdapter, например:

$di->setShared("auth", function () {
    $authManager =  new Phalcon\Auth\ManagerFactory();

    $authManager->extendProviderAdapter("mongo", function($security, $configProvider) {
        return new App\Security\Adapter\Mongo($security, $configProvider);
    });

    return $authManager;
});

Методы

Задать требуемый доступ к контроллеру

Метод access() позволит задать требуемый доступ к контроллеру, из коробки auth - для аутентифицированных, guest - для гостей.

$this->auth->access("auth") 
<?php

declare(strict_types=1);

namespace App\Controllers;

class ProfileController extends ControllerBase
{
    public function onConstruct()
    {
        $this->auth->access("auth");
    }

    public function indexAction()
    {
    }
}

Проверка аутентификации текущего пользователя

Чтобы определить, аутентифицирован ли пользователь, выполняющий входящий HTTP-запрос, вы можете использовать метод check(). Этот метод вернет true, если пользователь аутентифицирован:

$this->auth->check(); 
//check authentication

Например, вы можете проверить на странице формы входа, что если пользователь вошел в систему, то не показывать ему форму ввода:

public function loginFormAction()
{
    if ($this->auth->check()) { 
        //redirect to profile page 
        return $this->response->redirect(
            "/profile", true
        );
    }
}

Получение аутентифицированного пользователя

При обработке входящего запроса вы можете получить доступ к аутентифицированному пользователю с помощью метода user(). Результатом будет провайдер, указанный в конфигурации config->auth, в соответствии с интерфейсом Sinbadxiii\PhalconAuth\AuthenticatableInterface.

Вы также можете запросить идентификатор пользователя (ID) с помощью метода id():

$this->auth->user(); //get the user

$this->auth->id(); //get user id

Попытка аутентификации

Метод attempt() используется для обработки попыток аутентификации из формы входа в ваше приложение:

$username = $this->request->getPost("username");
$password = $this->request->getPost("password");

//attempt login with credentials
if ($this->auth->attempt(['username' => $username, 'password' => $password])) {

 //success attempt
 ...
}

//fail attempt

Метод attempt() принимает в качестве первого аргумента массив пар ключ/значение. Значения в массиве будут использоваться для поиска пользователя в таблице базы данных пользователей. Итак, в приведенном выше примере пользователь будет получен по значению столбца имени пользователя. Если пользователь найден, хешированный пароль, хранящийся в базе данных, будет сравниваться со значением пароля, переданным методу. Вы не должны хешировать значение входящего запроса пароля, так как пароль уже автоматически хэшируется, чтобы сравнить его с хешированным паролем в базе данных. Аутентифицированный сеанс будет запущен для пользователя, если хешированные пароли совпадают.

Помните, что пользователи из вашей базы данных будут запрашиваться на основе конфигурации «поставщика».

Метод attempt() вернет true, если аутентификация прошла успешно. В противном случае будет возвращено false.

Указание дополнительных учетных данных

Вы также можете добавить запрос дополнительных данные в дополнение к электронной почте/имени пользователя и паролю. Для этого просто добавьте условия запроса в массив, переданный методу attempt(). Например, мы можем проверить, является ли пользователь опубликованным is_published:

$username = $this->request->getPost("username");
$password = $this->request->getPost("password");

//attempt login with additional credentials
if ($this->auth->attempt(['username' => $username, 'password' => $password, 'is_published' => 1])) {

 //success attempt
 ...
}

//fail attempt

"Запомнить меня"

Если вы хотите обеспечить функциональность «запомнить меня» в своем приложении, вы можете передать логическое значение в качестве второго аргумента метода попытки.

Если это значение равно true, пользователь будет аутентифицироваться на неопределенный срок или до тех пор, пока пользователь не выйдет из системы вручную с помощью logout(). Таблица users_remember_tokens содержит столбец строки токена, который будет использоваться для хранения токена «запомнить меня»:

$username = $this->request->getPost("username");
$password = $this->request->getPost("password");
$remember = this->request->getPost('remember') ? true : false;

//attempt login with credentials and remember
if ($this->auth->attempt(['username' => $username, 'password' => $password], $remember)) {

 //success attempt
 ...
}

//fail attempt

Используйте метод viaRemember(), чтобы проверить, аутентифицирован ли пользователь с помощью файла cookie «запомнить меня»:

//use method viaRemember to check the user was authenticated using the remember me cookie
$this->auth->viaRemember();

Аутентифицировать пользовательский экземпляр

Если вам нужно установить существующий пользовательский экземпляр в качестве текущего аутентифицированного пользователя, вы можете передать пользовательский экземпляр методу login(). Данный пользовательский экземпляр должен быть реализацией Sinbadxiii\PhalconAuth\AuthenticatableInterface.

Этот метод аутентификации полезен, когда у вас уже есть действующий экземпляр пользователя, например, сразу после регистрации пользователя в вашем приложении:

$user = Users::findFirst(1);
// Login and Remember the given user
$this->auth->login($user, $remember = true);

Аутентифицировать пользователя по идентификатору

Для аутентификации пользователя с использованием первичного ключа записи в базе данных вы можете использовать метод loginById(). Этот метод принимает первичный ключ пользователя, которого вы хотите аутентифицировать:

//and force login user by id 
$this->auth->loginById(1, true);

Аутентифицировать пользователя один раз

Используя метод once() вы можете аутентифицировать пользователя в приложении для одного запроса. При вызове этого метода не будут использоваться сессии или файлы cookie:

//once auth without saving session and cookies
$username = $this->request->getPost("username");
$password = $this->request->getPost("password");
$this->auth->once(['username' => $username, 'password' => $password]);

Выход

Чтобы вручную разлогинить пользователя из вашего приложения, вы можете использовать метод logout(). После этого удалится вся информация об аутентификации из сессии пользователя, так что последующие запросы уже не будут аутентифицированы.

$this->auth->logout();
//log out user 

HTTP Basic Authentication

Базовая аутентификация HTTP обеспечивает быстрый способ аутентификации пользователей вашего приложения без настройки специальной страницы «входа в систему». Достаточно передать в заголовке Authorization, значение Basic и пары емейл (либо другое поле пользователя) и пароль, разделенные двоеточием и закодированые base64_encode()

Метод $this->auth->basic("email") позволит создать свой Access для использования доступа с помощью Auth Basic.

Аргумент email указывает на то, что поиск пользователя будет осуществляться по полям email и password. Указав другое поле, например username, поиск будет осуществляться по паре username и password:

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;
use Sinbadxiii\PhalconAuth\Exception;

class AuthWithBasic extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->basic("email")) {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        throw new Exception("Basic: Invalid credentials.");
    }
}

После запроса, в сессию запишется пользователь, и последующие запросы, могут уже не содержать пользовательские данные в заголовке Authorization, до тех пор пока сессия не "стухнет".

Basic HTTP-аутентификация без сохранения состояния

Вы можете использовать базовую аутентификацию HTTP без сохранения пользователя в сессии. Это в первую очередь полезно, если вы решите использовать HTTP-аутентификацию для аутентификации запросов к API вашего приложения. Для этого можно создать Access, который вызывает метод onceBasic(), например:

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;
use Sinbadxiii\PhalconAuth\Exception;

class AuthWithBasic extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->onceBasic("email")) {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        throw new Exception("Basic: Invalid credentials.");
    }
}

После запроса, ни куки, ни сессия не будут содержать данные о пользователе, и следущий запрос так же должен содержать пользовательские данные заголовка Authorization, иначе будет вызвано исключение Sinbadxiii\PhalconAuth\Exceptions

Events

Event Name Can stop operation?
beforeLogin Yes
afterLogin No
beforeLogout Yes
afterLogout No

License

The MIT License (MIT). Please see License File for more information.