MojoAuth Hosted Login Page with Vanilla PHP
Introduction
PHP is one of the most widely used server-side scripting languages for web development. While there are many PHP frameworks available, sometimes you may need to implement authentication in a plain PHP project without using a framework. This guide will walk you through the process of connecting your vanilla PHP application to MojoAuth's Hosted Login Page using OpenID Connect (OIDC).
We'll be using the web-token/jwt-framework
and facile-it/php-openid-client
libraries for OIDC implementation, which are well-maintained PHP libraries for OpenID Connect.
Links:
- MojoAuth Hosted Login Page Documentation (opens in a new tab)
- facile-it/php-openid-client 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 8.0 or higher installed on your development machine
- Composer for PHP package management
- Basic knowledge of PHP
Project Setup
Let's start by creating a new project directory and installing the necessary packages:
# Create a new directory for your project mkdir mojoauth-php-example cd mojoauth-php-example # Initialize composer composer init --no-interaction --require "facile-it/php-openid-client:^1.0" --require "web-token/jwt-framework:^3.0" --require "guzzlehttp/guzzle:^7.0" --require "nyholm/psr7:^1.0" --require "nyholm/psr7-server:^1.0"
Project Structure
Create the following file structure for your project:
mojoauth-php-example/ ├── composer.json ├── composer.lock ├── config/ │ └── config.php ├── public/ │ └── index.php ├── src/ │ ├── Auth/ │ │ └── OpenIdConnectClient.php │ └── Session/ │ └── SessionManager.php └── vendor/
Configuration
Create a config/config.php
file to store your MojoAuth credentials:
<?php return [ 'mojoauth' => [ 'issuer_url' => 'https://your-project.auth.mojoauth.com', 'client_id' => 'your-client-id', 'client_secret' => 'your-client-secret', 'redirect_uri' => 'http://localhost:8000/callback.php', 'scopes' => ['openid', 'profile', 'email'] ], 'session' => [ 'name' => 'mojoauth_session', 'lifetime' => 1440, // 24 hours 'path' => '/', 'secure' => false, // Set to true in production with HTTPS 'httponly' => true ] ];
Session Management
Create src/Session/SessionManager.php
to handle session management:
<?php namespace App\Session; class SessionManager { public function __construct(array $config) { // Set session cookie parameters session_set_cookie_params([ 'lifetime' => $config['lifetime'], 'path' => $config['path'], 'secure' => $config['secure'], 'httponly' => $config['httponly'], 'samesite' => 'Lax' ]); // Set session name session_name($config['name']); // Start session if not already started if (session_status() === PHP_SESSION_NONE) { session_start(); } } public function set(string $key, $value): void { $_SESSION[$key] = $value; } public function get(string $key, $default = null) { return $_SESSION[$key] ?? $default; } public function has(string $key): bool { return isset($_SESSION[$key]); } public function remove(string $key): void { if ($this->has($key)) { unset($_SESSION[$key]); } } public function clear(): void { session_unset(); } public function regenerate(): bool { return session_regenerate_id(true); } public function destroy(): bool { $this->clear(); return session_destroy(); } }
OpenID Connect Client
Create src/Auth/OpenIdConnectClient.php
to handle OIDC authentication:
<?php namespace App\Auth; use Facile\OpenIDClient\Client\ClientBuilder; use Facile\OpenIDClient\Client\ClientInterface; use Facile\OpenIDClient\Client\Metadata\ClientMetadata; use Facile\OpenIDClient\Issuer\IssuerBuilder; use Facile\OpenIDClient\Service\Builder\AuthorizationServiceBuilder; use Facile\OpenIDClient\Authorization\AuthorizationRequest; use Facile\OpenIDClient\Service\Builder\UserInfoServiceBuilder; use Facile\OpenIDClient\Token\TokenSetInterface; use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7Server\ServerRequestCreator; use GuzzleHttp\Client as HttpClient; use App\Session\SessionManager; use function random_bytes; use function bin2hex; class OpenIdConnectClient { private ClientInterface $client; private SessionManager $session; public function __construct(array $config, SessionManager $session) { $this->session = $session; $this->initialize($config); } private function initialize(array $config): void { $httpClient = new HttpClient([ 'verify' => true, // Always verify SSL in production ]); $issuerBuilder = new IssuerBuilder($httpClient); $issuer = $issuerBuilder->build($config['issuer_url']); $clientMetadata = ClientMetadata::fromArray([ 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'], 'redirect_uris' => [$config['redirect_uri']], '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(): TokenSetInterface { $psr17Factory = new Psr17Factory(); $creator = new ServerRequestCreator( $psr17Factory, // ServerRequestFactory $psr17Factory, // UriFactory $psr17Factory, // UploadedFileFactory $psr17Factory // StreamFactory ); $serverRequest = $creator->fromGlobals(); // Get state and code from request $params = $serverRequest->getQueryParams(); // Verify the state to prevent CSRF if (!isset($params['state']) || $params['state'] !== $this->session->get('oauth_state')) { throw new \RuntimeException('Invalid state parameter'); } // Exchange the authorization code for tokens $tokenSet = $this->client->handleCallback( $serverRequest->getQueryParams(), [ '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 the Entry Point
Create a public/index.php
file:
<?php require_once __DIR__ . '/../vendor/autoload.php'; // Load configuration $config = require_once __DIR__ . '/../config/config.php'; // Initialize session manager $sessionManager = new App\Session\SessionManager($config['session']); // Check if the user is logged in $isLoggedIn = $sessionManager->has('user'); $user = $isLoggedIn ? $sessionManager->get('user') : null; // Initialize OpenID Connect client $openIdClient = new App\Auth\OpenIdConnectClient($config['mojoauth'], $sessionManager); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MojoAuth OIDC PHP Example</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } pre { background: #f4f4f4; padding: 10px; border-radius: 5px; overflow: auto; } </style> </head> <body> <h1>MojoAuth OIDC PHP Example</h1> <?php if ($isLoggedIn): ?> <p>Logged in as <?= htmlspecialchars($user['name'] ?? $user['email'] ?? 'User') ?></p> <p><a href="profile.php">View Profile</a> | <a href="logout.php">Logout</a></p> <?php else: ?> <p><a href="login.php">Login with MojoAuth</a></p> <?php endif; ?> </body> </html>
Implement Login
Create a public/login.php
file:
<?php require_once __DIR__ . '/../vendor/autoload.php'; // Load configuration $config = require_once __DIR__ . '/../config/config.php'; // Initialize session manager $sessionManager = new App\Session\SessionManager($config['session']); // Initialize OpenID Connect client $openIdClient = new App\Auth\OpenIdConnectClient($config['mojoauth'], $sessionManager); // Get the authorization URL $authorizationUrl = $openIdClient->getAuthorizationUrl($config['mojoauth']['scopes']); // Redirect to MojoAuth Hosted Login Page header('Location: ' . $authorizationUrl); exit;
Implement Callback
Create a public/callback.php
file:
<?php require_once __DIR__ . '/../vendor/autoload.php'; // Load configuration $config = require_once __DIR__ . '/../config/config.php'; // Initialize session manager $sessionManager = new App\Session\SessionManager($config['session']); // Initialize OpenID Connect client $openIdClient = new App\Auth\OpenIdConnectClient($config['mojoauth'], $sessionManager); try { // Handle the callback and get tokens $tokenSet = $openIdClient->handleCallback(); // Get user info from token $userInfo = $openIdClient->getUserInfo($tokenSet); // Store user info and tokens in session $sessionManager->set('user', $userInfo); $sessionManager->set('tokens', [ 'access_token' => $tokenSet->getAccessToken(), 'id_token' => $tokenSet->getIdToken(), 'expires_at' => $tokenSet->getAccessTokenExpiration() ]); // Redirect to profile page header('Location: profile.php'); exit; } catch (\Exception $e) { die('Error during authentication: ' . $e->getMessage()); }
Implement Profile Page
Create a public/profile.php
file:
<?php require_once __DIR__ . '/../vendor/autoload.php'; // Load configuration $config = require_once __DIR__ . '/../config/config.php'; // Initialize session manager $sessionManager = new App\Session\SessionManager($config['session']); // Check if the user is logged in if (!$sessionManager->has('user')) { header('Location: index.php'); exit; } // Get user info from session $user = $sessionManager->get('user'); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Profile - MojoAuth OIDC PHP Example</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } pre { background: #f4f4f4; padding: 10px; border-radius: 5px; overflow: auto; } </style> </head> <body> <h1>Profile</h1> <p>Welcome <?= htmlspecialchars($user['name'] ?? $user['email'] ?? 'User') ?>!</p> <h2>User Information</h2> <pre><?= htmlspecialchars(json_encode($user, JSON_PRETTY_PRINT)) ?></pre> <p><a href="index.php">Home</a> | <a href="logout.php">Logout</a></p> </body> </html>
Implement Logout
Create a public/logout.php
file:
<?php require_once __DIR__ . '/../vendor/autoload.php'; // Load configuration $config = require_once __DIR__ . '/../config/config.php'; // Initialize session manager $sessionManager = new App\Session\SessionManager($config['session']); // Clear the session $sessionManager->destroy(); // Redirect to home page header('Location: index.php'); exit;
Autoloading (Composer Configuration)
Update your composer.json
file to include autoloading configuration:
{ "require": { "facile-it/php-openid-client": "^1.0", "web-token/jwt-framework": "^3.0", "guzzlehttp/guzzle": "^7.0", "nyholm/psr7": "^1.0", "nyholm/psr7-server": "^1.0" }, "autoload": { "psr-4": { "App\\": "src/" } } }
Then run:
composer dump-autoload
Testing the Flow
-
Start a local PHP development server:
cd public php -S localhost:8000
-
Open your browser and navigate to
http://localhost:8000
-
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 and redirect you to the profile page
-
Your user profile information will be displayed on the profile page
Production Considerations
When deploying this application to production, consider the following:
- HTTPS: Always use HTTPS in production
- Session Security: Update session configuration with secure cookies
- Error Handling: Implement more robust error handling
- Logging: Add proper logging for authentication events
- Environment Variables: Store sensitive credentials in environment variables instead of code
Next Steps
- Token Validation: Add more robust token validation and signature verification
- Refresh Tokens: Implement refresh token functionality
- Persistent Storage: Use a database to store sessions instead of PHP's default file-based sessions
- Security Headers: Add security headers to protect against common web vulnerabilities
- Rate Limiting: Implement rate limiting to prevent abuse