MojoAuth Hosted Login Page with CodeIgniter
Introduction
CodeIgniter is a powerful PHP framework with a small footprint. It is known for its speed, simplicity, and flexible MVC architecture. CodeIgniter provides a simple and elegant toolkit for building fully-featured web applications with minimal configuration required.
This guide will walk you through the process of connecting your CodeIgniter 4 application to MojoAuth's Hosted Login Page using OpenID Connect (OIDC).
Links:
- MojoAuth Hosted Login Page Documentation (opens in a new tab)
- CodeIgniter 4 Documentation (opens in a new tab)
Prerequisites
Before you begin, make sure you have:
- A MojoAuth account and an OIDC application set up in the MojoAuth dashboard
- Your Client ID and Client Secret from the MojoAuth dashboard
- PHP 7.4 or higher (PHP 8.0+ recommended) installed on your development machine
- Composer for PHP package management
- CodeIgniter 4.x installed
- Basic knowledge of the CodeIgniter framework
Create a New CodeIgniter Project
If you don't have an existing CodeIgniter project, you can create a new one with the following commands:
composer create-project codeigniter4/appstarter mojoauth-codeigniter-example cd mojoauth-codeigniter-example
Install Required Packages
We'll use the facile-it/php-openid-client
library for OIDC authentication. Install it using Composer:
composer require facile-it/php-openid-client composer require web-token/jwt-framework composer require guzzlehttp/guzzle composer require nyholm/psr7
Configure Environment Variables
CodeIgniter uses a .env
file for environment variables. Update your .env
file with your MojoAuth credentials:
# MojoAuth Configuration mojoauth.clientId = 'your-client-id' mojoauth.clientSecret = 'your-client-secret' mojoauth.redirectUri = 'http://localhost:8080/auth/callback' mojoauth.issuerUrl = 'https://your-project.auth.mojoauth.com' # Session app.sessionDriver = 'CodeIgniter\Session\Handlers\FileHandler' app.sessionExpiration = 7200
Make sure your session configuration is properly set up in app/Config/App.php
.
Create OIDC Helper
Create a helper file to handle OIDC authentication. Create a new file at app/Helpers/OidcHelper.php
:
<?php namespace App\Helpers; use Facile\OpenIDClient\Client\ClientBuilder; use Facile\OpenIDClient\Client\Metadata\ClientMetadata; use Facile\OpenIDClient\Issuer\IssuerBuilder; use Facile\OpenIDClient\Authorization\AuthorizationRequest; use Facile\OpenIDClient\Service\Builder\AuthorizationServiceBuilder; use Facile\OpenIDClient\Service\Builder\UserInfoServiceBuilder; use Facile\OpenIDClient\Token\TokenSetInterface; use GuzzleHttp\Client as HttpClient; class OidcHelper { private $client; private $session; public function __construct() { $this->session = \Config\Services::session(); $this->initialize(); } private function initialize(): void { $httpClient = new HttpClient([ 'verify' => true, // Always verify SSL in production ]); $issuerBuilder = new IssuerBuilder($httpClient); $issuer = $issuerBuilder->build(getenv('mojoauth.issuerUrl')); $clientMetadata = ClientMetadata::fromArray([ 'client_id' => getenv('mojoauth.clientId'), 'client_secret' => getenv('mojoauth.clientSecret'), 'redirect_uris' => [getenv('mojoauth.redirectUri')], 'response_types' => ['code'], ]); $clientBuilder = new ClientBuilder(); $this->client = $clientBuilder ->setIssuer($issuer) ->setClientMetadata($clientMetadata) ->build(); } public function getAuthorizationUrl(array $scopes = ['openid', 'profile', 'email']): string { // Generate and store PKCE code verifier $codeVerifier = $this->generateCodeVerifier(); $this->session->set('code_verifier', $codeVerifier); // Generate code challenge from verifier $codeChallenge = $this->generateCodeChallenge($codeVerifier); // Generate state to prevent CSRF $state = bin2hex(random_bytes(16)); $this->session->set('oauth_state', $state); $authorizationService = (new AuthorizationServiceBuilder())->build(); $request = new AuthorizationRequest( $this->client, [ 'scope' => implode(' ', $scopes), 'response_type' => 'code', 'state' => $state, 'code_challenge' => $codeChallenge, 'code_challenge_method' => 'S256', 'redirect_uri' => $this->client->getMetadata()->getRedirectUri(), ] ); return $authorizationService->getAuthorizationUri($request); } public function handleCallback(array $queryParams): TokenSetInterface { // Verify the state to prevent CSRF if (!isset($queryParams['state']) || $queryParams['state'] !== $this->session->get('oauth_state')) { throw new \RuntimeException('Invalid state parameter'); } // Exchange the authorization code for tokens $tokenSet = $this->client->handleCallback( $queryParams, [ 'code_verifier' => $this->session->get('code_verifier'), 'redirect_uri' => $this->client->getMetadata()->getRedirectUri(), ] ); // Clean up the session $this->session->remove('code_verifier'); $this->session->remove('oauth_state'); return $tokenSet; } public function getUserInfo(TokenSetInterface $tokenSet): array { $userInfoService = (new UserInfoServiceBuilder())->build(); return $userInfoService->getUserInfo($this->client, $tokenSet)->toArray(); } private function generateCodeVerifier(): string { return bin2hex(random_bytes(32)); } private function generateCodeChallenge(string $codeVerifier): string { $hash = hash('sha256', $codeVerifier, true); return rtrim(strtr(base64_encode($hash), '+/', '-_'), '='); } }
Create User Model
Create a User model to handle user data. Create a new file at app/Models/UserModel.php
:
<?php namespace App\Models; use CodeIgniter\Model; class UserModel extends Model { protected $table = 'users'; protected $primaryKey = 'id'; protected $useAutoIncrement = true; protected $allowedFields = [ 'name', 'email', 'provider', 'provider_id', 'avatar', 'email_verified_at' ]; protected $useTimestamps = true; protected $createdField = 'created_at'; protected $updatedField = 'updated_at'; protected $dateFormat = 'datetime'; public function findOrCreateFromOidc(array $userInfo) { // Try to find the user by email $user = $this->where('email', $userInfo['email'])->first(); if (!$user) { // Create a new user $userData = [ 'email' => $userInfo['email'], 'name' => $userInfo['name'] ?? null, 'provider' => 'mojoauth', 'provider_id' => $userInfo['sub'] ?? null, 'avatar' => $userInfo['picture'] ?? null, 'email_verified_at' => date('Y-m-d H:i:s'), ]; $this->insert($userData); $user = $this->where('email', $userInfo['email'])->first(); } return $user; } }
Create Database Migration
Create a migration file to set up the users table. Run the following command:
php spark make:migration CreateUsersTable
Then update the migration file in app/Database/Migrations/TIMESTAMP_create_users_table.php
:
<?php namespace App\Database\Migrations; use CodeIgniter\Database\Migration; class CreateUsersTable extends Migration { public function up() { $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true, ], 'name' => [ 'type' => 'VARCHAR', 'constraint' => 255, 'null' => true, ], 'email' => [ 'type' => 'VARCHAR', 'constraint' => 255, ], 'provider' => [ 'type' => 'VARCHAR', 'constraint' => 255, 'null' => true, ], 'provider_id' => [ 'type' => 'VARCHAR', 'constraint' => 255, 'null' => true, ], 'avatar' => [ 'type' => 'VARCHAR', 'constraint' => 255, 'null' => true, ], 'email_verified_at' => [ 'type' => 'DATETIME', 'null' => true, ], 'created_at' => [ 'type' => 'DATETIME', 'null' => true, ], 'updated_at' => [ 'type' => 'DATETIME', 'null' => true, ], ]); $this->forge->addKey('id', true); $this->forge->addUniqueKey('email'); $this->forge->createTable('users'); } public function down() { $this->forge->dropTable('users'); } }
Create Authentication Controller
Create a controller to handle authentication with MojoAuth. Create a new file at app/Controllers/AuthController.php
:
<?php namespace App\Controllers; use App\Controllers\BaseController; use App\Helpers\OidcHelper; use App\Models\UserModel; use CodeIgniter\HTTP\ResponseInterface; class AuthController extends BaseController { protected $oidcHelper; protected $userModel; protected $session; public function __construct() { $this->oidcHelper = new OidcHelper(); $this->userModel = new UserModel(); $this->session = \Config\Services::session(); } public function login() { // Generate authorization URL and redirect $authorizationUrl = $this->oidcHelper->getAuthorizationUrl(); return redirect()->to($authorizationUrl); } public function callback() { try { // Get the callback parameters $params = $this->request->getGet(); // Handle the callback and get tokens $tokenSet = $this->oidcHelper->handleCallback($params); // Get user info from token $userInfo = $this->oidcHelper->getUserInfo($tokenSet); // Find or create user $user = $this->userModel->findOrCreateFromOidc($userInfo); // Store user info and tokens in session $this->session->set('user_id', $user['id']); $this->session->set('user', $userInfo); $this->session->set('tokens', [ 'access_token' => $tokenSet->getAccessToken(), 'id_token' => $tokenSet->getIdToken(), 'expires_at' => $tokenSet->getAccessTokenExpiration() ]); // Redirect to dashboard return redirect()->to('/dashboard'); } catch (\Exception $e) { return redirect()->to('/login')->with('error', 'Authentication failed: ' . $e->getMessage()); } } public function logout() { // Clear the session $this->session->remove(['user_id', 'user', 'tokens']); // Redirect to home return redirect()->to('/'); } }
Create Dashboard Controller
Create a controller for the dashboard and profile pages. Create a new file at app/Controllers/DashboardController.php
:
<?php namespace App\Controllers; use App\Controllers\BaseController; use CodeIgniter\HTTP\ResponseInterface; class DashboardController extends BaseController { protected $session; public function __construct() { $this->session = \Config\Services::session(); } public function index() { // Check if user is logged in if (!$this->session->has('user')) { return redirect()->to('/login'); } // Get user info from session $user = $this->session->get('user'); return view('dashboard', ['user' => $user]); } public function profile() { // Check if user is logged in if (!$this->session->has('user')) { return redirect()->to('/login'); } // Get user info from session $user = $this->session->get('user'); return view('profile', ['user' => $user]); } }
Create Authentication Filter
Create a filter to protect routes that require authentication. Create a new file at app/Filters/AuthFilter.php
:
<?php namespace App\Filters; use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; class AuthFilter implements FilterInterface { /** * Do whatever processing this filter needs to do. * By default it should return the given $request, but may also return a response. * * @param RequestInterface $request * @param array|null $arguments * @return mixed */ public function before(RequestInterface $request, $arguments = null) { $session = \Config\Services::session(); if (!$session->has('user')) { return redirect()->to('/login'); } } /** * Allows After filters to inspect and modify the response * object as needed. This method does not allow any way * to stop execution of other after filters, short of * throwing an Exception or Error. * * @param RequestInterface $request * @param ResponseInterface $response * @param array|null $arguments * @return mixed */ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { // Do nothing } }
Register the filter in app/Config/Filters.php
:
public $aliases = [ 'csrf' => \CodeIgniter\Filters\CSRF::class, 'toolbar' => \CodeIgniter\Filters\DebugToolbar::class, 'honeypot' => \CodeIgniter\Filters\Honeypot::class, 'invalidchars' => \CodeIgniter\Filters\InvalidChars::class, 'secureheaders' => \CodeIgniter\Filters\SecureHeaders::class, 'auth' => \App\Filters\AuthFilter::class, ];
Configure Routes
Update your app/Config/Routes.php
file to add authentication routes:
<?php namespace Config; // Create a new instance of our RouteCollection class. $routes = Services::routes(); /* * -------------------------------------------------------------------- * Router Setup * -------------------------------------------------------------------- */ $routes->setDefaultNamespace('App\Controllers'); $routes->setDefaultController('Home'); $routes->setDefaultMethod('index'); $routes->setTranslateURIDashes(false); $routes->set404Override(); // The Auto Routing (Legacy) is very dangerous. It is easy to create vulnerable apps // where controller filters or CSRF protection are bypassed. // If you don't want to define all routes, please use the Auto Routing (Improved). // Set `$autoRoutesImproved` to true in `app/Config/Feature.php` and set the following to true. // $routes->setAutoRoute(false); /* * -------------------------------------------------------------------- * Route Definitions * -------------------------------------------------------------------- */ // We get a performance increase by specifying the default // route since we don't have to scan directories. $routes->get('/', 'Home::index'); // Authentication routes $routes->get('/login', 'Home::login'); $routes->get('/auth/login', 'AuthController::login'); $routes->get('/auth/callback', 'AuthController::callback'); $routes->get('/auth/logout', 'AuthController::logout'); // Protected routes $routes->group('', ['filter' => 'auth'], function($routes) { $routes->get('/dashboard', 'DashboardController::index'); $routes->get('/profile', 'DashboardController::profile'); }); /* * -------------------------------------------------------------------- * Additional Routing * -------------------------------------------------------------------- */
Update Home Controller
Update your app/Controllers/Home.php
file to add login method:
<?php namespace App\Controllers; class Home extends BaseController { protected $session; public function __construct() { $this->session = \Config\Services::session(); } public function index() { // Check if user is logged in $isLoggedIn = $this->session->has('user'); $user = $isLoggedIn ? $this->session->get('user') : null; return view('home', ['isLoggedIn' => $isLoggedIn, 'user' => $user]); } public function login() { // If already logged in, redirect to dashboard if ($this->session->has('user')) { return redirect()->to('/dashboard'); } return view('login'); } }
Create Views
Create the necessary views for the application.
First, create a basic layout file at app/Views/layout.php
:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= $title ?? 'MojoAuth CodeIgniter Example' ?></title> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> <style> .container { max-width: 800px; } .pre-scrollable { max-height: 300px; overflow-y: auto; } </style> </head> <body> <!-- Navigation Bar --> <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> <div class="container"> <a class="navbar-brand" href="<?= site_url('/') ?>">MojoAuth CodeIgniter</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav ms-auto"> <?php if (session()->has('user')): ?> <li class="nav-item"> <a class="nav-link" href="<?= site_url('/dashboard') ?>">Dashboard</a> </li> <li class="nav-item"> <a class="nav-link" href="<?= site_url('/profile') ?>">Profile</a> </li> <li class="nav-item"> <a class="nav-link" href="<?= site_url('/auth/logout') ?>">Logout</a> </li> <?php else: ?> <li class="nav-item"> <a class="nav-link" href="<?= site_url('/login') ?>">Login</a> </li> <?php endif; ?> </ul> </div> </div> </nav> <!-- Main Content --> <div class="container mt-4"> <?php if (session()->getFlashdata('error')): ?> <div class="alert alert-danger"> <?= session()->getFlashdata('error') ?> </div> <?php endif; ?> <?php if (session()->getFlashdata('success')): ?> <div class="alert alert-success"> <?= session()->getFlashdata('success') ?> </div> <?php endif; ?> <?= $this->renderSection('content') ?> </div> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script> </body> </html>
Create the home page view at app/Views/home.php
:
<?= $this->extend('layout') ?> <?= $this->section('content') ?> <div class="text-center"> <h1 class="mb-4">Welcome to MojoAuth CodeIgniter Example</h1> <?php if ($isLoggedIn): ?> <p class="lead"> You are logged in as <?= esc($user['name'] ?? $user['email'] ?? 'User') ?>. </p> <div class="mt-4"> <a href="<?= site_url('/dashboard') ?>" class="btn btn-primary me-2">Go to Dashboard</a> <a href="<?= site_url('/auth/logout') ?>" class="btn btn-outline-danger">Logout</a> </div> <?php else: ?> <p class="lead"> This is an example of how to integrate MojoAuth's Hosted Login Page with CodeIgniter 4. </p> <div class="mt-4"> <a href="<?= site_url('/login') ?>" class="btn btn-primary">Login with MojoAuth</a> </div> <?php endif; ?> </div> <?= $this->endSection() ?>
Create the login page view at app/Views/login.php
:
<?= $this->extend('layout') ?> <?= $this->section('content') ?> <div class="card"> <div class="card-header"> <h2 class="text-center">Login</h2> </div> <div class="card-body"> <div class="text-center"> <a href="<?= site_url('/auth/login') ?>" class="btn btn-primary btn-lg"> Login with MojoAuth </a> </div> </div> </div> <?= $this->endSection() ?>
Create the dashboard view at app/Views/dashboard.php
:
<?= $this->extend('layout') ?> <?= $this->section('content') ?> <h1 class="mb-4">Dashboard</h1> <div class="card mb-4"> <div class="card-body"> <h5 class="card-title">Welcome, <?= esc($user['name'] ?? $user['email'] ?? 'User') ?>!</h5> <p class="card-text">You have successfully authenticated with MojoAuth.</p> <a href="<?= site_url('/profile') ?>" class="btn btn-primary">View Profile</a> </div> </div> <?= $this->endSection() ?>
Create the profile view at app/Views/profile.php
:
<?= $this->extend('layout') ?> <?= $this->section('content') ?> <h1 class="mb-4">User Profile</h1> <div class="card mb-4"> <div class="card-body"> <?php if (isset($user['picture'])): ?> <div class="text-center mb-3"> <img src="<?= esc($user['picture']) ?>" alt="Profile Picture" class="rounded-circle" style="width: 100px; height: 100px; object-fit: cover;"> </div> <?php endif; ?> <h5 class="card-title"><?= esc($user['name'] ?? 'N/A') ?></h5> <p class="card-text">Email: <?= esc($user['email'] ?? 'N/A') ?></p> <h6 class="mt-4">All User Information:</h6> <pre class="pre-scrollable"><?= esc(json_encode($user, JSON_PRETTY_PRINT)) ?></pre> </div> </div> <?= $this->endSection() ?>
Run Migrations
Run the migration to create the users table:
php spark migrate
Testing the Flow
-
Start the CodeIgniter development server:
php spark serve
-
Open your browser and navigate to
http://localhost:8080
-
Click on "Login with MojoAuth"
-
You will be redirected to MojoAuth's Hosted Login Page
-
After successful authentication, you will be redirected back to your application's callback URL
-
The application will process the authentication response, create or retrieve your user account, and redirect you to the dashboard
-
Navigate to the profile page to see your user information
Advanced Configuration
Token Refresh
You can implement token refresh by creating a helper method in the OidcHelper class:
public function refreshToken(string $refreshToken) { return $this->client->refreshToken($refreshToken); }
Implementing a BaseController with Authentication
Create a custom BaseController for authenticated routes. Create a new file at app/Controllers/AuthenticatedController.php
:
<?php namespace App\Controllers; use CodeIgniter\Controller; use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Psr\Log\LoggerInterface; class AuthenticatedController extends Controller { protected $session; /** * Constructor. */ public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { // Do Not Edit This Line parent::initController($request, $response, $logger); // Initialize session $this->session = \Config\Services::session(); // Check if user is authenticated if (!$this->session->has('user')) { return redirect()->to('/login'); } // Make user info available to all views $this->data['user'] = $this->session->get('user'); } }
Production Considerations
When deploying this application to production, consider the following:
- HTTPS: Always use HTTPS in production
- Environment Variables: Use proper environment variables management
- Database Security: Ensure proper database security configurations
- Session Security: Configure secure session handling
- Rate Limiting: Implement rate limiting for authentication routes
Next Steps
- Error Handling: Improve error handling with custom error pages
- User Management: Add user management features
- Logging: Add detailed logging for authentication events
- CSRF Protection: Ensure CSRF protection is enabled
- API Authentication: Extend the authentication to protect API endpoints