AZURE_CLIENT_ID, 'redirectUri' => AZURE_REDIRECT_URI, 'urlAuthorize' => AZURE_AUTHORITY . '/oauth2/v2.0/authorize', 'urlAccessToken' => AZURE_AUTHORITY . '/oauth2/v2.0/token', 'urlResourceOwnerDetails' => 'https://graph.microsoft.com/v1.0/me', 'scopes' => 'openid profile email User.Read' ]); // Step 1: No authorization code - initiate OAuth flow if (!isset($_GET['code'])) { // Generate PKCE code verifier and challenge $codeVerifier = bin2hex(random_bytes(32)); // 64-character random string $codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '='); // Store code verifier in session for later use $_SESSION['oauth2_code_verifier'] = $codeVerifier; // Generate authorization URL with PKCE parameters $authorizationUrl = $provider->getAuthorizationUrl([ 'scope' => 'openid profile email User.Read', 'code_challenge' => $codeChallenge, 'code_challenge_method' => 'S256', 'response_type' => 'code', 'response_mode' => 'query' ]); // Store state for CSRF protection $_SESSION['oauth2state'] = $provider->getState(); // Redirect to Azure AD header('Location: ' . $authorizationUrl); exit; } // Step 2: Authorization code received - exchange for access token elseif (isset($_GET['code'])) { // Verify state to prevent CSRF attacks if (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) { unset($_SESSION['oauth2state']); unset($_SESSION['oauth2_code_verifier']); die('Invalid state. Possible CSRF attack.'); } try { // Retrieve code verifier from session if (!isset($_SESSION['oauth2_code_verifier'])) { die('Code verifier not found in session.'); } $codeVerifier = $_SESSION['oauth2_code_verifier']; // Exchange authorization code for access token with PKCE $accessToken = $provider->getAccessToken('authorization_code', [ 'code' => $_GET['code'], 'code_verifier' => $codeVerifier ]); // Get user information from Microsoft Graph API $request = $provider->getAuthenticatedRequest( 'GET', 'https://graph.microsoft.com/v1.0/me', $accessToken->getToken() ); $client = new \GuzzleHttp\Client(); $response = $client->send($request); $userData = json_decode($response->getBody(), true); // Store user information in session $_SESSION['authenticated'] = true; $_SESSION['user_id'] = $userData['id']; $_SESSION['user_name'] = $userData['displayName'] ?? $userData['userPrincipalName']; $_SESSION['user_email'] = $userData['userPrincipalName'] ?? $userData['mail']; $_SESSION['access_token'] = $accessToken->getToken(); $_SESSION['last_activity'] = time(); // Initialize user files array for tracking uploads $_SESSION['user_files'] = []; // Clean up temporary session variables unset($_SESSION['oauth2state']); unset($_SESSION['oauth2_code_verifier']); // Regenerate session ID for security session_regenerate_id(true); // Redirect to main application header('Location: index.php'); exit; } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) { // Handle authentication errors die('Authentication failed: ' . htmlspecialchars($e->getMessage())); } catch (\Exception $e) { // Handle other errors die('An error occurred: ' . htmlspecialchars($e->getMessage())); } }