login through Google integration into Codeigniter 4 with IonAuth - dgvirtual - 07-09-2022
I have successfully integrated Login through Google Oauth2 method into my app, where I am using IonAuth authentication library. I will share the steps, and hopefully, if someone notices anything insecure in my code, please let me know...
So, first of all, I needed to create an app on https://console.cloud.google.com/apis/credentials with Oauth consent screen. That is easy to do following a video tutorial here: https://www.youtube.com/watch?v=a5puLR051Is so I will not go into details.
Then I installed google api client php library with dependencies, easiest done via Composer:
Code: composer require google/apiclient:^2.12
It is advisable to then remove all the unnecessary stuff 100+ Google services, to thin the library. You will only need Oauth2 service, everything else can go (see the library page on packagist how that is done).
Then I needed to extend the IonAuth library.
Created an extended model and placed it in app/Models:
PHP Code: <?php namespace App\Models;
class IonAuthModel extends \IonAuth\Models\IonAuthModel {
// admittedly, this will only work if $identity is email // it needs a little tweaking so that the query below searches not for identity (which could be email or username), but // for email proper, as Google will return email; will fix that in the future // function is a heavily modified version of the login() function public function login_google(string $identity): bool { $this->triggerEvents('pre_login');
if (empty($identity)) { $this->setError('IonAuth.login_unsuccessful'); return false; }
$this->triggerEvents('extra_where'); $query = $this->db->table($this->tables['users']) ->select($this->identityColumn . ', email, id, password, active, last_login') ->where($this->identityColumn, $identity) ->limit(1) ->orderBy('id', 'desc') ->get();
$user = $query->getRow();
// if we foun the user by the email supplied by Google... if (isset($user)) { // and if the user is not inactive... if ($user->active == 0) { $this->triggerEvents('post_login_unsuccessful'); $this->setError('IonAuth.login_unsuccessful_not_active');
return false; } // we declare success! $this->setSession($user);
$this->updateLastLogin($user->id); // hardly needed, but why not $this->clearLoginAttempts($identity); $this->clearForgottenPasswordCode($identity);
$this->session->regenerate(false);
$this->triggerEvents(['post_login', 'post_login_successful']); $this->setMessage('IonAuth.login_successful');
return true; }
$this->triggerEvents('post_login_unsuccessful'); // could write a line here that is specific to this method $this->setError('IonAuth.login_unsuccessful');
return false; } }
Created an extended library and placed it in app/Libraries (only needed to extend the constructor to use the extended model, so perhaps I could have sourced the original function and only overwritten the $this->ionAuthModel property?):
PHP Code: <?php namespace App\Libraries;
class IonAuth extends \IonAuth\Libraries\IonAuth {
public function __construct() { // Check compat first $this->checkCompatibility();
$this->config = config('IonAuth');
$this->email = \Config\Services::email(); helper('cookie');
$this->session = session();
$this->ionAuthModel = model('IonAuthModel');
$emailConfig = $this->config->emailConfig;
if ($this->config->useCiEmail && isset($emailConfig) && is_array($emailConfig)) { $this->email->initialize($emailConfig); }
$this->ionAuthModel->triggerEvents('library_constructor'); } }
Then I extended the Auth.php controller and placed it in app/Controllers (to be fair, the controller has long been extended, so you will see some of my own code there):
PHP Code: <?php namespace App\Controllers; use Google;
class Auth extends \IonAuth\Controllers\Auth { //I need an extended constructor: public function __construct() { parent::__construct(); //this is my new extended IonAuth library $this->ionAuth = new \App\Libraries\IonAuth();
// some of my stuff was here... //google client code $this->googleClient = new Google\Client(); $this->googleClient->setClientId("this-is-your-client-id.apps.googleusercontent.com"); $this->googleClient->setClientSecret("this-is-your-client-secret"); $this->googleClient->setRedirectUri(base_url('auth/login_google')); // I will only need email, since that is all I want Google to ascertain $this->googleClient->addScope("email");
} public function login() { // skipping everything, only wanted to show you the // google login button that should be // passed to the views along the data: // so lots of commented out code here $this->data['google_button'] = '<a class="btn btn-info" href="'.$this->googleClient->createAuthUrl().'" >Prisijungti per <i class="bi bi-google"></i></a>';
echo view('common/header_nologin_view', $this->data); echo view('auth/login_view', $this->data); echo view('common/footer_nologin_view'); // } commented out condition bracket } // our most important method here, which gets data from google, passes it through the library // to the model, where it gets verified and access is then granted public function login_google() {
$token = $this->googleClient->fetchAccessTokenWithAuthCode($this->request->getVar('code')); if(!isset($token['error'])){ $this->googleClient->setAccessToken($token['access_token']); session()->set("AccessToken", $token['access_token']);
$googleService = new \Google_Service_Oauth2($this->googleClient); // here we get an email of the user that Google swears really belongs // to the person trying to login $user_email = $googleService->userinfo->get()->email; } else { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound($token['error']); }
// the controller method calls the library which directly calls the model here if ($this->ionAuth->login_google($user_email)) { //if the login is successful //redirect them back to the home page $this->session->setFlashdata('message', $this->ionAuth->messages()); return redirect()->to('/')->withCookies(); } else { // if the login was un-successful // redirect them back to the login page $this->session->setFlashdata('message', $this->ionAuth->errors($this->validationListTemplate)); return redirect()->back()->withInput(); } } // all the other controller methods here }
Since my app functionality is not publically accesible, I also added the new route auth/login_google to the exceptions list of the filter that checks login in app/Config/Filters.php and to the list of filter that checks if someone is trying to access a route being logged in that is only available to non-logged in users. Here is the full file:
PHP Code: <?php
namespace Config;
use CodeIgniter\Config\BaseConfig; use CodeIgniter\Filters\CSRF; use CodeIgniter\Filters\DebugToolbar; use CodeIgniter\Filters\Honeypot;
class Filters extends BaseConfig { /** * Configures aliases for Filter classes to * make reading things nicer and simpler. * * @var array */ public $aliases = [ 'csrf' => CSRF::class, 'toolbar' => DebugToolbar::class, 'honeypot' => Honeypot::class, 'checkLogin' => \App\Filters\CheckLogin::class, 'checkNonLogin' => \App\Filters\CheckNonLogin::class, 'checkMaintenanceMode' => \App\Filters\CheckMaintenanceMode::class, ];
/** * List of filter aliases that are always * applied before and after every request. * * @var array */ public $globals = [ 'before' => [ 'checkLogin' => [ 'except' => [ //list all exceptions 'cron/*', //cron needs access 'auth/login', 'auth/login_google', 'auth/forgot_password', 'auth/reset_password/*' ] ] //just add like this // 'honeypot', // 'csrf', ], 'after' => [ 'toolbar', // 'honeypot', ], ];
/** * List of filter aliases that works on a * particular HTTP method (GET, POST, etc.). * * Example: * 'post' => ['csrf', 'throttle'] * * @var array */ public $methods = [];
/** * List of filter aliases that should run on any * before or after URI patterns. * * Example: * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']] * * @var array */ public $filters = [ // should be moved to globals? 'checkMaintenanceMode' => [ 'before' => [ '', '*', ] ], // should be moved to globals? 'checkNonLogin' => [ 'before' => [ 'auth/login', 'auth/login_google', 'auth/forgot_password', 'auth/reset_password/*', ] ], ]; }
Finally I added the Google button line to the login view (hope it is clear enough, only posting that one line):
I think that was all. Now, when I open the login page at https://mysecretwebsite.lt/auth/login , besides the main login form I see a Google login button. When I press it I am redirected to Google Oauth screen where I authenticate and get redirected back into my app.
It was so easy that I am curious if it is all that secure... So, does my code look secure?
Hope it will be useful to someone.
|