Implemented complete Microsoft Authentication Library (MSAL) / Azure AD Single Sign-On (SSO) system following Ferrero app pattern. KEY FEATURE: Toggle authentication on/off via environment variable - SSO_ENABLED=false → Mock user, no login required (local dev) - SSO_ENABLED=true → Full Azure AD authentication (production) NEW FILES: - composer.json - Firebase JWT dependency - .env.example - Environment variable template - env_loader.php - Parse .env file - JWTValidator.php - Validate JWT tokens from Azure AD - AuthMiddleware.php - Core auth orchestrator with login UI - auth.php - Authentication API (login/logout/status) - auth-test.php - Debug authentication status - AUTH_README.md - Complete setup documentation UPDATED FILES: - config.php - Load env vars, add SSO constants - index.php - Require auth, add logout button, MSAL script - api.php - Add authentication check - enhance_prompt.php - Add authentication check - .gitignore - Exclude .env and vendor/ AUTHENTICATION FLOW: 1. User visits app → Auth check 2. If SSO disabled → Mock "Local Developer" user 3. If SSO enabled → Validate JWT from cookie 4. If no token → Show MSAL login page 5. User signs in → Token validated → Cookie set → App loads SECURITY FEATURES: ✅ httpOnly cookies (XSS prevention) ✅ SameSite=Lax (CSRF prevention) ✅ JWT signature validation ✅ Claims validation (exp, nbf, aud, iss) ✅ JWKS from Azure AD ✅ 24-hour token expiration ✅ Secure flag for HTTPS DEPENDENCIES INSTALLED: - firebase/php-jwt v6.11.1 TESTING: - Local: SSO disabled by default in .env - Server: Set SSO_ENABLED=true with Azure AD credentials - Cannot test MSAL locally (redirect URI bound to server) DEPLOYMENT: 1. Install composer dependencies 2. Configure .env with Azure AD credentials 3. Set SSO_ENABLED=true when ready 4. Visit auth-test.php to verify setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
64 lines
2.2 KiB
PHP
64 lines
2.2 KiB
PHP
<?php
|
|
/**
|
|
* Authentication Test & Debug Page
|
|
* Shows current authentication status and configuration
|
|
*/
|
|
|
|
header('Content-Type: text/plain');
|
|
|
|
require_once __DIR__ . '/config.php';
|
|
require_once __DIR__ . '/AuthMiddleware.php';
|
|
|
|
echo "=== MSAL Authentication Test ===\n\n";
|
|
|
|
echo "1. SSO Configuration:\n";
|
|
echo " Enabled: " . (SSO_ENABLED ? 'YES' : 'NO') . "\n";
|
|
echo " Tenant ID: " . (SSO_TENANT_ID ?: 'NOT SET') . "\n";
|
|
echo " Client ID: " . (SSO_CLIENT_ID ?: 'NOT SET') . "\n\n";
|
|
|
|
echo "2. Testing AuthMiddleware:\n";
|
|
try {
|
|
$auth = new AuthMiddleware();
|
|
echo " ✓ AuthMiddleware loaded successfully\n";
|
|
echo " SSO Enabled: " . ($auth->isSSOEnabled() ? 'YES' : 'NO') . "\n\n";
|
|
|
|
echo "3. Authentication Status:\n";
|
|
$status = $auth->isAuthenticated();
|
|
echo " Authenticated: " . ($status['authenticated'] ? 'YES' : 'NO') . "\n";
|
|
|
|
if ($status['authenticated']) {
|
|
echo " User Name: " . ($status['user']['name'] ?? 'Unknown') . "\n";
|
|
echo " User Email: " . ($status['user']['preferred_username'] ?? $status['user']['upn'] ?? 'Unknown') . "\n";
|
|
} else {
|
|
echo " Error: " . ($status['error'] ?? 'Unknown') . "\n";
|
|
}
|
|
|
|
echo "\n4. Cookie Check:\n";
|
|
echo " auth_token cookie: " . (isset($_COOKIE['auth_token']) ? 'PRESENT' : 'NOT PRESENT') . "\n";
|
|
|
|
if (isset($_COOKIE['auth_token'])) {
|
|
$tokenLength = strlen($_COOKIE['auth_token']);
|
|
echo " Token length: " . $tokenLength . " chars\n";
|
|
echo " Token preview: " . substr($_COOKIE['auth_token'], 0, 50) . "...\n";
|
|
}
|
|
|
|
echo "\n5. Environment Variables:\n";
|
|
echo " SSO_ENABLED env: " . (getenv('SSO_ENABLED') ?: 'NOT SET') . "\n";
|
|
echo " SSO_TENANT_ID env: " . (getenv('SSO_TENANT_ID') ?: 'NOT SET') . "\n";
|
|
echo " SSO_CLIENT_ID env: " . (getenv('SSO_CLIENT_ID') ?: 'NOT SET') . "\n";
|
|
|
|
echo "\n6. Session Info:\n";
|
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
|
echo " Session active: YES\n";
|
|
echo " Session ID: " . session_id() . "\n";
|
|
} else {
|
|
echo " Session active: NO\n";
|
|
}
|
|
|
|
echo "\n=== Test Complete ===\n";
|
|
|
|
} catch (Exception $e) {
|
|
echo " ✗ Error: " . $e->getMessage() . "\n";
|
|
echo " Stack trace:\n";
|
|
echo $e->getTraceAsString() . "\n";
|
|
}
|