Using built-in Authorization in Azure Web Apps

If you publish an Azure web app, it becomes public. If you need to protect your web app to only allow domain-based users to access the application, you can use standard functionality in Azure and Entra. You can even configure the application, so it can only be accessed by either specific users, or users in a specific group.

The steps

Here are the overarching steps:

  1. Create a new Application Registration to represent the web app.
  2. Configure settings in the Application registration and the Enterprise application.
  3. Update Authorization settings in the Web application.

Create a new application registration and Enterprise Application

  1. Create a new app and give it a good name. A recommended pattern is app-FullNameOfTheApplication-ENV-serviceTypeAbbriviation, such as: app-CoolAIStuff-DEV-web.
  2. During creation, you can ignore the value for Redirect URI. for now.

Configure the Application Registration

Branding and properties

Update the Home page URL to point to your web app.

Under Authentication
  1. Click + Add a Platform and choose Web from the fly-out to the right.
  2. The Redirect URIs needs to be the full URL to the startpage of the app with this suffix: /.auth/login/aad/callback. If the url is https://coolaistuff-dev-web.azurewebsites.net the Redirect URI will be https://coolaistuff-dev-web.azurewebsites.net/.auth/login/aad/callback
  3. This following part is due to how you want to implement authentication within your app. This way you can protect your app without any code changes. Under Implicit grant and hybrid flows select ID Tokens (used for implicit and hybrid flows)
  4. Click Configure to proceed.
  5. Under Supported account types make sure to only select the single tenant option. unless you actually need to support other tenants. Lastly make sure that public client flows is not allowed.
Under Expose an API

The setting here is how your web application will communicate with Azure Entra. Make sure you configure everything correctly.

Application ID URI: Simply click the Add-link and then click save in the fly-out to the right. The URI should contain the client ID of the application registration.

Add a scope

  1. Scope name: user_impersonation
  2. Who can consent: Admins and users
  3. Admin consent display name: Access [name of your web app]
  4. Admin consent description: Allow access to [name of your web app]
  5. Repeat for the User settings.
  6. Click Add scope to save it.
Grant consent

To allow the application to use user impersonation, and make it possible for a user to login, you need to grant consent for the application. Under API Permissions, find and click on the “Grant admin consent for [domain name]“ option.

Under Owners

Assign one or more users to be responsible. This is only used for reference, if we want to know who owns an app.

Configure the Enterprise Application

This part is only needed if you want to add functionality to only allow specific users to access your web app. If not, everyone at the connected Entra domain has access. Finding the Enterprise application: Search for the application registration name on the Entra overview page.

Under Properties

Slide Assigment required to Yes. This will stop everyone in the connected Entra domain from gaining access. It will lock access to the users defined under the next step.

Under Users and Groups

Here you configure which users should have access to your web application.

  1. To add a user or a group, simply click + Add user/group at the top.
  2. Click on non selected under Users and groups.
  3. In the fly-out find the user or group you want to add.
  4. Click select and the Assign to add the user/group as being able to access the app.

When you assign a group to an application, only users directly in the group will have access. The assignment does not cascade to nested groups. As a bonus, in any Entra Group you can assing an owner and allow that owner to add or remove users in the group, effectively making them responsible for their own application. This is something that can be blocked by IT security policies though.

Update Authentication settings in the Web application

It is now time to add the protection you configured during application registration setup.

Authentication

  1. Click Add identity provider
  2. Select Microsoft as the identity provider
  3. App registration type, select the Pick an existing… option.
  4. In the dropdown, find your app registration you created earlier. You must use the name.
  5. Client secret expiration. Select Recommended 180 days.
  6. Client application requirement. Make sure Allow requests only from this application itself is selected.
  7. Identity requirement. Make sure Allow requests from any identity is selected.
  8. Tenant requirement. This depends on your needs. Usually you just have your own tenant.
  9. Pick save and start testing the application protection. The first sign is that you might need to login to the application.

If you update any access settings, such as adding a user to a group you have assigned to be able to access the application, it may take some time before the update is picked up and implemented by the app.

Password free connections to SQL databases

In the old days, we used SQL Server Logins to access information. You usually sent the name and password in clear text as part of a connection string. That was 15 years ago.

