Previously I have written about authenticating against Azure Functions using Azure AD. While that’s a good and powerful way of authenticating and identifying who is invoking the Function, sometimes one wants a quick and dirty solution and want to provide users with Function access keys to authenticate.
You can read more about Function access keys here. Point is you can have one or more keys (an example: Yft7x2TbMbDXWgal7zD_908xC2uVovxoT7EOSYhM7ZJxAzFuTz5_vQ==
) and when calling the Function URL via http you tack this to the end. So, for instance, if the Function URL is https://rakfunckeystest832.azurewebsites.net/api/HttpTrigger1
you call it thus: https://rakfunckeystest832.azurewebsites.net/api/HttpTrigger1?code=Yft7x2TbMbDXWgal7zD_908xC2uVovxoT7EOSYhM7ZJxAzFuTz5_vQ==
.
This is way easier than using Azure AD as you have less things to setup (App Registrations and stuff), plus usually the team setting those up is a separate Identity team and you’ll need to involve and wait for them to put it in place.
My thing was, suppose I have more than one key and I want some way of differentiating between them within the Function code, is that possible? A quick Google found nothing except this forum post which didn’t have a good answer.
To begin with, I created a Function app (PowerShell in my case) and a new HTTP triggered function with that.
Next, I went to the Function Keys section and added two keys:
Note the key values. In my case user1 is n_k5ySaOAAlg0GlL4qjUejlajikAuQoUVpK0fZyHr7cNAzFuQkfPxA==
and user2 is LUDG_h1acgI_SsuUymEjyXO7h4XjMYH0D_o31zoWr8PJAzFuX5fmRA==
.
I figured I should call the Function with one of these keys and see what appears in the headers. So I modified the default code with these lines so it outputs the headers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using namespace System.Net # Input bindings are passed in via param block. param($Request, $TriggerMetadata) # Write to the Azure Functions log stream. Write-Host "PowerShell HTTP trigger function processed a request." # Interact with query parameters or the body of the request. $name = $Request.Query.Name if (-not $name) { $name = $Request.Body.Name } $Request.Headers $body = "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." if ($name) { $body = "Hello, $name. This HTTP triggered function executed successfully." } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $body }) |
Next, I went to the Monitor section and to the Logs. Separately, I invoked the Function. Via PowerShell I did this basically:
1 |
Invoke-RestMethod -Uri 'https://rakfunckeystest832.azurewebsites.net/api/HttpTrigger2?code=n_k5ySaOAAlg0GlL4qjUejlajikAuQoUVpK0fZyHr7cNAzFuQkfPxA==' |
I am using the user1 key above. And I could see that the headers include the full URL that’s used, which is good coz now I can match on that to get the key.
I also realized that if I am calling the Function via the Test/Run button on the portal, the headers are different. Importantly, there’s no URL any more but I can get the key used via a new header:
So I modified the default code this to identify the user from the key used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
using namespace System.Net # Input bindings are passed in via param block. param($Request, $TriggerMetadata) # Write to the Azure Functions log stream. Write-Host "PowerShell HTTP trigger function processed a request." # Interact with query parameters or the body of the request. $name = $Request.Query.Name if (-not $name) { $name = $Request.Body.Name } $body = "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." $keyMappings = @{ "n_k5ySaOAAlg0GlL4qjUejlajikAuQoUVpK0fZyHr7cNAzFuQkfPxA==" = "User 1" "LUDG_h1acgI_SsuUymEjyXO7h4XjMYH0D_o31zoWr8PJAzFuX5fmRA==" = "User 2" } if ($Request.Headers.'x-functions-key'.Length -ne 0) { # Get the key from the header property if present $functionKey = $Request.Headers.'x-functions-key' } else { # Else match the URL and get the key from that # The regex searches for '?code=<whatver>' followed by an optional ampersand # The <whatever> is captured as the code. It uses named matches - that's what ?<code> stands for. if ($Request.Headers.'x-original-url' -match '\?code=(?<code>.+)\&?') { $functionKey = $Matches.code } else { $functionKey = $null } } if ($keyMappings.Keys -contains $functionKey) { $body = "Hello $($keyMappings.$functionKey)" } else { $body = "Unable to identify the access key used." } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $body }) |
And that’s it. Now when I invoke the function I know who’s calling it:
Nice.
Note: I have deleted the Functions used in the example above so they won’t work anymore.