service = $service; } #[Computed] public function mfaBehaviorOptions(): array { return array_map(fn($case) => [ 'id' => $case->value, 'name' => match ($case) { FederatedIdpMfaBehaviorEnum::AcceptIfMfaDoneByFederatedIdp => 'Accept if MFA Done by Federated IdP', FederatedIdpMfaBehaviorEnum::EnforceMfaByFederatedIdp => 'Enforce MFA by Federated IdP', FederatedIdpMfaBehaviorEnum::RejectMfaByFederatedIdp => 'Always perform MFA (Reject Federated IdP MFA)', }, ], FederatedIdpMfaBehaviorEnum::cases()); } public function mount(): void { $config = config('services.microsoft_graph'); $this->tenantId = (string) ($config['tenant_id'] ?? ''); $this->clientId = (string) ($config['client_id'] ?? ''); $this->clientSecret = (string) ($config['client_secret'] ?? ''); $this->domainName = (string) ($config['domain_name'] ?? ''); $this->displayName = (string) ($config['display_name'] ?? 'Company SSO'); $this->mfaBehavior = FederatedIdpMfaBehaviorEnum::tryFrom( (string) ($config['mfa_behavior'] ?? '') ) ?? FederatedIdpMfaBehaviorEnum::AcceptIfMfaDoneByFederatedIdp; if ( !empty($this->tenantId) && !empty($this->clientId) && !empty($this->clientSecret) && !empty($this->domainName) ) { $this->credentialsLoaded = true; } if (file_exists(storage_path("app/saml/idp.crt"))) { $this->certificateLoaded = true; } if ($this->credentialsLoaded) { $this->checkStatus(true); } } public function checkStatus(bool $silent = false): void { $this->attempt( action: function () use ($silent) { $credentials = new MicrosoftGraphCredentialsData( tenantId: $this->tenantId, clientId: $this->clientId, clientSecret: $this->clientSecret, domainName: $this->domainName, displayName: $this->displayName, mfaBehavior: $this->mfaBehavior, ); $result = $this->service->getFederationConfig($credentials); foreach ($result["transactions"] as $tx) { $this->debugResponses[] = array_merge($tx, [ "timestamp" => now()->format("Y-m-d H:i:s"), ]); } if ($result["status"] === "success") { if ($result["config"] !== null) { $this->connectionStatus = ConnectionStatusEnum::Connected; $this->federationId = $result["config"]["id"]; $this->activeFederationConfig = $result["config"]; if (!$silent) { $this->success( "Federation is active on Microsoft Entra ID!", ); } } else { $this->connectionStatus = ConnectionStatusEnum::Disconnected; $this->federationId = null; $this->activeFederationConfig = null; if (!$silent) { $this->success( "Domain is managed. No federation configuration found.", ); } } } else { $this->connectionStatus = ConnectionStatusEnum::Error; if (!$silent) { $this->error( $result["message"] ?? "Failed to check status.", ); } } }, showSuccess: false, ); } public function connect(): void { if (!$this->certificateLoaded) { $this->error( "Missing public certificate! Run php artisan saml:generate-keys first.", ); return; } $this->attempt( action: function () { $credentials = new MicrosoftGraphCredentialsData( tenantId: $this->tenantId, clientId: $this->clientId, clientSecret: $this->clientSecret, domainName: $this->domainName, displayName: $this->displayName, mfaBehavior: $this->mfaBehavior, ); $certPem = file_get_contents(storage_path("app/saml/idp.crt")); $issuerUri = route("saml.metadata"); $passiveSignInUri = route("saml.sso"); $activeSignInUri = route("saml.sso"); $signOutUri = route("home"); $result = $this->service->connectFederation( credentials: $credentials, certPem: $certPem, issuerUri: $issuerUri, passiveSignInUri: $passiveSignInUri, activeSignInUri: $activeSignInUri, signOutUri: $signOutUri, ); foreach ($result["transactions"] as $tx) { $this->debugResponses[] = array_merge($tx, [ "timestamp" => now()->format("Y-m-d H:i:s"), ]); } if ($result["status"] === "success") { $this->connectionStatus = ConnectionStatusEnum::Connected; $this->federationId = $result["config"]["id"] ?? null; $this->activeFederationConfig = $result["config"]; $this->success( "SAML IDP successfully federated with Microsoft Entra ID!", ); } else { $this->connectionStatus = ConnectionStatusEnum::Error; $this->error( $result["message"] ?? "Failed to connect federation.", ); } }, showSuccess: false, ); } public function disconnect(): void { if (empty($this->federationId)) { $this->error("No active federation ID found. Check status first."); return; } $this->attempt( action: function () { $credentials = new MicrosoftGraphCredentialsData( tenantId: $this->tenantId, clientId: $this->clientId, clientSecret: $this->clientSecret, domainName: $this->domainName, displayName: $this->displayName, mfaBehavior: $this->mfaBehavior, ); $result = $this->service->disconnectFederation( $credentials, $this->federationId, ); foreach ($result["transactions"] as $tx) { $this->debugResponses[] = array_merge($tx, [ "timestamp" => now()->format("Y-m-d H:i:s"), ]); } if ($result["status"] === "success") { $this->connectionStatus = ConnectionStatusEnum::Disconnected; $this->federationId = null; $this->activeFederationConfig = null; $this->success( "SAML IDP disconnected from Microsoft Entra ID. Domain returned to Managed.", ); } else { $this->connectionStatus = ConnectionStatusEnum::Error; $this->error( $result["message"] ?? "Failed to disconnect federation.", ); } }, showSuccess: false, ); } public function clearDebug(): void { $this->debugResponses = []; $this->success("Debug outputs cleared."); } }; ?>
To enable the Microsoft Graph API integration, please configure the environment, to include needed values.
A verified public certificate is required to sign SAML requests sent to Microsoft Entra ID. No certificate was found.
Connect this IDP to Microsoft Entra to sync active configurations.
Federation transactions will appear here as HTTP raw records when you click Check, Connect, or Disconnect.