Authenticated access to Azure Functions using Azure AD (Client Credentials Grant)

I really love this topic of Azure AD and Azure Functions, Logic Apps, etc. It’s probably the one topic that I genuinely get excited about nowadays and have put in a lot of effort recently. A year ago OAuth 2.0 and stuff was gibberish to me; a few years before that Azure AD was this new thing out there I didn’t know much about it. But nowadays I seem to be rolling in Azure AD and authentication and enjoying it!

I am not directly responsible for managing Azure AD in my firm, but these topics such as authenticated access to Azure Functions, Logic Apps, calling these from Power Automate, making HTTP calls to Azure from Power Automate… all of which I blogged about in the recent past have got me more and more into OAuth 2.0. I am far from being a master in it, and even now I am winging my way through a lot of it, but I sure am getting better at it. 🤞🏼

Anyways, all that happy talk done let’s get down to business…

(Note: Even though the title talks about Client Credentials Grant this post ended up being a long-winded one and talks about ROPC Flow and how to setup an Azure Function for Azure AD authentication too. Will need some light fast-forwarding to get to Client Credentials Grant if that’s all you are interested in, but I recommend you don’t so you get some “flavour” of what’s coming 😊).

We use this product called Intapp Integration Builder. It’s a neat tool, I haven’t used it at all (first time today) but others use it and I was looking into it because someone asked if I could call an Azure Function from this. And sure, one can do that – it supports HTTP/ REST requests, so it’s just a case of creating an HTTP triggered Azure Function, set it an access key, and use that to call the Azure Function from Integration Builder.

But of course I don’t want to do that. That access key becomes too important. So naturally I want to use Azure AD authentication for the Function and must figure out how I can get Integration Builder to call the Function after authentication. Hence my foray into this.

Setting up a Function with Azure AD auth

To repeat parts of my earlier post on setting up Azure AD auth for a Function:

Create a Function App and enable Azure AD authentication. It’s under Settings > Authentication.

Click on “Add identity provider”.

Select Microsoft. Create a new app registration. Give it a sensible name. I went with the following:

Ensure you tick the “Require authentication”. “HTTP 401”. And not in the screenshot, “Token store”.

In the next screen (Permissions) click “Add permission” and add “profile”.

(Reason for this is in my previous post. This permission lets me get the details of the user who’s signing in. It is not strictly needed for what I am doing here, but I like to keep things consistent).

If you go ahead, you should be able to add the provider.

Note that the successful creation of this depends on either you being an admin who has the rights to create App Registrations (have the Application Administrator role basically). Or your admin has allowed users to register applications themselves.

Next step is to click on the Identity provider and that will take you to Azure AD, to the App Registration. Click on API permissions. Click on “Grant admin consent”. (Again, this option is only available if you have the appropriate permissions).

I am also going to go ahead and enable public flows.

This will let me use a Resource Owner Password Credential Flow later on (basically, authenticate with a username/ password). Again, this is not strictly needed for what I finally end up doing… but it’s good to explore this one too.

While on that screen, ensure “ID tokens” is ticked.

That’s the token Azure Functions needs.

So far so good? You can also click the Edit button next to the Identity provider to see what’s been setup.

Note that the Issuer Url has your Tenant Id in it. Also note that the allowed token audiences are api://<appId of this registration>. I point out these just for info; we’ll come back to this later.

Next step is to actually create a Function (what we did above was create the Function App). I am going to create an HTTP trigger one.

Notice I changed the Authorization level to Anonymous.That’s because the Function will be authenticated to using Azure AD.

Next, I replace the sample contents of the Function with this:

This is some dummy code I created that outputs the name of the person connecting to the Function Logs. I use this for testing. The Function doesn’t do anything else.

Let’s try calling the Function un-authenticated:

Doesn’t work as expected.

The ROPC Flow

First I am going to do the ROPC Flow. This is an authentication Flow where you enter a username/ password basically. It is not very secure (coz passwords?!) and doesn’t support MFA for instance. I could have gone with the Device Code Flow liked I did previously but since my end goal is to run this in an automated fashion from Intapp Integrate, I choose to go with passwords instead.

