CakePHP 2 user database/password migration for Cakephp 4 - authentication

My customer has program which is made with CakePHP 2 (not my project) and he wants to upgrade it to newest CakePHP.
I noticed that there was tutorial for Custom Password Hasher (legacy / sha1 + blowfish) but it has been deprecated since 4.0 and should use authentication plugin.
How i can make that kind of procedure without touching core code which check if password needs rehash and then create new hash on database?
On loadAuthenticator i cannot put different hasher for that.

Yes but old valid user gives on login "Authentication\Authenticator\Result Object ( [_status:protected] => FAILURE_IDENTITY_NOT_FOUND [_data:protected] => [_errors:protected] => Array ( [Password] => Array ( ) ) )"
and newly created user with new has Authentication\Authenticator\Result Object ( [_status:protected] => SUCCESS etc..
I have made that check with that tutorial but how it can be valid if that not "exists"? SQL-query returns 1 row with old users email.
public function login() {
$this->viewBuilder()->setLayout('login');
if($this->request->is(['post','put'])) {
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result->isValid()) {
$authService = $this->Authentication->getAuthenticationService();
// Assuming you are using the `Password` identifier.
if ($authService->identifiers()->get('Password')->needsPasswordRehash()) {
// Rehash happens on save.
$user = $this->Users->get($this->Authentication->getIdentityData('id'));
$user->password = $this->request->getData('password');
$this->Users->save($user);
}
// Redirect to a logged in page
return $this->redirect([
'controller' => 'Pages',
'action' => 'display',
'home'
]);
}
}
}
It will never goes inside "result->isvalid()" with old user.

Related

Yii2 Authentication not working as expected

I am working on a micro service. It has basically login and registration. I followed the Yii2 official guide. But now i am facing an issue. When i try to send request to the endpoints which are protected ( Only users with access_token can make request ) It works but very strange it checks all the rows in the database and if access_token is matches any rows in the database then it allows the request. But what i want - I am trying to get users information, if i pass the token i want only the information which belongs to current user ( Whose token is in request ) .
I am doing this in my UserController -
public function behaviors() {
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
];
$behaviors['authenticator']['only'] = ['view'];
return $behaviors;
}
And in User model have implemented this method -
public static function findIdentityByAccessToken($token, $type = null) {
return static::findOne(['access_token' => $token]);
}
Where i am doing wrong?
If the access_token in the request matches the access_token value for any of the users in the database, then, like you say, the authentication filter will forward the request to the corresponding action on your controller.
At this point the application user component points to the user that was found, you can use the user's component id to recover the record matching the request's user from the database.
// Get the user record
$current_user = Yii::$app->user->identity;
// Do something with the user record
return [
'username' => $current_user->username,
'last_update' => $current_user->updated_at
...
];

How do I save a password hash so I can match an already hashed password?

