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."); } }; ?>
@if(!$credentialsLoaded)

Environment Configuration Required

To enable the Microsoft Graph API integration, please configure the environment, to include needed values.

@endif @if($credentialsLoaded && !$certificateLoaded)

SAML Signing Certificate Missing

A verified public certificate is required to sign SAML requests sent to Microsoft Entra ID. No certificate was found.

@endif
Status: @if($connectionStatus === ConnectionStatusEnum::Connected) Federated @elseif($connectionStatus === ConnectionStatusEnum::Disconnected) Managed @elseif($connectionStatus === ConnectionStatusEnum::Pending) Connecting... @else Error @endif
MICROSOFT_GRAPH_TENANT_ID
MICROSOFT_GRAPH_CLIENT_ID
MICROSOFT_GRAPH_CLIENT_SECRET
MICROSOFT_GRAPH_DOMAIN_NAME
MICROSOFT_GRAPH_DISPLAY_NAME
MICROSOFT_GRAPH_MFA_BEHAVIOR

local Identity Provider Endpoints

Issuer URI (Entity ID) {{ route('saml.metadata') }}
Passive Sign-In Endpoint (SSO) {{ route('saml.sso') }}
Active Sign-In Endpoint (WS-Trust) {{ route('saml.sso') }}
Sign-Out URI {{ route('home') }}
Check Status
@if($connectionStatus === ConnectionStatusEnum::Connected) Disconnect Federation @else Connect & Federate @endif
@if($activeFederationConfig)
Federation ID {{ Str::limit($activeFederationConfig['id'], 18) }}
Preferred Protocol {{ $activeFederationConfig['preferredAuthenticationProtocol'] ?? 'Unknown' }}
Entra MFA Rule {{ $activeFederationConfig['federatedIdpMfaBehavior'] ?? 'Unknown' }}
Active Signing Certificate Thumbprint
{{ hash('sha1', base64_decode($activeFederationConfig['signingCertificate'])) }}
@else

No Active Federation

Connect this IDP to Microsoft Entra to sync active configurations.

@endif
@if(count($debugResponses) > 0) Clear Logs @endif
@if(count($debugResponses) > 0)
@foreach(array_reverse($debugResponses) as $index => $log)
@if(!empty($log['request_headers']) || !empty($log['request_body']))
@if(!empty($log['request_headers']))
Request Headers
{{ json_encode($log['request_headers'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}
@endif @if(!empty($log['request_body']))
Request Payload
{{ json_encode($log['request_body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}
@endif
@endif
Response Body Payload
{{ json_encode($log['response_body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}
@endforeach
@else

Terminal Output Empty

Federation transactions will appear here as HTTP raw records when you click Check, Connect, or Disconnect.

@endif