I've actually figured this out but I couldn't find anything about this until brainstorming with another developer - tracing through the core code to figure out what was going on.
The problem is quite simple - after upgrading from CakePHP v1.3 to v2.5.9 the login (authentication) doesn't work. But there is no error message to tell you why it's not working.
As is noted in the 2.0 Migration Guide:
The AuthComponent was entirely re-factored for 2.0, this was done to help reduce developer confusion and frustration. In addition, AuthComponent was made more flexible and extensible. You can find out more in the Authentication guide.
The Authentication guide mentioned explains all well and good how you should get it to work for a new installation but nothing about what you need to do to migrate.
The further problem is that there is no error to tell you what is going on.
I copied the code for the UsersController.php -> login method from the Authentication guide section on Identifying users:
public function login() {
if ($this->request->is('post')) {
// Important: Use login() without arguments! See warning below.
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirectUrl());
// Prior to 2.3 use
// `return $this->redirect($this->Auth->redirect());`
}
$this->Session->setFlash(
__('Username or password is incorrect'),
'default',
array(),
'auth'
);
}
}
In my AppController.php I had the following:
public $components = array(
'Session', 'P28n', 'Store', 'SiteStore', 'UserAccessLevel', 'Auth'
);
Then in AppController.php -> beforeFilter:
$this->Auth->authorize = array('Controller');
$this->Auth->loginError = __('Login failed, invalid username or password. Please try again.');
$this->Auth->authError = __('Please log-in.');
$this->Auth->allow('login', 'logout');
The only thing for sure that I knew is that $this->Auth->login() is returning false. But the problem could be anything.
The problem was hashing of passwords. Easy once you know the answer.
I'd got as far as adding in the Simple password hashing component suggested in the Authentication guide:
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'passwordHasher' => array(
'className' => 'Simple',
'hashType' => 'sha256'
)
)
)
)
);
But this still failed, but I couldn't confirm that password hashing was definitely the cause. It took tracing the code through to BaseAuthenticate::_findUser that was definitely failing on the passwords to confirm it.
At this point I then made a stab that the hashing of the passwords from CakePHP could be made to match the Simple passwordHasher.
The passwords in CakePHP 1.3 are saved using sha1, and switching 'hashType' => 'sha1' fixed the problem:
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'passwordHasher' => array(
'className' => 'Simple',
'hashType' => 'sha1'
)
)
)
)
);
Related
I'm new to Silex and Symfony and decided to use Silex to build a (really) small app which uses an authentication. The auth process is working. I'm currently working on a little setup page which allows the user to define his username and password. The auth looks like this:
$app['security.firewalls'] = array(
'admin' => array(
'pattern' => '/private',
'form' => array('login_path' => '/login', 'check_path' => '/private/login_check'),
'logout' => array('logout_path' => '/private/logout', 'invalidate_session' => true),
'users' => array(
$username => array('ROLE_ADMIN', $passwd)
)
)
);
$app->register(new Silex\Provider\SecurityServiceProvider());
No other type of user is defined appart from admin. The setup is meant to be run once by an admin.
I've tried customizing the username and the password the following ways:
First thing (this is common to everything I tried)):
// Default password and username
$username='admin'; $passwd='5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==';`
The following didn't work (no magical update):
`$app->post('/setup', function(Request $request) use ($app, $username, $passwd) {
$username = $request->get('username');
$passwd= $request->get('passwd');
});
so I tried something like:
$app->post('/setup', function(Request $request) use ($app) {
$username = $request->get('username');
$passwd= $request->get('passwd');
$app['security.firewalls']['admin']['users'] = array($username => array('ROLE_ADMIN', $passwd));
$app->register(new Silex\Provider\SecurityServiceProvider());
});
with and without registering a new SecurityServiceProvider, and redifining the "users" part of the array. Merging didn't help either.
I'm a bit out of idea here :S How can I perform that?
Dynamic Way
You are not saving your User anywhere. I assume you define $username and $passwd before you define $app['security.firewalls'], otherwise that should already throw an error.
What you need is an User Provider. Right now you are not saving the new user(s), and you are trying to just re-define your two static variables - without checking the given values if I may add - and that is wrong in multiple ways :)
Either you dive in a little bit and check out how to Define a Custom User Provider in Silex and if you have special requirements to an User-Object also probably how to create an User-Entity, or you use some already written user provider for the Silex security service, like:
Silex-SimpleUser by Jason Grimes
Static Way
If you don't want to dynamically add/edit/delete users or you just have a handful of users, you can always just add your static users to the users array:
'users' => array(
'admin1' => array('ROLE_ADMIN', 'encodedAndSaltedPW1'),
'admin2' => array('ROLE_ADMIN', 'encodedAndSaltedPW2'),
'admin3' => array('ROLE_ADMIN', 'encodedAndSaltedPW3'),
)
In this case you would need to encode and salt the PWs like described in the docs:
// find the encoder for a UserInterface instance
$encoder = $app['security.encoder_factory']->getEncoder($user);
// compute the encoded password for foo
$password = $encoder->encodePassword('foo', $user->getSalt());
Also see the docs for the SecurityServiceProvider for more information.
Your Way: Single-User-Provider
After your comments I understand your needs better.
You could of course have an own ordinary "Single-User-Provider" - this could go something like this:
$app->post('/setup', function(Request $request) use ($app) {
$username = $request->get('username');
$passwd= $request->get('passwd');
$file = 'config.json';
$config = array($username => array('ROLE_ADMIN', $passwd));
file_put_contents($file, json_encode($config));
return $app->redirect($app['url_generator']->generate('login'));
});
Be aware that a json-file, without proper permissions, could be read by anyone - so make sure to put the encoded/salted password in it, by no means the readable one.
And then you would need to read the config.json into your Firewall-Configuration:
$app['security.firewalls'] = array(
'admin' => array(
'pattern' => '/private',
'form' => array('login_path' => '/login', 'check_path' => '/private/login_check'),
'logout' => array('logout_path' => '/private/logout', 'invalidate_session' => true),
'users' => json_decode(file_get_contents('config.json'))
)
);
Please notice, that this code is untested and should more likely give you an idea. I hope this helps.
CakePHP v.2.4...
I'm following this documentation trying to set up the Auth component to use my custom password hashing class:
App::uses('PHPassPasswordHasher', 'Controller/Component/Auth');
class AppController extends Controller {
// auth needed stuff
public $components = array(
'Session',
'Cookie',
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array('username'=>'email', 'password'=>'password'),
'passwordHasher' => 'PHPass'
)
),
Inside my UsersController::login() I debug the return from $this->Auth->login(); and it always returns false, even when I submit the correct email / password.
(NOTE: It looks strange to me that the login() takes no parameters, but the docs seem to imply that it looks into the the request data automatically. And this would make sense if my configurations aren't correctly causing it to check the User.email field instead username.)
The post data from the submitted login form looks like this:
array(
'User' => array(
'password' => '*****',
'email' => 'whatever#example.com'
)
)
What am I missing?
Update2
I'm starting to suspect that the default hashing algorithm is getting used instead of my custom class. I tried to match the examples in the docs but they're quite vague on how to do this.
Here's the contents of app/Controller/Component/Auth/PHPassPasswordHasher.php
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PHPassPasswordHasher extends AbstractPasswordHasher {
public function hash($password) {
$hasher = new new PasswordHash( 8, true );
return $hasher->HashPassword($password);
}
public function check($password, $hashedPassword) {
debug('PHPassHasher'); die('Using custom hasher'); //<--THIS NEVER HAPPENS!
$hasher = new new PasswordHash( 8, true );
return $hasher->CheckPassword($password, $hashedPassword);
}
}
AHA! The debug() never appears... so I'm pretty sure the problem is with my custom hasher configuration(s).
Update3
Additional clue: I experimented by setting various default hashing algorithms (Ex: "Simple", "Blowfish") and creating users. The hashes which show up in the DB are all the same which tells me that my config settings are getting ignored completely.
Update4
I debugged $this->settings inside the constructor of /lib/Cake/Controller/Component/Auth/BaseAuthenticate.php and my custom hasher settings are in there:
array(
'fields' => array(
'password' => 'password',
'username' => 'email'
),
'userModel' => 'User',
'scope' => array(),
'recursive' => (int) 0,
'contain' => null,
'passwordHasher' => 'PHPass'
)
You need to rename your password hasher class to have the suffix "PasswordHasher", and only provide the non-suffixed name in the 'className' argument.
eg:
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PHPassHasherPasswordHasher extends AbstractPasswordHasher {
// functions
}
The example from the docs sets the classname to 'Simple', which then loads 'SimplePasswordHasher'.
You might find that having a name of PHPassHasherPasswordHasher is a bit silly, it's up to you what you want to call it. Perhaps PHPassPasswordHasher might be a bit more appropriate (and then use the classname argument 'PHPass').
EDIT: It seems as if Cake has issues when multiple capital letters are used one after the other (eg. PHPass), so the right way to do it is to change the password hasher class to the following:
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PhpassPasswordHasher extends AbstractPasswordHasher {
// functions
}
... and make sure the filename matches the classname: PhpassPasswordHasher.php.
Thanks to SDP for the discussion, I learnt something today!
According to the docs:
To configure different fields for user in $components array:
// Pass settings in $components array
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array(
'username' => 'email',
'password' => 'password'
)
)
)
)
);
Source
I finally got this working. We were on the right track by renaming the file/class to comply with Cake conventions. I had to go one step further and change the capitalization as well:
PHPassPasswordHasher.php --> PhpassPasswordHasher.php
class PHPassPasswordHasher... --> class PhpassPasswordHasher...
Phew!
ps: Many many thanks to #Ben Hitchcock for support on this.
Explanation:
Attempting to use this OAuth2 Plugin for CakePHP:
https://github.com/thomseddon/cakephp-oauth-server
Have followed the instructions, and am now going to this URL:
http://mysite/oauth/login?response_type=code&client_id=NGYcZDRjODcxYzFkY2Rk&
redirect_url=http%3A%2F%2Fwww.return_url.com
(We had made a client in the database with the same info he used in the example)
It brings up a log-in box for Email and Password, but fails authentication every time. I believe it's failing because by the time it gets to Cake's FormAuthenticate->authenticate() method, the settings have reverted to 'username'=>'username' and 'passwordHasher'=>'Simple'.
If we add these lines to the FormAuthenticate (above $fields = ...):
$this->settings['fields']['username'] = 'email';
$this->settings['passwordHasher'] = 'Blowfish';
Then the log-in works successfully.
Things We've tried:
Putting this in our AppController, the OAuthAppController, the OAuthController (all in beforeFilter):
$this->OAuth->authenticate = array(
'userModel' => 'Members',
'fields' => array(
'username' => 'email'
)
);
We've tried changing it to the new format like 2.3 in all of those places, as well as in the initial $components array in my AppModel:
$this->OAuth->authenticate = array(
'Form' => array(
'passwordHasher' => 'Blowfish',
'fields' => array('username'=>'email', 'password'=>'password'),
)
);
Closing:
At this point, I'm looking for any way (other than modifying the actual CakePHP core) to get it to be able to log-in with email instead of username (and hopefully that will solve the same issue with having it revert from Blowfish to Simple as well.
We've already tried heavily modifying the OAuth Plugin (to no avail) and aren't opposed to trying that again, but we can't figure out what to change.
Instead of using this in the OAuthController:
$this->OAuth->authenticate = array(
'Form' => array(
'passwordHasher' => 'Blowfish',
'fields' => array('username'=>'email', 'password'=>'password'),
)
);
Change it to this (notice removal of the "O" so it calls the regular "Auth"):
$this->Auth->authenticate = array(
'Form' => array(
'passwordHasher' => 'Blowfish',
'fields' => array('username'=>'email', 'password'=>'password'),
)
);
Or, take it a step further, and set your $this->OAuth->authenticate array in your own AppController, then, in the OAuthController do this (instead of the above):
$this->Auth->authenticate = $this->OAuth->authenticate;
Okies this question is similar to one I have asked recently on Stack Overflow, but I'm basically just using the code from the CakePHP Book rather than my own code to try and understand why something is not working.
Basically the idea is to allow a user to login using their email address as well as their username in version 2.0 of Cake. However it always returns that the details are incorrect but I can STILL login with the username, so basically the override in the AppController does not change anything... More so I'm trying to figure out how to allow both fields for logging in.
As discussed in the original post here: Login with email address or username in CakePHP v2.0 #nIcO has put together something that could pontentially work for both fields BUT the issue explained here causes it not to work.
Any ideas? Anyone got email login working with version 2.0.
// AppController
public $components = array(
'Auth' => array(
'loginAction' => array(
'controller' => 'users',
'action' => 'login'
),
'authenticate' => array(
'Form' => array(
'fields' => array('username' => 'email')
)
)
)
);
// UsersController
public function 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');
}
}
}
Though id added it in UsersController (not in AppController), but this worked for me for email as username:
public $components = array('Auth');
//beforeFilter in UsersController
function beforeFilter() {
parent::beforeFilter();
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->authenticate = array(
'Form' => array(
'fields' => array('username' => 'email')
)
);
}
Hope it helps in some way
This is the best implementation I found: http://bin.cakephp.org/view/1831131032
I like how some of the logic has been moved into the Model and clears up the Controller logic and makes it more MVC. Hopefully this will help others.
with cake 1.3 for autologin facebook users i get the user info from database:
$userInfo = $this->User->find('first', array(
'fields' =>array('User.username','User.password'),
'conditions' => array(
'source_id' => $fb_user_id,
'source' => "facebook",
)
));
password comming from this select is hashed.
and i use this method for authentication
$this->Auth->login($userInfo);
but now with cake 2
var_dump($this->Auth->login($userInfo));
always return false;
i 'm not sure but i think that now the login() method need a clear password??
any solution? and excuse my English
Try $this->Auth->login($userInfo['User']), to login user in cakephp 2.0 you should use array with users data, without mention model
If its will not help then check if your Auth component configuration is right...
I use
$this->Auth->authenticate = array(
'all' => array(
'userModel' => 'User',
),
'Form'
);
$this->Auth->loginAction = {url};
$this->Auth->logoutAction = {url}