I am implementing a "remember me" feature (CakePHP 3) that stores username and a hashed password in a cookie. My problem is that I am not successful in matching the hashed password in the cookie with the hashed password in the database.
I am using the DefaultPasswordHasher to generate both the database hash and the cookie hash. I initially thought these would match, but this doesn't seem to be the case.
My second thought was that the DefaultPasswordHasher would have a function that could verify that both hashes came from the same password, but its check() function takes a plain text password: https://api.cakephp.org/3.3/class-Cake.Auth.DefaultPasswordHasher.html
Similar questions online all seem to apply to older versions of Cake or are inconclusive.
From the User entity:
protected function _setPassword($password)
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}
From the UsersController login() function
// Remember login?
if ($this->request->getData()['remember_me'] == "true") {
// Hash the user's password
$savedUser = [
'password' => (new DefaultPasswordHasher)->hash($this->request->getData()['password']),
'username' => $this->request->getData()['username']
];
// Save login for 1 month
$result = $this->Cookie->write('cookie_name', $savedUser, true, '1 month');
}
From the AppController initialize() function:
// Load logged in user
$loggedInUserID = $this->Auth->user('id');
// If not logged in try cookie
if( !$loggedInUserID && $cookie = $this->Cookie->read('cookie_name') ) {
error_log("Attempting to log in from cookie. Username: ".$cookie['username']);
$user = $this->Users->find('all', [
'conditions' => [
'username' => $cookie['username'],
'password' => $cookie['password'],
'is_deleted' => 0
]
])
->first();
// If a user was found, try to login
if ($user) {
if($this->Auth->login($user)) {
error_log("Successfully logged in from cookie. Username: ".$cookie['username']);
$loggedInUserID = $this->Auth->user('id');
} else {
error_log("Couldn't log in from cookie. Username: ".$cookie['username']);
$this->redirect('/users/logout'); // destroy session & cookie
}
}
}
(I kept my temporary error log messages for clarity.)
The cookie seems to be saved correctly, but its hash does not match the database, which means a user matching the cookie is never found.
Is the problem that the two hashes should match? Or should I be using a function to match the two hashes with each other?
First things first, you may want to have a look at the new authentication plugin, it ships with a cookie authenticator out of the box!
https://book.cakephp.org/authentication/1.1/en/
That being said, your premise is wrong, the fact that the two hashes do not match is by design, and the correct behavior, and there is no method that could match them.
What you should do, is store a unique identifier in your cookie, let's say the username, alongside a hash that is built from a second credential, a hash which you need to be able to build again when trying to login via the cookie data. Let's say for example a hash of the username + the hashed password, the hashed password because you need that exact value again to recreate the plain value in order to compare it against the hash stored in the cookie, ie you cannot (and should not anyways) use the plain password, as it wouldn't be available anymore on subsequent requests.
Then when using the cookie data for logging in, use the unique identifier (username) to query the user, and compare the plain value (username + hashed password) to the cookie hash via the password hasher's check() method.
With your given example that could look something like this (disclaimer, this is untested example code):
$user = $this->Users->get($this->Auth->user('id'));
$savedUser = [
'username' => $user->username,
'token' => (new DefaultPasswordHasher())->hash(
$user->username . ':' . $user->password
),
];
// ...
That queries and uses the currently logged in user, so you have to do this after invoking $this->Auth->identify()!
// ...
$user = $this->Users
->find()
->where([
'username' => $cookie['username'],
'is_deleted' => 0,
])
->first();
if ($user) {
$plain = $user->username . ':' . $user->password;
if ((new DefaultPasswordHasher())->check($plain, $cookie['token'])) {
// the hash matches, log the user in
$user = $user->toArray();
unset($user['password']); // but do not put the password in the session
$this->Auth->setUser($user);
} else {
// the hash doesn't match, do something else
}
}
Note the use of setUser(), there is no login() method in the CakePHP 3.x AuthComponent, the login() method is from CakePHP 2.x! Also ideally you'd handle all this in a custom authentication object, and an event handler for the Auth.afterIdentify event, and keep the logic out of the controller.
See also
Cookbook > Controllers > Components > AuthComponent > Manually Logging Users In
Cookbook > Controllers > Components > AuthComponent > Creating Custom Authentication Objects

the auth_key of yii2 is'nt interference in cookie base

