Posted in:

Check the updated version of this tutorial

Azure Functions allows you to protect access to your HTTP triggered functions by means of authorization keys. For each function you can choose an "authorization level". anonymous means no API key is required, function means a function specific API key is required. So in this case each function has its own keys. And admin means you are required to provide the special "master" host key, which is a single key that can be used to call any function in your function app.

To call a protected function you either provide the key as a query string parameter (in the form code=<API_KEY>) or you can provide it as a HTTP x-functions-key header.

Accessing and managing keys in the portal

The Azure portal makes it nice and simple to discover the values these keys. First of all, if we navigate to any function in the portal, you'll see a "Get Function URL" link:

image

When we click it, it constructs the URL we need to call including the code query string parameter. This dialog also lets us access values for both types of key - the "function" keys specific to this function, and the "host" keys that can be used on all functions, including the special "_master" host key. You can read more about these key types here.

image

We can manage the keys for an individual function by heading into the "manage" tab for that function:

image

In here we get the ability to view, renew or revoke each individual function key as well as the host keys. You can create multiple function or host keys, which is great as it allows you to provide separate keys to every client you want to grant access to your function, or to implement key cycling.

image

Using the key management API

Now although its very convenient to manage keys in the portal, before long you'll probably want to manage these values programatically, and that's where things get a little bit tricky. There is a key management API which allows you to access the values of keys as well as to generate new ones, delete keys, or update them with new auto-generated actions.

This is ideal if you want to automate the deployment of your function app and programatically discover the keys you need to call the functions, but I quickly ran into a problem. How do you authorize calls to this API? I was familiar with authorizing calls to the Kudu API, which requires you to pass the deployment user and password in a basic auth header. I showed how to do this in a post I wrote a while back on deploying web apps with the kudu zipdeploy API.

But unfortunately, this technique doesn't work for the key management API. I eventually stumbled across a GitHub issue that led me to the answer, so I thought I'd document my solution.

Getting the credentials to access the key management API is a two step process. The first step is calling the Kudu API, calling the api/functions/admin/token endpoint which provides us with a token (a JWT), that we can use as a bearer token to call the key management API.

I'm using the Azure CLI and Powershell, but these techniques could be adapted to whatever language or scripting tool you're using.

The first step is that we need to get the credentials to call the Kudu API. If you're authenticated with the Azure CLI, you can do that by calling the az webapp deployment list-publishing-profiles command and extracting the userName and userPWD for MSDeploy. You need to provide the function app name and resource group name.

The username and password are the ones you can get from the portal by downloading the "publish profile" for your function app. My powershell function also converts the user name and password into a base 64 encoded string in the right format to be used as a basic auth header.

function getKuduCreds($appName, $resourceGroup)
{
    $user = az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup `
            --query "[?publishMethod=='MSDeploy'].userName" -o tsv

    $pass = az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup `
            --query "[?publishMethod=='MSDeploy'].userPWD" -o tsv

    $pair = "$($user):$($pass)"
    $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
    return $encodedCreds
}

OK, now we have the credentials we need to call Kudu, we can use this information to call the functions/admin/token endpoint. So my next powershell function uses these credentials in a basic authorization header to get a JWT token we can use as a bearer token.

Then it uses that bearer token to call the key management API, on the admin/functions/<FUNCTION_NAME>/keys endpoint to retrieve all the keys for a specific function. I'm just picking out the first key in this example, but you could do something more elaborate if you wanted to access a key by name. Note that unlike the Kudu APIs, which are at https://<YOUR_APP_NAME>.scm.azurewebsites.net, this API is hosted by the Azure Functions runtime itself, so you find it at https://<YOUR_APP_NAME>.azurewebsites.net

