From aff7d88cfda236087b4e5be537ef607abb13dc2b Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Sat, 7 Feb 2026 12:53:57 +0100 Subject: [PATCH] Add delegated permissions step --- scripts/New-PublicClientApplication.ps1 | 147 ++++++++---------------- scripts/create-pcs.sh | 10 ++ 2 files changed, 61 insertions(+), 96 deletions(-) diff --git a/scripts/New-PublicClientApplication.ps1 b/scripts/New-PublicClientApplication.ps1 index 1620c05..8b7660f 100644 --- a/scripts/New-PublicClientApplication.ps1 +++ b/scripts/New-PublicClientApplication.ps1 @@ -4,7 +4,6 @@ param( [Alias("n")] [string]$AppName, - [switch]$UsePowershellModules, [Alias("h")] [switch]$Help ) @@ -16,7 +15,6 @@ function Show-Usage { Write-Host "Usage: ./New-PublicClientApplication.ps1 -AppName " Write-Host "Options:" Write-Host " -AppName, -n Application display name (required)" - Write-Host " -UsePowershellModules Use Az.Accounts/Az.Resources cmdlets instead of Azure CLI" Write-Host " -Help, -h Show this help message and exit" } @@ -85,109 +83,66 @@ $azureServiceMgmtScopeId = "41094075-9dad-400e-a0bd-54e686782033" $azureDevOpsAppId = "499b84ac-1321-427f-aa17-267ca6975798" $azureDevOpsScopeId = "ee69721e-6c3a-468f-a9ec-302d16a4c599" -if ($UsePowershellModules) { - if (-not (Get-Command Get-AzADApplication -ErrorAction SilentlyContinue)) { - throw "Get-AzADApplication cmdlet not found. Install Az.Resources." - } - if (-not (Get-Command New-AzADApplication -ErrorAction SilentlyContinue)) { - throw "New-AzADApplication cmdlet not found. Install Az.Resources." - } - if (-not (Get-Command Update-AzADApplication -ErrorAction SilentlyContinue)) { - throw "Update-AzADApplication cmdlet not found. Install Az.Resources." - } +if (-not (Get-Command az -ErrorAction SilentlyContinue)) { + throw "Azure CLI 'az' is required." +} - $azContext = Get-AzContext - if ($null -eq $azContext) { - throw "No Azure context found. Run Connect-AzAccount first." - } +# Find the app by name +$existingAppId = az ad app list --display-name $AppName --query "[0].appId" -o tsv +if ($LASTEXITCODE -ne 0) { + throw "Failed to query existing applications." +} +if (-not [string]::IsNullOrWhiteSpace($existingAppId)) { + Write-Error "Application '$AppName' already exists." + exit 1 +} - $existingApp = Get-AzADApplication -DisplayName $AppName -First 1 - if ($null -ne $existingApp) { - Write-Error "Application '$AppName' already exists." - exit 1 - } +# Create the app +$appId = az ad app create --display-name $AppName --query "appId" -o tsv +if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($appId)) { + throw "Failed to create application '$AppName'." +} - $requiredResourceAccess = Get-RequiredResourceAccess ` - -M365GraphAppId $m365GraphAppId ` - -M365GraphScopeId $m365GraphScopeId ` - -AzureDevOpsAppId $azureDevOpsAppId ` - -AzureDevOpsScopeId $azureDevOpsScopeId ` - -AzureServiceMgmtAppId $azureServiceMgmtAppId ` - -AzureServiceMgmtScopeId $azureServiceMgmtScopeId +$requiredResourceAccess = Get-RequiredResourceAccess ` + -M365GraphAppId $m365GraphAppId ` + -M365GraphScopeId $m365GraphScopeId ` + -AzureDevOpsAppId $azureDevOpsAppId ` + -AzureDevOpsScopeId $azureDevOpsScopeId ` + -AzureServiceMgmtAppId $azureServiceMgmtAppId ` + -AzureServiceMgmtScopeId $azureServiceMgmtScopeId | ConvertTo-Json -Depth 10 -Compress - $webConfig = @{ - implicitGrantSettings = @{ - enableAccessTokenIssuance = $true - enableIdTokenIssuance = $true - } - } +$publicClientRedirectUris = @( + "http://localhost", + "msal${appId}://auth" +) | ConvertTo-Json -Compress - # Create first to obtain appId needed for msal://auth redirect URI. - $newApp = New-AzADApplication ` - -DisplayName $AppName ` - -SignInAudience "AzureADMyOrg" ` - -IsFallbackPublicClient ` - -PublicClientRedirectUri @("http://localhost") ` - -RequiredResourceAccess $requiredResourceAccess ` - -Web $webConfig +# Configure app to match "Azure Node Playground Public". +az ad app update ` + --id $appId ` + --set ` + "signInAudience=AzureADMyOrg" ` + "isFallbackPublicClient=true" ` + "requiredResourceAccess=$requiredResourceAccess" ` + "publicClient.redirectUris=$publicClientRedirectUris" ` + "web.implicitGrantSettings.enableAccessTokenIssuance=true" ` + "web.implicitGrantSettings.enableIdTokenIssuance=true" | Out-Null - if ($null -eq $newApp -or [string]::IsNullOrWhiteSpace($newApp.AppId)) { - throw "Failed to create application '$AppName' via Az.Resources." - } +if ($LASTEXITCODE -ne 0) { + throw "Failed to configure application '$AppName'." +} - $appId = $newApp.AppId +# Azure CLI is used to grant admin consent. - Update-AzADApplication ` - -ApplicationId $appId ` - -SignInAudience "AzureADMyOrg" ` - -IsFallbackPublicClient ` - -RequiredResourceAccess $requiredResourceAccess ` - -PublicClientRedirectUri @("http://localhost", "msal${appId}://auth") ` - -Web $webConfig | Out-Null -} else { - # Find the app by name - $existingAppId = az ad app list --display-name $AppName --query "[0].appId" -o tsv - if ($LASTEXITCODE -ne 0) { - throw "Failed to query existing applications." - } - if (-not [string]::IsNullOrWhiteSpace($existingAppId)) { - Write-Error "Application '$AppName' already exists." - exit 1 - } +# Ensure service principal exists before granting tenant-wide admin consent. +az ad sp create --id $appId | Out-Null +if ($LASTEXITCODE -ne 0) { + throw "Failed to ensure service principal exists for '$AppName' ($appId)." +} - # Create the app - $appId = az ad app create --display-name $AppName --query "appId" -o tsv - if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($appId)) { - throw "Failed to create application '$AppName'." - } - - $requiredResourceAccess = Get-RequiredResourceAccess ` - -M365GraphAppId $m365GraphAppId ` - -M365GraphScopeId $m365GraphScopeId ` - -AzureDevOpsAppId $azureDevOpsAppId ` - -AzureDevOpsScopeId $azureDevOpsScopeId ` - -AzureServiceMgmtAppId $azureServiceMgmtAppId ` - -AzureServiceMgmtScopeId $azureServiceMgmtScopeId | ConvertTo-Json -Depth 10 -Compress - - $publicClientRedirectUris = @( - "http://localhost", - "msal${appId}://auth" - ) | ConvertTo-Json -Compress - - # Configure app to match "Azure Node Playground Public". - az ad app update ` - --id $appId ` - --set ` - "signInAudience=AzureADMyOrg" ` - "isFallbackPublicClient=true" ` - "requiredResourceAccess=$requiredResourceAccess" ` - "publicClient.redirectUris=$publicClientRedirectUris" ` - "web.implicitGrantSettings.enableAccessTokenIssuance=true" ` - "web.implicitGrantSettings.enableIdTokenIssuance=true" | Out-Null - - if ($LASTEXITCODE -ne 0) { - throw "Failed to configure application '$AppName'." - } +# Grant admin consent for configured delegated permissions. +az ad app permission admin-consent --id $appId | Out-Null +if ($LASTEXITCODE -ne 0) { + throw "Failed to grant admin consent for '$AppName' ($appId)." } Write-Host "Created application '$AppName'" diff --git a/scripts/create-pcs.sh b/scripts/create-pcs.sh index 8b2a10e..1e83b05 100755 --- a/scripts/create-pcs.sh +++ b/scripts/create-pcs.sh @@ -114,6 +114,16 @@ EOF web.implicitGrantSettings.enableIdTokenIssuance=true \ 1>/dev/null + # Ensure service principal exists before granting tenant-wide admin consent. + az ad sp create --id "$APP_ID" 1>/dev/null 2>/dev/null || true + + # Grant admin consent for configured delegated permissions. + az ad app permission admin-consent --id "$APP_ID" 1>/dev/null + if [[ $? -ne 0 ]]; then + echo "Error: Failed to grant admin consent for '$APP_NAME' ($APP_ID)." + exit 1 + fi + echo "Created application '$APP_NAME'" echo "appId: $APP_ID" }