Authentication and Authorization plugin

I have the Authentication and Authorization plugins, all set in Application.php, AppController.php

I have a role column in the user database
1 - admin
0 - user

I only want to define access to the controller, e.g. ArticleController.php and add…

public function beforeFilter(EventInterface $event)
{
     parent::beforeFilter($event);

     $this->Authorization->can(1);
}

This means that only the user with role = 1 has access to this controller, otherwise users have access everywhere.

Please help how to create this authorization? Is it good to use the Authorization plugin, or write your own component for it?

I created this script src/Policy/RequestPolicy.php

<?php
namespace App\Policy;

use Authorization\Policy\RequestPolicyInterface;
use Cake\Http\ServerRequest;
use Authorization\Policy\ResultInterface;

class RequestPolicy implements RequestPolicyInterface
{
    /**
     * Method to check if the request can be accessed
     *
     * @param \Authentication\IdentityInterface|null $identity Identity
     * @param \Cake\Http\ServerRequest $request Server Request
     * @return \Authorization\Policy\ResultInterface|bool
     */
    public function canAccess($identity, ServerRequest $request): bool|ResultInterface
    {
        $role = 0;
        if(!empty($identity)){
            $data = $identity->getOriginalData();
            $role = $data['authorization'];
        }
        if(!empty($request->getParam('prefix'))){
            if ($request->getParam('prefix') == 'Pages/Manage') {
                return (bool)($role === 1);
            } else {
                return true;
            }
        }

        return true;
    }
}

If the prefix = ‘Pages/Manage’, then only the user with role = 1 has access there, does it work, is this a good solution?

https://book.cakephp.org/authorization/3/en/middleware.html#handling-unauthorized-requests

$middlewareQueue->add(new AuthorizationMiddleware($this, [
    'unauthorizedHandler' => [
        'className' => 'Authorization.Redirect',
        'url' => Router::url([
            'prefix' => 'Pages/Web',
            'plugin' => null,
            'controller' => 'Auth',
            'action' => 'login',
        ]),
        'queryParam' => 'redirectUrl',
        'exceptions' => [
            MissingIdentityException::class,
            ForbiddenException::class
        ],
    ],
]));

If I use Router:: , I get a Missing error, apparently the Router is not available, while the documentation says that it is, where is the problem please?

Are you sure you’ve got a use statement to import it? As a static class::method, I would expect this to work.

Yes I do, it works for me in authentication, but not here.

        $service->loadAuthenticator('Authentication.Form', [
            'fields' => $fields,
            'loginUrl' => Router::url([
                'prefix' => 'Pages/Web',
                'plugin' => null,
                'controller' => 'Auth',
                'action' => 'login',
            ]),
        ]);

What, exactly, is the error message you get? What does it say is missing?

This

So, the Router is available, it’s the route that is not. Where do you create the route that this is supposed to be matching?

I don’t quite understand, I have the same route in the authenticator that works. It doesn’t work here, if we put the url there eg ‘/user/login’ it works, but not when using Route.

I’m wondering if it’s an order of operations thing, maybe this is being called before that route is added?

I don’t know why, but here is my middleware

    /**
     * Setup the middleware queue your application will use.
     *
     * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
     * @return \Cake\Http\MiddlewareQueue The updated middleware queue.
     */
    public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
        $securityHeaders = new SecurityHeadersMiddleware();
        $securityHeaders
            ->setCrossDomainPolicy()
            ->setReferrerPolicy()
            ->setXFrameOptions()
            ->setXssProtection()
            ->noOpen()
            ->noSniff();

        $csrf = new CsrfProtectionMiddleware([
            'httponly' => true,
            'secure' => true,
        ]);

        $middlewareQueue
            // Security Header Middleware
            ->add($securityHeaders)

            // HTTPS Enforcer Middleware
            ->add(new HttpsEnforcerMiddleware([
                'hsts' => ['maxAge' => 31536000, 'includeSubDomains' => true],
            ]))

            // Catch any exceptions in the lower layers,
            // and make an error page/response
            ->add(new ErrorHandlerMiddleware(Configure::read('Error'), $this))

            // Handle plugin/theme assets like CakePHP normally does.
            ->add(new AssetMiddleware([
                'cacheTime' => Configure::read('Asset.cacheTime'),
            ]))

            // Add routing middleware.
            // If you have a large number of routes connected, turning on routes
            // caching in production could improve performance.
            // See https://github.com/CakeDC/cakephp-cached-routing
            ->add(new RoutingMiddleware($this))

            // Parse various types of encoded request bodies so that they are
            // available as array through $request->getData()
            // https://book.cakephp.org/4/en/controllers/middleware.html#body-parser-middleware
            ->add(new BodyParserMiddleware())

            // Cross Site Request Forgery (CSRF) Protection Middleware
            // https://book.cakephp.org/4/en/security/csrf.html#cross-site-request-forgery-csrf-middleware
            ->add($csrf)

            // Add the AuthenticationMiddleware. It should be
            // after routing and body parser.
            ->add(new AuthenticationMiddleware($this))

            // Add the AuthorizationMiddleware *after* routing, body parser
            // and authentication middleware.
            ->add(new AuthorizationMiddleware($this, [
                'unauthorizedHandler' => [
                    'className' => 'Authorization.Redirect',
                    'url' => Router::url([
                        'prefix' => 'Pages/Web',
                        'plugin' => null,
                        'controller' => 'Auth',
                        'action' => 'login',
                    ]),
                    'queryParam' => 'redirect',
                    'exceptions' => [
                        MissingIdentityException::class,
                        ForbiddenException::class
                    ],
                ],
            ]))
            ->add(new RequestAuthorizationMiddleware());

        return $middlewareQueue;
    }

and authorization service

    /**
     * Returns a service provider instance.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request Request
     * @return \Authorization\AuthorizationServiceInterface
     */
    public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
    {
        $mapResolver = new MapResolver();
        $mapResolver->map(ServerRequest::class, RequestPolicy::class);
        return new AuthorizationService($mapResolver);
    }

No routes are being loaded until the routing middleware runs, so you cannot use the router at that point, you are calling it in the context of the middleware() call, not in the context of the authorization middleware actually running. It works in getAuthenticationService() because that method is being invoked when the authentication middleware actually runs, which happens after the routing middleware runs.

Note that the docs do not state that you can use the Router::url() in the authorization config, what they do state is that you can pass routing URL arrays to the url option when using the CakeRedirect handler (as apposed to the regular Redirect handler):

$middlewareQueue->add(new AuthorizationMiddleware($this, [
    'unauthorizedHandler' => [
        'className' => 'Authorization.CakeRedirect',
        'url' => [
            'prefix' => 'Pages/Web',
            'plugin' => null,
            'controller' => 'Auth',
            'action' => 'login',
        ],
        // ...
    ],
]));
1 Like

Thank You, if use

'className' => 'Authorization.CakeRedirect',

Still the same error, do I need to add something somewhere?