allowUnauthenticated() for Controllers in subdirectory with prefix not working

Hello,
I have a problem to make specific routes/methods of my api controllers public so they are accessable without authentication.

I’m trying to add an api to my project and added the subdirectory Api to my controllers directory. Inside this directory are the controllers for the api. Some of the methods should be public and accessable without authentication and the rest should be private and only accessable with authentication and authorization. I already tried to add the public endpoints to the controllers beforeFilter() method with allowUnauthenticated(). The problem is that the beforeFilter() is called after the authentication process and so the allowUnauthenticated() method is never used (the application will always return 403 unauthorized error). I used the same approach in my UsersController for the main project and there the allowUnauthenticated() method is working fine.

I’m using the authentication plugin and for login the api uses httpBasic (to create a jwt token) and jwt authentication.

This is how I created my routes:

// routes.php
$routes->prefix('Api', function (RouteBuilder $builder) {

    $builder->scope('/v1', function (RouteBuilder $builder) {
        $builder->connect('/login', [
            'controller' => 'Users',
            'action' => 'login',
        ]);
...

And this is my UsersController inside the Api directory:

// src/Controllers/Api/UsersController.php
class UsersController extends AppController
{
    public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);
        $this->Authentication->allowUnauthenticated(['login', 'passwordResetRequest']);
    }

The middleware for authentication is created like this:

//Application.php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
    {
        $service = new AuthenticationService();

        $fieldsAuthentificator = [
            IdentifierInterface::CREDENTIAL_USERNAME => 'login',
            IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
        ];

        if ($request->getParam('prefix') == 'Api') {
                    $service->setConfig([
                        'unauthenticatedRedirect' => Router::url(['prefix' => 'Api', 'controller' => 'Users', 'action' => 'login'], false),
                        'queryParam' => 'redirect',
                    ]);
        
                    $service->loadIdentifier('Authentication.JwtSubject');
        
                    $service->loadAuthenticator('Authentication.Jwt', [
                        'returnPayload' => false,
                        'queryParam' => 'token',
                        'secretKey' => Security::getSalt()
                    ]);
        
                    $service->loadAuthenticator('Authentication.HttpBasic', [
                        'fields' => $fieldsAuthentificator,
                        'loginUrl' => Router::url(['prefix' => 'Api', 'controller' => 'Users', 'action' => 'login'], false),
                    ]);
                } else {
...

Does anyone know a solution for my problem or at least an approach how to handle public and private endpoints for an api?

Hint: I already found out that the application always returns 403 unauthorized because the unauthorizedChallenge() method inside cakephp’s HttpBasicAuthenticator.php class throws an exception. After adding ‘‘skipChallenge’ => true’ to the loadAuthenticator() insde the application.php the problem is fixed but now the api always returns status code 200 with an empty result. So I don’t think this is the best approach.

Thanks in advance for the help :slight_smile: