minLength data validation is not working with Auth component for CakePHP - authentication

Let's say I have a user registration and I'm using the Auth component (/user/register is allowed of course).
The problem is if I need to set a minLength validation rule in the model, it doesn't work since the Auth component hashes the password therefore it's always more than my minlength password and it passes even if it's blank.
How do I fix this issue? Thanks in advance!

Essentially, you have to rename the password field (for example, to "pw") to prevent the Auth component from hashing it automatically. Then, if the password passes the validation rules, hash it and save the hash under the password key. This is usually done in the beforeFilter() callback as this article describes.
It is also possible to validate the data and hash the password in the controller. This practice is generally discouraged, but it might be a little easier to wrap your head around if you're just starting out with CakePHP.
// this code would go after: if (!empty($this->data)
// and before: $this->User->save($this->data)
// validate the data
$this->User->set($this->data);
if ($this->User->validates()) {
// hash the password
$password_hash = $this->Auth->password($this->data['User']['pw'];
$this->data['User']['password'] = $password_hash;
}

hmm.. here's what I consider best practice: Left the password field as is. Include a second password field 'pw2' so the user can re-type the password. Advantages:
prevent user typo
Auth won't hash pw2. In the model, you can write a custom validation method for password (because you need to check if the 2 passwords are the same too)
var $validate = array(
'password' => array(
'rule' => array('checkPwd')
)
);
function checkPwd($check) {
if(!isset($this->data[$this->alias]['password']) ||
!isset($this->data[$this->alias]['pw2']))
return 'Where are the passwords?';
if($this->data[$this->alias]['password'] !==
Security::hash($this->data[$this->alias]['pw2'],null,true))
return 'Passwords are not the same';
if(strlen($this->data[$this->alias]['pw2']))<10)
return 'Password not long enough';
return true;
}
One little thing, in the form view, set the 'value'=>'' for both passwords fields.

Related

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

CakePHP Accessing Passed Form Data in table afterSave()

SOLVED! I created a $password property in my Entity class thinking I needed to. The password field is added to the Entity through the entire request. None of the code posted below had to be changed.
I am using friendsofcake/Crud to build a REST api. When I save my User models, I have an afterSave() event that does the password salting and hashing. The POST data sent to my UsersController:add() method includes a ['password'] parameter, but my UsersTable has ['hash','salt'] fields.
Basically, I need to know if and where the POST['password'] parameter is, and if it is available in the UsersTable afterSave() method. Otherwise, my system is hashing and salting empty password strings!
I am new to CakePHP and having trouble finding straightforward answers for 3.*.
Here is the afterSave() method in my UsersTable class.
/**
* Updates Meta information after being created.
*/
public function afterSave(Event $event, Entity $entity, ArrayObject $options)
{
if($entity->returnAfterSave) return;
// update the public id
if(empty($entity->public_id))
{
$hashids = new Hashids(static::HASH_SALT, static::HASH_LENGTH);
$entity->public_id = $hashids->encode($entity->id);
}
// generate a password salt
if(empty($entity->salt))
{
$entity->generateSalt();
}
// salt and hash the password
if(empty($entity->hash))
{
// NOTE: I figured since the form data
// is loaded into the entity, I created a $password property
// in my User Entity class. This assumption may be the problem??
// Update: It was a bad assumption. Removing said property solved issue.
$entity->hashPassword();
}
// save the changes
$entity->returnAfterSave = true;
$this->save($entity);
}
Also, I understand that this code will not properly save any new passwords, I will update the code for password changes after my creates are working properly.

Eloquent Auth Login not working