function getFunctionKey([string]$appName, [string]$functionName, [string]$encodedCreds)
{
    $jwt = Invoke-RestMethod -Uri "https://$appName.scm.azurewebsites.net/api/functions/admin/token" -Headers @{Authorization=("Basic {0}" -f $encodedCreds)} -Method GET

    $keys = Invoke-RestMethod -Method GET -Headers @{Authorization=("Bearer {0}" -f $jwt)} `
            -Uri "https://$appName.azurewebsites.net/admin/functions/$functionName/keys" 

    $code = $keys.keys[0].value
    return $code
}

With all these pieces in place, we are now in a position to put them together to get the authorization key for a specific function in our application:

$appName = "myapp"
$functionName = "myfunc"
$resourceGroup = "myresourcegroup"
$kuduCreds = getKuduCreds $appName $resourceGroup
$code =  getFunctionKey $appName $functionName $kuduCreds
$funcUri = "https://$appName.azurewebsites.net/api/$functionName?code=$code"

# call the function
Invoke-RestMethod -Method POST -Uri $funcUri

Obviously that's just showing how to retrieve the keys for a function, but once you know how to authorize a call to this API, calling the other methods is pretty straightforward.

Hope you found this helpful. It's certainly been very useful for me in automating tests for my function apps.

Want to learn more about how easy it is to get up and running with Azure Functions? Be sure to check out my Pluralsight courses Azure Functions Fundamentals and Microsoft Azure Developer: Create Serverless Functions

Comments

Comment by Nikolai Blackie

Awesome post thanks, was very useful. I used this to create a script for adding function keys on deployment
Param(
[string]$appName,
[string]$resourceGroup,
[string]$functionName,
[string]$funcKeyName,
[string]$funcKeyValue
)
Write-Host "Begin UpdateAppFunctionKey..."
$user = az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup --query "[?publishMethod=='MSDeploy'].userName" --o tsv
Write-Host "Publish user: $user"
$pass = az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup --query "[?publishMethod=='MSDeploy'].userPWD" --o tsv
$pair = "$($user):$($pass)"
$kuduCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
Write-Host "kuduCreds: $kuduCreds"
$jwt = Invoke-RestMethod -Uri "https://$appName.scm.azurewebsites.n..." -Headers @{Authorization=("Basic {0}" -f $kuduCreds)} -Method GET
Write-Host "jwt: $jwt"
$data = @{
"name" = "$funcKeyName"
"value" = "$funcKeyValue"
}
$json = $data | ConvertTo-Json;
Write-Host "json: $json"
$keys = Invoke-RestMethod -Method PUT -Headers @{Authorization=("Bearer {0}" -f $jwt)} -ContentType "application/json" -Uri "https://$appName.azurewebsites.net/a..." -body $json
Write-Host "End UpdateAppFunctionKey...$keys"

Nikolai Blackie
Comment by Sarbis

Hi Nikolai!
Thanks for sharing the script! This is exactly what I needed.
But do you know what could be the issue if I'm getting the "Response status code does not indicate success: 403 (Forbidden)." in response for the PUT request?
Should I maybe authorize or do some sort of configuration in the portal for the function app to allow api access?

Sarbis
Comment by Sarbis

Nvm!
Found my error - had the "scm" left in the PUT's url which obviously sends request to Kudu.

Sarbis
Comment by Joey Eng

We tried this out and it worked, but my coworker said getting the kudu creds would require you to be an owner of the resource group. So he didn't think this method would work when you run this from VSTS. Ideas?

Joey Eng
Comment by Mark Heath

not sure I completely understand your issue, but you should be able to create a service principal (https://markheath.net/post/... that allows you to make the necessary calls to replicate this from VSTS

Mark Heath
Comment by Tom Hundley

thank you!!!

Tom Hundley
Comment by iyerusad

**Az Powershell** edition (for use in VSTS windows agent)
```
$RSGROUP="myrsgroup"
$WEBAPPNAME="myfunctionapp"
$DeploymentUrl = Get-AzWebAppContainerContinuousDeploymentUrl -ResourceGroupName $RSGROUP -Name $WEBAPPNAME
$userpass = $DeploymentUrl.split("@")[0].Replace("https://","")
$kuduCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($userpass))
$jwt = Invoke-RestMethod -Uri "https://$WEBAPPNAME.scm.azurewebsite..." -Headers @{Authorization=("Basic {0}" -f $kuduCreds)} -Method GET
$keyURL="https://$WEBAPPNAME.azurewebsites.ne..."
$masterkey=(Invoke-RestMethod $keyURL -Headers @{Authorization="Bearer $jwt"}).value
```
gist: https://gist.github.com/fla...

iyerusad
Comment by crowcoder

FYI, the "updated version of this tutorial" 404s

crowcoder
Comment by Mark Heath

thanks, I've fixed the link (should be live soon)

Mark Heath