I copied code from examples to create a basic login screen based on the table individuals with email and password. My AppController has this:
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email', 'password' => 'password'],
'userModel' => 'Individuals']
],
'loginAction' => [
'controller' => 'Individuals',
'action' => 'login'
],
'loginRedirect' => [
'controller' => 'Associations',
'action' => 'login'
],
'logoutRedirect' => [
'controller' => 'Association',
'action' => 'login',
'home'
]
]);
Password resets are done via a token emailed to the user. The controller saves the unencrypted value and /src/Model/Entity/Individual.php has _setPassword that ensures the database has an encrypted value. Every save for the same password is different but that, I gather, is normal.
protected function _setPassword($password) {
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}
My login function started with the standard stuff, but it always returns false
$user = $this->Auth->identify();
My login function now has this debug code that always gets a "no match"
debug($this->request->data);
$email = $this->request->data['email'];
$pwd = $this->request->data['password'];
$user = $this->Individuals->find()
->select(['Individuals.id', 'Individuals.email', 'Individuals.password'])
->where(['Individuals.email' => $email])
->first();
if ($user) {
if ((new DefaultPasswordHasher)->check($pwd, $user->password)) {
debug('match');
}
else{
debug('no match');
}
if ($user->password == (new DefaultPasswordHasher)->hash($pwd)) {
debug('match2');
}
else {
debug('no match2');
}
}
There's a lot more code in and around that and I'm pretty confident I've got it right. Let me know if you need more. I'm keen to crack this.
thanks in advance.
Related
I'm doing the test for my UserController and I'm having trouble with my login test. I'm using cakephp4 with phpunit.
The test I'm doing is this:
public function testLogin(): void
{
$this->enableSecurityToken();
$this->enableCsrfToken();
$this->get('/user/login');
$this->assertResponseOk();
$this->post('/user/login', [
'DNI_CIF' => '22175395Z',
'password' => '$2y$10$ND67aMGqm.qK86MW1wuW9OQLC9vyJQGUn2HnLuSInwrFbXQKBT.V.'
]);
$this->assertResponseCode(302); //Si correcto redirige
// $this->assertSession(1, 'Auth.User.id');
}
My UserController:
public function login()
{
$this->request->allowMethod(['get', 'post']);
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result && $result->isValid()) {
return $this->redirect('/');
}
// display error if user submitted and authentication failed
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Alias de usuario o contraseƱa incorrecta.'));
}
}
My Application:
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/user/login'),
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'resolver' => [
'className' => 'Authentication.Orm',
'userModel' => 'User',
],
'fields' => [
'username' => 'DNI_CIF',
'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' => 'DNI_CIF',
'password' => 'password',
],
'loginUrl' => Router::url('/user/login'),
]);
return $authenticationService;
}
But I'm having this error:
.......object(Authentication\Authenticator\Result)#826 (3) {
["_status":protected]=>
string(27) "FAILURE_CREDENTIALS_MISSING"
["_data":protected]=>
NULL
["_errors":protected]=>
array(1) {
[0]=>
string(27) "Login credentials not found"
}
}
Can someone tell my what I'm doing wrong?
I tried several combinations like:
'username' => '22175395Z',
'password' => '$2y$10$ND67aMGqm.qK86MW1wuW9OQLC9vyJQGUn2HnLuSInwrFbXQKBT.V.'
or:
'DNI_CIF' => '22175395Z',
'password' => 'prueba'
or
'username' => '22175395Z',
'password' => 'prueba'
but nothing works.
In order to validate against a password stored in the database, the stored password must be hashed, and the submitted password must not be.
A want to add, in case of someone having the same problem, to be careful if you are using a user added in Fixture to login because it's password may not be hashed and login test will not work. I added a user and used it in the login test.
This question is asked many times in the stack overflow but I tried every accepted solution.
I'm new to cake PHP and I was assigned to add JWT in our application. Previously the team used the default cake sessions. In order to integrate, I used admad/cakephp-jwt-auth. So In the AppController
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Recurring');
$this->loadComponent('Auth', [
'storage' => 'Memory',
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'user_name',
'password' => 'password',
],
'contain' => ['Roles']
],
'ADmad/JwtAuth.Jwt' => [
'parameter' => 'token',
'userModel' => 'CbEmployees',
'fields' => [
'username' => 'id'
],
'queryDatasource' => true
]
],
'unauthorizedRedirect' => false,
'checkAuthIn' => 'Controller.initialize'
]);
}
I have to use CbEmployees which is our user model.
Then in my custom controller, I add my login function
public function login()
{
$user = $this->Auth->identify();
if (!$user) {
$data = "Invalid login details";
} else {
$tokenId = base64_encode(32);
$issuedAt = time();
$key = Security::salt();
$data = JWT::encode(
[
'alg' => 'HS256',
'id' => $user['id'],
'sub' => $user['id'],
'iat' => time(),
'exp' => time() + 86400,
],
$key
);
}
$this->ApiResponse([
"data" => $data
]);
}
Then I call this function using postman with body
{
"username": "developer",
"password": "dev2020"
}
I always get the response as Invalid login details. So the suggested solution is to check the password data type and length. The password is varchar(255). Another solution is to check the password in the entity. In the entity I have
protected function _setPassword($password)
{
if (strlen($password) > 0) {
return Security::hash($password, 'sha1', true);
// return (new DefaultPasswordHasher)->hash($password);
}
}
I specifically asked why the team is using Security::hash($password, 'sha1', true); due to migration from cake 2 to cake 3 they have to use the same.
Why I'm getting always Invalid login details? What I'm doing wrong here? I can log in the using the same credentials when I'm using the application.
I have a working project that i want to extend with authentication. I followed the Simple Authentication and Authorization Tutorial here.
Logging in works and i can print the Username etc. But the access control is not working. The Method isAuthorized is simply not working. What am i missing here?
Edit - I receive the below error;
"You are not authorized to access that location."
Part of my AppController:
public $components = array('Flash', 'RequestHandler', 'Cookie', 'Session', 'Auth' => array(
'loginRedirect' => array(
'controller' => 'status',
'action' => 'index'
),
'logoutRedirect' => array(
'controller' => 'user',
'action' => 'login'
),
'authenticate' => array(
'Form' => array(
'passwordHasher' => 'Blowfish'
),
),
'authorize' => array('Controller')
));
public function isAuthorized($user)
{
if (isset($user['role']) && $user['role'] === 'admin') return true;
return false;
}
Part of my taskController:
public function isAuthorized($user)
{
debug($user); die();
if ($this->action === 'index') return true;
if (in_array($this->action, array('edit', 'delete')))
{
$postId = (int) $this->request->params['pass'][0];
if ($this->Post->isOwnedBy($postId, $user['id'])) return true;
}
return parent::isAuthorized($user);
}
Part of UsersController:
public function login()
{
if ($this->request->is('post'))
{
if ($this->Auth->login())
{
return $this->redirect($this->Auth->redirectUrl()); // This is being called after login so it seems to work!
}
$this->Flash->error(__('Invalid username or password, try again'));
}
}
Back in May, I posted this question. I'm trying to do the same thing again on a different app, but I haven't found a solution to this problem. I do have more information and better code, so I'm hoping you guys can help me sort this out.
Use Case:
Doctor's office has a website with admin users. The users login successfully with CakePHP's Auth via User model and UsersController.
Doctors have referring physicians with completely different profiles and actions. Doctors need to login via example.com/physicians/login. However, this login is failing with this
authError => 'You are not authorized to access that location.'
Here is my code in AppController:
class AppController extends Controller {
public $helpers = array('Form', 'Html', 'Time', 'Session', 'Js' => array('Jquery'));
public $components = array(
'Session',
'Auth' => array(
'autoRedirect' => false,
'authorize' => 'Controller'
)
);
public function beforeFilter() {
$this->Auth->allow('index', 'view', 'edit', 'display', 'featured', 'events', 'contact', 'signup', 'search', 'view_category', 'view_archive', 'addComment', 'schedule', 'login');
}
}
And here is my UsersController that is working:
class UsersController extends AppController {
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'userModel' => 'User',
'fields' => array(
'username' => 'username',
'password' => 'password'
)
)
),
'loginRedirect' => array('controller' => 'users', 'action' => 'admin'),
'logoutRedirect' => array('controller' => 'pages', 'action' => 'index'),
'loginAction' => array('controller' => 'users', 'action' => 'login'),
'sessionKey' => 'Admin'
)
);
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('add', 'login', 'logout');
}
function isAuthorized() {
return true;
}
public function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash(__('Invalid username or password, try again'));
}
}
}
public function logout() {
$this->Session->destroy();
$this->redirect($this->Auth->logout());
}
Here is my PhysiciansController code that is NOT working:
class PhysiciansController extends AppController {
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'userModel' => 'Physician',
'fields' => array(
'username' => 'username',
'password' => 'password'
)
)
),
'loginRedirect' => array('controller' => 'physicians', 'action' => 'dashboard'),
'logoutRedirect' => array('controller' => 'pages', 'action' => 'index'),
'loginAction' => array('controller' => 'physicians', 'action' => 'login'),
'sessionKey' => 'Physician'
)
);
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->authorize = array(
'Actions' => array(
'userModel' => 'Physician',
'actionPath' => 'physicians'
)
);
$this->Auth->allow('login', 'logout');
// $this->Session->write('Auth.redirect','/physicians/index');
}
function isAuthorized() {
return true;
}
public function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect(array('controller' => 'physicians', 'action' => 'dashboard'));
} else {
$this->Session->read();
debug($this->Auth);
$this->Session->setFlash(__('Invalid username or password, try again'));
}
}
}
public function logout() {
$this->Session->destroy();
$this->redirect($this->Auth->logout());
}
I really don't want to start over and switch to ACL -- I'm not sure that's necessary for just two logins. Help would be very much appreciated!
EDIT: Joshua's answer below is awesome and super helpful. I implemented it, but I'm still receiving an unauthorized error when I try to login as a Physician via /phys/physican/login (prefix/controller/action). The Admin setup works great. Here's the debug code when I try to login:
object(AuthComponent) {
components => array(
(int) 0 => 'Session',
(int) 1 => 'RequestHandler'
)
authenticate => array(
'Form' => array(
'userModel' => 'Physician'
)
)
authorize => false
ajaxLogin => null
flash => array(
'element' => 'default',
'key' => 'auth',
'params' => array()
)
loginAction => array(
'controller' => 'physicians',
'action' => 'phys_login'
)
loginRedirect => null
logoutRedirect => '/'
authError => 'You are not authorized to access that location.'
allowedActions => array()
request => object(CakeRequest) {
params => array(
'prefix' => '*****',
'plugin' => null,
'controller' => 'physicians',
'action' => 'phys_login',
'named' => array(),
'pass' => array(),
'phys' => true,
'_Token' => array(
'key' => 'ad1ea69c3b2c7b9e833bbda03ef18b04079b23c3',
'unlockedFields' => array()
),
'isAjax' => false
)
data => array(
'Physician' => array(
'password' => '*****',
'username' => 'deewilcox'
)
)
query => array()
url => 'phys/physicians/login'
base => ''
webroot => '/'
here => '/phys/physicians/login'
}
response => object(CakeResponse) {
}
settings => array()
}
OK I've got a way to do it. You know about prefix routing? If not, read my answer here: CakePHP/MVC Admin functions placement That answer describes how to set up a single routing prefix ('admin'). But you can have any number - just like this:
Configure::write('Routing.prefixes', array('admin','phys','member','user'));
// now we have admin, phys, member and user prefix routing enabled.
What you can do is have all the doctor's methods use 'admin' prefix routing, and all the physicians methods use 'phys' prefix routing.
So the below is code I've hacked together pretty quickly, so it might not be perfect but it should show the concept. Here it is in pseudo code for the before filter method of your app controller:
if (USER IS TRYING TO ACCESS AN ADMIN PREFIXED METHOD) {
Then use the users table for auth stuff
} else if (USER IS TRYING TO ACCESS A PHYS PREFIXED METHOD) {
Then use the physicians table for auth stuff
} else {
It's neither an admin method, not a physicians method. So just always allow access. Or always deny access - depending on your site
}
Here's my app controller code:
App::uses('Controller', 'Controller');
class AppController extends Controller {
public $components = array('Security','Cookie','Session','Auth','RequestHandler');
public $helpers = array('Cache','Html','Session','Form');
function beforeFilter() {
if ($this->request->prefix == 'admin') {
$this->layout = 'admin';
// Specify which controller/action handles logging in:
AuthComponent::$sessionKey = 'Auth.Admin'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
$this->Auth->loginAction = array('controller'=>'administrators','action'=>'login');
$this->Auth->loginRedirect = array('controller'=>'some_other_controller','action'=>'index');
$this->Auth->logoutRedirect = array('controller'=>'administrators','action'=>'login');
$this->Auth->authenticate = array(
'Form' => array(
'userModel' => 'User',
)
);
$this->Auth->allow('login');
} else if ($this->request->prefix == 'phys') {
// Specify which controller/action handles logging in:
AuthComponent::$sessionKey = 'Auth.Phys'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
$this->Auth->loginAction = array('controller'=>'users','action'=>'login');
$this->Auth->logoutRedirect = '/';
$this->Auth->authenticate = array(
'Form' => array(
'userModel' => 'Physician',
)
);
} else {
// If we get here, it is neither a 'phys' prefixed method, not an 'admin' prefixed method.
// So, just allow access to everyone - or, alternatively, you could deny access - $this->Auth->deny();
$this->Auth->allow();
}
}
public function isAuthorized($user){
// You can have various extra checks in here, if needed.
// We'll just return true though. I'm pretty certain this method has to exist, even if it just returns true.
return true;
}
}
Note the lines:
AuthComponent::$sessionKey = 'Auth.Admin'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
and
AuthComponent::$sessionKey = 'Auth.Phys'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
What that does is allows a person to be logged in as both a physician, and an admin, in the one browser, without interfering with each other's session. You may not need it in the live site, but it's certainly handy while testing.
Now, in you're respective controllers, you'll need straight-forward login/logout methods, with the appropriate prefix.
So, for admin prefixing, in your users controller:
public function admin_login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
}
}
}
public function admin_logout() {
$this->Session->setFlash('Successfully Logged Out');
$this->redirect($this->Auth->logout());
}
And in your physicians controller:
public function phys_login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
}
}
}
public function phys_logout() {
$this->Session->setFlash('Successfully Logged Out');
$this->redirect($this->Auth->logout());
}
Like I said, that all code I hacked together pretty quickly, so it might not work verbatim, but it should show the concept. Let me know if you have any questions.
Instead of
$this->Session->write('Auth.redirect','/physicians/index');
you should use
setcookie("keys", value);
I would like to implement CakePHP 2 website over existing database with plain-text password field.
This is my AppController
class AppController extends Controller {
public $components = array(
'Session',
'Auth' => array(
'loginRedirect' => array('controller' => 'users', 'action' => 'index'),
'logoutRedirect' => array('controller' => 'users', 'action' => 'home'),
'authError' => 'You cannot view this page',
'authorize' => array('Controller'),
'authenticate' => array(
'Form' => array(
'userModel' => 'User',
'fields' => array('username' => 'user_id', 'password' => 'user_password')
)
)
)
);
public function isAuthorized($user) {
return true;
}
function beforeFilter() {
$this->Auth->allow('home');
//$this->Auth->authenticate = $this->User;
parent::beforeFilter();
}
This is my UserController.
class UsersController extends AppController {
public $paginate = array(
'fields' => array('user_id', 'user_desc', 'user_password'),
'limit' => 25,
'order' => array(
'user_id' => 'asc'
)
);
function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash('Cannot Login');
}
}
}
}
This is my User model
class User extends AppModel {
public $name = 'User';
public $primaryKey = 'user_id';
public $belongsTo = 'Group';
}
According to those files above, when I pressed the Login button on login.ctp, I saw
select * from users where user_password = 'this_is_hashing_password'
on the sql dump section.
So, how to turn-off the automatic hashing algorithm, so the login() will compare the user input to the database stored password as plain-text???
I have tried lots of reading on the CakePHP book but I cannot find any, also using hashPasswords($data) technique which found from the internet is not working.
Please help.
Kongthap.
The best answer really is to batch-process your stored passwords so they are hashed, however there are cases where you may be adding a Cake app to an existing application that hashes passwords differently (say by not hashing them at all), so the question is valid even if the goal in this case is not.
Try these resources for modifying Cake's password hashing function, depending on your Cake version:
Cake 2.x
Cake 1.3
I got plain text password working in Cakephp 3, this should only be use for DEVELOPMENT purpose, you should never store password in plain text in production.
That being said, during development, plain text password allows me to focus on login instead of implementing a fully functional user encrypt/decrypt logic. Which is going to be replaced by an OAuth / SAML module anyway...
OK here comes the source code:
ROOT/src/Auth/PlainTextPasswordHasher.php
<?php
namespace App\Auth;
use Cake\Auth\AbstractPasswordHasher;
/**
* Plain text password for demo use, DO NOT PUSTH THIS TO PROD
*/
class PlainTextPasswordHasher extends AbstractPasswordHasher
{
public function hash($password)
{
return $password;
}
public function check($password, $hashedPassword)
{
return $password === $hashedPassword;
}
}
ROOT/src/Controller/PagesController.php
<?php
class PagesController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'username',
'password' => 'password',
],
'passwordHasher' => [
'className' => 'PlainText',
],
'userModel' => 'YourUsers',
]
],
'loginAction' => [
'controller' => 'Logins',
'action' => 'login'
]
]);
}
}
Source: This video https://www.youtube.com/watch?v=eASSNS1f3V4 and this section of the official doc: https://book.cakephp.org/3.0/en/controllers/components/authentication.html#creating-custom-password-hasher-classes