benedmunds / CodeIgniter-Ion-Auth

Simple and Lightweight Auth System for CodeIgniter
http://benedmunds.com/ion_auth/
MIT License
2.35k stars 1.14k forks source link

Ion-Auth two factor authentication hook #1309

Closed BartMommens closed 5 years ago

BartMommens commented 5 years ago

This Github Issue section is meant to track bugs with the library itself Please post generic support issues to the CodeIgniter forums or StackOverflow.

BartMommens commented 5 years ago

Dear Ben, Dear Followers of this Repo,

I'm looking to integrate a two Factor authentication section to this library. For this i will be using Google Authenticator. Since the other libraries for this haven't been updated for over 2years i'm considering implementing it myself.

I've been analysing your library today and it looks very nice especially with the use of hooks.

Now i have a question about where to implement it. I've seen other forks adjusting the source of the library but well then you can compromise the entire library by itself.

I've found that your library emits events right after the user is verified by login and password.

There the following function is called

$this->set_session($user);

This function then triggers the following event

$this->trigger_events('pre_set_session');

So i was wondering if i would hook on this event to write my TOTP logic in another controller.

If the authentication was successful, nothing would happen and the code would just follow it's course. Yet if the authentication did not match i would log user out by calling the

$this->ion_auth->logout();

And after that redirect him straight to the login page again. Does this look like a valid way to handle this or do you think it would impose a security risk / bug since right after triggering the 'pre_set_session' the library itself sets the user data in the session and writes the cookie.

Code insight:

function __construct() {
        parent::__construct();
        $this->load->library('ion_auth');
        $event = 'pre_set_session';
        $name = 'call_custom_function';
        $class = $this;
        $method = 'additional_authentication';
        $args = array();
        $this->ion_auth->set_hook($event, $name, $class, $method, $args);
}

public function additional_authentication()
    {
        if(!$this->GoogleAuthLib->Authenticate()){
            $this->ion_auth->logout();
            redirect('/login');
            return false;
        }
    }
/**** THIS IS THE ORIGINAL SET SESSION FUNCTION FROM ION_AUTH_MODEL *****/
public function set_session($user)
    {
                //The trigger where i want to hook in
        $this->trigger_events('pre_set_session');

        $session_data = [
            'identity'             => $user->{$this->identity_column},
            $this->identity_column => $user->{$this->identity_column},
            'email'                => $user->email,
            'user_id'              => $user->id, //everyone likes to overwrite id so we'll use user_id
            'old_last_login'       => $user->last_login,
            'last_check'           => time(),
        ];

        $this->session->set_userdata($session_data);

        $this->trigger_events('post_set_session');

        return TRUE;
    }

Or the other approach is creating a custom authentication Library on top of Ion_auth with a rewritten login function. public function login($identity, $password, $remember=FALSE) This might be a better solution over the hook, since i have more control over what's going on and i leave the ion_auth library fully intact, and all the functions are available.

Any advice or suggestions regarding this matter ?

Best regards, Bart

I got the hooks idea from this article:

http://codebyjeff.com/blog/2013/01/using-hooks-with-ion_auth

benedmunds commented 5 years ago

Hey,

That’s definitely a valid way to do it, if you get this working it’d be great. The post_login_successful hook might be a better fit for this.

Another way to do it is to write a class that extends Ion auth to wrap the login functionality.

Personally I think using hooks is a more future proof way though.

BartMommens commented 5 years ago

Hey Ben,

Thank you for your feedback, the only downside i could think of with using the hooks is the following:

For example users that didn't enable 2FA should not be presented with the input field. So if you authenticate the user at first via username / password and then check the DB if they have 2FA enabled there is no way in presenting the user with a 2FA screen without setting the cookie..

Won't this approach pose a security risk ? or is it ok to set the cookie -> then present an additional screen with 2FA input. And if the input is incorrect kick the user out.

Sorry for all of these questions, but i want to do it the correct way, so that in the future i also might be able to help people in the CI community that use ION AUTH.

Thanks in advance for your insight and help.

benedmunds commented 5 years ago

Good point, you'd need to have another cookie for 2fa success, then modify the logged in check to also check for that, redirecting users to the 2fa input page if they dont have that second cookie.

