I'm trying to implement a forgot password function in CakePHP 3.x.
I have created a form that accepts a user's email:
<?= $this->Form->create()?>
<div class="form-group">
<?= $this->Form->input('email', array('class' => 'form-group','autocomplete' => 'off' ,'required' => 'required'))?>
</div>
<div class="form-group">
<?= $this->Form->button('Reset Password', array('class' => 'form-group primary'))?>
</div>
<?= $this->Form->end()?>
In my controller I'm trying to find the user by the email, if the email exist then a random password will be generated and the password will be updated for that email id:
use Cake\ORM\TableRegistry;
use Cake\Auth\DefaultPasswordHasher;
public function forgotPassword($email = null){
if($this->request->is('post')) {
$email = $this->request->data['email'];
$emails = TableRegistry::get('Users');
$user = $emails->find()->where(['email' => $email ])->first();
if (!$user) {
$this->Flash->error(__('No user with that email found.'));
return $this->redirect(['controller' => 'Users','action' => 'forgotPassword']);
}else{
$random = 'a';
$hasher = new DefaultPasswordHasher();
$val = $hasher->hash($random);
$data = $this->Users->password = $val;
if ($this->Users->save($data)) {
$this->Flash->success(__('Password changed Succesfully.'));
return $this->redirect(['controller' => 'Users','action' => 'forgotPassword']);
}
}
}
}
You haven't actually stated a specific problem/question, but I think I might know what could help.
The whole DefaultPasswordHasher bit should be in the UsersEntity file, like in the tutorial: Blog tutorial
With the hashing properly placed in the entity like in the example it will automatically be called as soon as you use either PatchEntity or NewEntity (I think, confirmation please?).
Secondly, the $this->[model]->save() function works on entities, not just on data. So you would find the user's entity, patch the entity and then save it:
...} else {
$newpass = 'randomstring';
$user = $this->Users->PatchEntity($user, ['password' => $newpass]);
if ($this->Users->save($user)) ...
Related
Clicking forgot password for the first time will not display pass_key in the email view but stored in pass_key field
clicking forgot password for the second time and so on will get the value of pass_key from previous pass_key insertion
UsersController.php
<?php
public function forgotPassword()
{
if ($this->request->is('post')) {
$query = $this->Users->findByEmail($this->request->data['email']);
$user = $query->first();
if (is_null($user)) {
$this->Flash->error('Email address does not exist. Please try again');
} else {
$passkey =Text::uuid(); // create key
$timeout = time() + DAY;
$url = Router::url(['controller' => 'users', 'action' => 'reset'], true) . '/' . $passkey; // Im just generate this link in the view and pass
$status = $this->Users->updateAll(array('Users.pass_key' => $passkey,'timeout' => $timeout), array('Users.id' => $user->id));
if($status){
$this->getMailer('User')->send('forgotPasswordEmail', [$user]);
$this->Flash->success(__('We already sent a link to your email'));
return $this->redirect(['action' => 'login']);
} else {
$this->Flash->error('Error saving reset passkey/timeout');
}
}
}
}
?>
UserMailer.php
<?php
namespace App\Mailer;
use Cake\Mailer\Mailer;
class UserMailer extends Mailer
{
public function forgotPasswordEmail($user)
{
// attach a text file
$this->attachments([
'text for user.txt'=> [
'file'=> 'files/instruction.txt',
'mimetype'=>'plain/text',
'contentId'=>'3734hf38'
],
// attach an image file
'edit.png'=>[
'file'=>'files/ourlogo.png',
'mimetype'=>'image/png',
'contentId'=>'734h3r38'
]
])
->to($user->email)
->emailFormat('html')
->subject(sprintf('Forgot Password link %s', $user->username))
->viewVars([
'username'=> $user->username,
'useremail'=>$user->email,
'passkey' => $user->pass_key,
'userid' => $user->id
])
// the template file you will use in this emial
->template('forgotPasswordEmail') // By default template with same name as method name is used.
// the layout .ctp file you will use in this email
->layout('customLayout');
}
}
?>
Template\Email\html\forgot_password_email.ctp
<?php
use Cake\Routing\Router;
?>
<p>Your username is: <?=$username?> </p>
<p>Click on the link below to Reset Your Password.</p>
<?php $resetUrl = Router::url(['controller' => 'users', 'action' => 'reset/' . $passkey]);?>
<p>Click here to Reset Your Password</p>
?>
<?php
$url = Router::url(['controller' => 'users', 'action' => 'reset'], true) . '/' . $passkey; // Im just generate this link in the view and pass
$status = $this->Users->updateAll(array('Users.pass_key' => $passkey,'timeout' => $timeout), array('Users.id' => $user->id));
if($status){
var_dump($url);exit; I get the exact pass_key but I dont know how to pass the url to forgot_password_email.ctp . I just know how to pass the using this [$user]
var_dump($user->pass_key);exit; will retrieve the previous transaction not current pass_key
.............
?>
Your problem is that you are generating the pass key and saving it to the database, but not updating the $user object, so when you pass that to the view it still has the old version.
Try this:
$this->Users->patchEntity($user, [
'pass_key' => Text::uuid(), // create key
'timeout' => time() + DAY,
]);
if ($this->Users->save($user)) {
Here, we update the $user entity with the new values, so that they are there for the view, and use the save function instead of updateAll.
Also, in your view, you should change to the following:
$resetUrl = Router::url(['controller' => 'users', 'action' => 'reset', $passkey]);
When I'm registering a new user in the Laravel framework, I'm currently doing it like this,
// Creating a new user
$user = new User;
$user->firstname = $data['firstname'];
$user->lastname = $data['lastname'];
$user->email = $data['email'];
$user->password = bcrypt($data['password']);
$user->save();
This works great, and I am able to login to the application. However, I want the user to have an option to change their passwords in their settings page. Doing this, i used the same technique, using
$newPass = bcrypt($response->new_password);
and updating the user field. However, after doing this, I'm not able to login? I'm using the built in authentication service in laravel for the registration/login.
What am I doing wrong here? and should i do it another way?
I also tried to bcrypt my current password, and i got an completely different hash than the one stored in the database.
This so confusing..
Updated with controller code,
// Validation
$this->validate($request, [
'email' => 'email',
'password' => 'min:8|confirmed',
'current_password' => 'required',
]);
// Getting the user ID
$userId = Auth::id();
// Dummy hack check, change later.
if(!Auth::attempt(['id' => $userId, 'password' => $request->current_password]))
{
return redirect('settings')->with('alert','current password is wrong.');
}
// Everything is validated and ok to proceed
if($request->email)
{
$data['email'] = $request->email;
}
if($request->password)
{
$data['password'] = bcrypt("helloworld");
}
$user = User::where('id',$userId)->update($data);
dd($data);
Dump data for the inputs,
+request: ParameterBag {#40 ▼
#parameters: array:5 [▼
"_token" => "JQIIuCjiKQmbK0X5zCM6czYD1vIoh4PGjLO4qrFm"
"email" => "testing#gmail.com"
"password" => "thisisnewpass"
"password_confirmation" => "thisisnewpass"
"current_password" => "helloworld"
]
}
This code is closer to how Laravel handles resetting a user's password internally. Give it a try.
// Getting the User
$user = Auth::user(); // Gets the currently logged in User
$credentials = [
'id' => $user->id,
'password' => $request->input('current_password')
];
// Make sure current password is correct
if (!Auth::validate($credentials)) { // Checks the User's credentials
return redirect('settings')->with('alert','current password is wrong.');
}
// Change the password
if ($request->has('password')) {
$user->password = bcrypt($request->input('password'));
}
// Save any changes
$user->save();
It looks like you're using the same form to update the User's email address too, so update the code to fit your needs.
Storing the password in an new variable seems to fix the issue (not sure why?) however, this is the code that made everything work,
// Validation
$this->validate($request, [
'email' => 'email',
'password' => 'min:8|confirmed',
'current_password' => 'required',
]);
// Getting the user ID
$userId = Auth::id();
$newPassword = $request->password;
// Dummy hack check, change later.
if(!Auth::attempt(['id' => $userId, 'password' => $request->current_password]))
{
return redirect('settings')->with('alert','Wrong password.');
}
// Everything is validated and ok to proceed
if($request->email)
{
$data['email'] = $request->email;
}
if($request->password)
{
$data['password'] = bcrypt($newPassword);
}
// Getting, and checking if the current password is corrent.
$user = User::where('id',$userId)->update($data);
echo $newPassword . "<br><br>";
dd($data);
If there is any explanations that i don't see, please let me know why. However, it's working now.
For Laravel in year 2017, this is how we roll:
//create a setter method in your controller
public function setPasswordAttribute( $password ) {
if ( $password !== null ) {
if ( is_null(request()->bcrypt) ) {
$this->attributes['password'] = bcrypt($password);
} else {
$this->attributes['password'] = $password;
}
}
}
Check this link they all are talking about placing it in model but it works inside my own controller.
Need help because i'm still new to Yii2. I want to encrypt the password before saving it to the database. So i'm using sha1 but the problem is that the password field in the form has contents when i apply this line of code in the controller shown below.
$model->password = sha1($model->attributes['password']);
This is the Controller create method:
public function actionCreate()
{
$model = new Employeeinformation();
//$model->password = sha1($model->attributes['password']);
$model->created_date = date('Y-m-d H:i:s');
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->employee_id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
This is the form:
<div class="employeeinformation-form">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'employee_id')->textInput(['minlength' => true, 'maxlength' => true]) ?>
<?= $form->field($model, 'password')->passwordInput(['maxlength' => true]) ?>
<?= $form->field($model, 'last_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'first_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'hired_date')->widget(\yii\jui\DatePicker::classname(), [
'language' => 'en',
'dateFormat' => 'yyyy-MM-dd',
]) ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
Screenshot of my problem:
http://i.imgur.com/YTDW1Ud.png
Thank you in advance.
I want to encrypt the password before saving it to the database.
No you don't. Well, you might think you want to encrypt the password, but if you're trying to protect users you actually want to hash the password, not encrypt it.
SHA1 doesn't provide encryption, it's a hash function. This is a very common misconception. You can learn more about basic cryptography terms and concepts at the linked blog post.
More importantly: You don't want a fast hash like SHA1 for passwords. Use password_hash() and password_verify() and you'll have secure password storage. You don't even need to particularly care what these functions do internally to use them correctly.
public function actionCreate()
{
$model = new Employeeinformation();
$post = Yii::$app->request->post();
if ($model->load($post)) {
$model->password = password_hash($model->password, PASSWORD_DEFAULT);
$model->created_date = date('Y-m-d H:i:s');
if ($model->save()) {
return $this->redirect(['view', 'id' => $model->employee_id]);
}
}
return $this->render('create', [
'model' => $model,
]);
}
When employees login, you just need to do this:
if (password_verify($request->password, $storedEmployeeData->hashed_password)) {
// Success
}
Yii2 comes with user module in advanced setup. See how it store user passwords in encrypted way.
You can use setPassword() method in User Model to get hashed passwords.
public function setPassword($password)
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
and call this method before saving model data.
public function signup()
{
if ($this->validate()) {
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
if ($user->save()) {
return $user;
}
}
return null;
}
Also look at the Yii2 doc for passwords and authentication.
The content for password is there because you set the attribute before sending the data through the save (and validate) method.
If you like to do it in the controller, you can do it as the following:
public function actionCreate()
{
$model = new Employeeinformation();
if ($model->load(Yii::$app->request->post())){
$model->password = sha1($model->password);
$model->created_date = date('Y-m-d H:i:s');
if ($model->save())
return $this->redirect(['view', 'id' => $model->employee_id]);
}
return $this->render('create', [
'model' => $model,
]);
}
Another way, is to do the password hashing in the beforeSave method of the Employeeinformation model (add this method inside the model class):
public function beforeSave($insert)
{
if(isset($this->password))
$model->password = sha1($model->password);
$model->created_date = date('Y-m-d H:i:s');
return parent::beforeSave($insert);
}
If done using the beforeSave method, these two lines in the controller code can be removed as they are no longer necessary:
$model->password = sha1($model->password);
$model->created_date = date('Y-m-d H:i:s');
However, referring to http://www.yiiframework.com/doc-2.0/guide-security-passwords.html, it is not recommended to use md5 or sha1 for password encryption. Yii2 provide two helper functions to generate & verify password.
Use this to encrypt password:
$hash = Yii::$app->getSecurity()->generatePasswordHash($password);
And to verify it:
if (Yii::$app->getSecurity()->validatePassword($password, $hash)) {
// all good, logging user in
} else {
// wrong password
}
This is a better choice than sha1 that is used in the original code you posted.
you can look at User model for example, there are method setPassword()
public function setPassword($password)
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
this is how to you set password on database, and also it's already encrypt by yii2 encription
$password = md5($password);
Best way to handle, make sure to correlate this to the login screen to check
$passwordentered = md5($passwordentered);
if ($passwordentered = "Correct"){
"Grant Access"
}
Hope this helps.
In your model add:
public function beforeSave()
{
$this->password=md5($this->password);
return true;
}
Now add this to your controller:
$model->password = md5($model->password);
I'm currently working on a project using the Phalcon Framework that has pages with complex forms and a lot of inputs, to break it down nicely I'm dividing the forms into a step-by-step process.
How would one validate the form on each step before going to the next step and then save the whole form on the final step?
I can't seem to find anything documented about this sort of process as it likes to validate the form in it's entirety if I use the form builder.
Simple, just create a custom methods in your form class to validate any step, and the posted data from some step save into message class and store it into session by "stepX", when posted data is not valid just set defaults from post. When valid save it into session as i describe above.
For example how i mean "controller"
<?php
class MyController extends BaseController {
public function processStep1Action(){
$form = new MyForm();
if($this->request->isPost()){//im using my custom request class
if(!$form->isValid($this->request->getPost()){
//error messages goes here
$form->setDefaultsFromRequest($this->request); // it will set the filled data
}
else {
$messageClass = new MyMessageContainer();
$messageClass->setData($this->request);//inside parse requested data into message class, or parse it as $messageClass->name = $this->request->getPost('name');
$this->session->save('step1',$messageClass); //maybe it would be want to serialize it
//then redirect to the step 2 or x
}
}
}
}
So in the next step you can access data from sessions $this->session->get('step1'); so you can in final step load all posted data and store it into DB.
I hope this helps! :)
here is my form maybe it can be helpful for you.
<?php
namespace Manager\Library\Forms\User;
use Phalcon\Forms\Form,
Phalcon\Forms\Element\Email,
Phalcon\Forms\Element\Select,
Phalcon\Forms\Element\Password,
Phalcon\Forms\Element\Check,
Phalcon\Validation\Validator\Confirmation,
Phalcon\Validation\Validator\StringLength,
Phalcon\Forms\Element\Submit,
Phalcon\Validation\Validator\PresenceOf,
Model\Group;
class AddUser extends Form {
public function initialize()
{
$email = new Email('email');
$email->addValidators(array(
new \Phalcon\Validation\Validator\Email(array(
'message' => 'Nezadali jste email nebo má nesprávny tvar(email#domena.tld).'
))
));
$this->add($email);
$this->initGroupElement();
$password = new Password('password');
$password
->addValidator(new StringLength(array('min' => 6,'messageMinimum' => 'Nezadali jste heslo nebo je příliš krátke, minimální počet znaků je 6.')))
->addValidator(new Confirmation(array('with' => 'password-again',"message" => "Zadané hesla se neshodují.")));
$this->add($password);
$repeatPassword = new Password('password-again');
$this->add($repeatPassword);
$this->initializeProfileElements();
$active = new Check('active',array('value' => 1));
$this->add($active);
$this->add( new Submit('save') );
\Phalcon\Tag::setDefault('password', '');
\Phalcon\Tag::setDefault('password-again', '');
}
public function initializeEdit(){
$email = new Email('email');
$email->addValidators(array(
new \Phalcon\Validation\Validator\Email(array(
'message' => 'Nezadali jste email nebo má nesprávny tvar(email#domena.tld).'
))
));
$this->add($email);
$this->initGroupElement();
$password = new Password('password');
$this->add($password);
$repeatPassword = new Password('password-again');
$this->add($repeatPassword);
$this->initializeProfileElements();
$active = new Check('active',array('value' => 1));
$this->add($active);
$this->add( new Submit('save') );
\Phalcon\Tag::setDefault('password', '');
\Phalcon\Tag::setDefault('password-again', '');
}
protected function initGroupElement(){
$auth = \Core\Auth::getIdentity();
$groups = new Group();
// $groups->addColumns(array('id','name'));
//set global condition about Super Admin
$groups->addFilter('id', 1,'<>');
if($auth){
//set restrictions for main groups
if((int)$auth->group_id === 1){ //super admingroup
//no filter
}
else if((int)$auth->group_id === 2){ //admin group
$groups->addFilter('id', 1,'>');
}
else if((int)$auth->group_id === 6){//Provozovatel group
$groups->addFilter('id',array(3,6,7));
$groups->addFilter('public', 1,'=',true);
}
else { // other groups
$groups->addFilter('public', 1);
}
}
$groups = $groups->findFiltered();
$groupElement = new Select('group');
foreach($groups as $group){
$groupElement->addOption(array($group->id => $group->name));
}
$this->add($groupElement);
}
protected function initializeProfileElements(){
$forename = new \Phalcon\Forms\Element\Text('forename');
$this->add($forename);
$surname = new \Phalcon\Forms\Element\Text('surname');
$this->add($surname);
$street = new \Phalcon\Forms\Element\Text('street');
$this->add($street);
$postal = new \Phalcon\Forms\Element\Text('postal');
$this->add($postal);
$city = new \Phalcon\Forms\Element\Text('city');
$this->add($city);
$ic = new \Phalcon\Forms\Element\Text('ic');
$this->add($ic);
$dic = new \Phalcon\Forms\Element\Text('dic');
$this->add($dic);
}
public function setDefault($fieldName,$value){
\Phalcon\Tag::setDefault($fieldName, $value);
}
public function setDefaults($object){
if($object instanceof \Model\User){
$this->setDefaultsFromObject($object);
}
else if($object instanceof \Phalcon\Http\Request){
$this->setDefaultsFromRequest($object);
}
}
protected function setDefaultsFromObject(\Model\User $user){
$profile = $user->getRelated('\Model\Profile');
\Phalcon\Tag::setDefaults(array(
'email' => $user->email,
'group' => $user->group_id,
'active' => $user->active,
'forename' => $profile->forename,
'surname' => $profile->surname,
'street' => $profile->street,
'city' => $profile->city,
'postal' => $profile->postal,
'ic' => $profile->IC,
'dic' => $profile->DIC
));
}
protected function setDefaultsFromRequest(\Phalcon\Http\Request $request){
\Phalcon\Tag::setDefaults(array(
'email' => $request->getPost('email'),
'group' => $request->getPost('group'),
'active' => $request->getPost('active')
));
\Phalcon\Tag::setDefaults(array(
'forename' => $request->getPost('forename'),
'surname' => $request->getPost('surname'),
'street' => $request->getPost('street'),
'city' => $request->getPost('city'),
'postal' => $request->getPost('postal'),
'ic' => $request->getPost('ic'),
'dic' => $request->getPost('dic')
));
}
}
In addition to Kamil's answer, another option to consider is to use Javascript on the front-end to handle your multi-step form. This will add some complexity as you will need to have the javascript to handle the form steps and do preliminary validation, but it only requires a single submit where you can validate content within a single method.
I'm trying the Laravel's Auth class but everytime i attempt to log in a user, the method returns false. Here's my code:
Routes.php
Route::get('new-user', function() {
return View::make('register');
});
Route::post('new-user', function() {
$name = Input::get('name');
$email = Input::get('email');
$password = Hash::make(Input::get('password'));
$user = new User;
$user->name = $name;
$user->email = $email;
$user->password = $password;
$user->save();
});
Route::get('login', function() {
return View::make('login');
});
Route::post('login', function() {
$user = array(
'email' => Input::get('email'),
'password' => Hash::make(Input::get('password'))
);
if (Auth::attempt($user)) {
//return Redirect::intended('dashboard');
return "ok.";
} else {
return "Wrong.";
}
});
views/login.blade.php
{{ Form::open(array('url' => 'login', 'method' => 'post')) }}
<h1>Login:</h1>
<p>
{{ Form::label('email', 'Email: ') }}
{{ Form::text('email') }}<br />
{{ Form::label('password', 'Password: ') }}
{{ Form::password('password') }}<br />
</p>
<p>
{{ Form::submit('Login') }}
</p>
{{ Form::close() }}
config/auth.php
return array(
'driver' => 'eloquent',
'model' => 'User',
'table' => 'users',
'reminder' => array(
'email' => 'emails.auth.reminder', 'table' => 'password_reminders',
),
);
The database has the email & password fields, and the password field is varchar(60).
Whenever i send the login info to /login it returns me "Wrong."
I really can't see whats wrong here?
Your code is bugging out because you are passing the wrong variables to Auth::attempt(). That method requires an array with keys username, password and optionally remember. In that light, your above code should be:
Route::post('login', function()
{
$credentials = [
'username' => Input::get('email'),
'password' => Input::get('password')
];
dd(Auth::attempt($credentials));
});
Hope that helps.
Also I'll give you snippets of extra code to improve your work flow. Route to store new user:
Route::post('register', function()
{
$input = Input::only(['username', 'email', 'password']);
// validate data
Eloquent::unguard();
$user = User::create($input);
Auth::loginUsingId($user->id);
return Redirect::to('dashboard');
});
Then in your user model add the method
public function setPasswordAttribute()
{
$this->password = Hash::make($this->password);
}
This way the password will be automatically hashed every time it's set
Don't hash the password before attempt:
$user = array(
'email' => Input::get('email'),
'password' => Input::get('password')
);
if (Auth::attempt($user)) {
//return Redirect::intended('dashboard');
return "ok.";
} else {
return "Wrong.";
}
this will not work because auth::attempt converts password to hash using bcrypt, and looks for that hash in users table to match.
in short the password should be a hash stored in database table for auth::attempt to work.
that is why your if() condition failing.
you can use bcrypt(password) to store password as hash in database and then use auth::attempt
below is from laravel docs
https://laravel.com/docs/5.2/authentication#authenticating-users
The attempt method accepts an array of key / value pairs as its first
argument. The values in the array will be used to find the user in
your database table. So, in the example above, the user will be
retrieved by the value of the email column. If the user is found, the
hashed password stored in the database will be compared with the
hashed password value passed to the method via the array. If the two
hashed passwords match an authenticated session will be started for
the user.
The attempt method will return true if authentication was successful.
Otherwise, false will be returned.
You should implement UserInterface class provided by laravel within your model class:
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface
{
And remember that it has 2 abstract methods that you should declare at your model. You can follow original User.php model
Check your password Length. It must be 60 or higher in database.