So now we’ve come to the last part of this series. We need a way to let users back in when they forget their passwords. So we’re going to need a form, a mechanism to notify them with a unique token, as well as another form to actually do the resetting of the password. Plenty of going on here, so let’s start coding.
Update 9/5/2016: Updated flaw in token creation and token checking for better security. Thanks to comment by Mohammad and his findings.
Update 7/20/2016: Added index.php file to the views directory. This is the view that is being used once a successful login is processed.
The Forgot your Password form
In our controller “Main.php”, let’s add a new action called “forgot”. The code is below and I’ll explain what is inside:
public function forgot()
{
$this->form_validation->set_rules('email', 'Email', 'required|valid_email');
if($this->form_validation->run() == FALSE) {
$this->load->view('header');
$this->load->view('forgot');
$this->load->view('footer');
}else{
$email = $this->input->post('email');
$clean = $this->security->xss_clean($email);
$userInfo = $this->user_model->getUserInfoByEmail($clean);
if(!$userInfo){
$this->session->set_flashdata('flash_message', 'We cant find your email address');
redirect(site_url().'main/login');
}
if($userInfo->status != $this->status[1]){ //if status is not approved
$this->session->set_flashdata('flash_message', 'Your account is not in approved status');
redirect(site_url().'main/login');
}
//build token
$token = $this->user_model->insertToken($userInfo->id);
$qstring = $this->base64url_encode($token);
$url = site_url() . 'main/reset_password/token/' . $qstring;
$link = '' . $url . '';
$message = '';
$message .= 'A password reset has been requested for this email account
';
$message .= 'Please click: ' . $link;
echo $message; //send this through mail
exit;
}
}
The action opens up the same way we have with our other forms. We have our validation rules for our “email” input field. In our form, all we’re really asking for our user is to enter their password. So we just need to run a couple of rules against this value: valid email and required. Once our validation passes, we also clean the data with our xss_clean() method which is loaded automatically.
To learn more about the Security Helper click here.
Now we check if the email submitted exists in our database through our Model method: getUserInfoByEmail(). So add the code below to our Model (User_Model.php). This method returns the record on success and false on fail.
public function getUserInfoByEmail($email)
{
$q = $this->db->get_where('users', array('email' => $email), 1);
if($this->db->affected_rows() > 0){
$row = $q->row();
return $row;
}else{
error_log('no user found getUserInfo('.$email.')');
return false;
}
}
We also need another method that checks grabs the user information. This method called “getUserInfo()” simply returns the row and false if none is found:
public function getUserInfo($id)
{
$q = $this->db->get_where('users', array('id' => $id), 1);
if($this->db->affected_rows() > 0){
$row = $q->row();
return $row;
}else{
error_log('no user found getUserInfo('.$id.')');
return false;
}
}
So back in our “forgot” action, we simply set a couple of error messages when we receive a false from our model, or proceed with the token creation “insertToken()” on success. Note that we are reusing this method from our previous action when creating a new user.
The code for our view is below. Just some messaging our form and input fields. Notice we’re using CodeIgniter’s Form class.
<div class="col-lg-4 col-lg-offset-4">
<h2>Forgot Password</h2>
<p>Please enter your email address and we'll send you instructions on how to reset your password<br>
<?php
$fattr = array('class' =--> 'form-signin');
echo form_open(site_url().'main/forgot/', $fattr); ?>
</p>
<div class="form-group">
<?php echo form_input(array(
'name'=-->'email', 'id'=> 'email', 'placeholder'=>'Email', 'class'=>'form-control', 'value'=> set_value('email'))); ?>
<?php echo form_error('email');?>
</div>
<p><?php echo form_submit(array('value'=-->'Submit', 'class'=>'btn btn-lg btn-primary btn-block')); ?>
<?php echo form_close(); ?></p>
</div>
Now go ahead and try out your form. Try an email that doesn’t exist – and try one that does. With our unique token code in our action, we continue to construct the link and “echo” to the browser.
Remember that this is to be emailed to the user but we are simply echoing it out for demo purposes. The link should look like below:
The Reset Password Form
So now we have a unique link that we’ve sent to our forgetful user. Once they click on this link – they better have a page to land on, with a way to reset their password. So let’s build that now.
Let’s create another action in our controller, name it “reset_password()” and add the code below:
public function reset_password()
{
$token = $this->base64url_decode($this->uri->segment(4));
$cleanToken = $this->security->xss_clean($token);
$user_info = $this->user_model->isTokenValid($cleanToken); //either false or array();
if(!$user_info){
$this->session->set_flashdata('flash_message', 'Token is invalid or expired');
redirect(site_url().'main/login');
}
$data = array(
'firstName'=> $user_info->first_name,
'email'=>$user_info->email,
'token'=>base64_encode($token)
);
$this->form_validation->set_rules('password', 'Password', 'required|min_length[5]');
$this->form_validation->set_rules('passconf', 'Password Confirmation', 'required|matches[password]');
if ($this->form_validation->run() == FALSE) {
$this->load->view('header');
$this->load->view('reset_password', $data);
$this->load->view('footer');
}else{
$this->load->library('password');
$post = $this->input->post(NULL, TRUE);
$cleanPost = $this->security->xss_clean($post);
$hashed = $this->password->create_hash($cleanPost['password']);
$cleanPost['password'] = $hashed;
$cleanPost['user_id'] = $user_info->id;
unset($cleanPost['passconf']);
if(!$this->user_model->updatePassword($cleanPost)){
$this->session->set_flashdata('flash_message', 'There was a problem updating your password');
}else{
$this->session->set_flashdata('flash_message', 'Your password has been updated. You may now login');
}
redirect(site_url().'main/login');
}
}
So our users will land in our page called “reset_password”, with a token in the url. First we need to process that and determine if its a good token. This is accomplished by our model’s isTokenValid() method. This method is the same method we had in our “complete” action when we are registering a new user – so this is nothing new.
And if you remember, this method returns user information on success – so we go on with our setup for the form. We pass this information using $data array straight to our view, reset_password.php:
<div class="col-lg-4 col-lg-offset-4">
<h2>Reset your password</h2>
<h5>Hello <?php echo $firstName; ?>, Please enter your password 2x below to reset</h5>
<p><?php
$fattr = array('class' =--> 'form-signin'); echo form_open(site_url().'main/reset_password/token/'.$token, $fattr); ?></p>
<div class="form-group">
<?php echo form_password(array('name'=-->'password', 'id'=> 'password', 'placeholder'=>'Password', 'class'=>'form-control', 'value' => set_value('password'))); ?> <?php echo form_error('password') ?></div>
<div class="form-group">
<?php echo form_password(array('name'=-->'passconf', 'id'=> 'passconf', 'placeholder'=>'Confirm Password', 'class'=>'form-control', 'value'=> set_value('passconf'))); ?> <?php echo form_error('passconf') ?></div>
<p><?php echo form_hidden('user_id', $user_id);?>
<?php echo form_submit(array('value'=-->'Reset Password', 'class'=>'btn btn-lg btn-primary btn-block')); ?>
<?php echo form_close(); ?></p>
</div>
Clicking the link with the token will run our view. And if you did it correctly, it should render like below:
Finally, we do some validation and sanitation on the passwords entered. Once passed, we continue to encrypt our user’s password through the create_hash() method. Remember this method is from our Password class that we’ve also used when creating new users. Then we update the user’s password on success.
Conclusion
So there you have our basic user registration system, explained in detail. Remember that this is mainly to get a grasp on what goes on behind the scenes of such a system as well as learning the ropes of MVC. It is also important to know that this is not production code. There are plenty of improvements to be made. But this should be enough to get you started.
Michael, thank you for the full tutorial, well made and very usefull, I went up a step more understanding MVC and CI.
After I have logged in successfully, I am getting the following error.
Unable to load the requested file: index.php
You have to add an empty file named “index.php” inside the view directory
Hey, could you give me a guide on how can I modify this to actually send tokens to email adress ?
So in the method “forgot()”, there is a part where I echo $message; exit. You simply do a mail($to,$subject,$message);
Ohhh thank, but I worked on this some other way and both register and forgot works perfectly. Thank you very much for this tutorial and great code 🙂
thanks a lot , it is working nicely , i am new to CI , and this tutorial helping me in learning this , but forget password is not working for me , ???
any help ???
You can stop the code by echoing out every step of the way. I would start when the page loads after the link is clicked. Simply echo out what is being processed.
Upon pressing Complete on the password/password confirm page, I get the error: “Unable to load the requested file: index.php”. Any idea what’s going wrong?
that looks like a configuration issue with CI
You have to add an empty file named “index.php” inside the view directory
Fatal error: Call to undefined method User_model::getUserInfo() in C:\xampp\htdocs\xxxxxxxxx\application\models\user_model.php on line 68
Thanks for such a nice tutorial. I appreciate your efforts.
Although you made a tiny mistake. Notice I was getting an error:
Call to undefined method Auth_model::getUserInfo()
I believe you forgot to mention getUserInfo() method in tutorial but its there in the github(https://github.com/michaelsoriano/user-registration-codeigniter) package.
Anybody facing same issue can copy or create getUserInfo() method from User_model.php hosted at github (https://github.com/michaelsoriano/user-registration-codeigniter).
Thank you.
Exact getUserInfo() method URL to github User_model.php is as follows:
https://github.com/michaelsoriano/user-registration-codeigniter/blob/master/application/models/User_model.php#L74
Anybody facing Call to undefined method Auth_model::getUserInfo() can copy method from above github hosted file.
Thank you.
I was also getting another error:
Unable to load the requested class: Password
As Michael mentioned, we are using Password Class we need to add it to our libraries directory. If anybody getting same issue, you can grab Password.php from github(https://github.com/michaelsoriano/user-registration-codeigniter/tree/master/application/libraries) too.
Thank you.
Yes I did forget to add that. But its there now. Thanks for the spotting the errors. Will update the post.
An Error Was Encountered
Unable to load the requested file: index.php
Please help. Thanks
Sounds like a configuration. Make sure your routes are set up correctly. Create an issue in my Github page so I can track.
You have to add an empty file named “index.php” inside the view directory
With this structure, anyone can use email verification token for resetting password in one day.
I think this can be treated as a bug.
If you look in Part 2, I didn’t include a stronger token check (such as a shorter time frame for the token to be valid). I’m leaving that up to you. You can definitely make the code better – submit a merge request in Git.
With this structure one can reset someone else’s password using his own token :|.
I may have overlooked this. Can you explain your findings?
@Mohammad – updated code to fix this issue: https://github.com/michaelsoriano/user-registration-codeigniter/commit/7812ad2e94b6bf2f232476fc108275dbe6a20d4a.
Thank you so much… It is working nicely..
I am getting an error..
An uncaught Exception was encountered
Type: Error
Message: Call to undefined function redirect()
Filename: /Users/xxxxxx/application/controllers/Main.php
Line Number: 20
Did you create a redirect function?
when i click complete button showing the following link
http://localhost/ci_last/index.php/main/complete/token/ZDA5OTU2NjFmZGZlYzQwOTgzZTNmYjYwZTM5YzE4NDQ
and message
unable to load class:password
Check that the class “Password” has been loaded. The file should be: /application/libraries/Password.php
am getting a error while clicking when i click on this link http://localhost/lasttime/index.phpmain/complete/token/MTdjYzRlZmE3YmY3ZDllNjNjOTk2ODNkZDlkOGY1NTc
“object not found error”
and in above link after index.php am not getting a slash(/), will that matter?
Thanks for the great code. It has been a major help!
I have now added another view that only logged in users should be able to see. How would you recommend checking that a user is logged in before making a page visible? At the moment it’s possible just to type the name of the view in the url, and it will be displayed.
you can create a function “isloggedin()” and add it to your controllers (maybe in the construct) – So it runs each time. So when you load your page, it should check it.
Amazing tutorial! Just one question. Is it possible to make the $url a route instead of using the controller and function name in the URL?
yes I believe so.
Thanks for code! its working
please send us even more pages like forgot_password.php registration page and login page with activation email
All these pages are included in the GIT repo.
Hello, i used the exact same code and when I click on send email no email is send which is weird.
Do you know what I could do to fix it?
The code is only supposed to output the message in the browser. Assuming you don’t have mail service setup. You can take that message and send it via mail().
Hi
This is a really great tutorial.
I have one question – After I login through the login page, and then hit the home url, my session goes away. I have to again put in the login credentials.
http://[::1]/CItest//main/login/
This is my login page. After I login, I get this page http://[::1]/CItest/main/
Then if I hit localhost/CItest in the url, I am again prompted the login window at http://[::1]/CItest//main/login/
How do I ensure that the logged in session stays throughout the site ?
In session, you can make a key “is_logged_in” and set it to “true”. This should remain persistent throughout.
When i go the link with the token i got a 404 not found error
This is where is my file for new password /password/new
And with the token /password/new/token/ (token)
Can you help me ?
Thanks for the awesome work 🙂
you can delete this post it’s solved
can anyone give me information about content-base recommendation algorithm ?
please..
these tutorial is hard, try putting the extensions of the files and more preferably raw codes than what you call codeigniter, when teaching.
proper naming of the files also, the teaching is scattered for me though.
whenever i am trying to login with correct credential still it is loading me the login page only. seems validation rules are always returning false. also i tried echoing posted userinfo but that is always showing empty. any idea. haven’t changed anything in code so far.
There is a problem with my code.
in Codeigniter 3
Message: Call to undefined method Main::base64url_encode()
Filename: C:\xampp\htdocs\webmodify\application\controllers\Main.php
Line Number: 43