Saturday, June 13, 2026

Custom Extensions for Microsoft Entra Privileged Identity Management (PIM)

 You can now bring your own business logic into the Microsoft Entra Privileged Identity Management workflow with Custom Extensions. Validate ticket numbers against your ticketing system, verify required training completion, collect and validate additional business context, or integrate with your own internal approval and compliance processes before granting Just-In-Time access.

Whether you're enforcing security requirements, meeting compliance obligations, or integrating with existing business systems, Custom Extensions provide a flexible and powerful way to tailor PIM workflows to your organization's unique needs. The possibilities are endless. See https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/privileged-identity-management-custom-extensions 



Monday, April 6, 2026

Re-prompt for Multi-factor authentication on every Just-In-Time request using Privileged Identity Management

 Microsoft Entra Privileged Identity Management now re‑prompts users for Multi‑Factor Authentication on every Just‑In‑Time activation request, even if they’ve already signed in with MFA.

See how we’re raising the bar for secure privileged access https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/groups-role-settings#on-activation-require-microsoft-entra-conditional-access-authentication-context



Tuesday, December 3, 2024

Connect to Azure PowerShell with Conditional Access Authentical Context

You can require users who are eligible for a role to satisfy Conditional Access policy requirements. For example, you can require users to use a specific authentication method enforced through Authentication Strengths, elevate the role from an Intune-compliant device, and comply with terms of use. Learn more here https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-resource-roles-configure-role-settings#on-activation-require-microsoft-entra-conditional-access-authentication-context 

However, once this is enabled, users cannot activate their eligible roles using Azure PowerShell since there is no way to login to Azure PowerShell with authentication context. 

To solve this issue, you can first acquire a token with authentication context and use it to connect to Azure PowerShell.

First, remember to disconnect so an older token is not used by Azure PowerShell

DisConnect-AzAccount

Now, acquire a token with authentication context. Replace c1 with the id of your authentication context. Learn more here https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-cloud-apps#configure-authentication-contexts 

$MsResponse = Get-MSALToken -Scopes @("https://management.core.windows.net//.default openid profile offline_access") -ClientId "04b07795-8ddb-461a-bbee-02f9e1bf7b46" -RedirectUri "http://localhost:8400/" -Authority "https://login.microsoftonline.com/common" -Interactive -ExtraQueryParameters @{claims='{"access_token":{"xms_cc":{"values":["CP1"]},"acrs":{"essential":true, "value":"c1"}}}'} 

Connect to Azure PowerShell with the acquired access token and provide the upn of the user if prompted for AccountId

Connect-AzAccount -AccessToken $MsResponse.AccessToken 

Activate your eligible role. Replace $scope, $role and $principal with your values. Learn more here https://learn.microsoft.com/en-us/powershell/module/az.resources/new-azroleassignmentschedulerequest?view=azps-13.0.0#example-3-activate-a-new-role-assignment-schedule-request-as-user 

$guid = (New-Guid).Guid 

$startTime = Get-Date -Format o 

$scope = "/subscriptions/c896b064-0cd9-49d5-a7df-c82df3dc60f3" 

$role = "/subscriptions/c896b064-0cd9-49d5-a7df-c82df3dc60f3/providers/Microsoft.Authorization/roleDefinitions/18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" 

$principal = "290190c3-1372-4076-99a5-9efa5a1a18e3" 

New-AzRoleAssignmentScheduleRequest -Name $guid -Scope $scope -ExpirationDuration PT1H -ExpirationType AfterDuration -PrincipalId $principal -RequestType SelfActivate -RoleDefinitionId $role -ScheduleInfoStartDateTime $startTime -Justification "test" 


Wednesday, September 30, 2020

Azure AD PowerShell for Azure Resources in PIM

Have you wondered how to user Azure AD PowerShell for Azure Resources in PIM. This is a little tricky since the id's in OData do not support slashes but the id's for Azure resources contains slashes. Hence the id for these resources are mapped to a GUID in Graph.
To query the azure resources in Graph, you will need to pass an ExternalId filter and the rest should be straight forward.
Below is an example of adding a user as Eligible Owner on a subscription.

The pre-requisite is that you have already installed Azure AD Preview PowerShell by following these steps https://docs.microsoft.com/en-us/powershell/azure/active-directory/install-adv2?view=azureadps-2.0

