CakePHP 3 - Login Error - Call to a member function identify on boolean - cakephp

Hello I'm using CakePHP 3 to simple setup a site which some pages of it need user to login first.
It was fine when I put the loadComponent('Auth', blablabla) code in initialize() of AppController.php.
src\Controller\AdminController.php
...
public function login() {
if ($this->request->is('post')) {
$admin = $this->Auth->identify();
if ($admin) {
$this->Auth->setUser($admin);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
...
src\Controller\AppController.php
...
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'Authenticate' => [
'Form' => [
'userModel' => 'Admin',
'Fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Admin',
'action' => 'login',
]
]);
$this->Auth->allow(['display']);
}
...
At this point, I needed to login in order to view all other pages of the site.
But I tried to put this same authentication setup in another controller called JustController, and after I logged in, a fetal error stated
Call to a member function identify() on boolean
has been shown.
It should be possible to setup authentication in other controllers so that the site can have more than 1 set of login system instead of covering whole site by setting up in AppController, doesn't it?
Thank you.

Related

cakedc/users autologin after registration

I'm using the cakephp 4.2.6 with the cakedc/users plugin. I'd like to automatically login the user after registration without the need to first activate the account by clicking on the link in e-mail.
My idea is to creating an Event-Listener and listening to the Users.Global.afterRegister Event, but then I don't know how to login the user with the new Authentication.
this is what I ended up with and it seems to work so far:
<?php
namespace App\Event;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Cake\Event\EventListenerInterface;
use Cake\Datasource\FactoryLocator;
use Cake\Log\Log;
class UserEventListener implements EventListenerInterface
{
public function implementedEvents(): array
{
return [
'Users.Global.afterRegister' => 'autoLogin',
];
}
public function autoLogin($event)
{
$user = $usersTable->get($event->getData('user')->id);
$request = $event->getSubject()->getRequest();
$response = $event->getSubject()->getResponse();
$authenticationService = $this->getAuthenticationService($request);
$authenticationService->persistIdentity($request, $response, $user);
}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => \Cake\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
'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\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
]);
return $authenticationService;
}
}
There is a similar use case covered in the plugin documentation https://github.com/CakeDC/users/blob/master/Docs/Documentation/Events.md
Your approach looks correct to me, using the afterRegister event to create the listener in a controller where you have access to the Authenticatication component,
// note this example is handling auto-login after the user validated the email sent (click the validation token link sent to his email address)
EventManager::instance()->on(
\CakeDC\Users\Plugin::EVENT_AFTER_EMAIL_TOKEN_VALIDATION,
function($event){
$users = $this->getTableLocator()->get('Users');
$user = $users->get($event->getData('user')->id);
$this->Authentication->setIdentity($user);
}
);
or using the request to retrieve the authentication service and set the identity like it's done here https://github.com/cakephp/authentication/blob/master/src/Controller/Component/AuthenticationComponent.php#L273

common method to check status before all controllers in CakePHP

I have an application in cakephp where a logged in user must have a status of active otherwise it should redirect to a controller where user must have submit his application.
Actually i want to implement on all the controllers whenever a user tried to access any controller action it should check the status automatically and if status is not active it should redirect to the user application. How can i implement it in the Appcontroller.
My Appcontroller contents are :
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler', [
'enableBeforeRedirect' => false,
]);
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authError' => 'You have been logged out due to period of inactivity.',
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'loginRedirect' => [
'controller' => 'Users',
'action' => 'dashboard'
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'logout'
],
]);
You can use beforeFilter Method in your AppController to perform such a check.
Suppose the name of your Controller where you want to Redirect is YourController, then your method should look like.
public function beforeFilter( $event ) {
if ($this->Auth->user()) { //If User is logged in.
if ( $this->request->controller != 'YourController' ) { //If Request Controller is other than YourController
$status = $this->Auth->user('status'); //Get the Status
if( $status != 'active' ) { //If Status is not active
//Redirect Here
return $this->redirect(
['controller' => 'YourController', 'action' => 'index'];
);
}
}
}
}

CakePHP 3 JWT-Auth gives 401 Unauthorized error

I'm using CakePHP 3.6 and JWT Auth to enable token-based authentication in my application and frontend is written in Angular 6.
My login controller is like
<?php
namespace App\Controller\Api;
use Cake\Event\Event;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Utility\Security;
use Crud\Controller\Component\CrudComponent;
use Firebase\JWT\JWT;
class UsersController extends AppController
{
public function initialize()
{
parent::initialize();
$this->Auth->allow(['add', 'token']);
}
public function token()
{
$user = $this->Auth->identify();
if (!$user) {
throw new UnauthorizedException('Invalid username or password');
}
$this->set([
'success' => true,
'data' => [
'token_type' => 'Bearer',
'expires_in' => 604800,
'token' => JWT::encode([
'sub' => $user['id'],
// 'exp' => time() + 604800
],
Security::getSalt())
],
'_serialize' => ['success', 'data']
]);
}
}
AppController.php contents
namespace App\Controller\Api;
<?php
use Cake\Controller\Controller;
class AppController extends Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.View',
'Crud.Add',
'Crud.Edit',
'Crud.Delete'
],
'listeners' => [
'Crud.Api',
'Crud.ApiPagination'
]
]);
$this->loadComponent('Auth', [
'storage' => 'Memory',
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
],
'finder' => 'auth'
],
'ADmad/JwtAuth.Jwt' => [
'parameter' => 'token',
'userModel' => 'Users',
'finder' => 'auth',
'fields' => [
'username' => 'id'
],
'queryDatasource' => true
]
],
'unauthorizedRedirect' => false,
'checkAuthIn' => 'Controller.initialize'
]);
}
}
On sending request from the angular application to generate token works fine and following response is received.
But when using the token to send the request to other endpoints giving an error
401: Unauthorized access
The request/response header has token
What I tried?
I tried with disabling exp while generating an access token.
tried with disabling debug in CakePHP application.
It is working great when CakePHP server application is run locally.
in your .htaccess try this rule (if mod_rewrite is activated) :
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
With the Bitnami stack of LAMP (on EC2 AWS instance for example), the php-fdm module filter the header of every requests, and the "authorization" header is screwed up.
With this line, you can force to create a $_HTTP variable with the original Authorization header.
Regards
Check in cakephp code if you are receiving the AUTHORIZATION in headers.