Here’s how I do it via PowerShell:

Replace all the values as appropriate. Point is, you put all these into the body of a REST API call to login.microsoftonline.com and you get tokens.

Note that this might not work out of the box by default, especially if MFA is turned on. You might get an error like this:

If you’ve been paying attention to my blog posts you know that 00000003-0000-0000-c000-000000000000 is the app Id for Microsoft Graph. What needs doing here is to exempt the account you are authenticating with from MFA somehow. What you do exactly depends on the specific environment, but for example here I am excluding this account from my default policy that asks for MFA and I create a new one that requires MFA for this account for all apps except the App Registration I created earlier.

Here I am excluding this account from my default MFA policy:

And here I have a second policy that applies only to the user I excluded above, and applies to all apps except the “xxx Authentication” App Registration created by the Function.

Not visible in the screenshot, I am enforcing MFA under “Access controls”. This way when my PowerShell snippet requests for a token, it is allowed as long as it is only for that App Registration.

And here in lies the power of Azure AD and why I’d rather use Azure AD authentication instead of Function Keys. I could have fine tuned this policy further… like say block all access to the above App Registration unless its from a specific IP!

For instance, now I can get access token with the above policy in place:

Now let me create a named location called “Home” as an example and add my home IP to it.

Then I create the following policy:

It applies to all users.

And just to the App Registration created by the Azure Function:

It does not apply if the connection is from the Home location.

And what does it do? Block access!

I enable and turn this on.

Can I still get a token from my home machine? Yes.

What if I jump to a different machine I have access to, not at home (e.g. Cloud Shell)? In theory I should not get a token but in my testing I kept getting a token… and I am not sure why. Maybe the Conditional Access policies take a while to kick in? I waited for 2-3 hours, tweaking various things, but couldn’t get it to work. Azure AD’s own “What If” tells me the policy should block my getting a token from Cloud Shell. Heck, if I invert the policy to block “all cloud apps” then it blocks everything from outside my home network, including access to the Function App Registration; and if I then exclude the Function App Registration from the policy it blocks everything except this App Registration (getting a token)… So I know it all works fine, and yet it isn’t.  I’ll update the post if I get to the bottom… meanwhile, know that you can use Conditional Access to tweak things (and it might be a frustrating experience 😀).

What I ended up doing as an interim is to block this user from everything except the App Registration. This way the account is useless except for getting an access token to this App Registration.

Again, if I remove the exclusion I do above then I can’t get tokens either. So it works, but doesn’t where I want… aaaargh! 🤬

(Update: this started working some 3-4 hours later; so a total for 7-8 hours after I made the policy!

Yay!)

Anyways… assuming someone is still reading this post and following along 😛 we were on the following code before I digressed:

At this point $aadToken has the tokens from Azure AD. We are interested in the ID token. To authenticate with the Azure Function one has to exchange that the Function Middleware for a token that can be sent to Azure Function. You can do that thus:

This token can then be used as the bearer token when talking to the Azure Function.

And this will succeed. In fact, I can even see the calling user in my Function output as I am extracting that from the token.

Intapp

Coding this in Intapp Integration Builder is surprisingly easy (kudos to the product! It has been a while since one used a product that isn’t web based and you forget how good that is. In this day of when everything is cloud based you forget that there used to be a time when you had programs that just ran on your computer and responded quickly etc.)

The tasks are straight forward and correspond to the PowerShell from above:

Here’s the first task for instance:

As you can see it maps to what I was doing with PowerShell. Here’s how that HTTP request looks like – I specify the settings explicitly.

I was pleased to find I can parse the response, stored in the variable above, and extract just the ID token.

And the second task:

Again, straight forward. For the HTTP Request I send a JSON body directly.

(I forgot the untick the send/ save cookies like I did for the first HTTP request. I don’t think it matters either way).

And lastly, the third task is where I call the Function with the bearer token.

The HTTP request:

