Building a user registration system – Part 3: The Password Reset Form

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.

View in Github

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.

On a side note, I've really enjoyed the videos from Laracasts.com. These are high quality web developer tutorials on topics and technologies ranging from Laravel, React, Vue and many more. Head over to Laracasts and check them out!
<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'=&gt; 'email', 'placeholder'=&gt;'Email', 'class'=&gt;'form-control', 'value'=&gt; set_value('email'))); ?>
<?php echo form_error('email');?>
</div>
<p><?php echo form_submit(array('value'=-->'Submit', 'class'=&gt;'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.

forgot-password

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'=&gt; 'password', 'placeholder'=&gt;'Password', 'class'=&gt;'form-control', 'value' =&gt; set_value('password'))); ?&gt; <?php echo form_error('password') ?></div>
<div class="form-group">
<?php echo form_password(array('name'=-->'passconf', 'id'=&gt; 'passconf', 'placeholder'=&gt;'Confirm Password', 'class'=&gt;'form-control', 'value'=&gt; 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'=&gt;'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:


reset-password

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.

affiliate link arrowdigitalocean banner

46 Comments

      • 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 🙂

        Reply
  1. 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 ???

    Reply
    • 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.

      Reply
  2. 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?

    Reply
  3. Fatal error: Call to undefined method User_model::getUserInfo() in C:\xampp\htdocs\xxxxxxxxx\application\models\user_model.php on line 68

    Reply
  4. 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.

    Reply
  5. 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?

    Reply
  6. 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.

    Reply
    • 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.

      Reply
  7. 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?

    Reply
  8. please send us even more pages like forgot_password.php registration page and login page with activation email

    Reply
  9. 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?

    Reply
    • 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().

      Reply
  10. 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 ?

    Reply
    • In session, you can make a key “is_logged_in” and set it to “true”. This should remain persistent throughout.

      Reply
  11. 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 🙂

    Reply
  12. 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.

    Reply
  13. 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.

    Reply

Leave a Comment.