Scalable Login System for CodeIgniter – Ion_Auth
*This article assumes you are familiar with the structure and usage of the CodeIgniter MVC Framework and basic OOP techniques
If you are building an application that will live on the web, at some point you will most likely need to implement a way for users to log in, even if it’s a single user. This “How-to” will appeal to two different groups of people
- “Dude, forget ‘Scalable’, just show me how to add log in functionality to my application.”
- “I’ve got the log in thing figured out, but there has to be a better way!”
These were quotes from my mouth at two different points in my progression as a developer that prefers to use CodeIgniter. What I’d like to share, is my light-bulb moment in the realm of user authentication. I’ll quickly run you through how to get the basics of Ion_Auth (my preferred authentication library) up and running. Then I’ll get more into the “Ah ha!” for me, which was how you structure your Controllers to limit/eliminate the constant need to make sure the visitor is an authenticated user.
Setting up Ion_Auth
If you already have a authentication system up and running, feel free to skip the the next section. You will just need to replace Ion_Auth’s logic with your system’s logic. Ion_Auth – Download Here - GitHub Straight from the Ion_Auth ReadMe
Just copy the files from this package to the correspoding folder in your application folder. For example, copy Ion_auth/config/ion_auth.php to system/application/config/ion_auth.php.
Once you have the files copied, make sure you use the proper .sql file to create the needed database tables in your database. Then you need to take a look at system/application/config/ion_auth.php you do not neccessarily need to change anything off the bat, but be sure to familiarize yourself with it. One more thing before it is ready for use, make sure you have the proper dependancies autoloaded along with ion_auth in system/application/config/autoload.php
$autoload['libraries'] = array('database','session','email','ion_auth');
Now that you have have Ion_Auth available for use, you can review the docs and example files in order to more fully utilize the library, for now I will be simply showing how to setup your “protected” controllers once you have registered users.
Constructing your Controllers
Generally, with Ion_Auth you just use
$this->ion_auth->logged_in();
to check if a user is logged in. It returns a boolean True/False and then you act accordingly. When I first started using this system with CodeIgniter, I was putting this check in every function of my Controllers. This seemed ugly and redundant, so in an attempt to streamline and re-use code, I would construct a private function for the controller and then would just call that function at the beginning of every function, saving a few lines of code. Then the natural progression for me moved the authentication into the constructor of the Controller, which again seemed good, but even still was a block of code that was copy/pasted to every controller constructor. That is when the light-bulb went off. I had read about how to extend CodeIgniter libraries, but what I realized is that the Controller is just another library. You extend CodeIgniter libraries with the Prefix you define in your system/application/config/config.php which is by default “MY_”. If this feels over your head, it is not. Just stick with me and you’ll see it is very simple. I will go ahead and start in and show you. If I want to extend the base Controller, contained in system/core/Controller.php, to contain my authentication codez, I would simply create MY_Controller.php in system/application/core/ I will show you an extended Controller contained within MY_Controller.php
class Admin_Controller extends CI_Controller {
//Class-wide variable to store user object in.
protected $the_user;
public function __construct() {
parent::__construct();
//Check if user is in admin group
if ( $this->ion_auth->is_admin() ) {
//Put User in Class-wide variable
$this->the_user = $this->ion_auth->user()->row();
//Store user in $data
$data->the_user = $this->the_user;
//Load $the_user in all views
$this->load->vars($data);
}
else {
redirect('/');
}
}
}
Here you can see it looks like a normal controller you would make in your controllers directory, but I’ve wrapped in the auth logic in the constructor and created a class-wide variable $this->the_user to give the controller global access to the currently logged in user. When I did this in each and every controller I would create, my class-wide variable was set to private, but because this is a base from which I plan to extend I have made it protected to allow the controllers that extend this one to have access. Also I have used $this->load->vars() to load $the_user into all views served up by this controller. (now you can use $the_user in all views to represent your user object, $the_user->first_name, etc…) Also, you can setup as many base controllers as you like in MY_Controller.php. Below, I’ll show you a copy of my basic setup that utilizes 2 types of users (admins, users), and also provides a base controller for those types to share for common tasks between them (like editing the user profile, changing password, etc…). Just click “MY_Controller.php” to expand the code if you’re insterested.
class Admin_Controller extends CI_Controller {
protected $the_user;
public function __construct() {
parent::__construct();
if($this->ion_auth->is_admin()) {
$this->the_user = $this->ion_auth->user()->row();
$data->the_user = $this->the_user;
$this->load->vars($data);
}
else {
redirect('/');
}
}
}
class User_Controller extends CI_Controller {
protected $the_user;
public function __construct() {
parent::__construct();
if($this->ion_auth->in_group('user')) {
$this->the_user = $this->ion_auth->user()->row();
$data->the_user = $this->the_user;
$this->load->vars($data);
}
else {
redirect('/');
}
}
}
class Common_Auth_Controller extends CI_Controller {
protected $the_user;
public function __construct() {
parent::__construct();
if($this->ion_auth->logged_in()) {
$this->the_user = $this->ion_auth->user()->row();
$data->the_user = $this->the_user;
$this->load->vars($data);
}
else {
redirect('/');
}
}
}
Implementing your extended Controllers
So you’ve got some Controllers sitting my MY_Controller.php, now what? Use em! The first consideration is how I structure my controller directories, which allows for one level of directory organization.
application/
→ controllers/
→ admin/
→ home.php
→ user/
→ home.php
→ common_auth/
→ all_user_groups_can_use_this_stuff.php
→ public/
→ auth.php
...
...
Notice I have a subfolder for each type of auth group, a common_auth folder for controllers that have shared functionality, and a public folder that is self explanitory.
So now lets create a simple Auth controller to process a login:
class Auth extends CI_Controller {
function __construct() {
parent::__construct();
}
function index() {
redirect('/');
}
/**
* Global Login function to log user in and direct to proper area
*
* @return void
* @author Jonathan Johnson
**/
function login() {
if($_POST) { //clean public facing app input
$identity = $this->input->post('identity', true);
$password = $this->input->post('password', true);
//Ion_Auth Login fun
if($this->ion_auth->login($identity,$password)) {
//capture the user
$user = $this->ion_auth->user()->row();
redirect($user->group.'/home');
/*redirect to the proper home
controller using the user
groups as folder names */
}
else {
// set error flashdata
$this->session->set_flashdata(
'error',
'Your login attempt failed.'
);
redirect('/');
}
}
redirect('/');
}
/**
* Global logout function to destroy user session
*
* @return void
* @author Jonathan Johnson
**/
function logout() { //Basic Ion_Auth Logout function
$this->ion_auth->logout();
redirect('/');
}
}
Now that we have the user redirected to their respective areas, we need to build the controllers to receive them. Here I will extend one of our Controllers in MY_Controller.php, and build the “Home” controller for an Admin.
class Home extends Admin_Controller {
function __construct() {
parent::__construct();
}
function index() {
// do stuff here -
// remember that $this->the_user is available in this controller
// as is $the_user available in any view I load
}
}
Notice on line three, I am not extending Controller, I am extending our “Admin_Controller” that lives in MY_Controller.php. Remember you can do this with all three (or more) controllers you have in your custom Controller library.
Working Example using CI 2.x and Ion_Auth 2.x -> https://github.com/jondavidjohn/auth_example
Follow the commit history, tried to make it as linear and easy to follow as possible…
Jonathan
Every now and again someone comes along and restores a little bit of my faith in humanity with their willing to share information in a simple fashion. Today, it’s you.
I’m tired of seeing blogs where people either skirt around an issue because they don’t understand it, or give examples that are stupidly complex for anyone without a PHD to understand. The way you have explained your first approaches and how you finally got to this result is perfect, as I’m currently in (or was) the same situation. For this, you get +2 internets.
Thank you, sir
I had some problems installing ion_auth, the SQL provided
did not work with MySQL. Amongst many errors
where “CREATE TABLE users (
id int NOT NULL IDENTITY(1,1)”
Any tips? I’d love to implement this solution.
I’d say that’s outside the scope of this article. I’d check the Ion_Auth Documentation and possibly inquire with an issue in the github issues page for the repository.
Just want to say thank you for this information, it is very well explained and easy to follow. You are greatly appreciated.
Thanks.
Just a quick request… the article above has a broken link. When I click on “Auth Directory Structure” I am directed to:
http://jondavidjohn.com/wp-content/uploads/2011/01/Screen-shot-2011-01-04-at-10.28.49-PM.png
That directory structure link would be helpful to all of us. Thanks for the great work and thanks for sharing.
Also, you don’t mention where you get this: $user -> group. Some info on this, please!
Yes, that was a typo, check the code again, I think I clarified it.
Thanks for this – it was very helpful
Just a small change that I made to avoid the constant duplication in the MY_Controller class:
I have once common controller that sets the logon:
$data->the_user = $this->ion_auth->user()->row();
$this->the_user = $data->the_user;
$this->load->vars($data);
And then the other controllers (for different permission levels) extend that common parent and then redirect if their condition isn’t met:
if(!$this->ion_auth->logged_in()) {
redirect(‘/’)
}
if(!$this->ion_auth->is_group(‘user’)) {
redirect(‘/’)
}
Just a minor change to avoid some duplication – when you have a lot of different permission levels and groups it adds up.
“I had some problems installing ion_auth, the SQL provided
did not work with MySQL. Amongst many errors
where “CREATE TABLE users (
id int NOT NULL IDENTITY(1,1)”
Any tips? I’d love to implement this solution.”
This happens because you are using the wrong script. You are using ion_auth_mssql.sql instead of ion_auth.sql. Happened to me a couple of hours ago while I was installing the library.
Thnx Jonathan for taking the time to write this guide. I’m currently going through it. Everything looks fine so far. =D
Many thanks for this. I’ve been coding for many years in another language but am new to CI (& MVC in general, and still at ‘tourist-level’ with my PHP!) so have been having difficulty understanding how to ‘correctly’ implement Ion Auth. (As its own controller? Or is controllers/auth.php just an example and the functions should just be worked into my own controllers?)
Anyway, this post has helped a lot, so thank you. However, I think I may have spotted some typos (either that or I understand even less than I think!).
End of paragraph after “Constructing your Controllers”: “I would simply create MY_Controller.php in system/application/core/”. Should that not be just /application/core/?
In the code just after that, you have:
//Put User in Class-wide variable
$data->the_user = $this->ion_auth->user()->row();
//Store user in $data
$data->the_user = $this->the_user;
Should the first assignment not be to $this->the_user?
@JamesHarvard
You’re correct, I’ve made some revisions to make it more clear, I’m trying to keep up with the most recent versions of the software used, but as you can see, sometimes it doesn’t work out like that
Where’s actually located your auth.php file, noticed you did’nt mention it in your structure. Secondly, what kind of typical methods users can share in auth_common_controller.php?
I’ll update that thanks.
Typically things like updating the user’s information or password, things that are common to all users of your system regardless of user group.
Thanks for the awesome post. You definitely have a gift for this stuff, as you don’t really see a a lot when it comes to tutorials and the like. As was said earlier, people either don’t know what they’re talking about or simply don’tk now when to stop talking about irrelevant crap, you’ve got it right.
Great job! very good explanation!
just a small detail: i am getting an error on this line:
$this->the_user = $this->ion_auth->user()->row();
i looked in the library this function does not exist… am i missing something?
Thanks!
That has been a pain point as the method of getting the current user has changed a few times very recently. Make sure you have the latest version, and consult the docs/source on the correct way to get the current user.
Yeah – it looks like the latest version of ion_auth doesn’t join the users’ group to the users() function anymore. Apparently this was done so that a user could belong to more than one group. Now there is a separate function – get_users_groups() – that will return all the groups the user belongs to. I still only let each user belong to one group – and this modification to the login function does the trick:
$user_group = $this->ion_auth->get_users_groups()->row();
redirect($user_group->name.’/home’);
Good work Jonathan,
Would you know how it’s possible to pass a requested URL through the login page. So if I request site/controller/x but am not logged in the original requested URL is lost. Its a real annoyance for users to have to go and click the link again after they have logged in.
Many thanks for any advice.
Regards
Lee
Right, so the common approach to this is to pass the original target url in the redirected login url as a query string (GET) parameter, and then once a successful login takes place, use the query string parameter (if present) to send them on to once they login.
Make sure you url(encode|decode)() on both ends.
Nice way to fully secure desired controllers. Annotations would be great to have fine grain security, at controller method level, like spring-security.
Thanks you for this great resource.
I only had an issue where I was getting the following error:
PHP Fatal error: Uncaught exception ‘Exception’ with message ‘Undefined method Ion_auth::is_group() called’ …
The error occurred because I was using version 2.3.2 and the is_group method was renamed to in_group
https://github.com/benedmunds/CodeIgniter-Ion-Auth/commit/30f8ec1c3a3589f77c40261e2a4f022fc077757b
Thanks, will fix it in the post.
New to the CI space – Would this be also applicable to creating a site that gives each user their own MySQL user account so that there’s no bottleneck with just one DB account for all users on at the same time?
Thanks in advance!
Thanks
No, there is no bottleneck created with an application using only one DB account. This is how most Web Applications work.
Scalable Login System for CodeIgniter – Ion_Auth .. http://t.co/Uzgl38HM /via @jondavidjohn #OpenSource #php
aha, inspired for implementing with HMVC structure, thx before.
Thanks for the tutorial, very helpful.
It seems you forget to mention that the url_helper should be loaded, without it the redirect function is not available.
Great post. Thank you.
You could add to My_Controller
$data = new stdClass();
In my case without that I get this error
Creating default object from empty value.
Very good explanation. Thank you very much for this great solution.
@KG: The reason you got the error was because you used the script for MSSQL on MySQL. You shouls simply use ion_auth.sql and it will work.
Can I use your library in code igniter 213, bacouse folder application is not in folder system?
Thanks for future answer
Sorry for first question, but it is not function logout on view , how can i fix this bug?