Flow/Power Automate and Function Apps – Securing a Function App with a Service Principal

By | November 3, 2020

I’ve written a fair bit about how I use Function apps and Flow to get data about 365 environments. There are clear advantages and disadvantages of each. Flow is incredibly slow, difficult to process data in and is in general restricted to what you can take out of Microsoft Graph. Function Apps allow you to grab (almost) anything you can get with powershell.

Ideally we want to be able to use the…power…of powershell with the convenience of flow. Its a very doable task, the first part is pretty obviously securing your function so only logged in entities can read the data you are exfiltrating.

Lets get to it, I’m using the function I built earlier and a copy of my template function code…

Create A function

Create a new function in your function App, mine is called “MailboxStats”

Once the function is created you will be at the overview page, click “Get Function URL”

Save this URL, you can if you wish test it in an API tool (there’s postman or presumably something else.) If you leave the demo code in the function you can pass a “name” variable and get a response:

Hello, JeanLucPicard. This HTTP triggered function executed successfully.

Register the function in AAD

We now need to secure the function so everyone cant copy our potentially confidential data. To do this we are going to turn on App Service Authentication with Azure AD log on. I’ll warn you up front – thanks to Microsoft’s dynamic approach to their UI where this setting is changed between me taking the original set of screenshots and writing the text. So here’s where it is right now.

Go to the Function App blade itself (not the function anymore) and on the left hand menu hit “Authentication/Authorization”. On this page is toggle switch for App Service Authentication, turn that baby on. You can now change the “Action to take when request is not authenticated” to “Log in with Azure Directory.”

Now click Azure Active Directory to set up the service provider. I would highly recommend hitting express and accepting the defaults here, so a new App is created in AAD with the AAD app name the same as Function App name

Click Save back at the Function App blade.

Set Function Security

If you want to test you can go back to postman and rerun the test, now you will get a 401 error –

You do not have permission to view this directory or page.

If you stick your URL and variable in a browser you’ll get an AAD login prompt:

If you log in with any AAD account it will ask for consent so the app can read your profile then return the hello string:

Hello, JeanLucPicard. This HTTP triggered function executed successfully.

That’s great and all but we don’t want any user to be able to use this function so lets do some chopping of permissions…

If you go to your Azure Active Directory blade in the azure portal and search through Enterprise Applications you will find your function app is now registered in here

Click the App. First thing we want to do is Hit Properties and Set “User assignment required” to Yes and “Visible to Users” to No. I also set a description:

Save the properties page and now users who do not specifically have the app assigned cant access it. If you test with a different account (not the same one from above – its already got permission to the app – you will receive a “You do not have permission to view this directory or page.” error)

Next we want to get rid of the consent pop up. Go to Security | Permissions and click “Grant Admin Connect for <<Your organisation>>” You’ll go through the consent process and land back at the Enterprise App Blade.

Now we have an app set up so specifically allowed users can access it and no one else can. You can add users on the Users and Groups section. If you click it you will see that the test user you accessed the app with before setting the “user Assignment Required” switch and the admin user you used to Grant organisational consent are in here. You can use +Add User to add more users if you wish.

This will work, you can use a flow connector and username and password to access the app now BUT we aren’t done by a long shot.

Set up Certificate Authentication

We want to be able to log onto our app with a certificate. The more adventurous will have jumped off ahead and tried to add your App registration created for function apps to the Users and Groups for the Enterprise Application and found you cant.

Go back to the Function App, back to Authentication/Authorization and Click Azure Active Directory again, this time click Advanced.

This will have a populated screen showing Client ID, Issuer Url and Allowed Token Audiences, everything should be filled in and Allowed Token audiences should be the root URL of you function app i.e.

https://<<FuntionApp>>.azurewebsites.net

Hit OK.

Now go back to the the Azure Active Directory then App Registrations.

We will be dealing with two app registrations here – the function app and the service principal. The service principal App Registration we created way back and is linked above. Lets look at the Function App App registration by searching for the function App name then clicking it. What we need to do is add an app role that can access this function.

Click Manifest and you’ll see the JSON that defines your App object.

You want the section marked “appRoles”. It will be blank now, you want to add:

{
  "allowedMemberTypes": [
    "Application"
  ],
  "description": "Service Principal callers",
  "displayName": "Service Principals",
  "id": <<GUID>>,
  "isEnabled": true,
  "lang": null,
  "origin": "Application",
  "value": "ServiceClient"
}

For id you just need to enter a unique GUID. You can generate one at https://www.guidgenerator.com/online-guid-generator.aspx – you’d have to be pretty unlucky to get a duplicate.

Dumping this into the manifest looks like:

Save the manifest.

We now have our Function App app registration with a new role which allows service principals to call it.

Now we go to our Service Principal App registration, you can use a new one of if you have been playing along at home you can use the one we built to run all of the function app “stuff”.

Once in the correct App Registration go to API permissions then + Add a Permission. Unlike previously where we have used standard Microsoft APIs this time go to “APIs my organization uses” and search for the function app.

If everything has gone well you will now see that there’s an Application Permission available on the function app, click it and it should be the ServiceClientapp role we just defined:

Add it, Save it, Grant admin consent for your organisation.

Test It

You can test your access with a bit of powershell. I use the MSAL.PS module to generate my tokens, you can just use Invoke-RestMethod to “https://login.microsoftonline.com/<<Your Tenant>>/oauth2/v2.0/token”.

Note we have two AppIDs – The first Apps ID for the service principal app is the enterprise registration which has the certificate attached to it and the api permissions, you can find it on the overview of your app registration in AAD. The second AppID is the App ID for the registration of the function app itself in AAD. You can find it on the Authentication\Authorization | Azure AD page of the Function App itself.

Get a token:

$AppID="<APP ID for SERVICE PRINCIPAL APP>"
$CertThumbprint="<<Cert thumpprint, obviously it need to be in your cert store>>"
$tenantid = "<<Your TenantID>>"    

Import-Module "D:\home\site\wwwroot\MSAL\MSAL.PS.psd1"
$cert = Get-Item "Cert:\CurrentUser\My\$($CertThumbprint)"
$MSALToken = Get-MsalToken -ClientId $appID -TenantId $tenantID -ClientCertificate $cert -Scopes "<AppID for the Function APP AAD app registration/.default"

This will give you a token, if you run it through JWT you should see it has your custom role for the your function app registration: (This is completely optional, just so you can see what’s going on)

  "roles": [
    "All"
  ],

Now you can use this token to access the function app:

$functionURI = "https://<<Your Function App>>.azurewebsites.net/api/MailboxStats?name=JeanLucPicard"
$headers = @{
    Authorization = 'Bearer ' + $MSALToken.accesstoken
}
Invoke-RestMethod -Uri $functionURI -Headers $headers

This will then return:

Hello, JeanLucPicard. This HTTP triggered function executed successfully.

Loading

Leave a Reply

Your email address will not be published. Required fields are marked *