Hi @HypJ3U50N,
Check my version of the tutorial. I have tested and it’s working. Compare with your files.
src/Application.php
<?php
declare(strict_types=1);
namespace App;
use Cake\Core\Configure;
use Cake\Core\Exception\MissingPluginException;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Exception\AuthorizationRequiredException;
use Authorization\Exception\ForbiddenException;
use Authorization\Policy\OrmResolver;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* Application setup class.
*
* This defines the bootstrapping logic and middleware layers you
* want to use in your application.
*/
class Application extends BaseApplication implements AuthorizationServiceProviderInterface, AuthenticationServiceProviderInterface
{
/**
* Load all the application configuration and bootstrap logic.
*
* @return void
*/
public function bootstrap(): void
{
// Call parent to load bootstrap from files.
parent::bootstrap();
if (PHP_SAPI === 'cli') {
$this->bootstrapCli();
}
/*
* Only try to load DebugKit in development mode
* Debug Kit should not be installed on a production system
*/
if (Configure::read('debug')) {
$this->addPlugin('DebugKit');
}
// Load more plugins here
$this->addPlugin('Authentication');
$this->addPlugin('Authorization');
}
/**
* 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
{
$middlewareQueue
// Catch any exceptions in the lower layers,
// and make an error page/response
->add(new ErrorHandlerMiddleware(Configure::read('Error')))
// 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. For that when
// creating the middleware instance specify the cache config name by
// using it's second constructor argument:
// `new RoutingMiddleware($this, '_cake_routes_')`
->add(new RoutingMiddleware($this))
->add(new AuthenticationMiddleware($this))
->add(new AuthorizationMiddleware($this,[
'unauthorizedHandler' => [
'className' => 'Authorization.Redirect',
'url' => '/users/login',
'queryParam' => 'redirectUrl',
'exceptions' => [
MissingIdentityException::class,
ForbiddenException::class,
],
],
]));
return $middlewareQueue;
}
/**
* Bootrapping for CLI application.
*
* That is when running commands.
*
* @return void
*/
protected function bootstrapCli(): void
{
try {
$this->addPlugin('Bake');
} catch (MissingPluginException $e) {
// Do not halt if the plugin is missing
}
$this->addPlugin('Migrations');
// Load more plugins here
}
/*
*/
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
$resolver = new OrmResolver();
return new AuthorizationService($resolver);
}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => '/users/login',
]);
return $authenticationService;
}
}
src/Controller/AppController.php
<?php
declare(strict_types=1);
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize(): void
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
/*
* Enable the following component for recommended CakePHP form protection settings.
* see https://book.cakephp.org/4/en/controllers/components/form-protection.html
*/
//$this->loadComponent('FormProtection');
$this->loadComponent('Authentication.Authentication');
$this->loadComponent('Authorization.Authorization');
}
public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
// for all controllers in our application, make index and view
// actions public, skipping the authentication check
$this->Authentication->addUnauthenticatedActions(['index', 'view', 'login']);
}
}
src/Controller/UsersController.php
<?php
declare(strict_types=1);
namespace App\Controller;
/**
* Users Controller
*
* @property \App\Model\Table\UsersTable $Users
* @method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
*/
class UsersController extends AppController
{
/**
* Index method
*
* @return \Cake\Http\Response|null|void Renders view
*/
public function index()
{
$this->Authorization->skipAuthorization();
$users = $this->paginate($this->Users);
$this->set(compact('users'));
}
/**
* View method
*
* @param string|null $id User id.
* @return \Cake\Http\Response|null|void Renders view
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$this->Authorization->skipAuthorization();
$user = $this->Users->get($id, [
'contain' => [],
]);
$this->set('user', $user);
}
/**
* Add method
*
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$user = $this->Users->newEmptyEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
$this->set(compact('user'));
}
/**
* Edit method
*
* @param string|null $id User id.
* @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$user = $this->Users->get($id, [
'contain' => [],
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
$this->set(compact('user'));
}
/**
* Delete method
*
* @param string|null $id User id.
* @return \Cake\Http\Response|null|void Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$user = $this->Users->get($id);
if ($this->Users->delete($user)) {
$this->Flash->success(__('The user has been deleted.'));
} else {
$this->Flash->error(__('The user could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
public function login()
{
$this->Authorization->skipAuthorization();
$result = $this->Authentication->getResult();
$this->request->allowMethod(['get', 'post']);
// regardless of POST or GET, redirect if user is logged in
if ($result->isValid()) {
// redirect to /articles after login success
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Articles',
'action' => 'index',
]);
return $this->redirect($redirect);
}
// display error if user submitted and authentication failed
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Invalid username or password'));
}
}
public function logout()
{
$this->Authorization->skipAuthorization();
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result->isValid()) {
$this->Authentication->logout();
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
}
}
}
src/Policy/ArticlePolicy.php
<?php
declare(strict_types=1);
namespace App\Policy;
use App\Model\Entity\Article;
use Authorization\IdentityInterface;
use Authorization\Policy\Result;
/**
* Article policy
*/
class ArticlePolicy
{
/**
* Check if $user can update Article
*
* @param Authorization\IdentityInterface $user The user.
* @param App\Model\Entity\Article $article
* @return bool
*/
public function canUpdate(IdentityInterface $user, Article $article)
{
if ($user->id == $article->user_id) {
return new Result(true);
}
// Results let you define a 'reason' for the failure.
return new Result(false, 'not-owner');
}
/**
* Check if $user can view Article
*
* @param Authorization\IdentityInterface $user The user.
* @param App\Model\Entity\Article $article
* @return bool
*/
public function canView(IdentityInterface $user, Article $article)
{
return $this->isAuthor($user, $article);
}
protected function isAuthor(IdentityInterface $user, Article $article)
{
return $article->user_id === $user->getIdentifier();
}
}
src/Controller/ArticlesController.php
<?php
declare(strict_types=1);
namespace App\Controller;
/**
* Articles Controller
*
* @property \App\Model\Table\ArticlesTable $Articles
* @method \App\Model\Entity\Article[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
*/
class ArticlesController extends AppController
{
/**
* Index method
*
* @return \Cake\Http\Response|null|void Renders view
*/
public function index()
{
$this->Authorization->skipAuthorization();
$this->paginate = [
'contain' => ['Users'],
];
$articles = $this->paginate($this->Articles);
$this->set(compact('articles'));
}
/**
* View method
*
* @param string|null $id Article id.
* @return \Cake\Http\Response|null|void Renders view
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$this->Authorization->skipAuthorization();
$article = $this->Articles->get($id, [
'contain' => ['Users'],
]);
$this->set('article', $article);
}
/**
* Add method
*
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$article = $this->Articles->newEmptyEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('The article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The article could not be saved. Please, try again.'));
}
$users = $this->Articles->Users->find('list', ['limit' => 200]);
$this->set(compact('article', 'users'));
}
/**
* Edit method
*
* @param string|null $id Article id.
* @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$article = $this->Articles->get($id, [
'contain' => [],
]);
$this->Authorization->authorize($article, 'update');
if ($this->request->is(['patch', 'post', 'put'])) {
$article = $this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('The article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The article could not be saved. Please, try again.'));
}
$users = $this->Articles->Users->find('list', ['limit' => 200]);
$this->set(compact('article', 'users'));
}
/**
* Delete method
*
* @param string|null $id Article id.
* @return \Cake\Http\Response|null|void Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$article = $this->Articles->get($id);
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The article has been deleted.'));
} else {
$this->Flash->error(__('The article could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
}
Edit the config/app_local.php to enable Debugkit ignore authorization
'DebugKit.ignoreAuthorization' => true, //add this line before 'debug' line