adhocore / phalcon-ext

Foundations, adapters, extensions, middlewares and utilities for Phalcon v4
MIT License
46 stars 3 forks source link

Auth Middleware #38

Open trenggana-klb opened 5 years ago

trenggana-klb commented 5 years ago

Hi @adhocore, I really like this phalcon-ext and try to use for my new journey with phalcon I have question about how to implement the ApiAuth Middleware on micro app I already try it, but why always return Permission denied for all my defined route.

Here are my snippets code

config.php
--------------

// The permission scopes required for a route
        'scopes' => [
            /'validation' => 'user',
        ],
index.php
-------------
<?php

use PhalconExt\Http\Middlewares;
use PhalconExt\Http\Middleware\ApiAuth;
use PhalconExt\Http\Middleware\Cache;
use PhalconExt\Http\Middleware\Cors;
use PhalconExt\Http\Middleware\Throttle;
use Phalcon\Http\Response;
use Phalcon\Mvc\Micro as MicroApplication;
use Phalcon\Mvc\Micro\Collection;
use Phalcon\Mvc\Router;
use Vinculum\Controller\MicroController;

// Micro app
$di = require __DIR__ . '/../app/bootstrap.php';

$app = new MicroApplication($di);

$app->getRouter()->setUriSource(Router::URI_SOURCE_GET_URL);

$app->mount((new Collection)
        ->setPrefix('/')
        ->setHandler(MicroController::class, true)
        ->get('/', 'indexAction', 'home')
        ->get('validation', 'validationAction')
        ->post('api/auth', 'authAction')
);

$app->notFound(function () {
    $data = [
        'code' => 404,
        'status' => 'not_found',
        'message' => 'Page not found',
    ];
    $response = new Response();
    return $response->setJsonContent($data)->setStatusCode(404);
});

(new Middlewares([Throttle::class, ApiAuth::class, Cors::class, Cache::class]))->wrap($app);
adhocore commented 5 years ago

Thanks for using it and opening an issue. I will take a close look and get back to you soon.

adhocore commented 5 years ago

have you set up full config as in example?

also can you check if tests/Http/Middleware/ApiAuthTest.php can help you with usge?

trenggana-klb commented 5 years ago

I already set up full config like yours, the /api/auth endpoint to generate tokens/refresh is working but the uri in scopes that have to use ApiAuth is always return permission denied even there is a Bearer tokens.

One more thing, what is exactly the values for field scopes in users table?

adhocore commented 5 years ago

scope is the permission or role or both.

for example if you have configured an uri '/some/uri' with 'admin' scope then access to this uri is allowed if and only if the scope value in jwt token contains 'admin'

you can set the scope value somehow for user for which you have to authenticate using jwt. here is factory implementation: https://github.com/adhocore/phalcon-ext/blob/master/src/Factory/ApiAuthenticator.php

adhocore commented 4 years ago

Hello, were you able to use the middleware?

trenggana-klb commented 4 years ago

Hi @adhocore, I already try to run in with boilerplate like yours (Phalcon Micro) and everythings work well. Then I have another question, how to implement this (JWT Auth) with my current Phalcon MVC Apps that have Basic Auth, I want to switch it with your jwt-api-auth. My boss tell me to continue (change) what my colleague did. Here is the snippet, he used it as plugin:

<?php
/**
 * Description of SecurityPlugin
 *
 * @path    apps/plugins/SecurityPlugin.php
 *
 */

namespace Somenamespace\Plugin;

use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;

class SecurityPlugin extends BasePlugin
{
    public function beforeExecuteRoute(Event $event, Dispatcher $dispatcher)
    {
        $this->logRequest();

        $username = $this->request->getServer('PHP_AUTH_USER');
        $password = $this->request->getServer('PHP_AUTH_PW');
        $status = true;

        if (empty($username) || empty($password)) {
            $this->setResponseHttpCode(self::HTTP_CODE_400);
            $this->setResponseMessage('Username & Password cannot be empty.');
            $status = false;
        }

        $user = $this->auth($username, $password);

        if (!$user) {
            $this->setResponseHttpCode(self::HTTP_CODE_401);
            $status = false;
        } else {
            if (!$this->developerMode()) {
                $acl = $this->acl($dispatcher, $user->getId());

                if (!$acl) {
                    $this->setResponseHttpCode(self::HTTP_CODE_403);
                    $status = false;
                } else {
                    $this->session->set('auth', $user->toArray());
                    $this->session->set('resource', $acl->toArray());
                }
            }
        }

        if (!$status) {
            $this->generateResponse();

            return false;
        }
    }

    private function auth($username, $password)
    {
        $user = $this->getApiUser($username, $password);

        if (!$user) {
            $this->setResponseMessage('Username & Password is not valid.');

            return false;
        }

        if ($user->getActive() == 0) {
            $this->setResponseMessage('User is not active.');

            return false;
        }

        return $user;
    }

    private function acl($dispatcher, $apiUserId)
    {
        $module = $this->router->getModuleName();
        $controller = $this->router->getControllerName();
        $action = $dispatcher->getParam('action_method');
        $apiResources = $this->getApiResources($module, $controller, $action, $apiUserId);

        if (!$apiResources) {
            $this->setResponseMessage("You don't have permission to access");

            return false;
        }

        return $apiResources;
    }

    private function getApiUser($username, $password)
    {
        $password = md5($password);

        return \Somenamespace\Base\Model\ApiUser::findFirst([
            "username = '$username' AND password = '$password'",
            'cache' => $this->_getCacheKeyModel([
                'apiUser',
                $username,
                $password,
            ]),
        ]);
    }

    private function getApiResources($module, $controller, $action, $apiUserId)
    {
        return \Somenamespace\Base\Model\ApiResource::findFirst([
            "api_user_id = $apiUserId AND module = '$module' AND controller = '$controller' AND action = '$action'",
            'cache' => $this->_getCacheKeyModel([
                'apiResource',
                $module,
                $controller,
                $action,
                $apiUserId,
            ]),
        ]);
    }
}

Any idea? I really appreciate....

adhocore commented 4 years ago

first thing to understand is jwt auth is meant for programmable api consumers and not usual browser requests made by end user. but i think you can still find a way out with some not-so-neat hacks around. (like saving jwt into cookie after successful login, which again not a recommendation from me)

for this reason the current jwt auth support is explicitly supported for micro api auth (edit: should work for mvc as well when used as json api server) and i wanted to have another middleware for mvc in #8 but never finished that (sorry to say that not enough motivation and time for that)

trenggana-klb commented 4 years ago

Alrite, thank you very much then..... I really appreciate this package

adhocore commented 4 years ago

a new version of this package has been released https://github.com/adhocore/phalcon-ext/releases/tag/v0.0.9