Full IGA using Azure AD – Getting app role assignments using PowerShell

In this post I will quickly demo how to use PowerShell to get app role assignments for all application using the Microsoft Graph.

You should have followed my previous post in order to have created an application, added some appRoles to the manifest and granted access to the Graph.

Let’s recap the important parts:

1 – Create an enterprise application in Azure AD

Screenshot_2

2 – Go to “app registrations”, find the app and add appRoles to the manifest

The following are provided by default, disable those and add some custom ones. See the below example roles.

Screenshot_7

{
    "allowedMemberTypes": [
        "User"
    ],
    "description": "Superuser",
    "displayName": "Superuser",
    "id": "1c55497a-32ec-4f17-a33e-a7157deb8d72",
    "isEnabled": true,
    "lang": null,
    "origin": "Application",
    "value": "Superuser"
},
{
    "allowedMemberTypes": [
        "User"
    ],
    "description": "Administrator",
    "displayName": "Administrator",
    "id": "62f71d53-5152-48fb-9e0e-be121f4fd736",
    "isEnabled": true,
    "lang": null,
    "origin": "Application",
    "value": "Administrator"
},
{
    "allowedMemberTypes": [
        "User"
    ],
    "description": "Frontline",
    "displayName": "Frontline",
    "id": "e580e3b7-1e4e-4c15-b820-256d36f60e68",
    "isEnabled": true,
    "lang": null,
    "origin": "Application",
    "value": "Frontline"
},
{
    "allowedMemberTypes": [
        "User"
    ],
    "description": "Manager",
    "displayName": "Manager",
    "id": "25864c8d-637b-4ce1-8283-e3c3678669ce",
    "isEnabled": true,
    "lang": null,
    "origin": "Application",
    "value": "Manager"
}

3 – Go back to enterprise applications and assign users some roles

Screenshot_2.png

4 – Grant access to the required Graph resources

Go to app registrations, find the application and go to “API permissions”. Click “Add a permission”:

Screenshot_3.png

Select the Microsoft Graph:

Screenshot_4.png

Choose Application permissions, as we are doing things without a user context:

Screenshot_5.png

Add “User.Read.All” and “Group.Read.All” and click save.

Screenshot_6
Screenshot_7

You will see that the added permissions now have “Not granted for <Organization>” as status. Click “Grant admin consent for <Organization>” in order to enable these new permissions:

Screenshot_8

This is what it should look like:

Screenshot_9.png

5 – Request data with PowerShell

First, generate a secret under app registrations in Azure AD. This is the $secret variable in the below PowerShells.

Screenshot_10.png

Go the enterprise application and copy the “Application ID” – this is the $clientid variable in the below PowerShells.

Copy “Object ID” – this is the $servicePrincipalId variable in the below PowerShells.

Screenshot_11.png

The $tenant variable can be either any custom domain registered in the tenant, the default domain or the tenant id (you can find it here) found on the app registrations page on your application.

The first example PowerShell will return a simple grid view with a multi valued column containing the roles of each assigned object. The script will not dig into the group members.

$clientid = "dd54e8b7-8c69-435a-a460-e7041f98ee30"
$secret = "HCtl:g:DFAc65BNpW0IUS6smY/tc@ARo"
$tenant = "e88da32e-db23-4b1e-af95-f808ac863371"
$servicePrincipalId = "60abe170-b93b-4c1b-b252-79d6667e468a"

Write-Verbose "Requesting token using client credential grant" -Verbose
$token = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token" -Method Post -Body "client_id=$clientid&scope=https://graph.microsoft.com/.default&client_secret=$secret&grant_type=client_credentials"

if($token.access_token) { 
    Write-Verbose "Received access token: $($token.access_token)" -Verbose 
} else {
    Read-Host "Could not find access token, check your settings"
}

$params = @{Headers = @{Authorization = "bearer $($token.access_token)"}}

# Get app manifest and build app role map
Write-Verbose "Fetching app manifest for service principal $servicePrincipalId" -Verbose
$servicePrincipalGraphResponse = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/servicePrincipals/$servicePrincipalId" @params
$appRolesMap = $servicePrincipalGraphResponse.appRoles | Foreach -Begin {$h = @{}} -End {$h} -Process {$h[$_.id] = $_}

# Get all app role assignments
$uri = "https://graph.microsoft.com/beta/servicePrincipals/$servicePrincipalId/appRoleAssignments"
$appRoleAssignments = @()

do {
    Write-Verbose "Getting app role assignments: $uri" -Verbose
    $response = Invoke-RestMethod -Uri $uri @params
    $uri = $response."nextLink"
    $appRoleAssignments += $response.value
} while($uri -ne $null)

