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 invalid_grant error
since
a conditional access policy is not met for Azure resources.
Example:
HTTP/1.1 400 Bad Request
{
"error": {
"code": "invalid_grant",
"message": "{\"Name\":\"MsalUiRequiredException\",\"Message\":\"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 '797f4846-ba00-4fd7-ba43-dac1f8f63013'.\\r\\nTrace ID: 7bdbe148-89e6-4493-a150-93dac7a06c00\\r\\nCorrelation ID: ff221ee5-ebb9-42d0-8f70-dbffde1b2104\\r\\nTimestamp: 2020-09-16 01:16:03Z\",\"Claims\":\"{\\\"access_token\\\":{\\\"capolids\\\":{\\\"essential\\\":true,\\\"values\\\":[\\\"051744ca-6abe-4095-b526-14a7f4033309\\\"]}}}\"}",
"innerError": {
"date": "2020-09-16T01:16:03",
"request-id": "82cd21d1-6413-4512-a2d1-fa1d0a3c5826",
"client-request-id": "82cd21d1-6413-4512-a2d1-fa1d0a3c5826"
}
}
}
To handle this, the user needs to catch the error, get the claims challenge and send in a login request with claims challenge as an extra query string parameter.
To learn more, see https://docs.microsoft.com/en-us/azure/active-directory/develop/conditional-access-dev-guide#scenario-app-performing-the-on-behalf-of-flow
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
#Acquire AAD token
function AcquireToken($claims){
$clientID = "dabc52c4-106b-4179-9df2-2f791f44ba14"
$redirectUri = "https://pimmsgraph"
$authority = "https://login.microsoftonline.com/common"
if($claims -ne $null)
{
$authResult = Get-MSALToken -Scopes @("https://graph.microsoft.com/.default") -ClientId $ClientID -RedirectUri $redirectUri -Authority $authority -Interactive -ExtraQueryParameters @{claims=$claims}
Set-Variable -Name mfaDone -Value $true -Scope Global
}
else
{
$authResult = Get-MSALToken -Scopes @("https://graph.microsoft.com/.default") -ClientId $ClientID -RedirectUri $redirectUri -Authority $authority -Interactive
}
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;
# Install msal.ps
if(!(Get-Module | Where-Object {$_.Name -eq 'PowerShellGet' -and $_.Version -ge '2.2.4.1'})) { Install-Module PowerShellGet -Force }
if(!(Get-Package msal.ps)) { Install-Package msal.ps }
$Authed = AcquireToken $global:clientID $global:redirectUri $global:resourceAppIdURI $global:authority $false
if ($Authed -eq $false)
{
return
}
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("invalid_grant"))
{
$errorObject = ConvertFrom-Json $err
$message = ConvertFrom-Json $errorObject.error.message
$claims = $message.claims
Write-Host "Prompting the user again since since a conditional access policy is enabled..." -ForegroundColor Green
AcquireToken $claims
DisplayResources
}
else
{
Write-Host $err -ForegroundColor Red
}
}
Write-Host ""