And that’s it!

Locking down

I should have mentioned this earlier, but it’s also good to lock down the Function App Registration to specific users. For this one needs to go to the Application instance in Azure AD. Click on the “Managed application” entry in the App Registration to go there.

Then, under “Properties” set “Assignment required”.

Don’t forget to Save!

Then under “Users and groups” add users/ groups who are allowed access.

Now only the selected user will be able to access the Function. No one else in the tenant will be able to access. If they try to get a token they’ll get the following error:

 

The Client Credentials Grant Flow

That’s all with the ROPC Flow. Now let’s take things up a notch. Instead of using a username and password, let’s use the Client Credentials Grant Flow. Here’s what that entails:

The OAuth 2.0 client credentials grant flow permits a web service (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling another web service. For a higher level of assurance, the Microsoft identity platform also allows the calling service to authenticate using a certificate or federated credential instead of a shared secret. Because the application’s own credentials are being used, these credentials must be kept safe – never publish that credential in your source code, embed it in web pages, or use it in a widely distributed native application.

In the client credentials flow, permissions are granted directly to the application itself by an administrator. When the app presents a token to a resource, the resource enforces that the app itself has authorization to perform an action since there is no user involved in the authentication.

Basically you create another App Registration, and that becomes the identity of the “app” you are using – Intapp in this case. You then grant this identity access to the Function’s App Registration.

So, step 1: create that in Azure AD.

Note its Application Id. And create a client secret.

Now we can request a token:

One thing to note is the scope. This is different from ROPC.

In our case we need one for the App Registration of the Function. We know its audience and this has to match that (with /.default added). For example I have:

So I use api://bfe05a86-1deb-42ed-85ac-7d69344e0fa2/.default.

If I were to run the above PowerShell I’ll get the following error:

This is because I limited the Function App Registration to a specific set of users/ groups. If I hadn’t done that the above would have worked.

Here’s how to grant the new App Registration rights to the Function one. (See also this link & this link for more details).

On the App Registration of the Function, go to App Roles and create a new one. The exact text doesn’t matter as long as you know what it is for.

Now go to the new App Registration and under its API permissions add a permission.

Select “APIs my organization uses” and type the name or App Id of the Function App Registration.

Click on that. Then “Application permissions”. And select the App Role previously created.

After that Grant admin consent.

Now the previous PowerShell snippet works!

Unlike the ROPC Flow now we only get an access token and not an ID token. So the subsequent PowerShell code has to exchange that for the Function authentication token.

And here we run into another error… one that stumped me for a long time yesterday night.

The access token is correct, and copy pasting it into jwt.ms shows the correct info too:

After a lot of poking around I finally realized the issue. The issuer (iss claim) for this one is “https://sts.windows.net/<tenantId>/“. While the same for the ID tokens is “https://login.microsoftonline.com/<tenantId>/v2.0“. And the issuer Url in the Authorization section of the Azure Function is “https://sts.windows.net/<tenantId>/v2.0“.

I would have thought the Issuer Url needs to match what the Function has… but maybe not, coz the ID token Issuer Urls don’t match. Nevertheless, I removed the “v2.0′ from the one in the Function to see if that helps.

Don’t forget to click Save! And what do ya know, it works! Hah. :)

(This is an example of me winging it but also feeling smart that I figured it out!)

The next step is same as the ROPC Flow. Exchange this token, and authenticate.

And that works!

Intapp

There’s no point going into details on the Intapp side, as it is similar to the previous setup just that I change the first HTTP request to use Client Credentials Grant instead of ROPC. Just a matter of changing the HTTP Request and associated variables basically; and also of capturing the “access_token” from the response as opposed to “id_token”.

And there you have it. Two ways of calling an Azure Function from IntApp (or any other platform) – the second being the preferred, where you associate an identity with the app and grant it rights.

On last thing

Turn off public client flows in the Function App Registration if you don’t plan on using ROPC.

With this turned off, if you do the ROPC Flow you’ll get the following error:

10th August 2024: An update.