A much cleaner and secure way is to use Azure Managed Identity. If your service supports it, use it. The following database services supports Managed Identity:

  • Azure SQL Database
  • Azure Database for MySQL
  • Azure Database for PostgeSQL

Example configuration using an Azure Function

Configure your Azure Function

Update the function to use system assigned managed identity. Simply navigate to Identity under settings and enable it.

1
2
This will create an enterprise application in Entra with the given Object ID.

Setting it up using Bicep

Here is a Function App fully configured:

resource azureFunctionApp 'Microsoft.Web/sites@2020-12-01' = { 
  name: funcName 
  location: location 
  kind: 'functionapp,linux' 
  identity: { 
    type: 'SystemAssigned' 
  } 

  properties: { 
    serverFarmId: funcServicePlan.id 
    siteConfig: { 
      linuxFxVersion: nodeVersion 
    } 
  } 
  tags: resourceGroup().tags 
} 

Create a user in your SQL Server

Open a query window into you SQL Server. Add the following query text:

CREATE USER [The-Name-Of-Your-Function] FROM EXTERNAL PROVIDER; 
ALTER ROLE db_datareader ADD MEMBER [The-Name-Of-Your-Function]; 
ALTER ROLE db_datawriter ADD MEMBER [The-Name-Of-Your-Function]; 
ALTER ROLE db_ddladmin ADD MEMBER [The-Name-Of-Your-Function]; 
GO 

The access rights might vary for you scenario. If you get and error about duplicate identities or similar see below.

Verify user creation

You can verify that the user has been added by running this script.

select name as username, 
       create_date, 
       modify_date, 
       type_desc as type, 
       authentication_type_desc as authentication_type 
from sys.database_principals 
where type not in ('A', 'G', 'R') 
      and sid is not null 
      and name != 'guest' 
order by username; 

The new user should show up in the resulting list.

Use it in your code

The easiest way to find what you need is to follow the documentation from Microsoft.

Here is a TypeScript example:

const { DefaultAzureCredential } = require("@azure/identity"); 
const credential = new DefaultAzureCredential(); // system-assigned identity 

export const connect = async (): Promise
<void> => { 
  const baseConfig = { 
    database: process.env.MSSQL_DATABASE, 
    server: process.env.MSSQL_HOST, 
    options: { 
      encrypt: process.env.MSSQL_ENCRYPT === "true", 
      trustServerCertificate: 
        process.env.MSSQL_TRUST_SERVER_CERTIFICATE === "true", 
    }, 
  }; 

  let config = {}; 

  if (process.env.AZURE_FUNCTIONS_ENVIRONMENT === "Development") { 
    // Running locally 
    config = { 
      ...baseConfig, 
      user: process.env.MSSQL_USER, 
      password: process.env.MSSQL_PASSWORD, 
    }; 
  } else { 
    // Running in Azure 
    const accessToken = await credential.getToken( 
      "https://database.windows.net/.default" 
    ); 
    config = { 
      ...baseConfig, 
      authentication: { 
        type: "azure-active-directory-access-token", 
        options: { 
          token: accessToken.token, 
        }, 
      }, 
    }; 
  } 

  return await sql.connect(config); 
}; 

Duplicate identities error

There is a problem when adding the identity as a user in SQL Server. The identity lookup in Entra is done using the name (not the object ID or client ID) and if you have multiple identities with the same name, you get an error.

This is usually due to you having configured the Azure Function to use the Authorization feature. (More information here). When you do this, out of the box, an Enterprise Application with the same name as the function is created. This is not the same Application as the managed identity created above.

The way to solve this is to:

  • Delete the automatically generated Enterprise Application created when you added Authorization. Remember to delete both the Enterprise application and the application registration.
  • Add the identity as a user in SQL Server, as described above.
  • Create a new application registration to be used in Authorization configuration. Tip: Use the same name as the function but put ‘app-’ as a prefix.
  • Configure the Authorization option in the Azure Function and make sure you use your new application identity.

New employer, same old me

vivicta-rgb-logo-green

I am back in consulting. On the 3rd of August 2026 I changed from Permobil to Vivicta. First of all, I would like to thank Permobil for five years of interesting and knowledgeable colleagues. Being part of building something that totally changed how the business do business. Good luck on your way forward.

