cakedc/users autologin after registration - cakephp

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

Related

Cakephp authentication plugin how can I implement login in admin prefix?

I'm trying to implement cakephp authentication plugin in admin prefix
Route I have written for admin prefix
$routes->prefix('admin', function (RouteBuilder $routes) {
$routes->connect('/',['controller'=>'AdminUsers','action'=>'login']);
$routes->fallbacks(DashedRoute::class);
});
In application.php , I have followed everything That mentioned in authentication documentation
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/admin'),
'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',
],
'userModel' => 'AdminUsers',
'loginUrl' => Router::url('/admin'),
]);
return $authenticationService;
}
I have changed users model to AdminUsers for database table admin_users
Now in admin/appController.php
I have loadComponent in initialize method
$this->loadComponent('Authentication.Authentication');
In before filter method I have added
$this->Authentication->allowUnauthenticated(['login']);
Now after submit login form I am getting error
Table class for alias Users could not be found.
In getAuthenticationService method I have changed model Users to AdminUsers. Why it's going for Users model rather then AdminUsers model ?
My Table/AdminUsersTable.php table class look likes
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AdminUsersTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('admin_users');
--------
After ndm comment I am able to change default model in application.php , But how can I change it in Admin/AppController.php ? Code that I have tried.
public function beforeFilter(EventInterface $event)
{
$service = new AuthenticationService();
$service->loadIdentifier('Authentication.Password', [
'resolver' => [
'className' => 'Authentication.Orm',
'userModel' => 'AdminUsers',
],
]);
$this->Authentication->allowUnauthenticated(['login','signup']);
$this->viewBuilder()->setLayout('admin');
}

how to create user role wise access control in user and role table joining in cakephp 3?

user table
role table
I just want to allow access control to role table set like: ctrl_view = 1 means this role can view any controller view.
How can I set different action in different role?
Follow conventions, user_role_id should be named "role_id", role_id only "id" and user_name should be "username" or inside your Auth configuration change the default fields name use for your connection form.
public function initialize()
{
//...
$this->loadComponent('Auth', [
'loginRedirect' => [
'controller' => 'Pages',
'action' => 'welcome',
'prefix' => 'admin'
],
'logoutRedirect' => [
'controller' => 'Users',
'action' => 'login',
'prefix' => false
],
'authError' => 'Unauthorized access...',
'authenticate' => [
'Form' => [
'fields' => ['username' => 'user_name', 'password' => 'password']
]
],
'authorize' => 'Controller',
'unauthorizedRedirect' => [
'controller' => 'Pages',
'action' => 'unauthorized'
],
]);
// ...
}
and inside your Appcontroller make somtehing like this
public function isAuthorized($user)
{
if(!is_null($this->Auth->user())): // if user is logged
$action = $this->request->getParam('action'); // get name action
$this->loadModel('Roles'); // load your model Roles
$query = $this->Authorizations->find() // find inside Roles
->where([
'Roles.role_id IN' => $user['user_role_id'], // where role_id is like user_role_id of current user
'Roles.ctl_'.$action => 1 // and where ctl_[action] is set to 1
])->toArray();
if (!empty($query)): // if we find an occurence, we allow the action
return true;
else: // else we don't authorize
return false,
endif;
/* previous lines can be change with this ----> return (!empty($query)); */
else: // if user is not connected we don't allow action
return false
endif;
}
and to finish, i think it's better to use "prefix", with prefix u can simplify your authorisation process (will no prefix i allow, with prefix i check my role table), for this you have to simply add these line in the beginin of your isAuthorized function:
if (!$this->request->getParam('prefix')) {
return true;
}
Hope it helps

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.

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

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.

CakePHP $this->Auth->login() not working

The web app is working great except for auth manual calls. I've been struggling with this for days. In my sample code I have temporarily rewritten the cookie to narrow down the cause. Here is my app controller snip:
App::import('Sanitize');
//uses('sanitize');
class AppController extends Controller {
var $components = array('Clean','Acl', 'Auth', 'Session', 'RequestHandler', 'Cookie', /* 'DebugKit.Toolbar' */);
var $helpers = array('uiNav','Flash','Html', 'Form', 'Session','Javascript','Ajax','Js' => array('Jquery'), 'Time','Js');
function beforeFilter() {
//Configure AuthComponent
$this->Auth->authorize = 'actions';
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'view');
$this->Auth->actionPath = 'controllers/';
$this->Auth->autoRedirect = false;
$this->Auth->allowedActions = array('display');
if(!$this->Auth->user()){
//$cookie = $this->Cookie->read('Auth.User');
$cookie = array('username' => 'chris22', 'password' => 'stuff');
if (!is_null($cookie)) {
$this->set('checking_cookie',$cookie);
if ($this->Auth->login($cookie)) {
$this->set('cookie_message','cookie validates!');
// Clear auth message, just in case we use it.
$this->Session->delete('Message.auth');
/* $this->redirect($this->Auth->redirect()); */
}
}
}
}
}
As you can see I'm just plugging the user name and password into $this->Auth->login and it's not working!!
I don't know if my user controller is relevent, but here is the login function for that too:
function login() {
if ($this->Auth->user()) {
if (!empty($this->data) && $this->data['User']['remember_me'] && isset($this->data['User']['password'])) {
$cookie = array();
$cookie['username'] = $this->data['User']['username'];
$cookie['password'] = $this->data['User']['password'];
$this->Cookie->write('Auth.User', $cookie, true, '+1 month');
//unset($this->data['User']['remember_me']);
$this->set('cookie-00', 'setting cookie.');
}else{ $this->set('cookie_message', 'not setting cookie.');}
$this->redirect($this->Auth->redirect());
}
}
Thanks!
EDIT - 1 - I think I know why this is not working for you.
Reason 1: $this->Auth->login takes data in the form of
array(
'User'=>array(
'username'=>'myusername',
'password'=>'mypassword'
)
)
Reason 2: $this->Auth->login does NOT hash the password.
You must send the password exactly as it appears in the database.
EVERYTHING BELOW THIS LINE IS POSSIBLE BUT NOT LIKELY IN THIS CASE:
Are you sure that when you originally created the username and password, the hashes were setup?
To check that your hashes match look at your users table with phpmyadmin or mysql workbench and find the password field for the user chris22
Compare that entry to your current hashing. To check your current hash, put the code below somewhere in a controller function (index) and navigate there.
debug(Security::hash('stuff'));
exit;
I hope this helps!
Make sure the fields are correctly assigned in the Auth component.
if you use different field names than username & password to connect, you must declare them in your controller like this way :
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array('username' => 'email', 'password' => 'mot_de_passe')
)
)
)

Resources