I'm building a web application in CakePHP 5 using the Authentication and Authorization plugins.
My users can be members of multiple clubs through a many-to-many relationship (using a join table). The complete app including authorization checks is working fine so far.
My Goal
I want to enforce (with middleware) that after logging in, a user must always be a member of at least one club before they can use the rest of the app (onboarding requirement).
If they are not a member, they should be redirected to a /clubs/join "join club" page, except for certain whitelisted actions like login, register, and logout.
What I've Done
I've created a custom RequireMembershipMiddleware in src/Middleware/RequireMembershipMiddleware.php:
<?php
declare(strict_types=1);
namespace App\Middleware;
use Authentication\IdentityInterface;
use Cake\Http\Response;
use Cake\Routing\Router;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RequireMembershipMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$identity = $request->getAttribute('identity');
if (!$identity) {
// Not logged in? Not our concern, pass along.
return $handler->handle($request);
}
// Assume 'clubs' relation is loaded on User
$user = $identity instanceof IdentityInterface ? $identity->getOriginalData() : $identity;
if (!empty($user->clubs) && count($user->clubs) > 0) {
// User has membership(s), proceed.
return $handler->handle($request);
}
// Whitelist some paths for unaffiliated users
$allowed = [
'/clubs/join',
'/logout',
'/users/logout',
'/users/login',
'/users/register',
];
$current = $request->getPath();
foreach ($allowed as $allowedPath) {
if (stripos($current, $allowedPath) === 0) {
return $handler->handle($request);
}
}
// Redirect to join club page
$response = new Response();
return $response
->withHeader('Location', Router::url('/clubs/join'))
->withStatus(302);
}
}
And in src/Application.php I have:
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
// ...
->add(new AuthenticationMiddleware($this))
->add(new AuthorizationMiddleware($this, [
'identityDecorator' => function ($auth, $user) {
return $user->setAuthorization($auth);
},
]))
->add(new RequireMembershipMiddleware());
return $middlewareQueue;
}
The Problem
If I visit any URL (as a logged-in user) that is not in the $allowed list, my middleware triggers as expected but is not redirecting to the onboarding page.
I keep getting this error/warning:
The request to /CONTROLLER/ACTION did not apply any authorization checks.
- I'm using CakePHP 5's Authentication and Authorization plugins.
What I've Tried/Considered
- Moving the middleware higher/lower in the stack doesn't fix the warning.
Question
How can I properly enforce this onboarding/membership requirement via middleware without running into the "did not apply any authorization checks" error?
Is there a CakePHP-idiomatic way to handle this?
Should I combine this logic with Authorization policies somehow?
Is there a best practice for this onboarding/guard type workflow?