I was always a consultant. Someone that people asked for to aid and support them, either with my time or my knowledge. I very much look forward to that being my life, even if I am now in the hands of the dreaded time report once again.

More on finding application registrations used by Logic Apps

This is a continuation of my earlier post about finding what Logic Apps are using a given application registration. In that post I showed how you can use KQL and the Azure Resource Graph Explorer to list all connectors that are using a given application registration. This post expands on that by answering the question “But what about Logic Apps that uses Client ID and secrets to authenticate in an HTTP action?”.

This comes with a caveat: This only looks in your parameters, but I assume everyone are using parameters when assigning Client ID and secret to a call in an HTTP action.

The solution

Enter Azure Resource Graph Explorer

This is a tool that uses KQL to query Azure resources. List all the VMs, show a list of all IP-addresses used etc etc. It is very very useful. Particularly to me, looking for application references.

Access

First off you need access to the resources you want to query. That might go without saying but I thought I just point that out.

Finding it

Use the search box in Azure (at the top of the page) and type resource graph. The service will show up in the result.

Using it

There are a number of predefined queries, and there is also an explorer to the left, showing you all types of Azure resources grouped by type. You can click any of these and they will show up in the query window.

The KQL

Simply paste this KQL in your window and update it to reflect your environment.

resources
| where type == "microsoft.logic/workflows"
  and subscriptionId == "YOUR SUBSCRIPTION ID"
| extend parameters = parse_json(properties.definition.parameters)
| mv-expand param = bag_keys(parameters)
| extend param = tostring(param)
| extend paramData = parameters[param]
| extend defaultValue = paramData.defaultValue
| where defaultValue == "Client ID of the app reg you are looking for"
| project name, param, defaultValue

This will present a nice list of the name of the Logic app and some additional data.

Accessing Soap Envelope and body properties in Liquid Templates

This thing drove me crazy. Batsh!t insane, but it is all better now because I found the solution.

The issue

You need to call a SOAP service that uses XML and you want to use Liquid templates instead of XSLT. You use the SOAP passthru as it is the most flexible way of calling a SOAP service.

The response body might look a little like this:

<Soap:Envelope xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <ns0:PriceList_Response xmlns:ns0="myNamespace">
      <PriceList>
        <companyId>1</companyId>
        <prices>
          <price>
            <productId>QWERTY123</productId>
            <unitPrice>1900.00</unitPrice>
            <currency>EUR</currency>
          </price>
        </prices>
      </PriceList>
    </ns0:PriceList_Response>
  </Soap:Body>
</Soap:Envelope>

You transform the payload into JSON and what to put it thru a Liquid template.

{
  "Soap:Envelope": {
    "@xmlns:Soap": "http://schemas.xmlsoap.org/soap/envelope/",
    "Soap:Body": {
      "ns0:PriceList_Response": {
        "@xmlns": "myNamespace",
        "PriceList": {
          "companyId": {
            "#text": "1"
          },
          "prices": {
            "price": {
              "productId": "1",
              "unitPrice": "19.00",
              "currency": "DKK"
            }
          }
        }
      }
    }
  }
}

How do you access the price property, in this payload? This will not work:{{ content.Soap:Envelope.Soap:Body.ns0:PriceList_Response.prices.price.unitPrice }}

That is because the colon is a special char in Liquid templates and I found no way of escaping it.

The solution

{{ content.['Soap:Envelope'].['Soap:Body'].['ns0:PriceList_Response'].prices.price.unitPrice }}

I was so happy not to be insane anymore.

A tip

Accessing properties deep in a document can be quite confusing and unclear if you need to use a string to get property values in your Liquid template. Use the ability to assign an object to a variable. This means that you can get a Liquid template that looks like this. It is used for assigning price using the payload above:

{% assign price = content.['Soap:Envelope'].['Soap:Body'].['ns0:PriceList_Response'].prices.price.unitPrice %}
"prices": [
    {
        "ProdId": "{{price.productId}}",
        "ProdPrice": "{{price.unitPrice}}",
        "Curr": "{{price.currency}}"
    }
]