Friday, August 2, 2019

Handle Conditional Access challenge for Privileged Identity Management on Microsoft Graph

Privileged Identity Management (PIM) for Azure resources api’s are available on Microsoft Graph (MSGraph) so that developers can automate the PIM operations like activation, assignment, etc. To learn more, see http://www.anujchaudhary.com/2018/02/powershell-sample-for-privileged.html

Some organizations enable conditional policies like Multi factor authentication (MFA) for accessing any Azure resources. When users go to PIM through Azure Portal, they are prompted for MFA while logging into the Azure Portal. When they access the PIM UI, everything works since they have already performed MFA.

However, if the users are accessing PIM api’s for Azure resources through MSGraph, they might not be prompted for MFA on login since no conditional access policy might be enabled for MSGraph. When a PIM api is called, it fails with 400 Bad Request interaction_required error since a conditional access policy is not met for Azure resources.
Example:
HTTP/1.1 400 Bad Request
"error":"interaction_required"
"error_description":"AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access 'xxx”
“claims”:” {""access_token"":{""capolids"":{""essential"":true,""values"":[""yyy""]}}}”

To handle this, the user need to catch the interaction_required error, get the claims challenge and send in a login request with claims challenge as an extra query string parameter.


Below is a PowerShell sample which showcases on how to handle the conditional access challenge when calling PIM api's on MSGraph. Just save this as a .ps1 file and run it with PowerShell.

Sceenshot










Source code

#Loads Active Directory Authentication Library
function Load-ActiveDirectoryAuthenticationLibrary(){
    $moduleDirPath = [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell\Modules"
    $modulePath = $moduleDirPath + "\AADGraph"

    if(-not (Test-Path ($modulePath+"\Nugets"))) {New-Item -Path ($modulePath+"\Nugets") -ItemType "Directory" | out-null}
    $adalPackageDirectories = (Get-ChildItem -Path ($modulePath+"\Nugets") -Filter "Microsoft.IdentityModel.Clients.ActiveDirectory*" -Directory)

    if($adalPackageDirectories.Length -eq 0){
        Write-Host "Active Directory Authentication Library Nuget doesn't exist. Downloading now ..." -ForegroundColor Yellow
        if(-not(Test-Path ($modulePath + "\Nugets\nuget.exe")))
        {
            Write-Host "nuget.exe not found. Downloading from http://www.nuget.org/nuget.exe ..." -ForegroundColor Yellow
            $wc = New-Object System.Net.WebClient
            $wc.DownloadFile("http://www.nuget.org/nuget.exe",$modulePath + "\Nugets\nuget.exe");
        }
        $nugetDownloadExpression = $modulePath + "\Nugets\nuget.exe install Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.14.201151115 -OutputDirectory " + $modulePath + "\Nugets | out-null"
        Invoke-Expression $nugetDownloadExpression
    }

    $adalPackageDirectories = (Get-ChildItem -Path ($modulePath+"\Nugets") -Filter "Microsoft.IdentityModel.Clients.ActiveDirectory*" -Directory)
    $ADAL_Assembly = (Get-ChildItem "Microsoft.IdentityModel.Clients.ActiveDirectory.dll" -Path $adalPackageDirectories[$adalPackageDirectories.length-1].FullName -Recurse)
    $ADAL_WindowsForms_Assembly = (Get-ChildItem "Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll" -Path $adalPackageDirectories[$adalPackageDirectories.length-1].FullName -Recurse)
    if($ADAL_Assembly.Length -gt 0 -and $ADAL_WindowsForms_Assembly.Length -gt 0){
        Write-Host "Loading ADAL Assemblies ..." -ForegroundColor Green
        [System.Reflection.Assembly]::LoadFrom($ADAL_Assembly[0].FullName) | out-null
        [System.Reflection.Assembly]::LoadFrom($ADAL_WindowsForms_Assembly.FullName) | out-null
        return $true
    }
    else{
        Write-Host "Fixing Active Directory Authentication Library package directories ..." -ForegroundColor Yellow
        $adalPackageDirectories | Remove-Item -Recurse -Force | Out-Null
        Write-Host "Not able to load ADAL assembly. Delete the Nugets folder under" $modulePath ", restart PowerShell session and try again ..."
        return $false
    }
}

#Acquire AAD token
function AcquireToken($queryParamater){
    $clientID = "dabc52c4-106b-4179-9df2-2f791f44ba14"
    $redirectUri = "https://pimmsgraph"

    $authority = "https://login.microsoftonline.com/common"
    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority,$false
    if($queryParamater -ne $null)
    {
        $authResult = $authContext.AcquireToken("https://graph.microsoft.com",$ClientID,$redirectUri,[Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Auto, [Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier]::AnyUser, $queryParamater)
        Set-Variable -Name mfaDone -Value $true -Scope Global
    }
    else
    {
        $authResult = $authContext.AcquireToken("https://graph.microsoft.com",$ClientID,$redirectUri,[Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always)
    }
    if($authResult -ne $null)
    {
        Write-Host "User logged in successfully ..." -ForegroundColor Green
    }
    Set-Variable -Name headerParams -Value @{'Authorization'="$($authResult.AccessTokenType) $($authResult.AccessToken)"} -Scope Global
    Set-Variable -Name assigneeId -Value $authResult.UserInfo.UniqueId -Scope Global

#List resources
function ListResources(){
    $url = $serviceRoot + "resources?`$filter=(type+eq+'subscription')" 
     Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $resources = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($resource in $resources.value)
    {
        $item = New-Object psobject -Property @{
        Id = ++$i
        ResourceId =  $resource.id
        ResourceName =  $resource.displayName
        ResourceType =  $resource.type
    }
    $obj = $obj + $item
}

return $obj
}

#Disaplay resources
function DisplayResources(){
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap Id,ResourceName,ResourceType
}

############################################################################################################################################################################

$global:serviceRoot = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/"
$global:MSGraphRoot = "https://graph.microsoft.com/v1.0/"
$global:headerParams = ""
$global:assigneeId = ""
$global:mfaDone = $false;

Load-ActiveDirectoryAuthenticationLibrary
AcquireToken

try
{
    DisplayResources
}
catch
{
    $stream = $_.Exception.Response.GetResponseStream()
    $stream.Position = 0;
    $streamReader = New-Object System.IO.StreamReader($stream)
    $err = $streamReader.ReadToEnd()
    $streamReader.Close()
    $stream.Close()

    if($err.Contains("interaction_required"))
    {
        $errorObject = ConvertFrom-Json $err
        $message = ConvertFrom-Json $errorObject.error.message
        $queryString = "claims=" + $message.claims
        Write-Host "Prompting the user again since since a conditional access policy is enabled..." -ForegroundColor Green
        AcquireToken $queryString
        DisplayResources
    }
    else
    {
        Write-Host $err -ForegroundColor Red
    }
}


Write-Host ""