Neulich kam eine Anfrage vom PowerApps Team zu mir, mit der Bitte den Admin Consent für den Wrap einer ihrer Apps zu erteilen. Das Wrap Feature in Power Apps ermöglicht es eine native App aus einer Canvas-App zu erstellen, welche dann über Microsoft Intune, Microsoft App Center, Google Play Store und Apple Business Manager verteilt werden kann. Da ich die App Registrations und deren API Permissions mit Infrastructure as Code (IaC) erstelle und gleich anschliessend jeweils den Admin Consent erteile, war ich etwas verwundert über die Anfrage. Ein Screenshot der Anfrage für den Wrap Prozess war für mich ebenfalls wenig aufschlussreich, um was es sich genau bei diesem Admin Consent handelt. PowerApps Wrap

Leider schweigt sich Microsoft über diesen Admin Consent in ihrer Dokumentation zum Wrap Prozess aus. In keinem der folgenden Links konnte ich nachlesen, was es damit auf sich hat

Klar, die pragmatische Lösung wäre dem PowerApps Author die Rolle Global Administrator zu erteilen aber dies entspricht nicht dem Least Privilege Ansatz, welcher von Microsoft gerne im Kontext von Zero Trust propagiert wird. Und auch ich versuche, wann immer möglich, die Trennung der Verantwortlichkeiten (Power Apps vs. Microsoft Entra ID) einzuhalten, was leider nicht immer ganz möglich ist.

Also habe ich mir genauer angeschaut was es mit diesem Admin Consent auf sich hat um alternative Wege zu finden.

Analyse

Als erstes habe ich mir die Global Administrator Rolle via Privileged Identity Management (PIM) zugewiesen, diesen Admin Consent für den Wrap in der PowerApps Platform erteilt und anschliessen in den Audit Logs im Entra ID gesucht was sich geändert haben könnte. Allerdings sah ich dort nur das Aktivieren der Rolle via PIM für meinen Account und keine weiteren Aktionen. Entra ID Audit Logs

Das bedeutet also, dass in einem anderen Service als Entra ID etwas geändert haben muss. Als nächstes habe ich mir mit den Microsoft Edge Developer Tools angeschaut welches API dieser Admin Consent denn aufruft und siehe da es handelt sich um das API für PowerApps. Eine Dokumentation zu diesem API habe ich (Stand November 2023) vergeblich gesucht.

Edge Developer Tools API Alert

Da ich den Admin Consent nicht via Power Apps machen möchte (weil der Account mit der Global Administrator Rolle dazu auf der PowerApp berechtigt sein müsste) schaue ich was für ein API Aufruf notwendig ist und ob ich diesen mit Powershell abbilden kann. Der Pfad /api/invoke, welchen ich in den Developer Tools sehe, scheint ein zentraler Eintrittspunkt im API zu sein und der effektive Endpoint könnte im Header X-Ms-Path-Query stehen. Also teste ich einen GET Request (mit dem Token aus den Edge Developer Tools) an die URL https://api.powerapps.com/providers/Microsoft.PowerApps/scopes/admin/allowedThirdPartyApps?api-version=2021-02-01 welcher ein voller Erfolg war.

1
2
$at ="eyJ0eXA***"
Invoke-RestMethod -Uri "https://api.powerapps.com/providers/Microsoft.PowerApps/scopes/admin/allowedThirdPartyApps?api-version=2021-02-01" -Headers @{Authorization="Bearer $at"}

Ich erhalte die Application ID’s aller Power Apps zurück, für die dieser Admin Consent erteilt wurde.

dfc8b0cc-****-4491-ac63-************
26f55556-****-4c87-ab2c-************

Das Schema für das Hinzufügen einer neuen Power App zu diesem Admin Consent konnte ich aus den Developer Tools entnehmen. Es handelt sich um einen PUT request und die Application ID wird via URL mitgegeben. Einen Body braucht es nicht.

1
2
$at ="eyJ0eXA***"
Invoke-RestMethod -Method PUT -Uri "https://api.powerapps.com/providers/Microsoft.PowerApps/scopes/admin/allowedThirdPartyApps/f99cccae-27c8-4992-a35c-b74e32f41760?api-version=2021-02-01" -Headers @{Authorization="Bearer $at"}

Token und Admin Consent für PowerApps

Die nächste Herausforderung war, herauszufinden, wie ich ein Token für das PowerApps API erhalten könnte. Ich nutze dazu den OAuth2 device code flow weil er am einfachsten in einem Powershell Script abzubilden ist ohne zusätzliche Module installieren zu müssen und mit Delegated Permissions funktioniert. Als Client ID nutze ich die Well Known GUID für Microsoft Azure Powershell. Den Wert für die Resource https://service.powerapps.com/ habe ich aus dem Token, welches ich via Edge Developer Tools mitgeschnitten hatte, herauslesen können. Sie entspricht also nicht der API Endpoint URI wie es zum Beispiel bei Microsoft Graph der Fall ist. Mit diesen Informationen konnte ich ein Powershell Script zusammenstellen, welches einen OAuth2 device code flow durchführt, den Admin Consent für eine neue Power App erteilen kann und dabei die Aufteilung der Verantwortung zwischen PowerApps und Entra ID berücksichtigt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Param(
    [Parameter(Mandatory = $true)][string]$ApplicationID
)
$msAzurePowershellClientID = "1950a258-227b-4e31-a9cf-717495945fc2"
$body = @{
    "client_id" = $msAzurePowershellClientID
    "resource"  = "https://service.powerapps.com/"
}
$authResponse = Invoke-RestMethod `
    -UseBasicParsing `
    -Method Post `
    -Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
    -Body $body
Start-Process "https://microsoft.com/devicelogin"
$authResponse
$start = Get-Date
while (1) {
    if ((Get-Date) -ge $start.AddSeconds($authResponse.expires_in)) {
        Write-Error "Timeout occured"
        return
    }
    Start-Sleep -Seconds $authResponse.interval
    $body = @{
        "client_id"  = $msAzurePowershellClientID
        "grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
        "code"       = $authResponse.device_code
    }
    try {
        $Tokens = Invoke-RestMethod `
            -UseBasicParsing `
            -Method Post `
            -Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" `
            -Headers $Headers `
            -Body $body
        break
    }
    catch {
        Write-Host "Waiting for Device Logon with $($authResponse.interval)s interval"
    }
}

$at = $Tokens.access_token

Invoke-RestMethod -Headers @{Authorization = "Bearer $at" } -Method PUT -Uri "https://api.powerapps.com/providers/Microsoft.PowerApps/scopes/admin/allowedThirdPartyApps/${ApplicationID}?api-version=2021-02-01"
Invoke-RestMethod -Headers @{Authorization = "Bearer $at" } -Uri "https://api.powerapps.com/providers/Microsoft.PowerApps/scopes/admin/allowedThirdPartyApps?api-version=2021-02-01"