$appRoleAssignments | 
    group principalId | 
    foreach {
        [PSCustomObject] @{
            DisplayName = $_.group.principalDisplayName | select -First 1
            Type = $_.group.principalType | select -First 1
            RoleIDs = $_.group.appRoleId RoleDisplayNames = $_.group.appRoleId | foreach{$appRolesMap[$_].DisplayName}
            RoleValues = $_.group.appRoleId | foreach{$appRolesMap[$_].Value}
        }
    } | Out-GridView

And here is an example for you that also digs into the different assigned groups and fetches all transitive members of those:

$clientid = "dd54e8b7-8c69-435a-a460-e7041f98ee30"
$secret = "HCtl:g:DFAc65BNpW0IUS6smY/tc@ARo"
$tenant = "e88da32e-db23-4b1e-af95-f808ac863371"
$servicePrincipalId = "60abe170-b93b-4c1b-b252-79d6667e468a"

Write-Verbose "Requesting token using client credential grant" -Verbose
$token = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token" -Method Post -Body "client_id=$clientid&scope=https://graph.microsoft.com/.default&client_secret=$secret&grant_type=client_credentials"
if($token.access_token) {
    Write-Verbose "Received access token: $($token.access_token)" -Verbose
} else {
        Read-Host "Could not find access token, check your settings"
}

$params = @{Headers = @{Authorization = "bearer $($token.access_token)"}}

# Get all users
$users = @()
$uri = "https://graph.microsoft.com/beta/users"

do {
    Write-Verbose "Getting all users: $uri" -Verbose
    $response = Invoke-RestMethod -Uri $uri @params
    $uri = $response."nextLink"
    $users += $response.value
} while($uri -ne $null)

$usersMap = $users | group -AsHashTable -Property id -AsString

# Get app manifest and build app role map
Write-Verbose "Fetching app manifest for service principal $servicePrincipalId" -Verbose

$servicePrincipalGraphResponse = Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/servicePrincipals/$servicePrincipalId" @params
$appRolesMap = $servicePrincipalGraphResponse.appRoles | Foreach -Begin {$h = @{}} -End {$h} -Process {$h[$_.id] = $_}

# Get all app role assignments
$uri = "https://graph.microsoft.com/beta/servicePrincipals/$servicePrincipalId/appRoleAssignments"
$appRoleAssignments = @()

do {
    Write-Verbose "Getting app role assignments: $uri" -Verbose
    $response = Invoke-RestMethod -Uri $uri @params
    $uri = $response."nextLink"
    $appRoleAssignments += $response.value
} while($uri -ne $null) 

# Process group memberships to get a full list of assigned users
$appRoleAssignmentsTransitive = $appRoleAssignments | 
    foreach {
        if($_.principalType -eq "Group") {
            $assignment = $_
            
            # Get all users
            $uri = "https://graph.microsoft.com/beta/groups/$($_.principalId)/transitiveMembers"
            
            do {
                Write-Verbose "Getting members of group $($_.principalId): $uri" -Verbose
                $response = Invoke-RestMethod -Uri $uri @params
                $uri = $response."nextLink"
                $response.value |
                    where '@odata.type' -eq '#microsoft.graph.user' |
                    foreach {
                        [PSCustomObject] @{
                            appRoleId = $assignment.appRoleId
                            principalId = $_.id
                            principalType = "User"
                            resourceId = $assignment.resourceId
                            resourceDisplayName = $assignment.resourceDisplayName
                            creationTimeStamp = $assignment.creationTimestamp
                            id = [Guid]::NewGuid()
                        }
                    }
            } while($uri -ne $null)
        } else {
            $_
        }
    }
    
$ExcelFile = "~\desktop\$([guid]::newguid()).xlsx"
Write-Verbose "Writing Excel file $ExcelFile" -Verbose

$appRoleAssignmentsTransitive | 
    group principalId | 
    foreach {
        [PSCustomObject] @{
            UserObjectID = $_.name
            DisplayName = $UsersMap[$_.name].DisplayName
            Mail = $UsersMap[$_.name].Mail
            RoleIDs = $_.group.appRoleId -join ","
            RoleDisplayNames = ($_.group.appRoleId | foreach{$appRolesMap[$_].DisplayName}) -join ","
            RoleValues = ($_.group.appRoleId | foreach{$appRolesMap[$_].Value}) -join ","
        }
    } | 
    Export-Excel -Path $ExcelFile -AutoFilter -AutoSize -FreezeTopRow ii $ExcelFile

That’s it, I have now gone through three ways of getting the application role assignments from Azure AD into your application:

  • SCIM provisioning
  • At sign in (OAuth ID Token or SAML Claim)
  • Fetching by Graph calls

The next posts will focus on how to actually manage application role assignments, dynamically assigning, using Entitlement Management to allow both internal and invited users to request access and other means. Stay tuned!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s