Private/Invoke-EntraMfa.ps1
function Invoke-EntraMfa { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $TenantId, [Parameter(Mandatory = $true)] [string] $User, [Parameter(Mandatory = $true)] [ValidateSet("Push","OTP","Challenge")] [string] $Mode, [Parameter(Mandatory = $false)] [PSCustomObject] $Challenge ) try { $headers = @{ "Authorization" = "Bearer $($script:ModuleSessionData.EntraIdSessions[$TenantId].Token)" } $result = [PSCustomObject]@{ Result = "KO" ChallengeId = $null ChallengeUri = $null } if ($Mode -eq "Push" -or $Mode -eq "OTP") { $xml = '<BeginTwoWayAuthenticationRequest><Version>1.0</Version><UserPrincipalName>__UPN__</UserPrincipalName><Lcid>en-us</Lcid><AuthenticationMethodProperties xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"><a:KeyValueOfstringstring><a:Key>OverrideVoiceOtp</a:Key><a:Value>false</a:Value></a:KeyValueOfstringstring><a:KeyValueOfstringstring><a:Key>OverrideNumberMatchingWithOTP</a:Key><a:Value>__USE_OTP__</a:Value></a:KeyValueOfstringstring></AuthenticationMethodProperties><ContextId>__GUID__</ContextId><SyncCall>true</SyncCall><RequireUserMatch>true</RequireUserMatch><CallerName>radius</CallerName><CallerIP>UNKNOWN:</CallerIP></BeginTwoWayAuthenticationRequest>' if ($Mode -eq "Push") { $body = $xml.Replace("__UPN__", $User).Replace("__USE_OTP__", "false").Replace("__GUID__", (New-Guid).Guid) } elseif ($Mode -eq "OTP") { $body = $xml.Replace("__UPN__", $User).Replace("__USE_OTP__", "true").Replace("__GUID__", (New-Guid).Guid) } $response = Invoke-RestMethod -Uri $Providers.EntraId.MfaEndpoint -Method POST -Headers $headers -Body $body -ContentType 'application/xml' -TimeoutSec 60 if (-not $response.BeginTwoWayAuthenticationResponse) { throw "Unexpected reply message" } switch ($response.BeginTwoWayAuthenticationResponse.AuthenticationResult) { "false" { $e = $response.BeginTwoWayAuthenticationResponse.Result.Value Write-Warning "MFA failed - '$e'" } "true" { $result.Result = "OK" } "challenge" { $result.Result = "CHALLENGE" $result.ChallengeId = $response.BeginTwoWayAuthenticationResponse.SessionId $result.ChallengeUri = $response.BeginTwoWayAuthenticationResponse.AffinityUrl } default { Write-Warning "MFA failed - Unexpected response" } } } elseif ($Mode -eq "Challenge") { if (-not $Challenge) { throw "Missing challenge data" } $xml = '<EndTwoWayAuthenticationRequest><Version>1.0</Version><SessionId>__CHALLENGE_ID__</SessionId><ContextId>__GUID__</ContextId><AdditionalAuthData>__OTP__</AdditionalAuthData><UserPrincipalName>__UPN__</UserPrincipalName></EndTwoWayAuthenticationRequest>' $body = $xml.Replace("__UPN__", $User).Replace("__OTP__", $Challenge.OTP).Replace("__GUID__", (New-Guid).Guid).Replace("__CHALLENGE_ID__", $Challenge.ChallengeId) $response = Invoke-RestMethod -Uri $Challenge.ChallengeUri -Method POST -Headers $headers -Body $body -ContentType 'application/xml' -TimeoutSec 60 if (-not $response.EndTwoWayAuthenticationResponse) { throw "Unexpected challenge reply message" } switch ($response.EndTwoWayAuthenticationResponse.AuthenticationResult) { "false" { $e = $response.EndTwoWayAuthenticationResponse.Result.Value Write-Warning "MFA failed - '$e'" } "true" { $result.Result = "OK" } default { Write-Warning "MFA failed - Unexpected challenge response" } } } return $result } catch { throw "Error processing MFA request - $_" } } |