I am using authentication plugin for login in cakephp.
application.php , configAuth() method code
protected function configAuth(): \Authentication\AuthenticationService
{
$authenticationService = new \Authentication\AuthenticationService([
// 'unauthenticatedRedirect' => '/cake_invo/users/login' , //<= manually working fine
'unauthenticatedRedirect' => \Cake\Routing\Router::url(['controller' => 'Users', 'action' => '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' => '/cake_invo/users/login' //<= manually working fine
'loginUrl' => \Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'login']),
]);
return $authenticationService;
}
middleware method
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
->add(new ErrorHandlerMiddleware(Configure::read('Error')))
->add(new AssetMiddleware([
'cacheTime' => Configure::read('Asset.cacheTime'),
]))
->add(new RoutingMiddleware($this))
// add Authentication after RoutingMiddleware
->add(new \Authentication\Middleware\AuthenticationMiddleware($this->configAuth()));
return $middlewareQueue;
}
Getting error like image
How can I solve this problem ?
After give command bin/cake routes
The problem is that your routes aren't loaded yet when you make the call to Router::url(), hence it will fail.
While the order of the middlewares is correct, ie the authentication middleware is being added after the routing middleware, you are building the authentication service immediately by invoking $this->configAuth(), which means that the Router::url() call will be invoked before any of the middlewares have run, specifically before the routing middleware has run, which is responsible for loading your routes.
Instead of passing a built authentication service instance, setup things as shown in the docs, that is make sure that your Application class implements \Authentication\AuthenticationServiceProviderInterface, change your configAuth method to match AuthenticationServiceProviderInterface::getAuthenticationService(), and then pass $this to the authentication middleware constructor instead. That way the method will only be invoked when the authentication middleware runs, that is after the routing middleware.
// ...
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Cake\Http\BaseApplication;
use Psr\Http\Message\ServerRequestInterface;
class Application extends BaseApplication implements AuthenticationServiceProviderInterface
{
// ...
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
// ...
return $authenticationService;
}
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
// ...
->add(new RoutingMiddleware($this))
->add(new AuthenticationMiddleware($this));
return $middlewareQueue;
}
// ...
}
Related
I'm currently implementing the login feature in CakePHP4.
So I wanted to divide it into a normal user model and an administrative user model.
I didn't know how to do this at all, and I spent all day looking for ways to implement it, but I couldn't do it.
I want to set up authentication in "routes.php", but I'm having trouble with "implementing AuthenticationServiceProviderInterface" in "Application.php". What should I do?
// routes.php
$routes->scope('/', function (RouteBuilder $builder) {
/......./
// ログイン
//$builder->connect('mgt-account/*', ['controller' => 'MgtAccount', 'action' => 'login', 'prefix' => 'Admin']);
$builder->connect('users/*', ['controller' => 'Users', 'action' => 'login', 'prefix' => 'Normal']);
/......./
});
$routes->prefix('Normal', function (RouteBuilder $routes) {
$loginUrl = Router::url('/normal/users/login');
$fields = [
'username' => 'mail',
'password' => 'password',
];
$service = new AuthenticationService([
'unauthenticatedRedirect' => $loginUrl,
'queryParam' => 'redirect',
]);
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields,
'loginUrl' => $loginUrl,
]);
$service->loadIdentifier('Authentication.Password', compact('fields'));
$routes->registerMiddleware(
'auth',
new \Authentication\Middleware\AuthenticationMiddleware($service)
);
$routes->applyMiddleware('auth');
//$routes->connect('/:controller');
$routes->fallbacks(DashedRoute::class);
});
$routes->prefix('Admin', function (RouteBuilder $routes) {
$loginUrl = Router::url('/admin/mgt-account/login');
$fields = [
'username' => 'mail',
'password' => 'password',
];
$service = new AuthenticationService([
'unauthenticatedRedirect' => $loginUrl,
'queryParam' => 'redirect',
]);
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields,
'loginUrl' => $loginUrl,
]);
$service->loadIdentifier('Authentication.Password', compact('fields'));
$routes->registerMiddleware(
'auth',
new \Authentication\Middleware\AuthenticationMiddleware($service)
);
$routes->applyMiddleware('auth');
//$routes->connect('/:controller');
$routes->fallbacks(DashedRoute::class);
});
<?php
// src/Application.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 Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
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
// I really want to comment out and delete this bottom.
implements 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
}
/**
* 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))
// I really want to comment out and delete this bottom.
->add(new AuthenticationMiddleware($this));
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
}
// I really want to comment out and delete this bottom.
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => '/normal/users/login',
'queryParam' => 'redirect',
]);
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'mail',
'password' => 'password',
]
]);
$authenticationService->loadAuthenticator('Authentication.Session');
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'mail',
'password' => 'password',
],
'loginUrl' => '/normal/users/login',
]);
return $authenticationService;
}
}
As this is my first time using "stackoverflow", I'm not sure how to ask a good question. I'd appreciate it if you could help me out.
I would appreciate it if you could point it out to me.
thank you.
I believe the answer is to load the correct middleware authenticator based on the current request (admin or regular user). But I have not multiple authentications yet so might not be correct. In Application.php method getAuthenticationService(), where you call loadIdentifier(), specify one "resolver" or the other depending on the current request URL (or however you distinguish an admin url).
The CakePHP 4.x documentation has a section on multiple authentication schemes. I believe you can use two different tables. Configuring Multiple Authentication Setups
This forum item may have the answer you need (the answer below the question): Multiple Authentication Setups
In this scenario, OurCustomAuth is currently returning an expected value of false, is reaching the appropriate else, but the users/error path keeps redirecting even though it's been made public and not requiring any authentication.
I've setup the new action:
C:\wamp\myapp\app>Console\cake AclExtras.AclExtras aco_update
Welcome to CakePHP v2.4.9 Console
---------------------------------------------------------------
App : app
Path: C:\wamp\myapp\app\
---------------------------------------------------------------
Created Aco node: controllers/Users/error
Aco Update Complete
In the UsersController, I've added the action to be made public:
public function beforeFilter() {
parent::beforeFilter ();
$this->Auth->allow ('logout', 'error');
}
In AppController, the Auth config:
public $components = array(
'Acl',
'Cookie',
'DebugKit.Toolbar', 'Session',
'Auth' => array(
'authenticate' => array('OurCustomAuth'),
'loginAction' => array('controller' => 'users', 'action' => 'view'),
'authError' => 'Did you really think you are allowed to see that?',
'authorize' => array('Actions' => array('actionPath' => 'controllers'))
)
);
...
public function beforeFilter() {
...
//Auto logging users in if they are not logged in
if (!AuthComponent::user('id')) {
if ($this->Auth->login()) {
//stuff here
} else {
$this->Session->setFlash(__('We could not authenticate you ...'));
return $this->redirect(array('controller' => 'Users', 'action' => 'error'));
}
}
...
}
The error I get in Firefox:
The page isn’t redirecting properly
Firefox has detected that the server is redirecting the request for
this address in a way that will never complete.
Update #1
$this->Auth->login() essentially grabs request headers, that in this case are intentionally wrong, which seems to redirect to the appropriate link. However, /users/error shouldn't cause a redirect as it's excluded from Authentication.
The problem is that you run your login code on every request, ie in the app controllers beforeFilter() method. So when that code redirects you to /users/error because you're not logged in, the code will run again for that controller/action, and redirect you again, and again, and again...
If you need to run this code for every request, then you'll have to check the allowed actions manually, ie the actions allowed via $this->Auth->allow(), and run your code only in case the current action isn't allowed. Check the code of AuthComponent::_isAllowed(), you can easily use that with minimal modifications:
$action = strtolower($this->request->params['action']);
if (!in_array($action, array_map('strtolower', $this->Auth->allowedActions))) {
//Auto logging users in if they are not logged in
if (!AuthComponent::user('id')) {
// ...
}
}
Reports has many ReportInstances
ReportInstances belongs to Reports
I would like to have the url /reports/:report-id/instances to point to the action index_by_report_id inside ReportInstancesController.php
How do I configure the routes.php accordingly?
UPDATE:
I tried nested resources as described here:
http://book.cakephp.org/3.0/en/development/routing.html#creating-nested-resource-routes
Here are my routes
$routes->resources('Reports', [
'map' => [
'standard' => [
'action' => 'standard',
'method' => 'GET',
]
]
]);
$routes->resources('Reports', function ($routes) {
$routes->resources('ReportInstances');
});
When I do a /reports/1/instances, it goes to ReportsController looking for action 1.
Please advise.
Do this in your routes.php
$routes->resources('Parents', function ($routes) {
$routes->resources('Children');
});
$routes->resources('Children');
In ChildrenController.php,
protected function _prepareConditions() {
$parentId = isset($this->request->params['parent_id']) ? $this->request->params['parent_id'] : null;
if ($parentId == null) {
return [];
}
return [
'Children.parent_id' => $parentId
];
}
public function index()
{
$conditions = $this->_prepareConditions();
$this->paginate = [
'contain' => ['Parents'],
'conditions' => $conditions
];
// ... and so on
You will be able to do the following:
/parents/1/children
/parents/1/children.json
/children
/children.json
Why this works?
http://book.cakephp.org/3.0/en/development/routing.html#creating-nested-resource-routes
tells us that basically to basically retrieve the parent id from the request params.
What it does not say explicitly is that, the routes will then reuse the basic 5 functions: index, add, view, delete, edit even when you nest them under a parent url.
Why do you still have a separate resources route for the Children?
This allows the /children and /children.json to work if you need them as well.
What about add?
I haven't tried that but I do not foresee any issues with using that as
/parents/1/children/add
/parents/1/children/add.json
/children/add
/children/add.json
So, I have a table that is auto-generated using DataTables. An action in my CakePHP grabs the data for that table, and formats it into JSON for datatables to use, this is the formatted JSON:
<?php
$data = array();
if (!empty($results)) {
foreach ($results as $result) {
$data[] = [
'name' => $result->name,
'cad' => $this->Number->currency($result->CAD, 'USD'),
'usd' => $this->Number->currency($result->USD, 'USD'),
'edit' => '<a href="' .
$this->Url->build(['controller' => 'Portfolios', 'action' => 'edit', $result->id]) .
'"><i class="fa fa-pencil"></i></a>',
'delete' => '<input type="checkbox" class="delete" value="' . $result->id . '">'
];
}
}
echo json_encode(compact('data'));
As you can see, I have a 'delete' option in there that outputs a checkbox with the value of the id of the corresponding element. When that checkbox is checked, a delete button is showing which sends this ajax request:
$('a#delete').on('click', function(e) {
e.preventDefault();
var checkedValues = [];
$('input.delete:checked').each(function() {
checkedValues.push($(this).val());
});
$.ajax({
url: $(this).attr('href'),
type: 'POST',
data: checkedValues
});
})
This ajax post goes to my controller action delete(). The problem I'm having is that I'm getting an error that states "Invalid Csrf Token". I know why this is happening, I'm submitting a form with Csrf protection on, that has no Csrf token added to it.
I can't figure out how to manually create a Csrf token for this situation (where the input values are generated after the page has loaded). Nor can I figure out how to disable Csrf protection. I read this, but the code is placed in the beforeFilter function, and as far as I understand it, that means it's run on every action, not just this one, and that's not what I want. Plus, to be completely honest, I would prefer a solution where I don't deactivate security functions.
Is there anyway to disable Csrf for this specific action, or is there a better way to do this?
read all about the CSRF component here
http://book.cakephp.org/3.0/en/controllers/components/csrf.html
you can disable for a specific action here:
http://book.cakephp.org/3.0/en/controllers/components/csrf.html#disabling-the-csrf-component-for-specific-actions
public function beforeFilter(Event $event) {
if (in_array($this->request->action, ['actions_you want to disable'])) {
$this->eventManager()->off($this->Csrf);
}
}
Above answer does not work in Cakephp 3.6 or later.
Cakephp add object of CsrfProtectionMiddleware in src/Application.php.
If you have to remove CSRF protection for specific controller or action then you can use following work around:
public function middleware($middlewareQueue)
{
$middlewareQueue = $middlewareQueue
// Catch any exceptions in the lower layers,
// and make an error page/response
->add(ErrorHandlerMiddleware::class)
// Handle plugin/theme assets like CakePHP normally does.
->add(AssetMiddleware::class)
// Add routing middleware.
// Routes collection cache enabled by default, to disable route caching
// pass null as cacheConfig, example: `new RoutingMiddleware($this)`
// you might want to disable this cache in case your routing is extremely simple
->add(new RoutingMiddleware($this, '_cake_routes_'));
/*
// Add csrf middleware.
$middlewareQueue->add(new CsrfProtectionMiddleware([
'httpOnly' => true
]));
*/
//CSRF has been removed for AbcQutes controller
if(strpos($_SERVER['REQUEST_URI'], 'abc-quotes')===false){
$middlewareQueue->add(new CsrfProtectionMiddleware([
'httpOnly' => true
]));
}
return $middlewareQueue;
}
So i needed a fix for cakephp 3.7 and using $_SERVER['REQUEST_URI'] is realllly not the way to go here. So here is how you are supposed to do it after reading through some documentation.
In src/Application.php add this function
public function routes($routes)
{
$options = ['httpOnly' => true];
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
parent::routes($routes);
}
Comment out the existing CsrfProtectionMiddleware
public function middleware($middlewareQueue)
{
...
// $middlewareQueue->add(new CsrfProtectionMiddleware([
// 'httpOnly' => true
// ]));
}
Open your config/routes.php add $routes->applyMiddleware('csrf'); where you do want it
Router::prefix('api', function ($routes)
{
$routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
$routes->fallbacks(DashedRoute::class);
});
Router::scope('/', function (RouteBuilder $routes)
{
$routes->applyMiddleware('csrf');
$routes->connect('/', ['controller' => 'Pages', 'action' => 'dashboard']);
$routes->fallbacks(DashedRoute::class);
});
Note that my api user now has no csrf protection while the basic calls do have it.
If you have more prefixes don't forgot to add the function there aswell.
in Application.php this worked for me....
$csrf = new CsrfProtectionMiddleware();
// Token check will be skipped when callback returns `true`.
$csrf->whitelistCallback(function ($request) {
// Skip token check for API URLs.
if ($request->getParam('controller') === 'Api') {
return true;
}
});
I read all the cakephp component Auth documentation in http://book.cakephp.org/3.0/en/controllers/components/authentication.html but I cant find a solution :-(
I'm trying to use different model called "Usuarios" in component Auth and change the field "username" by "cedula". This is my configuration in AppController.php:
public function initialize() {
parent::initialize();
$this->loadComponent('Flash');
$this->loadComponent('Auth');
$this->Auth->config('authenticate', [
'Basic' => [
'userModel' => 'Usuarios',
'fields' => [
'username' => 'cedula',
'password' => 'password']],
'Form' => ['userModel' => 'Usuarios',
'fields' => [
'username' => 'cedula',
'password' => 'password']]
]);
But nothing happend. No appear login form and session is open.
What i'm doing wrong?
Match the controller name to the template directory, and the method to the template.
If your controller is src/Controllers/Usuarios, method login(), then in src/Templates/Usuarios/ you need to have login.ctp as your view file.
If you have all that, it should work. By the way, if you are using 'password' as your password field, you don't need to specify that when you configure Auth; You only need to set 'username' => 'cedula' because you are changing the default.
Also, have you set anything in $this->Auth->allow() in the beforeFilter() method?