6

I need to be able to identify the key (ideally key name) provided in the header (x-functions-key) for the POST to the Azure Function in the Run method, e.g.

Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log, ClaimsPrincipal principal)

It is great to be able to protect access to the Azure Function adding Function Keys in the Azure Portal panel, but I must be able to tell which function key was used. Ideally it would be possible to associate claims on each function key, but as long as I can at least figure out which key was used I will be happy.

3
  • 1
    POST management.azure.com/subscriptions{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/functions/{functionName}/listsecrets?api-version=2016-08-01 Commented Nov 12, 2019 at 13:31
  • My azure function uses AuthorizationLevel.Function which means a function key must be provided in the header that matches one of the Function Keys set in the Azure Portal panel under "Manage". It is a great way to control access to an azure function, but I need to verify which vendor the provided key belongs to at runtime. I have not discovered any way to see which key was used. Not suprisingly there are no identities in ClaimsPrincipal principal. Commented Nov 12, 2019 at 15:07
  • Instead of getting the key name I can always just get the key from the header directly from the header: Microsoft.Extensions.Primitives.StringValues values; bool result = req.Headers.TryGetValue("x-functions-key", out values); This is less ideal, but a simple solution to identify access based on the key. Commented Nov 12, 2019 at 15:39

3 Answers 3

8

Simply get the claim "http://schemas.microsoft.com/2017/07/functions/claims/keyid" from the req.HttpContext.User.Claims object. It contains the key id in case a Function key was used.

Works like a charm, and does not require external lookups.

const string KEY_CLAIM = "http://schemas.microsoft.com/2017/07/functions/claims/keyid";
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    var claim = req.HttpContext.User.Claims.FirstOrDefault(c => c.Type == KEY_CLAIM);
    if (claim == null) 
    {
        log.LogError("Something went SUPER wrong");
        throw new UnauthorizedAccessException();
    }
    else
    {
        log.LogInformation( "Processing call from {callSource}", claim.Value);
    }
Sign up to request clarification or add additional context in comments.

3 Comments

is this still current? - i mean the http://schemas.microsoft.com/2017/07/functions/claims/keyid
Yes, it's still valid, as of today (08/04/2023) the claim uri is still http://schemas.microsoft.com/2017/07/functions/claims/keyid 👍
I had this working like a charm in .Net 3.1 with "in process model". Having upgraded to .Net 8.0 and "isolated worker model" it no longer works. The claims part of the header is empty.
0

In Azure Functions v1:

[FunctionName("MyAuthenticatedFunction")]
public static async Task<HttpResponseMessage> MyAuthenticatedFunction([HttpTrigger(AuthorizationLevel.Function)] System.Net.Http.HttpRequestMessage reqMsg, ILogger log)
{
    if (reqMsg.Properties.TryGetValue("MS_AzureFunctionsKeyId", out object val))
        log.LogInformation($"MS_AzureFunctionsKeyId: {val}");  
}

Code reference: WebJobs.Script.WebHost/Filters/AuthorizationLevelAttribute.cs#L77

Comments

0

For those interested here's a custom extension that uses the Claim as noted in prior answers to get the App Key Name easily from the User ClaimsPrincipal in Azure Functions.

In-Process Azure Function:

I believe this works with all versions but has been tested with Az Func v4.

/// <summary>
/// Read the current Azure Function App Key Name (the name of the token as configured in azure portal).
/// </summary>
public static string GetCurrentAzFuncAppTokenKeyName(this ClaimsPrincipal userPrincipal)
{
    const string AzFuncKeyClaimUri = "http://schemas.microsoft.com/2017/07/functions/claims/keyid";
    return userPrincipal?.Claims.FirstOrDefault(c => c.Type == AzFuncKeyClaimUri)?.Value;
}

In-process Usage (using ClaimsPrincipal) would be:

[FunctionName(nameof(DoTheThing))]
public Task<IActionResult> DoTheThing(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "do-the-thing")] HttpRequest httpRequest
)
{
    var azFuncKeyName = httpRequest.HttpContext.User.GetCurrentAzFuncAppTokenKeyName()
                        ?? throw new UnauthorizedAccessException("Azure Function Key Name claim was not found.");

    //Do all the things....
}

