Welcome Guest, Not a member yet? Register   Sign In
login through Google integration into Codeigniter 4 with IonAuth
#1

(This post was last modified: 07-11-2022, 01:41 AM by dgvirtual. Edit Reason: added explanatory comment )

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 &nbsp; <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):

PHP Code:
<?= $google_button ?>

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.
==

Donatas G.
Reply


Messages In This Thread
login through Google integration into Codeigniter 4 with IonAuth - by dgvirtual - 07-09-2022, 12:45 PM



Theme © iAndrew 2016 - Forum software by © MyBB