zend-frameworkoauth-2.0zend-framework-mvczend-framework3zf3

Using OAuth2 and ZF3-MVC to protect REST API


I'm trying to get https://github.com/zfcampus/zf-oauth2 working with my ZF3-MVC Application (ok, one solution could be to wait Apigility update).

I have successfully implemented the oauth2-server-php (https://github.com/bshaffer/oauth2-server-php), its zf-oauth2 module support (https://github.com/zfcampus/zf-oauth2) and adapted zf-oauth2 client for ZF3 (https://github.com/API-Skeletons/zf-oauth2-client).

However, I'm totaly stuck now trying to protect my API y following zf-oauth2 module's recommandation:

You can protect your API using the following code (for instance, at the top of a controller):

if (!$this->server->verifyResourceRequest(OAuth2Request::createFromGlobals()))
{
    // Not authorized return 401 error
    $this->getResponse()->setStatusCode(401);
    return;
}

where $this->server is an instance of OAuth2\Server (see the AuthController.php).

I've read this post (Using ZF2 Oauth2) but it's not compliant with ZF3. I guess there's a more efficient way rather than copying/pasting zf-oauth2 module's controller and factory to instantiate the server from scratch.

Would anyone have a clue on how to implement the instance of OAuth2\Server in my API controller?


Solution

  • I finally did it by my own. As I spent a significant amount time on this and saw that others where also looking for a solution, here is how I did it.

    At first, I suggest you read https://docs.zendframework.com/tutorials/in-depth-guide/models-and-servicemanager/ if you're not familiar with Dependency Injection and Factories (this was my case).

    module.config.php

    // In module/YourModule/config/module.config.php:
    namespace YourAppNamespace;
    
    use Zend\ServiceManager\Factory\InvokableFactory;
    
    return [
        'controllers' => [
            'factories' => [
                Controller\YourController::class => Factory\YourControllerFactory::class,
            ],
        ],
        'service_manager' => [ /** Your Service Manager Config **/ ]        
        'router' => [ /** Your Router Config */ ]
        'view_manager' => [ /** Your ViewManager Config */ ],
    ];
    

    YourControllerFactory.php

    // In module/YourModule/src/Controller/YourControllerFactory.php:
    namespace YourAppNamespace\Factory;
    
    use YourAppNamespace\Controller\YourController;
    use Interop\Container\ContainerInterface;
    use Zend\ServiceManager\Factory\FactoryInterface;
    
    class YourControllerFactory implements FactoryInterface
    {
        /**
         * @param ContainerInterface $container
         * @param string             $requestedName
         * @param null|array         $options
         *
         * @return YourController
         */
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            $controllerPluginManager = $container;
            $serviceManager          = $controllerPluginManager->get('ServiceManager');
    
            // Requires zf-campus/zf-oauth2
            $server   = $serviceManager->get('ZF\OAuth2\Service\OAuth2Server');
            $provider = $serviceManager->get('ZF\OAuth2\Provider\UserId');
    
            return new YourController($server, $provider);
        }
    }
    

    YourController.php

    // In module/YourModule/src/Controller/YourController.php:
    namespace YourAppNamespace\Controller;
    
    use ZF\OAuth2\Controller\AuthController;
    use OAuth2\Request as OAuth2Request;
    use ZF\OAuth2\Provider\UserId\UserIdProviderInterface;
    
    class YourController extends AuthController
    {
        public function __construct($serverFactory, UserIdProviderInterface $userIdProvider)
        {
            parent::__construct($serverFactory, $userIdProvider);
        }
    
        public function indexAction()
        {
            $server = call_user_func($this->serverFactory, "oauth");
    
            if (!$server->verifyResourceRequest(OAuth2Request::createFromGlobals())) {
                // Failure
                $response = $server->getResponse();
                return $this->getApiProblemResponse($response);
            }
    
            // Success
            echo json_encode(array('success' => true, 'message' => 'It works!'));
        }
    }
    

    Hope it helps!