Isolated Process Azure Function:

In the isolated process model you can't get the ClaimsPrincipal in the same way; it doesn't exist on the HttpRequestData model and if you try and use DI it'll simply be null. But the HttpRequestData instead has an Identities collection...

This was tested with Az Funcs v4 using .NET the latest Isolated model libraries Microsoft.Azure.Functions.Worker v1.21.0...

NOTE: This apparently does NOT work if you are using the (seemingly new compatibility library) Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore with the configuration method new HostBuilder().ConfigureFunctionsWebApplication() (which is non-standard).

So you need to be sure you are using the vanilla isolated function model with only the host configuration method new HostBuilder().ConfigureFunctionsWorkerDefaults()!

/// <summary>
/// Read the current Azure Function App Key Name (the name of the token as configured in azure portal).
/// </summary>
public static string? GetCurrentAzFuncAppTokenKeyName(this HttpRequestData httpRequestData)
{
    const string AzFuncKeyClaimUri = "http://schemas.microsoft.com/2017/07/functions/claims/keyid";
    return httpRequestData?.Identities.SelectMany(i => i.Claims).FirstOrDefault(c => c.Type == AzFuncKeyClaimUri)?.Value;
}

Isolated process Usage (using HttpRequestData) would be:

[FunctionName(nameof(DoTheIsolatedThing))]
public Task<IActionResult> DoTheIsolatedThing(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "do-the-isolated-thing")] HttpRequestData httpRequestData
)
{
    var azFuncKeyName = httpRequestData.GetCurrentAzFuncAppTokenKeyName()
        ?? throw new UnauthorizedAccessException("Azure Function Key Name claim was not found.");

    //Do all the isolated things....
}
  • Edit: Enabled code formatting for csharp.
  • Edit: Fixed Example code. Added example for Azure Functions Isolated Model.

8 Comments

I tried this with Azure Function isolated mode and it seems like it doesn't get the claim "schemas.microsoft.com/2017/07/functions/claims/keyid" and then I tried with in-process model it worked. so I guessed they removed this and now actually the isolated process is the recommanded one. In-Process is not supported in .NET 8 it seems.
@user874854 The information is available in the latest version of Azure Functions Isolated model, but not in the same place... as the ClaimsPrincipal is not available, but there is an Identities collection on the HttpRequestData that will have the claims (assuming you are using vanilla Isolated Function; otherwise it seems the new AspNetCore Extensions breaks this somehow and the claims are not initialized in the same way.
I've edited my answer to include an Isolated function method and example, as it seems it's not yet been posted anywhere in SO or on Google! And I'm not ashamed to admit I spent WAYY too much time looking for this (for my own migration) only to find that Visual Studio template I used spun up the project using the non-standard non-vanilla AspNetCore Extensions (headbang) that loses the AppKey claims (more info. in my edits above).
You say that AspNetCore Extensions is non-standard. But this is how Microsoft show you to do it learn.microsoft.com/en-us/azure/azure-functions/… It seems to say in this linked document that you have to use this for HttpTriggers. Do you know of documentation of how to do HttpTriggers without this package as I can't find information on that and I need to find a way to get the function key name as we had before migration to isolated worker model.
@RosieC I used the term non-standard loosely... perhaps the more accurate term is non-built-in approach. But you absolutely do NOT need the (large) AspNetCore dependency libraries to run an HttpTrigger in an Isolated Function. In fact, there appear to now be some updated docs that explicitly show there are now two ways to wire up an HttpTrigger via built-in or AspNetCore libraries (because MS built a Bridge for classical AspNetCore): learn.microsoft.com/en-us/azure/azure-functions/…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.