Connect-AzureAD
$resource = Get-AzureADMSPrivilegedResource -Provider azureResources -Filter "ExternalId eq '/subscriptions/38ab2ccc-3747-4567-b36b-9478f5602f0d'"
$roleDefinition = Get-AzureADMSPrivilegedRoleDefinition -ProviderId azureResources -ResourceId $resource.Id -Filter "DisplayName eq 'Owner'"
$subject = Get-AzureADUser -Filter "userPrincipalName eq 'upn'"

$schedule = New-Object Microsoft.Open.MSGraph.Model.AzureADMSPrivilegedSchedule
$schedule.Type = "Once"
$schedule.StartDateTime = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
$schedule.EndDateTime = (Get-Date).AddDays(30).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")

Open-AzureADMSPrivilegedRoleAssignmentRequest -ProviderId azureResources -Schedule $schedule -ResourceId $resource.Id -RoleDefinitionId $roleDefinition.Id -SubjectId $subject.ObjectId -AssignmentState "Eligible" -Type "AdminAdd" -Reason "Test"


Friday, February 14, 2020

Connect to Azure AD PowerShell with MFA

Sometimes you might want to connect to Azure AD PowerShell with MFA but there is no way for the PowerShell to prompt you for MFA unless you have MFA enforced on the account.

The scenario which I had was calling a cmdlet for Privileged Identity Management where I was activating a role which requires MFA https://docs.microsoft.com/en-us/powershell/module/azuread/?view=azureadps-2.0-preview#privileged_role_management

The solution is to get an access token with MFA and pass the token while connecting to PowerShell.

The pre-requisite is that you have already installed Azure AD Preview PowerShell by following these steps https://docs.microsoft.com/en-us/powershell/azure/active-directory/install-adv2?view=azureadps-2.0

# 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 }

# Get token for MS Graph by prompting for MFA
$MsResponse = Get-MSALToken -Scopes @("https://graph.microsoft.com/.default") -ClientId "1b730954-1685-4b74-9bfd-dac224a7b894" -RedirectUri "urn:ietf:wg:oauth:2.0:oob" -Authority "https://login.microsoftonline.com/common" -Interactive -ExtraQueryParameters @{claims='{"access_token" : {"amr": { "values": ["mfa"] }}}'}

# Get token for AAD Graph
$AadResponse = Get-MSALToken -Scopes @("https://graph.windows.net/.default") -ClientId "1b730954-1685-4b74-9bfd-dac224a7b894" -RedirectUri "urn:ietf:wg:oauth:2.0:oob" -Authority "https://login.microsoftonline.com/common"

Connect-AzureAD -AadAccessToken $AadResponse.AccessToken -MsAccessToken $MsResponse.AccessToken -AccountId: "upn" -tenantId: "tenantId"

# Call cmdlet which requires MFA
$resource = Get-AzureADMSPrivilegedResource -ProviderId AadRoles

$roleDefinition = Get-AzureADMSPrivilegedRoleDefinition  -ProviderId AadRoles -ResourceId $resource.Id -Filter "DisplayName eq 'Global Administrator'"

$subject = Get-AzureADUser -Filter "userPrincipalName eq 'upn'"

$schedule = New-Object Microsoft.Open.MSGraph.Model.AzureADMSPrivilegedSchedule
$schedule.Type = "Once"
$schedule.Duration="PT1H"
$schedule.StartDateTime = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")

Open-AzureADMSPrivilegedRoleAssignmentRequest -ProviderId AadRoles -Schedule $schedule -ResourceId $resource.Id -RoleDefinitionId $roleDefinition.Id -SubjectId $subject.ObjectId -AssignmentState "Active" -Type "UserAdd" -Reason "Test"



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 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.


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 ""

Thursday, June 20, 2019

SQL interceptors

SQL interceptors are a way to apply filtering by tenant for securing multi-tenant applications. Here is a good read on it http://xabikos.com/2014/11/18/Create-a-multitenant-application-with-Entity-Framework-Code-First-Part-2/

However, you need to be careful with it since they modify your query at runtime. 
Specifically, DbExpressionBuilder.Bind(databaseExpression) in the interceptor causes a random variable to be created which creates a random query text for the same query on every reinitialize which is generally a recycle on the VM where the application is running.

This puts unnecessary unnecessary pressure on QDS (Query Data Store).
Also, if you force a query plan for a specific query hash, a new query hash will be generated the next time so the forced plan will not work.

To fix this, make sure to bind it with a specific variable name like DbExpressionBuilder.BindAs(databaseExpression, "Filter")