BartMommens commented 5 years ago

Hey Ben,

Sorry for the late reply. As we discussed earlier, the prefered way was using the hooks. I managed to implement is using the 'post_login_successful'.

_(for users who consider implementing 2FA with IONAUTH) The approach i took was the following: After the user has successfully logged in, ion_auth triggers an event called 'post_login_successful' You can intercept or "listen" for that event by registering a hook:

This functionality is provided by ion_auth: set_hook($event, $name, $class, $method, $args)

here is my function: $this->ion_auth->set_hook("post_login_successful", "check_2fa_requirements", $this, "handle_2fa_checks", array());

More info about CI hooks: https://www.codeigniter.com/user_guide/general/hooks.html and great example: http://codebyjeff.com/blog/2013/01/using-hooks-with-ion_auth

So in the handle_2fa_checks function you check for a cookie that is used to verify 2FA. If it is not present you redirect the user to the 2FA screen and challenge him for his 2fa code input.

If the provided 2fa code is correct, you create a new cookie with some additional data (for later validation). If the cookie was created successfully you store its name in the userdata of the session. (i recommend a unique name (md5 of current timestamp or something ...))

You can now use that 2fa cookie to verify if the user has successfully authenticated with 2fa. If not you present him with the 2fa screen again or kick him out ( depending on your preferences)

Do keep in mind that the 2FA cookie alone should not be trusted, if you have specific sensitive functions or operations make sure to match the cookie with a record in the database!

For example: Storing the cookie name in the database after 2fa authentication was successful. This has one thing that can be seen as a CON and PRO at the same time. A user can only be active on one device at the same time. Logging in on another device will cause the other device to lose its link to the 2fa cookie. So the user will have to reauthenticate.

Hope this approach can help people thinking about integrating 2FA!

benedmunds commented 5 years ago

Thanks so much for writing this up. If you get time I'd recommend either open sourcing the code or writing a blog post. Would be great to be able to link to it for future people that need 2FA.

dualhub commented 5 years ago

@BartMommens I second Ben's recommendation to either open source or write a blog post about this. I'd be very, very interested to get this working on a project of mine.

BartMommens commented 5 years ago

@dualhub , i hope i'll find some time during the summer holiday season. The project where i've implemented it is currently in the fridge ( not tested atm :/ ) and swamped with other projects :/.

i hope following info might help you to get started.

Basically after authenticating with ion_auth, i check my database if an account needs 2FA, (simple boolean). you check you current session for another cookie (2fa cookie) if this is not set you just redirect the user to an additional view where he should enter his 2fa code. (can be a simple form) if the user fails 2 authenticate you log him out. If it was a success you create a new cookie with some additional data about the user (uAgent, IP, ...) and link that cookie 2 the session cookie. And then redirect him to you app / site index page and now he should be authenticated! an easy implementation for 2fa is google authenticator and the following library (actively maintained) : https://github.com/PHPGangsta/GoogleAuthenticator this handles everything for you. all you need to do is handle cookies. i set the secrets in the database manually, and send the codes to the user by email (bad practice i know) but it's all but done.

additional information: about codeigniter and google auth. https://shareurcodes.com/blog/integrating%20google%202%20factor%20authentication%20with%20codeIgniter

Hope this helps you in your thinking process .

Sorry about messy text, i'm in the train on my mobile device (and i have sausage fingers)

greets, Bart

dualhub commented 5 years ago

@BartMommens Thanks so much for this. Hearing the process in simple terms helps tremendously. I've already bookmarked PHPGangsta's library but it's good to hear that at least I got that bit correct :-).

Take care,

Jim

marios88 commented 5 years ago

@dualhub aslo check out otphp https://github.com/Spomky-Labs/otphp

BartMommens commented 5 years ago

@dualhub if you get stuck or need a second opinion feel free to contact me. Here 2 help 😉

oasin commented 4 years ago

@BartMommens Can you share how you got this done?

BartMommens commented 4 years ago

@BartMommens Can you share how you got this done?

@oasin unfortunately i'm unable to share the code since the project has moved to another party. Everything is explained in the comment of 22 Mar. with examples i used from both links.

hopefully you can continue based on this information.

Best regards, Bart