Well… We’ve come so far… I thought that it was simple to implement an authentication system with Ion Auth, but still some people don’t seem to understand how to implement this by using AJAX.
So let’s assume we would have a page that should allow us to login by using AJAX. Going to our welcome controller let’s convince it to render a page that has a login form.
<?php defined('BASEPATH') OR exit('No direct script access allowed'); class Welcome extends MY_Controller { public function index() { $this->load->helper('form'); $this->render('welcome/index_view'); } }
Now, for the brevity, we will simply copy/paste the login form we’ve created for the user controller:
<?php defined('BASEPATH') OR exit('No direct script access allowed');?> <div class="row"> <h1>Login</h1> <div class="col-lg-4 col-lg-offset-4"> <div class="login-message"></div> <?php echo form_open('user/login',array('class'=>'login-form form-horizontal'));?> <div class="form-group"> <?php echo form_label('Username','username');?> <div class="username_error"></div> <?php echo form_input('username','','class="form-control" id="username"');?> </div> <div class="form-group"> <?php echo form_label('Password','password');?> <div class="password_error"></div> <?php echo form_password('password','','class="form-control" id="password"');?> </div> <div class="form-group"> <label> <?php echo form_checkbox('remember','1',FALSE);?> Remember me </label> </div> <?php echo form_submit('submit', 'Log in', 'class="btn btn-primary btn-lg btn-block" id="login-button"');?> <?php echo form_close();?> </div> </div>
Now we only need to replace some lines so that we can use the form on any “potential pages” that will use this form. First of all we need to direct this form to a specific page that will deal with the login process. Then we need to create the div’s needed in order to contain the error messages.
In order to create an AJAX call, we need to use javascript, but… as usual, we are lazy and will use jQuery.
If you take a look at MY_Controller, you will see that the render() method is calling a “footer” part of a page from views/templates/_parts. To refresh your memory, you can read again the first part of this tutorial series (http://avenir.ro/authentication-system-with-ion-auth-and-ci3/lets-install-ion-auth-library/). That footer view in our case is called “public_master_footer_view.php” and it will look liKe this:
<?php defined('BASEPATH') OR exit('No direct script access allowed');?> </div> <footer> <div class="container"> <p class="footer">Page rendered in <strong>{elapsed_time}</strong> seconds. <?php echo (ENVIRONMENT === 'development') ? 'CodeIgniter Version <strong>' . CI_VERSION . '</strong>' : '' ?></p> </div> </footer> <script src="<?php echo site_url('assets/js/bootstrap.min.js');?>"></script> <?php echo $before_closing_body;?> </body> </html>
Of course, your view can look a bit different… You can at any time download my basic codeigniter start app on Github (https://github.com/avenirer/Base-CI).
Now… Let us modify it so that will do something when someone submits the form:
<?php defined('BASEPATH') OR exit('No direct script access allowed');?> </div> <footer> <div class="container"> <p class="footer">Page rendered in <strong>{elapsed_time}</strong> seconds. <?php echo (ENVIRONMENT === 'development') ? 'CodeIgniter Version <strong>' . CI_VERSION . '</strong>' : '' ?></p> </div> </footer> <script src="<?php echo site_url('assets/js/bootstrap.min.js');?>"></script> <?php echo $before_closing_body;?> <script> $('.login-form').on('submit', function(e) { var username = $("#username").val(); var password = $("#password").val(); $.ajax({ url: "<?php echo site_url('user/ajax_login');?>", type: "post", data: {username: username, password: password}, cache: false, success: function (json) { var message = json.message; console.log(message); } }); e.preventDefault(); }); </script> </body> </html>
Let’s see what we’ve done here… By using jQuery we told the page that when our login form (“.login-form”) is submitted we want javascript to take over and redirect the form to a method called ajax_login and pass it the username and password in POST variables. Also, on success, we will retrieve data in json format to work with it. For the moment, we will simply console log a message in order to see if it works or not.
Now, going to our User controller, we will create a method called ajax_login() and make it so that it will return a “hello world” message variable in json format when called.
public function ajax_login() { $response['message'] = 'hello'; header("content-type:application/json"); echo json_encode($response); }
If we now return to our login form and just submit it, if we have developer window opened, we will see that we are greeted with a hello message. Cool. Everything seems ok.
“BUT…”, you may ask… “WHY NOT USE THE LOGIN METHOD WE ALREADY BUILT???”. Niiiiice… You are on the right way towards DRY philosophy. So why not use the login() method we already created in a previous tutorials? We can do this simply by adding a new POST variable to the request, let’s call it “ajax” and give it the value “1“. (Did you know that CodeIgniter also has a method called is_ajax_request()…)
So, returning to our footer view, we simply change the ajax request:
url: "<?php echo site_url('user/login');?>", type: "post", data: {ajax: 1, username: username, password: password},
Cool… now let us delete the ajax_login() method from our User controller, and take a look at the login() method in the same controller:
public function login() { $this->data['title'] = "Login"; $this->load->library('form_validation'); $this->form_validation->set_rules('username', 'Username', 'trim|required'); $this->form_validation->set_rules('password', 'Password', 'trim|required'); if ($this->form_validation->run() === FALSE) { $this->load->helper('form'); $this->render('user/login_view'); } else { $this->ion_auth->set_hook('post_login_successful', 'get_gravatar_hash', $this, '_gravatar', array()); $remember = (bool) $this->input->post('remember'); $username = $this->input->post('username'); $password = $this->input->post('password'); if ($this->ion_auth->login($username, $password, $remember)) { redirect('dashboard'); } else { $_SESSION['auth_message'] = $this->ion_auth->errors(); $this->session->mark_as_flash('auth_message'); redirect('user/login'); } } }
So what do we do here? Let us for starters change the part where the user was not logged in…:
else { $_SESSION['auth_message'] = $this->ion_auth->errors(); $this->session->mark_as_flash('auth_message'); redirect('user/login'); }
In here, instead of redirecting to another page, we first ask if a POST variable named “ajax” was also sent with our form and if that ajax variable is set to “1“. If it is, instead of redirecting to a new page, we will send the necessary data in a json format:
else { if($this->input->post('ajax')) { $response['error'] = $this->ion_auth->errors(); header("content-type:application/json"); echo json_encode($response); exit; } $_SESSION['auth_message'] = $this->ion_auth->errors(); $this->session->mark_as_flash('auth_message'); redirect('user/login'); }
Now, returning to our footer view, we take over the message and put it just before the login form.
<script> $('.login-form').on('submit', function(e) { var username = $('#username').val(); var password = $('#password').val(); $.ajax({ url: "<?php echo site_url('user/login');?>", type: 'post', data: {ajax: 1, username: username, password: password}, cache: false, success: function (json) { var error_message = json.error; if(typeof error_message !== "undefined") { $(".login-message").html(error_message); } } }); e.preventDefault(); }); </script>
To test it, we should try to login with wrong credentials.
Now what if the user successfully logged in? for this we must look again inside the login() method of our User controller:
if ($this->ion_auth->login($username, $password, $remember)) { redirect('dashboard'); }
In here we should also verify if the user logged in using ajax, and act accordingly:
if ($this->ion_auth->login($username, $password, $remember)) { if($this->input->post('ajax')) { $response['logged_in'] = 1; header("content-type:application/json"); echo json_encode($response); exit; } redirect('dashboard'); }
Returning to our footer, after the “if” block we also insert an else if:
$('.login-form').on('submit', function(e) { var username = $('#username').val(); var password = $('#password').val(); $.ajax({ url: "<?php echo site_url('user/login');?>", type: 'post', data: {ajax: 1, username: username, password: password}, cache: false, success: function (json) { var error_message = json.error; var success = json.logged_in; if(typeof error_message !== "undefined") { $(".login-message").html(error_message); } else if(typeof success !== "undefined" && success=="1") { $(".login-message").html("You've been successfully logged in!"); $(".login-form").hide(); } } }); e.preventDefault(); });
As you can see, when someone logs in successfully, a message is set in the “.login-message” div and the login form is hidden. Now you may ask, what if the user is moving to another page, wouldn’t the login form appear again?
You could simply output the login form by first checking if the user is logged in or not by using the Ion Auth’s logged_in() method
<?php if(!$this->ion_auth->logged_in()) { echo 'the form...'; } else { echo 'logout button'; } ?>
Now how about if we try to post an empty form? Well, then we put the ajax call in an if else block:
$('.login-form').on('submit', function(e) { var username = $('#username').val(); var password = $('#password').val(); if(username.length==0 || password.length==0) { $(".login-message").html("You need to have a username and a password to login"); } else { $.ajax({ url: "<?php echo site_url('user/login');?>", type: 'post', data: {ajax: 1, username: username, password: password}, cache: false, success: function (json) { var error_message = json.error; var success = json.logged_in; console.log(json); if (typeof error_message !== "undefined") { $(".login-message").html(error_message); } else if (typeof success !== "undefined" && success == "1") { $(".login-message").html("You've been successfully logged in!"); $(".login-form").hide(); } } }); } e.preventDefault(); });
But this won’t help us on server side validation, so let us return to the login() method of the User controller, at the part where the form validation return false. In here we put our “ajax condition” and pass the errors to password_error and username_error:
if ($this->form_validation->run() === FALSE) { if($this->input->post('ajax')) { $response['username_error'] = form_error('username'); $response['password_error'] = form_error('password'); header("content-type:application/json"); echo json_encode($response); exit; } $this->load->helper('form'); $this->render('user/login_view'); }
Now returning to our footer… again… we finish the if-else block with the final else condition, by adding the error messages to their respective divs. Now let’s see the footer script again:
<script> $(".login-form").on("submit", function(e) { var username = $("#username").val(); var password = $("#password").val(); if(username.length==0 || password.length==0) { $(".login-message").html("You need to have a username and a password to login"); } else { $.ajax({ url: "<?php echo site_url('user/login');?>", type: "post", data: {ajax: 1, username: username, password: password}, cache: false, success: function (json) { var error_message = json.error; var success = json.logged_in; if (typeof error_message !== "undefined") { $(".login-message").html(error_message); } else if (typeof success !== "undefined" && success == "1") { $(".login-message").html("You've been successfully logged in!"); $(".login-form").hide(); } else { $(".username_error").html(json.username_error); $(".password_error").html(json.password_error); } } }); } e.preventDefault(); }); </script>
…And also take a look at the login() method in our User controller:
public function login() { $this->data['title'] = "Login"; $this->load->library('form_validation'); $this->form_validation->set_rules('username', 'Username', 'trim|required'); $this->form_validation->set_rules('password', 'Password', 'trim|required'); $this->form_validation->set_rules('ajax','AJAX','trim|is_natural'); if ($this->form_validation->run() === FALSE) { if($this->input->post('ajax')) { $response['username_error'] = form_error('username'); $response['password_error'] = form_error('password'); header("content-type:application/json"); echo json_encode($response); exit; } $this->load->helper('form'); $this->render('user/login_view'); } else { $remember = (bool) $this->input->post('remember'); $username = $this->input->post('username'); $password = $this->input->post('password'); $this->ion_auth->set_hook('post_login_successful', 'get_gravatar_hash', $this, '_gravatar', array()); if ($this->ion_auth->login($username, $password, $remember)) { if($this->input->post('ajax')) { $response['logged_in'] = 1; header("content-type:application/json"); echo json_encode($response); exit; } redirect('dashboard'); } else { if($this->input->post('ajax')) { $response['username'] = $username; $response['password'] = $password; $response['error'] = $this->ion_auth->errors(); header("content-type:application/json"); echo json_encode($response); exit; } $_SESSION['auth_message'] = $this->ion_auth->errors(); $this->session->mark_as_flash('auth_message'); redirect('user/login'); } } }
Just for the fun, let us see the form again. This form can be included anywhere you want:
<?php defined('BASEPATH') OR exit('No direct script access allowed');?> <div class="row"> <h1>Login</h1> <div class="col-lg-4 col-lg-offset-4"> <div class="login-message"></div> <?php echo form_open('user/login',array('class'=>'login-form form-horizontal'));?> <div class="form-group"> <?php echo form_label('Username','username');?> <div class="username_error"></div> <?php echo form_input('username','','class="form-control" id="username"');?> </div> <div class="form-group"> <?php echo form_label('Password','password');?> <div class="password_error"></div> <?php echo form_password('password','','class="form-control" id="password"');?> </div> <div class="form-group"> <label> <?php echo form_checkbox('remember','1',FALSE);?> Remember me </label> </div> <?php echo form_submit('submit', 'Log in', 'class="btn btn-primary btn-lg btn-block" id="login-button"');?> <?php echo form_close();?> </div> </div>
Hope you liked this tutorial. Sorry for not finding the time to make tutorials more often. Maybe if I drink some coffee it should help me. But don’t worry. There is a PayPal donation button in the right sidebar if you are in a giving mood.