* SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * See the LICENSE file in the project root for the full license text. */ declare(strict_types=1); namespace App\Auth; use Jumbojett\OpenIDConnectClient; use RuntimeException; /** * Thin factory around jumbojett/openid-connect-php configured for * Microsoft Entra ID (v2.0 endpoint) with Authorization Code + PKCE. * * Reads these env vars: * ENTRA_TENANT_ID * ENTRA_CLIENT_ID * ENTRA_CLIENT_SECRET * APP_BASE_URL */ final class OidcClient { public static function create(): OpenIDConnectClient { $tenant = self::env('ENTRA_TENANT_ID'); $clientId = self::env('ENTRA_CLIENT_ID'); $clientSecret = self::env('ENTRA_CLIENT_SECRET'); $baseUrl = rtrim(self::env('APP_BASE_URL'), '/'); $issuer = "https://login.microsoftonline.com/{$tenant}/v2.0"; $oidc = new OpenIDConnectClient($issuer, $clientId, $clientSecret); $oidc->setRedirectURL($baseUrl . '/auth/callback'); $oidc->addScope(['openid', 'profile', 'email']); $oidc->setCodeChallengeMethod('S256'); // PKCE // Entra's userinfo endpoint doesn't return new info beyond the id_token // for our scopes; rely on verified claims to avoid the extra round trip. return $oidc; } public static function isConfigured(): bool { foreach (['ENTRA_TENANT_ID', 'ENTRA_CLIENT_ID', 'ENTRA_CLIENT_SECRET', 'APP_BASE_URL'] as $k) { $v = getenv($k); if (!is_string($v) || $v === '') { return false; } } return true; } private static function env(string $name): string { $v = getenv($name); if (!is_string($v) || $v === '') { throw new RuntimeException("Required env var not set: {$name}"); } return $v; } }