I am trying to log in providing an email and password. I have tried hashing and not hashing the password myself. I run dd(Auth::attempt(Input::except('submit'))); and it returns false. Yes the array is correct. Yes that is what's in the database. I followed the attempt() to Illuminate/Auth/Guard.php attempt() The code for that function is below.
public function attempt(array $credentials = array(), $remember = false, $login = true)
{
$this->fireAttemptEvent($credentials, $remember, $login);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
// If an implementation of UserInterface was returned, we'll ask the provider
// to validate the user against the given credentials, and if they are in
// fact valid we'll log the users into the application and return true.
if ($this->hasValidCredentials($user, $credentials))
{
if ($login) $this->login($user, $remember);
return true;
}
return false;
}
This function has not be modified at all. dd($user); returns the correct information, an instance of User with the attributes pulled from the db. and dd($credentials); returns an array of the post information, an email and password.
dd($this->hasValidCredentials($user, $credentials)); returns boolean false.
I have no idea why. Let me know if more info is required. Thank you!
My password table had a character limit of 20. I'm an idiot. It works now.

Parse.com procedure for validating password requirements in Parse.Cloud.beforeSave(Parse.User

I am trying to find the best procedure for adding password requirements with Parse.com. It appears the easiest way would be to use a cloud function to execute right before user data is saved. My only caveat is that I want to validate user passwords only when the password is different from what is stored in the db or if the user does not exist in the db.
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
...
}
Couple of questions:
Does request.object.existed() work in beforeSave functions as it does with afterSave?
Can I access the user's password (using Parse.Cloud.useMasterKey()) via the request.object in the beforeSave functions? Or do I need to query the user's object inside the function.
In the log the input to the beforeSave function appears to have original and updated keys similar to the json below. However, I was not able to access the original and update json through the request.object. How would I access this data? It would be nice if the only check needed to be performed to verify whether a user's password as changed if a comparison between request.object.original.password !== request.object.updated.password
Sample cloudcode log output:
Input: {"original":{"email":"blah",
"firstname" : "blah",
"emailVerified":true,
"username":"blah",
"createdAt":"2014-04-28T23:05:47.452Z",
"updatedAt":"2014-0716T01:55:52.907Z",
"objectId":"blah",
"sessionToken":"blah"},
"update":{"firstname":"blah2"}}
Try something like this:
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
if (request.object.isNew()) {
// new object..
response.success();
} else {
if (request.object.dirtyKeys().indexOf("password") > -1) {
// Attempted to change the password.
response.error('No.');
}
}
});

viaRemember not work - laravel

Auth :: attempt works perfect, but when you pass the second parameter "true" apparently does not care or does not recover with viaRemember
viaRemember fails to work, check this
controller User
`$`userdata = array(
'email' => trim(Input::get('username')),
'password' => trim(Input::get('password'))
);
if(Auth::attempt(`$`userdata, true)){
return Redirect::to('/dashboard');
}
view 'dashboard', always show 777
#if (Auth::viaRemember())
{{666}}
#else
{{777}}
#endif
I have hit the same obstacle, so looking into the code one can see that viaRemember is not meant to be used as a function to check if the user was logged into the system in one of all the ways a user can be logged in.
'viaRemember' is meant to check if a user was logged into the system specifically via the `viaRemember' cookie.
From what I gather, authentication of user is remembered in two ways:
a via remember cookie.
The cookie value is compared to the via remember field in the users table.
a session cookie.
The cookie value is used in the server to get the session from the
session store. On the session object from the store there is data attached. One of the
data items is the user id connected to the session. The first time
the session was created, the system attached the user id to the data
of the season.
In Illuminate\Auth\Guard class:
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller))
{
$user = $this->getUserByRecaller($recaller);
}
return $this->user = $user;
}
The getUserByRecaller function is called only if the session cookie authentication did not work.
The viaRemember flag is only set in the getUserByRecaller function. The viaRemember method is only a simple getter method.
public function viaRemember()
{
return $this->viaRemember;
}
So in the end, we can use Auth::check() that does make all the checks including the viaRemember check. It calls the user() function in the Guard class.
It seems also the viaRemember is only an indicator. You need to do a type of Auth::check() the will get the process of authentication started and so the user() function will be called.
It seems that your project is on Laravel 4.0 but viaRemember() is added in Laravel 4.1! So that's expected.
in config\session.php file change the 'expire_on_close' = false to true and once you close restart your browser, it must be ok.