I have problem with auth_key , I have login form and it's work correctly without remember me and with remember me , but I read yii document , in that document wrote about remember me work with id and auth_key for create cookie to stay user in long time , i check the framework code and in there have three parameters (id, auth_key, expire_time()) i save auth_key in user table and it's code here
public function generateAuthKey()
{
$this->auth_key = Yii::$app->security->generateRandomString();
}
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
public function getAuthKey()
{
return $this->auth_key;
}
but i have problem , it's if a user login in site and i go to the user table and change the auth_key field , and now if user refresh the page it must be throw out the site because it's auth key is changed , but the user stay login in site , where is problem ?
The main use of auth_key is to authenticate the user by cookie (user don't have to put login data again). When you choose to be remembered at Login, this is how you are remembered. The system has to identify and login you somehow. It won't log out user if u change it.
You can try to change the key yourself in the "ValidateAutney" method, but this will be a bad practice, it is better to set the session time.
'session' => [
'class' => 'yii\mongodb\Session',
'writeCallback' => function($session)
{
return [
'user_id' => Yii::$app->user->id,
'agent' => Yii::$app->request->getUserAgent(),
'ip' => Yii::$app->request->getUserIP(),
'auth_key' => Yii::$app->security->generateRandomString(),
];
}
],
public function getAuthKey()
{
Yii::$app->session->open();
$query = new Query();
$query->select(['auth_key'])
->from('cache')
->where(['id'=> Yii::$app->session->id ]);
$row = $query->one();
return $row['auth_key'];
}

ZF2 Authentication

i am developing an application using ZF2. I have done the user authentication with username & password. But, i would like to check an additional column(example: status) in authentication.
I have done the following codes.
public function authenticate()
{
$this->authAdapter = new AuthAdapter($this->dbAdapter,
'usertable',
'username',
'password'
);
$this->authAdapter->setIdentity($this->username)
->setCredential($this->password)
->setCredentialTreatment('MD5(?)');
$result = $this->authAdapter->authenticate();
return $result;
}
How can i check the column 'status' in authentication?
Note: status value should be 1.
Thanks.
When I was building my authentication using zf2 and doctrine, I have created authorization plugin and customized this adapter for passing extra column for authentication.
You probably need to go on similar directions.
$adapter = new AuthAdapter($db,
'users',
'username',
'password',
'MD5(?)'
);
// get select object (by reference)
$select = $adapter->getDbSelect();
$select->where('active = "TRUE"');
// authenticate, this ensures that users.active = TRUE
$adapter->authenticate();
Reference
After changes your code should look something like this.
public function authenticate()
{
$this->authAdapter = new AuthAdapter($this->dbAdapter,
'usertable',
'username',
'password'
);
$select = $this->authAdapter->getDbSelect();
$select->where('status= "1"');
$this->authAdapter->setIdentity($this->username)
->setCredential($this->password)
->setCredentialTreatment('MD5(?)');
$result = $this->authAdapter->authenticate();
return $result;
}
ZF2 provides a another way to handle additional checks using other columns than the ones foreseen for identity and credential thanks to the method getResultRowObject. All columns of usertable in your example are available as properties of the object returned by getResultRowObject(). So you could expand your code with this :
if ($result->isValid()) {
$identityRowObject = $this->authAdapter->getResultRowObject();
$status = $identityRowObject->status;
// do whatever complex checking you need with $status...
}
Regards,
Marc

zfcUser getState in another module

how could i getState from zfcUser
in view/index.phtml i get it from $this->zfcUserIdentity()->getState();
but now i need to get this value ( state for this user who is logged in ), in other module /controller (this is my costum module controller)
so i need to get State from:
zfcUser/Entity/User
to
myModule/Controller
i watch this https://github.com/ZF-Commons/ZfcUser/wiki/How-to-check-if-the-user-is-logged-in but this solutons is not helpful
and this helps too, for me:
$sm = $this->getServiceLocator();
$auth = $sm->get('zfcuserauthservice');
if ($auth->hasIdentity()) {
$user_edit = $auth->getIdentity()->getPrem();
}
The state is a property from the user itself. So if you get the user throught the identification service, you can grab the state from there.
public function myFooAction()
{
if ($this->zfcUserAuthentication()->hasIdentity()) {
$user = $this->zfcUserAuthentication()->getIdentity();
$state = $user->getState();
}
}
Mind that when the user is not logged in, the if condition is false. Also the state can be null or any arbitrary value, so do not expect that every user returns a valid state (in other words, check the returned value!)
follow this code, I had same problem then I have manage how to use identity of logged in user via zfcUser
in other modules controller at topside,
use Zend\EventManager\EventManagerInterface;
then create this two function in the sameclass,
public function setEventManager(EventManagerInterface $events)
{
parent::setEventManager($events);
$controller = $this;
$events->attach('dispatch', function ($e) use ($controller) {
if (is_callable(array($controller, 'checkUserIdentity')))
{
call_user_func(array($controller, 'checkUserIdentity'));
}
}, 100);
}
public function checkUserIdentity()
{
if ($this->zfcUserAuthentication()->hasIdentity()) {
echo "<pre>"; print_r($this->zfcUserAuthentication()->getIdentity());die;
}
}
it will give this kind of output
Admin\Entity\User Object
(
[id:protected] => 2
[username:protected] =>
[email:protected] => rajat.modi#softwebsolutions.com
[displayName:protected] =>
[password:protected] => $2y$14$2WxYLE0DV0mH7txIRm7GkeVJB3mhD4FmnHmxmrkOXtUFL7S9PqWy6
[state:protected] =>
)
That's it you will automatically get Identity whether user is logged in if not then it will redirect to login page.
Hope this will helps