Setup Easy Auth for Logic Apps (Standard) with PowerShell

Recently I have been looking into setting up Azure AD authentication for Logic Apps (Standard). Doing some research I found that this was possible using Easy Auth and I found the following articles that explain this process: Enable Azure Active Directory Open Authentication (Azure AD OAuth) and Trigger workflows in Standard logic apps with Easy Auth and also Using EasyAuth to protect HTTP Triggers in Azure Logic Apps (Standard) this also contained a YouTube video. As this seemed a bit of work, I thought about automating this with PowerShell and the Azure CLI. I did create the Logic App (Standard) using the Azure portal, although you can also think about creating this using the CLI and Bicep for example. For now lets start with logging into Azure and setting and getting some metadata:

# Your data
$tenantName = "[tenantname].onmicrosoft.com"
$subscriptionId = "[guid of subscription]" # where the logic app is added
$resourceGroup = "[name of resource group]" # where the logic app is added
$logicAppName = "[logic app name]"
$locationLogicApp = "North Europe" # change with your region
$easyAuthAppName = "Easy Auth for Logic Apps" # change if needed

# Login to tenant and set subscription
$tenant = az login --tenant $tenantName | ConvertFrom-Json
az account set --subscription $subscriptionId
$tenantId = $tenant[0].tenantId

Then we need to create the app registration and principal we want to use to call the Logic App.

# Create App registration [Default User.Read]
$resourceAccess = "[{ \""resourceAccess\"": [{\""id\"": \""311a71cc-e848-46a1-bdf8-97ff7156d8e6\"", \""type\"": \""Scope\"" }], \""resourceAppId\"": \""00000002-0000-0000-c000-000000000000\"" }]"

$app = az ad app create --display-name $easyAuthAppName --enable-id-token-issuance true --sign-in-audience AzureADMyOrg --required-resource-accesses $resourceAccess | ConvertFrom-Json
$appObjectId = $app.id
$appPrincipal = az ad sp create --id $appObjectId

Having created the app registration we want to add an app secret:

# Wait
Start-Sleep 20

# Add and get secret to app registration
$secretResponse = az rest --method post --uri "https://graph.microsoft.com/v1.0/applications/$appObjectId/addPassword" | ConvertFrom-Json
Write-Host "Your app secret is (store securely): $($secretResponse.secretText)"

To retrieve the oid of the app registration, we can also use PowerShell. For this I modified a function I once create to parse jwt tokens, I added another function for retrieving the auth token. Please note that we will have to wait a bit after creating the app secret. As I would like to be able to use my own account, I will also retrieve my object id:

function GetAuthToken($tenantid, $scopeurl, $clientId, $secret) {
    $authurl = "https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token"
    $header_token = @{"Content-Type" = "application/x-www-form-urlencoded"}
    $creds = "grant_type=client_credentials&client_id=$clientId&client_secret=$secret&scope=$scopeurl/.default"
    $response = Invoke-RestMethod "$authurl" -Method Post -Body $creds -Headers $header_token
    return $response.access_token
}

function GetTokenInfo($token) {
    $jwtParts = $token.Split(".")
    $part = $jwtParts[1]
    $padding = $part.Length % 4
    if ($padding -gt 2) { $padding = 1 }
    if ($padding -gt 0) { 1..$padding | ForEach-Object { $part += "=" } }
    $converted = [System.Convert]::FromBase64String($part)
    $output = [System.Text.Encoding]::UTF8.GetString($converted)
    return $output | ConvertFrom-Json
}

Start-Sleep 60

# Get token info for app registration
$token = GetAuthToken $tenantId "https://management.azure.com" $app.appId $secretResponse.secretText
$tokenInfo = GetTokenInfo $token

# Get current user id
$user = az ad signed-in-user show | ConvertFrom-Json
$userId = $user.id

Now we have the token information and user id, we want to prepare the request we will send to the Auth Settings V2 rest API, for this I created a json file we will load and modify called AuthSettingsV2.json. For this I created a function:

$easyAuthPayload = Get-Content .\AuthSettingsV2.json -Raw | ConvertFrom-Json

function PrepareEasyAuthPayload($subscriptionId, $resourceGroup, $logicAppName, $tokenInfo, $audiences, $allowedIds, $location) {
    $easyAuthPayload.id = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/Microsoft.Web/sites/$logicAppName/config/authsettingsV2"
    $easyAuthPayload.location = $location
    $easyAuthPayload.properties.identityProviders.azureActiveDirectory.registration.openIdIssuer = $tokenInfo.iss
    $easyAuthPayload.properties.identityProviders.azureActiveDirectory.registration.clientId = $tokenInfo.oid
    $easyAuthPayload.properties.identityProviders.azureActiveDirectory.validation.allowedAudiences = $audiences
    $easyAuthPayload.properties.identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedPrincipals.identities = $allowedIds
}

# Prepare Easy Auth payload
$audiences = @("https://management.azure.com", "https://management.core.windows.net/")
$allowedIds = @($tokenInfo.oid, $userId)
PrepareEasyAuthPayload $subscriptionId $resourceGroup $logicAppName $tokenInfo $audiences $allowedIds $locationLogicApp

Now we will call the Auth Settings V2 rest API to configure Easy Auth:

# Set Easy Auth on Logic App
$token = $(az account get-access-token --resource=https://management.azure.com --query accessToken --output tsv) 
$requestUrl = "https://management.azure.com$($easyAuthPayload.id)?api-version=2021-02-01"

$request_header = @{"Accept" = "application/json" ; "Content-Type" = "application/json" ; "authorization" = "bearer $token"}
$jsonBody = $easyAuthPayload | ConvertTo-Json -Depth 8
$response = Invoke-RestMethod $requestUrl -Method Put -Headers $request_header -Body $jsonBody

That is it, easy isn't it? Well I did need to dive into it a bit, so I hope you will appreciate the effort.

Just one thing to do, check if it works. Just create a simple logic app with an http request and response that we can call, lets call it test:

Screenshot of test logic app

Then we need to call it using the app credentials or our own credentials (just (un)comment the $token lines):

$laurl = "https://[yourlogicappname].azurewebsites.net:443/api/test/triggers/manual/invoke?api-version=2022-05-01" # change to your logic app url
#$token = GetAuthToken $tenantId "https://management.azure.com" $app.appId $secretResponse.secretText
$token = $(az account get-access-token --resource=https://management.azure.com --query accessToken --output tsv) 
$request_header = @{"authorization" = "bearer $token"}                                                                      
Invoke-RestMethod $laurl -Method Post -Headers $request_header

Everything should work, and our response should be:

It worked!

Don't take things for granted and also do a test without adding the authorization header (we need to prevent a confirmation bias) and test it using the http post url with the access token. The first one should fail, the second one will succeed.