Easy Auth | Standard Logic App with Azure API Management

Posted by Andrew Wilson on Tuesday, November 26, 2024

GitHub Repository

Overview

The recent work that I have been doing with Standard Logic Apps and linking them as backends to Azure API Management has relied on the use of the Logic App Workflow SAS key for security. This is a valid authentication approach, but there are risks that you need to be aware of as well as best practices that you need to be abiding by. Such as:

  • Some Potential Risks:

    • If a SAS is leaked, it can be used by anyone who obtains it to call your Logic App Workflow.
    • If a SAS expires and the API Management API has not been updated to make use of the updated SAS, then the integration functionality will be hindered.
  • Some SAS Best Practices to mitigate risks:

    • Always use HTTPS - If a SAS is passed over HTTP and intercepted, an attacker can perform a man-in-the-middle attack and read the SAS.
    • Have a revocation plan - Make sure that you are prepared to respond if a SAS is compromised.
    • Have a rotation plan - Look to replace the SAS with new ones at regular intervals and include shorter time intervals for the expiration period.

There are other features that can be used to further restrict access to your Logic App, for instance adding IP Address Restrictions to limit traffic to only your API Management Instance.

But what if you require further governance whereby the Logic App requires a valid Entra ID bearer token in order to be invoked. To implement this we are going to make use of Easy Auth.

Easy Auth

Easy Auth is a built-in authentication and authorisation capability provided by Azure App Services and Azure Functions. Easy Auth makes use of federated identity whereby a third-party identity provider manages the user identities and authentication flow for you. Fortunately for us, Standard Logic Apps have the same foundations as Azure Functions and therefore Easy Auth is also extended to Logic Apps Standard.

Eay Auth

Easy Auth is a platform feature running on the same virtual machine as your application. Once enabled, any incoming HTTP requests will pass through this feature prior to being handled by your application. Easy Auth runs separately from your application code and can be configured using ARM settings or using a configuration file.

Using Easy Auth and Linking to API Management

As mentioned above, I would like to use Easy Auth to protect my HTTP Triggered Azure Logic App (Standard) workflows, but more importantly, I would like Azure API Management to be the only identity that can be used to make requests to my workflows as shown in the diagram below:

Eay Auth

Note:

As we will be invoking the Logic App HTTP triggered workflows with Easy Auth, we no longer need to specify the following parameters in the request:

  • sp: The permissions; generally ‘read’ or ‘write’
  • sv: The version number of the query parameters
  • sig: Shared-Access-Signature

Furthermore, I have opted for Infrastructure as Code (IaC) as my method of implementation, specifically Bicep.

