Building a user registration system – Part 1: The New User Form

Almost every website will have some form of public and restricted area. WordPress for instance, has the admin side where you can create posts, manage plugins, install themes etc. For this tutorial, we are going to create a similar mechanism.

One that will restrict users from accessing sections of the site. We will create a system that will allow them to create their own account, reset their password and even update their profile.

Update 8/13/2018: Added the entire CI application in the repo, allowing faster setup and debug. CI version is 3.0.2 The setup steps below might not be necessary for usage.

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

user-reg

I’m going to assume that you are familiar with CodeIgniter, or other MVC frameworks. You don’t have to be an expert – just know enough that you will be able to follow along without me having to explain too much. We are also going to utilize Bootstrap – so our pages will look nice. Finally, the focus of this tutorial is learning how the process works. We’re not really here to look at design patterns, performance or database design.

So ready to get started? Roll up your sleeves and let’s start writing.

Get things setup

So obviously we’re going to need to store data – so we need a database. We will need a table that will house our users. Below is the SQL code to get you up and running. I’m calling the database “user-registration” – which is a stupid name actually. Name yours anything you want.

CREATE DATABASE IF NOT EXISTS `user-registration` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `user-registration`;

CREATE TABLE IF NOT EXISTS `ci_sessions` (
  `id` varchar(40) NOT NULL,
  `ip_address` varchar(45) NOT NULL,
  `timestamp` int(10) unsigned NOT NULL DEFAULT '0',
  `data` blob NOT NULL,
  PRIMARY KEY (`id`),
  KEY `ci_sessions_timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `tokens` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `token` varchar(255) NOT NULL,
  `user_id` int(10) NOT NULL,
  `created` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=26 ;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `email` varchar(100) NOT NULL,
  `first_name` varchar(100) NOT NULL,
  `last_name` varchar(100) NOT NULL,
  `role` varchar(10) NOT NULL,
  `password` text NOT NULL,
  `last_login` varchar(100) NOT NULL,
  `status` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=42 ;

We’re going to use CodeIgniter 3.0.2. Download and install in your web server.
In config > config.php add the two lines below. I’ll explain these later.

$config['roles'] = array('subscriber', 'admin');
$config['status'] = array('pending', 'approved');

Open database.php, and add your database credentials. It would be something like:

$db['default'] = array(
	'dsn'	=> '',
	'hostname' => 'localhost',
	'username' => 'root',
	'password' => '',
	'database' => 'user-registration',
	... //additional stuff here...
);

Create a new controller named Main.php and a model, name it User_model.php. In the next sections, we will tie these things together.

But first let’s look at the workflow of our system:
process-user-reg

So our first step is the registration form. Users fill out this form which will only consist of their name and email. This will write a record to our database – but the account will only be in “pending” status. This will also trigger an email containing a token (which expires in a day), when clicked – will allow the user to finish the registration process. This form will have a password and password confirmation. This will update the record – and make the account “active”.

We will automatically login the user, but now that they have an account – they can actually utilize the login and reset password form.

It also makes sense for us to build the pages in this exact order. So we got: 1) register 2) complete 3) login 4) forgot password.

So inside our routes.php, add the default controller to ours:

$route['default_controller'] = 'main';

Also, let’s setup our autoload.php:

$autoload['helper'] = array('url');
$autoload['libraries'] = array('session');

Now we can move forward.

The Registration

So the very first thing we need is the HTML that contains our form. Let’s create a new file called “register.php” and put it in our “views” folder. Add the code below:

<div class="col-lg-4 col-lg-offset-4">
    <h2>Hello There</h2>
    <h5>Please enter the required information below.</h5>
<?php
    $fattr = array('class' => 'form-signin');
    echo form_open('/main/register', $fattr); ?>
    <div class="form-group">
      <?php echo form_input(array('name'=>'firstname', 'id'=> 'firstname', 'placeholder'=>'First Name', 'class'=>'form-control', 'value' => set_value('firstname'))); ?>
      <?php echo form_error('firstname');?>
    </div>
    <div class="form-group">
      <?php echo form_input(array('name'=>'lastname', 'id'=> 'lastname', 'placeholder'=>'Last Name', 'class'=>'form-control', 'value'=> set_value('lastname'))); ?>
      <?php echo form_error('lastname');?>
    </div>
    <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>
    <?php echo form_submit(array('value'=>'Sign up', 'class'=>'btn btn-lg btn-primary btn-block')); ?>
    <?php echo form_close(); ?>
</div>

Note that I’m not going to go through what goes in our header and footer template because it’s irrelevant to this tutorial. Just know that it’s basic HTML, along with a link to Bootstrap CSS. Our markup above is a mix of PHP and HTML. This is using CodeIgniter’s Form Class – which makes it real easy to do validation and data filter and sanitation.

So in our Main controller, let’s add a constructor and include the necessary components for our class. We’ll also set up our status and roles arrays inside this constructor:

class Main extends CI_Controller {
        public $status;
        public $roles;
        function __construct(){
            parent::__construct();
            $this->load->model('User_model', 'user_model', TRUE);
            $this->load->library('form_validation');
            $this->form_validation->set_error_delimiters('<div class="error">', '</div>');
            $this->status = $this->config->item('status');
            $this->roles = $this->config->item('roles');

So we’re loading our model “User_model” by default, the “form_validation” library and setting some HTML for our form errors. You’ll also notice the 2 properties (status and roles) – which grabs it from “$this->config”. Remember in our config file – we created 2 arrays? We’re simply outputting it here for easy access.

Now I know you’re saying this is better if these entries was in their own table in the database. Again, this is not a tutorial on database design. We’re simply doing it this way because it is not our focus. Let’s continue with our “register” method:

public function register()
        {
            $this->form_validation->set_rules('firstname', 'First Name', 'required');
            $this->form_validation->set_rules('lastname', 'Last Name', 'required');
            $this->form_validation->set_rules('email', 'Email', 'required|valid_email');
            if ($this->form_validation->run() == FALSE) {
                $this->load->view('header');
                $this->load->view('register');
                $this->load->view('footer');
            }else{
                if($this->user_model->isDuplicate($this->input->post('email'))){
                    $this->session->set_flashdata('flash_message', 'User email already exists');
                    redirect(site_url().'main/login');
                }else{
                    $clean = $this->security->xss_clean($this->input->post(NULL, TRUE));
                    $id = $this->user_model->insertUser($clean);
                    $token = $this->user_model->insertToken($id);
                    $qstring = $this->base64url_encode($token);
                    $url = site_url() . 'main/complete/token/' . $qstring;
                    $link = '' . $url . '';
                    $message = '';
                    $message .= 'You have signed up with our website
';
                    $message .= 'Please click: ' . $link;
                    echo $message; //send this in email
                    exit;
                };
            }
        }

Alright, plenty of going on up there. So the validation rules are setup initially. Our form contains 3 fields: Last name, First name and email. They’re all required fields, plus the email address must be in valid format.

You will notice in the block where our form is validated, we’re checking “isDuplicate()” from our model: $this->user_model. This is so that we won’t have duplicate records of the same email. Remember, we are using their email address as their username.

So if it’s a duplicate – we simply use CodeIgniter’s set_flashdata() and redirect them to the login page (which is not built yet).

If the email address is good, we continue by cleaning up the $_POST array. Notice that we have 2 methods right after the cleaning: insertUser() and insertToken(). So we write the record to our database, then we create a new token (let’s quickly run this SQL so we have this token table).

CREATE TABLE IF NOT EXISTS `tokens` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `token` varchar(255) NOT NULL,
  `user_id` int(10) NOT NULL,
  `created` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=26 ;

Another thing, you will also see that we’re using a local method called “base64url_encode()” – which is a way of outputting base64 code – that is in a web safe format. So in our controller, you can add these two methods in the bottom. Think of them as local “helper” methods.

public function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
public function base64url_decode($data) {
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}

I am not putting them in a the “helpers” for CI – because these methods are only to be used in controllers – since they are “URL” related. And as you can see, we only have one controller. But as your application grows, and you have many controllers, it’s best to move this into a real CI Helper.
Finally, we create our final URL, to our “complete” action and echo it out. In production, this will be done through an email, in HTML format.

Some Back-end Stuff

Our model “User_model” is empty. Let’s go ahead and fill it up. We need 3 methods for our register to work remember? It’s 1) isDuplicate(), 2) insertUser() and insertToken(). First let’s create a constructor with our roles and status arrays:

public function insertUser($d)
    {
            $string = array(
                'first_name'=>$d['firstname'],
                'last_name'=>$d['lastname'],
                'email'=>$d['email'],
                'role'=>$this->roles[0],
                'status'=>$this->status[0]
            );
            $q = $this->db->insert_string('users',$string);
            $this->db->query($q);
            return $this->db->insert_id();
    }
    public function isDuplicate($email)
    {
        $this->db->get_where('users', array('email' => $email), 1);
        return $this->db->affected_rows() > 0 ? TRUE : FALSE;
    }
    public function insertToken($user_id)
    {
        $token = substr(sha1(rand()), 0, 30);
        $date = date('Y-m-d');
        $string = array(
                'token'=> $token,
                'user_id'=>$user_id,
                'created'=>$date
            );
        $query = $this->db->insert_string('tokens',$string);
        $this->db->query($query);
        return $token . $user_id;
    }

So judging from the method names – they’re pretty self explanatory correct? Also, if you’ve worked with CodeIgniter before, you’ll notice the syntax of interacting with the database. It used to be called “active record”, which is now the Query Builder class. Makes it real easy for non DBA’s like me to write queries.

The three methods all return either an array or a string on success, and “false” on failure. The return values are handled accordingly in our controller methods discussed previously. Pay token is simply a random number that we create and insert into the database. This token has an accompanying user id and a time stamp in the same row. This makes it possible for us to fetch the user, and find if it’s a valid request, in the appropriate time frame.

Let’s break it here for now. We have plenty more to come in the next part(s).

54 Comments

  1. I’m using CodeIgniter 3.0.1 – I have downloaded the files and added everyone correctly – the form is enabled and I enter my name, email address but when the enter a password and click to continue, I get the message: Unable to load the requested class: Password
    I have also ensured that my autoload includes the ‘security’, but no luck – do you have any idea why this should be happening?

    Reply
  2. I am just getting my head around the MVC framework concept, and decided to use your user-registration system as my test. It has proven to have been a good choice.
    I pulled the updated version, added the password library, made a few tweaks to my system and then had it working as it should. I really appreciated the opportunity to do so, thank you for sharing.
    I had to read up on ‘sessions’ as I initially tried to get the $data(session) variable into my system front page, but could not find a way to do so, checking the changes from CI2 to CI3, I found the solution that worked for me was to use the global $_SESSION variable.

    Reply
  3. Thank You so much for this explanation. But i wonder, can i use these codes for real application such as online store, or do you have other codes for your real registration system that you use for yours?

    Reply
    • Create a new website. Download CodeIgniter. Once that’s running, follow the steps above. If you want, you can also download the files from Github – simply add them to the right directories in CI.

      Reply
  4. Hello Michael
    i am beginner for CI, and i was follow you code for signup.
    all process done successfully but when i login or enter 2 step confirm password i am getting a error below:
    An Error Was Encountered
    Unable to load the requested file: index.php
    please help me
    thanks

    Reply
  5. in this >> So in our Main controller, let’s add a constructor and include the necessary components for our class. We’ll also set up our status and roles arrays inside this constructor:
    where it placed the above code ? What’s in register.php file ?

    Reply
  6. Hello sir, thank you for this article. It’s very helping me to learn ci. I’ve got error when i fill email/password form with random text then submit it.
    Severity: Notice
    Message: Trying to get property of non-object
    Filename: models/User_model.php
    Line Number: 121
    Backtrace:
    File: /public_html/jogjalife/application/models/User_model.php
    Line: 121
    Function: _error_handler
    File: /public_html/jogjalife/application/controllers/Main.php
    Line: 154
    Function: checkLogin

    Reply
    • If i comment this code (/application/models/User_model.php) class checkLogin, error warning is gone :
      //if(!$this->password->validate_password($post[‘password’], $userInfo->password)){
      // error_log(‘Unsuccessful login attempt(‘.$post[’email’].’)’);
      // return false;
      //}
      //$this->updateLoginTime($userInfo->id);

      Reply
      • It solved yesterday.
        “I change $userInfo = $query->row_array();” instead “$userInfo = $query->row();” and also “if(!$this->password->validate_password($post[‘password’], $userInfo[‘password’])){” instead “if(!$this->password->validate_password($post[‘password’], $userInfo->password)){”
        And it works.

        Reply
  7. Hi Michael. Thanks for doing this; it’s extremely helpful! So I’m a CI newbie and just finished their tutorial. Now I’m onto yours and I generally get what’s going on. Silly question but which URL one would call to to test the code after part 1? I am doing this local (localhost) …

    Reply
  8. I am getting this error. Any ideas what it possibly be?
    Fatal error: Call to a member function isDuplicate() on null in C:\xampp\htdocs\artisan\application\controllers\Main.php on line 47

    Reply
  9. nice, another one registration system which I am using and its pretty cool is ArtDesignUI – artdesign-ui.com/php/registration-system/info . Its easy to use and comes with 27 js plugins.

    Reply
  10. I have another CI app that I would like to merge with this one in order to protect some sensitive data. The other app uses ajax with datatables to list db contents with members addresses, phone numbers, etc. How do you merge the classes from to separate applications into one?

    Reply
  11. i found this error
    Error Number: 1048
    Column ‘role’ cannot be null
    INSERT INTO `users` (`first_name`, `last_name`, `email`, `role`, `status`) VALUES (‘test’, ‘test’, ‘test@gmail.com’, NULL, NULL)
    how do i fix?

    Reply
  12. Hi Michael, great work!
    I would like some help as I’m getting an error on the confirm page of the registration process.
    This is from the CI logs:
    ERROR – 2017-04-22 21:07:41 –> Severity: Notice –> Undefined index: user_id C:\xampp\htdocs\application\models\User_model.php 85
    ERROR – 2017-04-22 21:07:41 –> Severity: Notice –> Undefined index: user_id C:\xampp\htdocs\application\models\User_model.php 90
    This is the updateUserInfo($post) function, where $this->db->where(‘id’, $post[‘user_id’]); is executed.
    I’ve been stuck on this for a whole day, any pointers?
    Thanks in advance

    Reply
  13. This is in continuation with Andi’s question.
    It did work for me but it doesn’t go through when you input correct credentials. It produces a ‘Trying to get property of non-object’. Looking at it, the updateLoginTime method is the culprit so I changed this line:
    $this->updateLoginTime($userInfo->id);
    to:
    $this->updateLoginTime($userInfo[‘id’]);
    …and it worked!
    Thanks!

    Reply
  14. Hi sir. This is the result when i run the system. pls help me sir. Thanks
    Severity: Warning
    Message: mysqli::real_connect(): (HY000/1044): Access denied for user ”@’localhost’ to database ‘user-registration’
    Filename: mysqli/mysqli_driver.php
    Line Number: 201
    Backtrace:
    File: C:\xampp\htdocs\User\application\controllers\Main.php
    Line: 12
    Function: model
    File: C:\xampp\htdocs\User\index.php
    Line: 315
    Function: require_once

    Reply
  15. Hello there! first of all great tutorial for people that are new to CI such as me! Many thanks!
    I’ve got a question for you… After logging in I’d like to redirect to my main page (which i already managed to do) and display a welcome message such as “Welcome ’email'”. So, I ask you: what would be the best way to access the email address in my homepage controller to pass the data i need to the view?
    Thank you!

    Reply
  16. P.s. I noticed a problem… I tried running the GIT hub version too and the problem is still there. When I log in no “welcome” message is displayed (the one originally setted with flash data). Also when I enter a fake user, no error message is displayed. Same thing for a good username but wrong password…

    Reply
  17. Not Found
    The requested URL was not found on this server.
    Apache/2.4.46 (Win64) OpenSSL/1.1.1g PHP/7.2.34 Server at localhost Port 80
    hi sir, i am beginner for CI.. i have getting this error..anyone can help me..?

    Reply
    • edit the .htaccess file
      delete old line and paste this
      RewriteEngine on
      RewriteCond $1 !^(index.php|resources|robots.txt)
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule ^(.*)$ index.php/$1 [L,QSA]
      thank me later

      Reply

Leave a Reply to Anu s kumar Cancel reply