Legacy Password Hasher Not Working Migrating CodeIgniter2 to Cake 3

I have a legacy CRM application that I am migrating from Code Igniter 2.x to Cake 3. I am attempting to implement a Legacy Password Hasher and then move everything to the Cake 3 password hasher.
I have been unable to get authentication to work. As you'll see I've had to deviate from various cake defaults, in particular all tables were created as singular names. Not sure if that's causing any issues. It's been an ordeal. If it matters, Admin exists as a plugin.
AppController
namespace Admin\Controller;
use App\Controller\AppController as BaseController;
class AppController extends BaseController
{
public function initialize() {
parent::initialize();
$this->loadComponent('Auth', [
'loginAction' => '/admin/login', // routes to /admin/user/login
'authenticate' => [
'Form' => [
'passwordHasher' => [
'className' => 'Legacy',
],
'fields' => [
'username' => ['email_address','handle'],
'password' => 'password',
],
'userModel' => 'User',
],
],
'storage' => 'Session'
]);
}
}
UserController
if( $this->request->is('post') ){
$this->loadModel('User');
$me = $this->User->find()->where([
'type IN ' => ['superuser','administrator','user','publisher'],
'status IN ' => ['active','suspended'],
'OR' => [
'email_address' => $this->request->data('username'), 'handle' => $this->request->data('username')
]
])->first();
if( $me->status == 'active' ){
$user = $this->Auth->identify();
if( $user ){
$this->Auth->setUser($user);
if( $this->Auth->authenticationProvider()->needsPasswordRehash() ){
$user = $this->User->get($this->Auth->user('id'));
$user->password = $this->request->data('password');
$this->User->save($user);
}
switch($user->type){
case 'superuser':
$url = '/admin/dashboard/super';
break;
case 'admin':
$url = '/admin/dashboard/';
break;
case 'publisher':
$url = '/admin/dashboard/publisher';
break;
case 'property':
$url = '/admin/dashboard/property';
break;
}
return $this->redirect($url);
}
}
$this->Flash->error(__('Username or password is incorrect'), [
'key' => 'auth'
]);
}
LegacyPasswordHasher
namespace App\Auth;
use Cake\Auth\AbstractPasswordHasher;
class LegacyPasswordHasher extends AbstractPasswordHasher
{
public function hash($password){
$salt_length = 16;
$salt = substr($password, 0, $salt_length);
return $salt . sha1($salt . $password);
}
public function check($password, $hashedPassword){
return $this->hash($password) === $hashedPassword;
}
}
The legacy password hasher does not even seem to be called. I did a work around to force authentication by doing my own check and then setting the auth user data. Still after that when rehashing the password using Cakes DefaultPasswordHasher authentication failed.
I figured this out. There were a few issues here. One, I needed to change the configuration to this:
$this->loadComponent('Auth', [
'loginAction' => '/admin/login',
'authenticate' => [
'Form' => [
'finder' => 'auth',
'passwordHasher' => [
'className' => 'Legacy',
],
'fields' => [
'username' => 'email_address',
'password' => 'password',
],
'userModel' => 'User',
],
],
'storage' => 'Session'
]);
Next, the field I posted needed to be email_address and not username. Last, there was a bug in my implementation of Code Igniters password hasher. It ended up needing to look like this:
public function hash($password){
$salt_length = 16;
$salt = substr($this->salt, 0, $salt_length);
return $salt . sha1($salt . $password);
}
public function check($password, $hashedPassword){
$this->salt = $hashedPassword;
return $this->hash($password) === $hashedPassword;
}
I also needed a custom finder query added to UserTable:
public function findAuth(\Cake\ORM\Query $query, array $options)
{
$query
->select(['id', 'email_address','handle','password'])
->where(['User.status' => 'active']);
return $query;
}
To handle users being able to login with a username or email_address I will just override the the posted value if its not an email address by looking up the user by handle prior to auth doing its thing.
I hope this helps someone.

Display/hide links cakephp 3.0

Sorry im new to cakephp 3.0. In my user table, there are two user types, admin and public. How do I display/hide links according to user types in default.ctp? Can anyone guide me thanks!!
This is my app controller
namespace App\Controller;
use Cake\Controller\Controller;
use Cake\Event\Event;
class AppController extends Controller
{
//...
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginRedirect' => [
'controller' => 'Users',
'action' => 'index'
],
'logoutRedirect' => [
'controller' => 'Pages',
'action' => 'display',
'home'
]
]);
}
public function beforeFilter(Event $event)
{
$this->Auth->allow(['controller' => 'Users', 'action' => 'add', 'index',
]);
}
}
The AppController you've posted has nothing to do with what I understand of your request so that's got me a little confused. Either way, you can access the session of the current logged in user through session variables.
For example - if your users table had the column 'type' in which the values 'public' or 'admin' were stored, it would look something like this:
<?php if ($this->session->read('Auth.User.type') == 'admin') { ?>
Link to admin functions
<?php } else { ?>
Boring public link
<?php } ?>
That's assuming you're working with logged in users. If you haven't got that far yet, read the CakePHP 3 tutorial on authentication and authorization.

Resources