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.
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:
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).
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?
Do another pull. Should be updated now.
Yes I do – that’s a library that is needed for hashing passwords. I didn’t include it in the repo. Let me update git. You will just need to pull again. Sorry about that.
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.
Glad you find it useful. Remember, there are many improvements that can be done with the code. This is just to get you up and running with the registration process and MVC. Part 2 is out too: http://michaelsoriano.com/building-a-user-registration-system-part-2-login-form/
Excellent stuff.Can you please tell me how to create a dynamic product details page for a shopping cart.
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?
Yes it’s a good starting point for a real application.
I think that email would be UNIQUE field type. Isn’t it?
TNX for the help!
hi sir! how can i use this locally? im running wamp
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.
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
That seems to be a CI configuration issue. Check your “Routes” to see why it’s looking for index.php.
Hello kavish,
the file index.php dos’t exist in the view directory… you must write one !
same issue from here,how to solve sir………
Hi Raju, try adding an index.php in the view directory
Thank you Thierry
Thank you for this code – its really well written and documented
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 ?
Did you mean what’s in the register.php in the view?
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
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);
yes, you need that to validate and update the database for the time.
make sure the model is properly loaded in checkLogin
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.
thank you for posting Andi
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) …
So each action in our “Main” controller represents a segment in the url. I believe just try going to localhost/main
worked great! thanks. sorry i’m a newbie but it’s up and running now!
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
I would do an echo on what is being passed to isDuplicate() – before line 47 and exit(); See what’s going on.
Any easy way to disable registration, in config or any other way?
Not as part of the current code, but yes – it would probably be part of config.
Silly me, I realized I could just comment out the code for the registration form, accomplishes the task nicely. Thanks for the reply though.
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.
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?
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?
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
I would make sure that you have “user_id” in your $post array. Do a print_r($post) exit and see.
can u add a edit profile feature
Hi Martin, yes that’s a very good feature to add.
I created an issue in Github so I can add this feature:
https://github.com/michaelsoriano/user-registration-codeigniter/issues/3
thanks very much and im new to codeigniter and am just fiddling around but how would i approach user roles
thanks
dude, this is the bomb. Thanks for this.
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!
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
It looks like you can’t connect because of permissions to MySql. Try fixing that.
Dude!! You forget to define username and database name in config>database file….
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!
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…
Thanks for letting me know. I’ll have to look and see.
Hi, there is no logout feature. Can you add it sir?
http://localhost/dashboard/websites/10/user-registration-codeigniter-master//main/login/
i have getting this error . im totally new to codeigniter , can anyone tell me what basic steps i need to use it. iave created the database and running in localhost.
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..?
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