For setup, we will need to conduct the following four steps:

  1. Configure the Logic App to Use Microsoft Entra sign-in.

    Working through the Microsoft instructions in the link above, you will require as minimum the following when setting up the Logic App Application Registration:

    • Select the supported account type. (I’m using Current tenant - single tenant)

    • Setup a Redirect URI for your Logic App:

      Select Web for platform and set the URI to <app-url>/.auth/login/aad/callback. For example, https://contoso.azurewebsites.net/.auth/login/aad/callback.

    • Make not of the Application (client) ID.

    • Create a Client Secret and store this securely (I’m using Azure DevOps Secure Library Variables as part of a deployment).

      This is a secret value that the application uses to prove its identity when requesting a token. This value is saved in your app’s configuration as a slot-sticky application setting named MICROSOFT_PROVIDER_AUTHENTICATION_SECRET. If the client secret isn’t set, sign-in operations from the service use the OAuth 2.0 implicit grant flow, which isn’t recommended.

    • Expose an API > Add > Save. This value uniquely identifies the application when it’s used as a resource, allowing tokens to be requested that grant access. It’s used as a prefix for scopes you create.

      I am using a single-tenant app and therefore using the default value. Appears as such api://<application-client-id>

    • Add the user_impersonation scope to your App Registration allowing Admins and users to consent.

  2. Make sure that API Management has been setup with Managed Identity. I am using System Assigned.

    @description('Deployment of the APIM instance')
    resource apimInstanceDeploy 'Microsoft.ApiManagement/service@2022-08-01' = {
      ...
      identity: {
        type: 'SystemAssigned'
      }
      ...
    }
    
  3. Store the App Registration Secret in KeyVault and Reference in your Logic App Settings to allow the Logic App to prove its identity.

    ...
    
    @secure()
    @description('The client secret for the Easy Auth App Registration')
    param applicationEasyAuthClientSecret string
    
    ...
    
    @description('Role Definition Id for the Key Vault Secrets User role')
    var keyVaultSecretsUserRoleDefId = '4633458b-17de-408a-b874-0445c86b69e6'
    
    ...
    
    @description('Deploy the Application Easy Auth App Registration Secret to Keyvault')
    resource vaultLogicAppRegSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
      name: '${applicationLogicAppName}-EasyAuth-Secret'
      parent: applicationKeyVaultDeploy
      properties: {
        contentType: 'string'
        value: applicationEasyAuthClientSecret
      }
    }
    
    ...
    
    @description('Deploy the Application Standard Logic App')
    resource applicationLogicAppStandardDeploy 'Microsoft.Web/sites@2024-04-01' = {
      name: applicationLogicAppName
      location: location
      identity: {
        type: 'SystemAssigned'
      }
      ...
      resource config 'config@2024-04-01' = {
        name: 'appsettings'
        properties: {
          ...
          MICROSOFT_PROVIDER_AUTHENTICATION_SECRET: '@Microsoft.KeyVault(VaultName=${applicationKeyVaultDeploy.name};SecretName=${vaultLogicAppRegSecret.name})'
          ...
        }
      }
    }
    
    ...
    
    @description('Create the RBAC for the Logic App to Read the Secret from Key Vault')
    resource applicationLogicAppRBACWithKV 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
      name: guid(applicationKeyVaultDeploy.id, applicationLogicAppStandardDeploy.id, keyVaultSecretsUserRoleDefId)
      scope: vaultLogicAppRegSecret
      properties: {
        principalId: applicationLogicAppStandardDeploy.identity.principalId
        roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', keyVaultSecretsUserRoleDefId)
        principalType: 'ServicePrincipal'
      }
    }
    
  4. Enable Easy Auth on the Standard Logic App using ARM/Bicep Template AuthSettingsV2 or ARM REST API. I am using a Bicep Template.

    @description('Setup the Easy Auth config settings for the Standard Logic App')
    resource applicationAuthSettings 'Microsoft.Web/sites/config@2023-01-01' = {
      name: 'authsettingsV2'
      parent: applicationLogicAppStandardDeploy // Existing Standard Logic App for Easy Auth to be enabled
      properties: {
        globalValidation: {
          requireAuthentication: true
          unauthenticatedClientAction: 'AllowAnonymous' // Do not change: See note below.
        }
        httpSettings: {
          requireHttps: true
          routes: {
            apiPrefix: '/.auth'
          }
          forwardProxy: {
            convention: 'NoProxy'
          }
        }
        identityProviders: {
          azureActiveDirectory: {
            enabled: true
            registration: {
              openIdIssuer: uri('https://sts.windows.net/', tenant().tenantId)
              clientId: logicAppEasyAuthClientId
              clientSecretSettingName: 'MICROSOFT_PROVIDER_AUTHENTICATION_SECRET'
            }
            validation: {
              allowedAudiences: environment().authentication.audiences // Azure Management Plane [management.core.windows.net and management.azure.com]
              defaultAuthorizationPolicy: {
                allowedPrincipals: {
                  identities: [
                    apimInstance.identity.principalId // APIM System Assigned Principal Id
                  ]
                }
              }
            }
          }
        }
        platform: {
          enabled: true
          runtimeVersion: '~1'
        }
      }
    }
    

    Note:

    1EasyAuth is managed by AppService, and for an incoming request, it is a hop that comes before LA Runtime. When EasyAuth is enabled for a Logicapp standard, all incoming requests are validated against the policies in your V2 Auth settings.

    If you have “unauthenticatedClientAction”: “Return401” and when the request fails with EasyAuth, those requests are not routed to LA runtime and will fail with 401 from AppService. Therefore, you will also observe broken portal experience with Return401. When you set it to “AllowAnonymous”, all calls (failed and successful) will be routed to the LA runtime. The LA runtime will know if the request failed with EasyAuth or was successful and will process the request accordingly. For example, to get run histories, we authenticate it on SAS specific to that run generated based on the Logic Apps access keys. LA runtime will know that this request failed with EasyAuth but it will be processed successfully as it has valid SAS. The underlying AppService platform will have no knowledge of validating other auth like SAS.

  5. Configure API Management to obtain a valid Bearer token and add it to the request Authorization Header. Implemented through APIM Policy.

    <!-- API ALL OPERATIONS SCOPE -->
    <policies>
        <inbound>
            <base />
            <!-- Uses System Assigned Managed Identity of the APIM Instance -->
              <authentication-managed-identity resource="https://management.azure.com/" output-token-variable-name="msi-access-token" ignore-error="false" />
              <set-header name="Authorization" exists-action="override">
     	         <value>@("Bearer " + (string)context.Variables["msi-access-token"])</value>
              </set-header>
        </inbound>
        <backend>
            <base />
        </backend>
        <outbound>
            <base />
        </outbound>
        <on-error>
            <base />
        </on-error>
    </policies>
    
  6. Configure API Management to Append the Logic App Workflow api-version query parameter to the request. Implemented through APIM Policy.

    <!-- API OPERATION SCOPE -->
    <policies>
        <inbound>
            <base />
            <rewrite-uri template="__uri__" />
            <set-query-parameter name="api-version" exists-action="append">
                <value>__api-version__</value>
            </set-query-parameter>
        </inbound>
        <backend>
            <base />
        </backend>
        <outbound>
            <base />
        </outbound>
        <on-error>
            <base />
        </on-error>
    </policies>
    

Summary

In short, we have defined our three A’s (Access, Authentication, Authorisation) providing further governance on our Standard Logic App and API Management through the use of Easy Auth. To see my worked example, have a look at my GitHub repository along with a README that explains how to get started.

Hope this helps and have fun.