Azure-functions-host: Support EasyAuth

Created on 25 Feb 2016  ·  89Comments  ·  Source: Azure/azure-functions-host

To make it possible for the Functions to perform authorization, we should pass the various auth claims into the context object.

Idea is to allow users to specify a new "user" auth level value to indicate that a function requires an EasyAuth validated identity.

needs-discussion

Most helpful comment

@ConnorMcMahon I've just been testing this out and have run into what I think may be a bug (at least, I hope it is!).

I've configured AD authentication on my function app and disallowed anonymous requests. When I've been deploying a function with a ClaimsPrincipal parameter, I've found that:

  1. If the authLevel is set to function and the function key is provided then the request is processed, and the ClaimsPrincipal is populated with two identities - one from the function runtime and one from the AAD token.
  2. If the authLevel is set to anonymous and the function key is omitted then the request is processed, but the ClaimsPrincipal doesn't get the AAD identity populated.
  3. If the authLevel is set to anonymous and the function key is provided then the request is processed, and the ClaimsPrincipal gets both identities again.

FYI, the documentation says that when an AD token is used for authentication, the function authLevel should be set to anonymous - hence my assumption that this is a bug.

I'm not sure if this issue is the right place for this, or if I should open a new issue?

All 89 comments

Retitling

Update here: investigated with @fabiocav on Friday and confirmed that .NET functions can just rely on the ClaimsPrincipal, per the code below. Other languages would have to rely on the headers, and that is not code we should make users write. Would be nice to expose as part of the context object or similar.

using System.Net;
using System.Threading;
using System.Security.Claims;

public static void Run(HttpRequestMessage req, TraceWriter log)
{
    if (!Thread.CurrentPrincipal.Identity.IsAuthenticated)
    {
        log.Info("Not authenticated");
        return;
    }

    ClaimsIdentity identity = (Thread.CurrentPrincipal as ClaimsPrincipal)?.Identity as ClaimsIdentity;
    foreach (var claim in identity.Claims)
    {
       log.Info($"{claim.Type} = {claim.Value}");
    }
}

@mattchenderson I assume both C# and F# will now work, since we just merged the new F# model. For Node, can you give some examples of what needs to be exposed? Is it just things like context.identity.isAuthenticated and context.identity.claims? I.e. can we simply translate the identity into a json object?

Also, we'd then start exposing one more enum value for our existing authLevel property: "user", to indicate that a function requires an authenticated identity. I've already made a provision for that in our enum in anticipation of this :)

@mathewc we may also want to consider making the identity information available as named inputs. Thoughts on that?

As this work progresses, it would be great to see support for and clear docs on OAuth based auth and what customers should do if they have been basing their work on OWIN middleware and are migrating to Functions.

do we have any updates as of now?

@ConnorMcMahon @cgillum We are starting to prototype this work now

@mathewc good to hear this. but possibly when we could expect a preview?

@YasothaDeviSivanandham If all goes according to plan, we're aiming to have something ready this Spring.

I have been spending the past few hours trying to wrap my head around this. Am I to understand correctly that the user identity is not provided in an Azure Function by default? I am curious on how operations committed against Azure Functions are recorded and audited if an identity is not readily available/accessible. For some context here, I have been trying to use System.Security.Claims.ClaimsPrincipal.Current.Identity.Name and no matter what I seem to do this always returns empty. Same with HttpContext.Features.Get<IHttpAuthenticationFeature>().User.Identity.

It really seems like the identity would be front, center, and easily available as you would want to know who did what when... what am I missing here?

I should also mention that I am using Azure Active Directory, so that might have something to do with is. So bizarre that the identity is so missing! It's tripping me out! 😆 The rest of Azure Functions is pretty awesome though. 👍

@Mike-EEE, If you are currently using our Authentication/Authorization feature and are running in a .NET language on Functions V1, your ClaimsPrincipal.Current should be populated with the "current" user.

The goal of this issue is to make this authentication information available across more languages and in Functions V2. This work should also more clearly define how the user-based authentication that EasyAuth provides interacts with the key-based authentication that Functions utilizes.

Ah! That explains it then. I am indeed using Authentication/Authorization with .NET/C#, but I am using v2. So it would seem that v2 currently does not support the ClaimsPrincipal.Current property.

Correct. V2 runs on .NET Core 2.0, which I believe has ClaimsPrincipal.Current set to null. This work should allow you to get the ClaimsPrincipal of the current user as a parameter instead.

This work should allow you to get the ClaimsPrincipal of the current user as a parameter instead.

OK awesome. That would be great! Much better than digging through object graphs trying to figure out what's what. Also, while I am here and at this, I'll add my .02 here and see if it might be possible to consider adding support for local development, too. I am not sure if that would be possible, but it would be super amazing. 😄

Essentially, I would like to log into my local development instance with my AAD account like I do with my published account, but of course would store the cookie locally. That way I could truly test locally how the function would execute in a published context, if that makes sense. Thank you for any consideration.

Thanks for the $0.02 Mike. :) Local development with support for AAD credentials is on our roadmap. No dates to share quite yet, but it is planned.

OK cool @cgillum ... Appreciate the engagement and activity around all of this. Thank you to all. FWIW, I have posted a solution to accessing current user/authentication that works in both local and deployed scenarios here:
https://stackoverflow.com/a/49402152/3602057

That should hold us over until we get the new magic installed. At least, it will for me. 😆

@cgillum Is there any update on the expected release of this functionality?

No specific dates have been committed yet, but we're hoping to have this available in time for the Functions v2 GA release (which also doesn't have a specific date, other than later this year).

Based on the above, it sounds like we should be able to retrieve claims passed to Azure Functions v1. Unfortunately ClaimsPrincipal.Current.Claims appears to be empty when we access it. Our setup is as follows.
We have an Azure Functions app (App1) with App Service Authentication enabled and an Express Azure AD app created. This app has an endpoint to return back the claims and HTTP headers to the caller.
We have another Azure Functions app (App2) with Managed Service Identity enabled.
When App2 calls App2, we get no claims, although the Authorization header is set to "bearer \ We can of course manually parse the JWT from the header and check the claims it contains. It would be much more convenient if Azure Functions could do that for us. And based on this thread it sounds like this should already work. Could anybody advise how we could get the claims collection populated?

UPD: Looks like our problem was caused by bad configuration. We'd put the function app's URL in the "App ID URI" of the AD app. After replacing that with https://\

As I understand it, in V2, ClaimsPrincipal.Current is null, and after this work is done, you'll be able to access it as a function parameter... But what can you do in the meantime to know what user is authenticated?

In the meantime, you can check for the existence of a X-MS-CLIENT-PRINCIPAL-ID request header to know if the user is authenticated.

Yes, but how do you know other stuff - like the user name? How do you do stuff like ClaimsPrincipal.Current.IsInRole?

The name can be found in the X-MS-CLIENT-PRINCIPAL-NAME request header. Regarding ClaimsPrincipal.Current.IsInRole, I don't believe that is even directly supported in Azure AD since they don't use the role claim. Can you describe your exact scenario in a bit more detail?

The intent is to have AD-authenticated users call a web function which will create a work-request on an azure queue. Other AD-authenticated users will use other functions to pull those work-requests off and process them (outside of Azure). The functions that are doing the processing will need to validate that the user that queued the request is actually permitted to do so. So I need a means to know for sure who called the azure function at the least. The request header you mentioned would do for that (providing it's not spoofable).

The IsInRole query was just because I haven't yet figured out how to create an azure function that can only be called by a limited set of users within the AD domain. Surely there's an easy answer there.

In the meantime, you can check for the existence of a X-MS-CLIENT-PRINCIPAL-ID request header to know if the user is authenticated.

Is this safe? Is there a guarantee that this header would be stripped from malicious incoming requests?

If you have EasyAuth enabled, those headers are stripped, and then only repopulated if the request is authenticated.

So what are the preferred way of doing Azure Active Directory authentication on Azure Functions V2 (C#) right now. I still find the ClaimsPrincipal.Current is till null after I have added AAD authentication under "Networking -> Authentication / Authorization" for a Function app.

This has been merged into the runtime, and should be coming out in the next release. It will operate separately than V1, but there is currently a sample of how it will work here.

Thanks!!

Version 1.0.24 of Microsoft.NET.Sdk.Functions just appeared on NuGet dated 2018/11/8, which is about a week after @ConnorMcMahon's comment, but as far as I can tell, this still isn't working. I get this error:

Microsoft.Azure.WebJobs.Host: Error indexing method 'Function1.Run'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'principal' to type ClaimsPrincipal. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).

Debugging locally, it prints out this on startup:

Azure Functions Core Tools (2.1.748 Commit hash: 5db20665cf0c11bedaffc96d81c9baef7456acb3)
Function Runtime Version: 2.0.12134.0

In Azure, the settings page shows: "Runtime version: 2.0.12165.0 (~2)".

Evidently the version up on Azure is using a newer runtime than with local debugging, but I see the same error there too, reported in Application Insights.

So does this mean that this change didn't make it out into the most recent drop of the tools?

This has been merged into the runtime, and should be coming out in the next release. It will operate separately than V1, but there is currently a sample of how it will work here.

@ConnorMcMahon Will we also be able to easily use 3rd party IdPs? Like IdentityServer (which will be integrated into .NET Core....).

Thanks!

(And hey Ian, old chap! :-) )

Yeah this is absolutely not working in Functions V2 yet. Identity is still null :(

I have to confess, I do not like the approach having ClaimsIdentity injected directly into the function. I do use dependency injection via AutofacOnFunctions . Most probably, the instance of ClaimsPrincipal is going to be used somewhere else, maybe even in an attribute to reject unwanted calls to the azure function at all. Is the claimsprincipal also available in a different way than being injected directly into the Azure Function?

Actually, same thing here: I do get the same error message as @idg10.

@holgerleichsenring can you give me an example of the use cases you would expect to be able to use the ClaimsPrincipal for? I took a look at AutofacOnFunctions, and am unclear how that could utilize the ClaimsPrincipal.

Also, it looks like the last few updates of the functions runtime have been hotfixes. I don't think the change has gone out yet.

@ConnorMcMahon: Imagine an "AuthorizationManager" that wants to retrieve the ClaimsPrincipal instance to determine if a certain Azure Function is allowed to execute. This would be doable via another extension (like AutofacOnFunctions is implemented) or via a service that is going to be injected by the [Inject] attribute. The objective of dependency injection here is keeping everything out of the Azure Function that does not rely on the task in question. In this short sample it is about returning information from a repository. Like in asp.net core, authorization is mostly implemented with attributes, decorating the method or inline with a service that avoids having the same boilerplate code in all functions that need authorization:

    public static class DimensionReader
    {
        [FunctionName("DimensionReader")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
            HttpRequest req, ILogger log,
            [Inject] IRepository<Models.Dimension> dimensionRepository,
            [Inject] AuthorizationManager autohorizationManager)
        {
            log.LogInformation("DimensionReader WebRequest Start");

            try
            {
                if (!autohorizationManager.IsAllowed(AuthorizationPermissions.Read))
                {
                    return new BadRequestObjectResult("Unauthorized");
                }

                var dimensions = dimensionRepository.Get();
                log.LogInformation("DimensionReader WebRequest End");
                return new OkObjectResult(dimensions);
            }
            catch (Exception ex)
            {
                log.LogError("DimensionReader WebRequest failed", ex);
                return new BadRequestObjectResult(new {message = ex.Message});
            }
        }
    }

In this (very simple) example, it would not be too much effort to pass over the ClaimsPrincipal in the IsAllowed() method. Imagine that also underlying services may want to check permissions before returning results. This would mean, I would need to pass the ClaimsPrincipal everywhere instead of having a service/ a static that I can encapsulate as a service that can be used whereever it is necessary to do authorization checks.

@holgerleichsenring the scenario you're describing, not too dissimilar to the way ASP.NET handles similar concerns, would be better handled by our filters feature, which is something that is currently in preview (one of the reasons for that is the fact that some of the functionality to cleanly support what you're describing is not present).

The filters feature would really need to be adjusted to support these scenarios...scoping limitations make filters very hard to use for anything with IoC

  • be resolved per invocation, not globally

  • potentially fix the threading bugs in DryIoc (webjobs creates a function invocation IServiceScope, but the implementation of resolving IServiceProviders in dryioc is bugged - it relies on ThreadId to determine the right scope to inject....which means that anything async in the context of a function can’t use the IServiceProvider from that function specific scope)

  • allow filters to actually resolve stuff dynamically (like get access to an IBinder)

  • allow filters to change/add parameter values or short circuit function invocation

@jnevins-gcm indeed. Those are some of the reasons why the feature hasn't been released yet, though, #2 above is orthogonal. WebJobs doesn't create a service scope today (or uses DryIoc), that's all in the Functions runtime. We're tracking some improvements to that, but if you can open an issue and share more information about the problems you're running into related to that, it would be great to have that documented.

fair - where do you want me to open it? functions repo?

problem is having a ThreadScopeContext in asynchronous code

https://github.com/Azure/azure-functions-host/blob/73bcc8eb43504e6f7ae8474b311264b5ebf4f724/src/WebJobs.Script.WebHost/DependencyInjection/DryIoc/Container.cs#L7973

This repo (host) is fine.

Do you know if there's an issue tracking this with DryIoc? It would be nice to contribute back if fixed.

Thanks!

I had not seen one there.

In Function Runtime Version: 2.0.12175.0 still same error as @idg10 describes
Very excited for release :)

For HTTP trigger, couldn't we just add an azure access token (which should have the roles defined) to the header and just manually decode the token to get the role?

Found this issue after banging my head for a while trying to figure out why my azure function aad Express [ / EasyAuth ] setup of application security wasn't surfacing a non-null instance of Thread.CurrentPrincipal within my functions.

My take away from this 02/25/16 to 11/20/18 issue exchange is that I can expect Thread.CurrentPrincipal to be present in case of functions v1 setup where EasyAuth is enabled but not yet in v2 setups.

In addition it appears the discussion around the functions v2 support is based on using an approach for surfacing authentication/authorization as shown in this sample that will work in some soon to be released 1.0.24+ of Microsoft.NET.Sdk.Functions nupkg drop.

I'd ask why do that instead of following function attribute [Authorize] and intra-function (System.Threading.Thread.CurrentPrincipal as System.Security.Claims.ClaimsPrincipal).Identity/.IsInRole/.Claims and/or System.Web.Mvc.Controller.User.IsInRole apis that we are used to in asp.net web app environment allowing function entry and intra-function authorization behaviors like those outlined in "09/18/18 Authorization in a web app using Azure AD application roles & role claims" and associated github repo TaskController.cs implementation excerpt shown below?

[HttpPost]
[Authorize(Roles = "Admin, Writer, Approver")]
public ActionResult TaskSubmit(FormCollection formCollection)
{
    if (User.IsInRole("Admin") || User.IsInRole("Writer"))  { /* use Admin and Writer relevant activity */ }
    else if (User.IsInRole("Approver"))  { /* do Approver role specific activity */ }
}

q1. Don't folks want the functions story to be as similar as possible to enact Authentication and Authorization code in functions as it is in asp.net web app so one can take knowledge they have acquired in the latter and reuse when working with aad secured functions deployments?

q2. Does the azure functions authentication/authorization switch enable support for active clients, i.e. ones that acquire an oauth token and pass it in api request's using "Authorization: Bearer <token>" header setting, and/or or passive clients, i.e. ones that go through a browser based openid connect signin experience where eventually the api endpoint issues an endpoint specific session token, e.g. access_token cookie?

@myusrn Our current approach would allow you to do something rather similar with the ClaimsPrincipal object we provide, as the ClaimsPrincipal object has a .IsInRole() method. As for why we went with injecting it as a parameter, we went with our design for the following reasons:

  1. Functions V2 is on .NET Core, not full framework, and some of the way these things are handled (like Thread.CurrentPrincipal) have fundamentally changed.
  2. Trying to override the Authorize header to add in functions specific logic would be a decent amount of work that would only help the .NET precompiled case.
  3. Injecting the value as a parameter is a very similar to how our bindings and triggers work today. The model also carries over better to other languages.

We could potentially add some syntactic sugar in the future to make our programming model match up more with what .NET developers expect, but I would track that as a separate issue likely. In general, you can treat the ClaimsPrincipal we provide you with as identical to the one you can grab off of Thraed.CurrentPrincipal in asp.net web apps.

@ConnorMcMahon Thanks for the explanation - that makes a lot of sense to me.

Do you know when this is likely to be released? It appears that the runtime today doesn't support this yet.

We are hoping to start the deployment for this release tomorrow, and it will take several days to finish deploying world wide.

@ConnorMcMahon thanks for details that all sounds good.

q1. So we monitor for an azure functions | in-portal / function app settings / runtime version v2.0.12180.0 update AND vs17 | azure function project | managed nuget packages | installed | Microsoft.Net.Sdk.Functions v1.0.24 update in the coming week? Once that happens one should be able to use the pattern outlined in your sample referenced earlier to introspect into authenticated identity and leverage IsInRole checks within functions to enable role based access control [rbac] style authorization?

q2. Will using the pattern outlined in sample, where function method signatures updated to include a ClaimsPrincipal principal parameter, only require configuration as straight forward as visiting application settings | authentication/authorization | app service authentication = off -> on, action = allow anonymous -> login with aad, aad mgmt. mode = off -> express, create new aad app to make light it up?

q3. Does this enable requests secured using both mobile / desktop active client, i.e. token acquired directly from aad and passed in http Authorization header as "Bearer <token>>, as well as browser passive client, i.e. passive client redirect signin and function site issued session token access_token cookie scenarios?

q4. Your sample is based on In-Portal based code, i.e. HttpTrigger1 / Run.csx, and not Visual studio 2017 based code, i.e. Function1 / vs17project/Function1.cs . Can we expect the latter to work as well when update releases?

q5. Any insights on whether or not this vs17 | attach debugger issue is a known problem that this week's update addresses? I ask because once a person turns on authentication/authorization it would appear to require that all vs17 functions project debugging be done against cloud deployment since localhost func.exe isn't going to invoke express/easyauth filter processing of requests.

@ConnorMcMahon I just created a new azure function using in-portal Http-Trigger run.csx and function app settings is still showing Runtime version: 2.0.12180.0 (~2).

I pasted your sample code noted earlier in this issue exchange for enabling express[/easyauth] authentication/authorization.

When i hit save and run i get No value was provided for parameter 'principal'. in Logs output window. Any updates as to where things stand in terms of now having access in v2 deployments to ClaimsPrincipal principal injected parameter?

The deployment should have just finished deploying to all regions this morning.

Great! Will there also be an update to the CLI/core tools soon to support this when running locally - or at least not throw an error when we have a ClaimsPrincipal in the function method signature?

The way this is currently designed should not be throwing an error with a ClaimsPrincipal in the function method signature. The ClaimsPrincipal you would receive would only have an identity for the Functions authorization level as opposed to your EasyAuth identity.

As for EasyAuth + CLI, there has already been some dev work into this, but we are waiting until the codebase for EasyAuth on Windows and Linux are unified, to ensure the local experience has exact parity both Functions on Linux and Functions on Windows. Expect a release to support EasyAuth in the CLI in early 2019.

Thanks @ConnorMcMahon. I don't mind the ClaimsPrincipal coming through as null for now, but perhaps you can advise what I'm doing wrong here as it's not working when I run this locally using the default Visual Studio function template with a ClaimsPrincipal added. Here is the function method signature:

[FunctionName("MyFunc")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req,
    ClaimsPrincipal claimsPrincipal,
    ILogger log)
{
}

When I run this, I get this error:

Microsoft.Azure.WebJobs.Host: Error indexing method 'MyFunc.Run'.

Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'claimsPrincipal' to type ClaimsPrincipal. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. config.UseServiceBus(), config.UseTimers(), etc.).

But if I remove the ClaimsPrincipal from the signature, I don't get the error. I'm using version 1.0.24 of the Microsoft.NET.Sdk.Functions package, and the CLI is reporting it's running v2.0.3 of the core tools and Functions runtime version 2.0.12115.0.

Am I missing a step somewhere?

I also just tried this using the CLI from NPM, running v2.2.70 of the core tools and Function runtime 2.0.12175.0. I got the same result.

Ah, this is only supported in versions later than 2.0.12210.0. I'm not positive we have a CLI update for that version yet.

Thanks. So I guess for now, the only option is to ifdef the code out for
development builds?

On 4 Dec 2018, at 7:29 am, Connor McMahon notifications@github.com wrote:

Ah, this is only supported in versions later than 2.0.12210.0. I'm not
positive we have a CLI update for that version yet.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/Azure/azure-functions-host/issues/33#issuecomment-443858812,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AFpG2TAIK91NM49giSsYUeVncA6MVOAvks5u1YnBgaJpZM4HjH4L
.

I'm am trying to validate this new ClaimsPrincipal object access support initially using the in-portal author, compile and run experience. As of this morning my functions app | function apps settings is reporting Runtime version: 2.0.12210.0 (~2) so it would appear we are good on that front.

Under platform features | network | authentication/authorization i have app service authentication [ / easyauth ??? ] turned on and "Action to take when request is not authenticated" = "Log in with Azure Active Directory" so that private browser client tests will produce a browser private client session cookie access_token authenticated request leaving the desktop app public client authorization header bearer token authenticated request scenario for later.

Under platform features | network | authentication/authorization | authentication providers | Azure active directory i have management mode = express selected and have confirmed i see entry for this app in azure active directory blade's app registrations (preview) | all applications and enterprise applications views.

I am using a sample code reference function implementation.

When i run code in portal console it says error that @johndowns was seeing specifically the following.
2018-12-03T21:34:28.578 [Error] Executed 'Functions.HttpTrigger2' (Failed, Id=730632f3-affb-41b3-b918-d94168ae8602) | Object reference not set to an instance of an object.

When i open the function url in a private browser session I get prompted for authentication to which i respond with one of my azuread tenant user credentials and do the first time application consent approval. After that it just says HTTP 500 error | That’s odd... the website can’t display this page?

If i comment out string[] identityStrings = principal.Identities.Select(GetIdentityString).ToArray(); it runs w/o errors.

Insights on what i'm doing wrong to make this authN/authZ functionality work using in-portal authoring, compile, debug experience while awaiting the vs17 project + localhost func.exe cli support?

@myusrn It's possible that the identities on your claims principal are missing some of the claims that GetIdentityString are looking for. If you look at the /.auth/me endpoint, you can see what claims are populated from your AAD tenant.

@ConnorMcMahon thanks for followup.

I find name and other claims in the https://<my azure functions app url>/.auth/me returned payload.

To bypass the possibility of claim retrieval code issues, which incidentally one can address by including the ? null check operator in Value property references, i'm using a ClaimsPrincipal parameter injection test that just looks to confirm that IsAuthenticated and Name property accessors return true and some non-null value respectively.

I'm finding those calls produce unexpected principal.Identity.IsAuthenticated = 'False' and principal.Identity.Name = 'null' result using a new private browser instance against function where I'm assuming successfully signed in user by fact that i get prompted for credentials when opening https://<my azure functions app url>/api/HttpTrigger2?name=foobar url and visiting https://<my azure functions app url>/.auth/me in separate tab from the same private browser instance dumps authenticated user details.

Thoughts?

public static async Task<IActionResult>  Run(HttpRequest req, ILogger log, ClaimsPrincipal principal)
{
    log.LogInformation("C# HTTP trigger function processed a request."); 

    var isAuthenticated = principal.Identity.IsAuthenticated; 
    var idName = string.IsNullOrEmpty(principal.Identity.Name) ? "null" : principal.Identity.Name;
    log.LogInformation($"principal.Identity.IsAuthenticated = '{isAuthenticated}' and principal.Identity.Name = '{idName}'");
    var owner = (principal.FindFirst(ClaimTypes.NameIdentifier))?.Value;
    //string[] identityStrings = principal.Identities.Select(GetIdentityString).ToArray();

    return new OkObjectResult($"principal.Identity.IsAuthenticated = '{isAuthenticated}' and principal.Identity.Name = '{idName}'");
    //return new OkObjectResult(string.Join(";", identityStrings));
}

private static string GetIdentityString(ClaimsIdentity identity)
{
    var userIdClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
    if (userIdClaim != null)
    {
        // user identity
        var userNameClaim = identity.FindFirst(ClaimTypes.Name);
        return $"Identity: ({identity.AuthenticationType}, {userNameClaim?.Value}, {userIdClaim?.Value})";
    }
    else
    {
        // key based identity
        var authLevelClaim = identity.FindFirst("http://schemas.microsoft.com/2017/07/functions/claims/authlevel");
        var keyIdClaim = identity.FindFirst("http://schemas.microsoft.com/2017/07/functions/claims/keyid");
        return $"Identity: ({identity.AuthenticationType}, {authLevelClaim?.Value}, {keyIdClaim?.Value})";
    }
}

Happy it is working :)
How can I set up Auth locally (ClientId and Issuer)?
in local.setting.json -> Host?
thanks

@ConnorMcMahon I've just been testing this out and have run into what I think may be a bug (at least, I hope it is!).

I've configured AD authentication on my function app and disallowed anonymous requests. When I've been deploying a function with a ClaimsPrincipal parameter, I've found that:

  1. If the authLevel is set to function and the function key is provided then the request is processed, and the ClaimsPrincipal is populated with two identities - one from the function runtime and one from the AAD token.
  2. If the authLevel is set to anonymous and the function key is omitted then the request is processed, but the ClaimsPrincipal doesn't get the AAD identity populated.
  3. If the authLevel is set to anonymous and the function key is provided then the request is processed, and the ClaimsPrincipal gets both identities again.

FYI, the documentation says that when an AD token is used for authentication, the function authLevel should be set to anonymous - hence my assumption that this is a bug.

I'm not sure if this issue is the right place for this, or if I should open a new issue?

@johndowns, good catch! If you could open a new issue so I could track a PR against it, that would be great.

@johndowns @ConnorMcMahon are you folks using a desktop app authorization header bearer token secured requests to verify in portal function implementation with ". . . , ClaimsPrincipal principal)" parameter injection or are you using browser/user agent session cookie access_token secured requests?

@johndowns are you using a vs17 azure functions project deployment to verify ". . . , ClaimsPrincipal principal)" parameter injection and since this currently cannot be f5 locally debugged using %localappdata%\azurefunctionstools\releases\2.11.3\cli\func.exe are you deploying to cloud and using functions | | monitor | | invocation details to view log.LogInformation() instrumentation added to enable this interim debugging solution for vs17 functions project with ClaimsPrincipal usage?

@myusrn I can get the sample working with the following authentication methods

  1. In browser server-directed flow
  2. Bearer token with an AAD access token for EasyAuth
  3. X-ZUMO-AUTH session token. You can generate a session token by hitting /.auth/login/aad with a POST request with the following body
    { "id_token": "", "access_token": "" }

@ConnorMcMahon thanks for clarifications supported authentication methods that helps.

@johndowns thanks for finding and mentioning the bug you found on this thread as that was what was causing me to get the unexpected IsAuthenticated = falseand Identity: (, , ) results. Once i set in portal function | Integrate | Authorization = Anonymous -> Function and included code in request i started getting expected IsAuthenticated = true and Identity: (aad, [email protected], Rf-5wbc2D1QRXm0s9VlJfzmMjt-rQ3IYSp6aIjWAbr0);Identity: (WebJobsAuthLevel, Function, default) results.

Please share issue that gets created to track work on this related bug that really can leave a person confused as to why things seem to not be working.

Also note that when i did launched and f5 localhost debug of my vs17 functions project this afternoon it pulled down a new %localappdata%\azurefunctionstools\releases\2.14.0\cli\func.exe replacing the previous \2.11.3\cli\func.exe . The output window showed Function Runtime Version: 2.0.12210.0 implying that local debugging now has required 2.0.12210.0+ runtime version in place as well. I tested (. . . , ClaimsPrincipal principal) parameter injection in the function signature and it was okay with that and does execute with an IsAuthenticated = true identity present but no an authenticated user one, GetIdentityString() output was Identity: (WebJobsAuthLevel, Admin, ). Perhaps this is addressed by calling localhost debug instances using unit test or desktop app with Authorization header bearer token secured call vs browser/user agent session access_token cookie secured call.

@ConnorMcMahon I have opened https://github.com/Azure/azure-functions-host/issues/3857 as requested.

Anyone had succes this easyAuth authentication of request using desktop app / postman request using function authZ code and authorization header bearer token to authN request, i.e. what @ConnorMcMahon referred to as 2. Bearer token with an AAD access token for EasyAuth above?

I ask because i'm now testing that target scenario and am getting 401 Unauthorized against same setup that works if I use browser/user agent request with function authZ code and session access_token cookie secured request, i.e. what @ConnorMcMahon referred to as 1. In browser server-directed flow above.

I was able to use X-ZUMO-AUTH header, i.e. what @ConnorMcMahon referred to as option 3. X-ZUMO-AUTH session token acquired by hitting /.auth/login/aad with a POST request and body { "id_token": "", "access_token": "" } outlined above but using /.auth/login/aad GET that returned me to .auth/login/done#token=<json containing authentication_token / X-ZUMO-AUTH value> query string encoded result. I pulled the authenticationToken from that result and attached it to function request with function authZ code and X-ZUMO-AUTH header containing that session token. While this was reassuring seeing it work I really need option 2 authorization header bearer token story to work so I can acquire tokens using Microsoft authentication library [msal] in wpf/uwp desktop app for token acquisition and refreshes.

@myusrn, Could you share your function app name? You can share it privately by following these instructions. I can help you investigate why scenario 2 is not working for you. I just tested it the other day and it worked for my application.

@ConnorMcMahon,

Below is log streaming ouput guids from calling my functions app using browser openid connect session cookie secured request.

2018-12-06T19:18:27.230 [Information] Executing 'Functions.HttpTrigger1' (Reason='This function was programmatically called via the host APIs.', Id=3b1f14e5-f67e-4289-a297-4e914c7c4fdc)
. . . 
2018-12-06T19:18:27.232 [Information] Executed 'Functions.HttpTrigger1' (Succeeded, Id=3b1f14e5-f67e-4289-a297-4e914c7c4fdc)

In my wpf app where i'm using using azuread v2 compliant msft authentication library [msal] vs azuread v1 compliant azure ad authentication library [adal], nuget to acquire access token used in Authorization header Bearer <access token> secured api requests I have scopes set to scopes = new string[] { "https://myfunctionsapp.azurewebsites.net/user_impersonation" } which does produce token with "scp" claim set to "user_impersonation". I expect that doesn't help you repro in your desktop/mobile test client unless you also have the azuread provisioned clientId and tenantId.

I'm fine with granting your work or msa account access to my resource group and associated azuread if you need ability to look closer at simple inportal + easyauth & express security setup and/or the desktop/mobile client app entry I have in azuread that has api permissions configured to allow requesting token with scope setting that aligns with azure function api its trying to call. Can send me message at [email protected] .

Anyone had succes this easyAuth authentication of request using desktop app / postman request using function authZ code and authorization header bearer token to authN request, i.e. what @ConnorMcMahon referred to as 2. Bearer token with an AAD access token for EasyAuth above?

I ask because i'm now testing that target scenario and am getting 401 Unauthorized against same setup that works if I use browser/user agent request with function authZ code and session access_token cookie secured request, i.e. what @ConnorMcMahon referred to as 1. In browser server-directed flow above.

I was able to use X-ZUMO-AUTH header, i.e. what @ConnorMcMahon referred to as option 3. X-ZUMO-AUTH session token acquired by hitting /.auth/login/aad with a POST request and body { "id_token": "", "access_token": "" } outlined above but using /.auth/login/aad GET that returned me to .auth/login/done#token= query string encoded result. I pulled the authenticationToken from that result and attached it to function request with function authZ code and X-ZUMO-AUTH header containing that session token. While this was reassuring seeing it work I really need option 2 authorization header bearer token story to work so I can acquire tokens using Microsoft authentication library [msal] in wpf/uwp desktop app for token acquisition and refreshes.

Try this link: https://www.bruttin.com/2017/11/21/azure-api-postman.html

It shows you how to use postman correctly.

@ConnorMcMahon, Things working now using desktop/mobile app authorization header bearer token secured requests.

The issue and fix for me was to ensure that acquired token has audience "aud" claim was set to the application (client) id guid of the functions app azuread registered application.

When using new azuread v2 endpoints compatible microsoft authentication library [msal] I was providing the scope parameter that matched what shows up in registered app's api permission settings. Turns you can arbitrarily configure the starting part of that scope argument to be whatever you want the issued tokens audience claim to contain. So the following did the trick.

//var scopes = new string[] { "https://azfndn1ipt.azurewebsites.net/user_impersonation" }; // matching azfndn1ipt express provisioned entry
var scopes = new string[] { "5270888e-273e-4bba-9283-872088abe9f8/user_impersonation" }; // arbitrarily assigned value to first part of scope which is set to app(client)id of function app registration
authResult = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());

@bryans2k thank you for the postman article reference on how to enable oauth token acquisition from azuread endpoints within postman as I've always been acquiring token using scratch app with adal -> msal calls and then pasting the result into postman request to test calls versus less flexible scratch app httpClient requests.

I was able to successfully use it to acquire token matching adal/msal produced versions by using the same desktop/mobile app azuread app registration with required scope claim of "function app id/user_impersonation" with only change being need to add logical authentication | redirect uris such as "https://clientapp/auth".

Not sure if i'll ever need the X-ZUMO-AUTH token, for scenarios other than when trying to debug if its working when oauth bearer token is not, but if that arises i'm curious if you've also been able to configure postman authorization token acquisition to carry out the /.auth/login/aad GET followed extraction of access token from the .auth/login/done#token=<json containing authentication_token / X-ZUMO-AUTH value> url?

@myusrn I've never tried but I think you'd have to change the callback to your own website to log the callback from AAD. I don't think Postman offers an option to pass-through the callback.

Hi there,

When adding the ClaimsPrincipal to the function:
public static async Task<IActionResult> RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequest req, ILogger log, ClaimsPrincipal principal)

We have our roles defined in AAD and can clearly see the roles in the req param's bearer token.

However, the principal param is only populated with
{
"type": "WebJobsAuthLevel",
"level": "Admin"
}

Our roles are not defined there. We have tried using all AuthorizationLevel, though documentation says to use Anonymous.

Any updates on this issue?

@maryammadzadeh, do you have App Service Authentication / Authorization enabled? Unfortunately, at this time the ClaimsPrincipal does not take Bearer tokens unless you have configured that feature with AAD.

@ConnorMcMahon is it possible to test this functionality locally? Or do we have to deploy to get AAD auth integration to flow the claims principals of bearer tokens?

In the next month or two we are planning on releasing ways to test EasyAuth locally with the Functions CLI. The work on the CLI is actually already done, but we are working on giving the Linux and Windows version of EasyAuth parity so that the local development experience matches up exactly with the production experience.

Appreciate the response. Could you create a link between the PR enabling that functionality and this issue? Thanks!

@JoshCollinsMSFT [ & @ConnorMcMahon ] in the mean time I was able to make use of "unit testing azure functions v2" -> https://medium.com/@tsuyoshiushio/writing-unit-test-for-azure-durable-functions-80f2af07c65e guidance to enable localhost unit tests of function app with injected principal containing test case relevant identity and claims.

See https://github.com/myusrn/lnsexploration/blob/master/xUnit.Tests/FunctionControllerUnitTest.cs and https://github.com/myusrn/lnsexploration/blob/master/xUnit.Tests/AzFuncApp1WebApp1UnitTests.cs for my implementation.

For localhost integration tests I wrote my production implementation with the following types of logic "if (principal.IsInRoleFuncApp("SomeRbacAuthZRole") || req.Host.Value == "localhost:7071")" to cover that case until things Conner & Co are working on get released .

@ConnorMcMahon I do have app service authN/authR enabled. I think I am just waiting for your MR to complete. I verified that AuthorizationLevel.Function works with a function key. When AuthorizationLevel.Anonymous work, then I should see my claims in the principal.

In the next month or two we are planning on releasing ways to test EasyAuth locally with the Functions CLI. The work on the CLI is actually already done, but we are working on giving the Linux and Windows version of EasyAuth parity so that the local development experience matches up exactly with the production experience.

Is this something that is already released? Currently working on a project that uses azure function authentication but there are some key differences when developing locally.

My claims when the function is deployed:

[
  {
    "type": "stable_sid",
    "value": "sid:<id>"
  },
  {
    "type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
    "value": "sid:<id>"
  },
  {
    "type": "http://schemas.microsoft.com/identity/claims/identityprovider",
    "value": "twitter"
  },
  {
    "type": "ver",
    "value": "3"
  },
  {
    "type": "nbf",
    "value": "1555961219"
  },
  {
    "type": "exp",
    "value": "1558553219"
  },
  {
    "type": "iat",
    "value": "1555961219"
  },
  {
    "type": "iss",
    "value": "<host>"
  },
  {
    "type": "aud",
    "value": "<host>"
  }
]

vs the claims I have when making a request against localhost:

[
    {
        "type": "http://schemas.microsoft.com/2017/07/functions/claims/authlevel",
        "value": "Admin"
    }
]

So right now it's very cumbersome to develop locally.

The work was slightly de-prioritized, so it is not out yet unfortunately. I don't believe there is too much work to be done to finalize that work item yet, but at this time I can't give an ETA for that feature unfortunately.

One way you could test you code changes locally would be to create a second function app with the same exact Authentication/Authorization config that your production function app uses. This function app would that uses function proxies to route requests to your local app. You would likely need to expose your local app to the internet using something like ngrok. Then, requests made to that proxy function app would go through the authentication process and populate the proper headers to populate that user identity before hitting your local code.

I understand the above method is very clunky and not ideal, but unfortunately that is probably the best option until we get this feature out the door.

@ConnorMcMahon thanks for your message! However, that sounds overly complicated :sweat_smile:.

Right now I'm using a System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler to get the claims from the X-ZUMO-AUTH token when I see there is only one claim that is http://schemas.microsoft.com/2017/07/functions/claims/authlevel. Not a fan of having "debug" code in the production codebase, but it will have to do for now.

One slight issue with that solution is that the claims don't match completely. The http://schemas.microsoft.com/identity/claims/identityprovider claim type when parsing the jwt token is not present, but there is claim of typeidp that has the same value.

Hello @JordyLangen, EasyAuth is supported in both V1 and V2. Please open a new issue if you have a scenario which is not working. Thanks.

Closed issue by mistake, which has already been triaged and moved to the backlog milestone. Reopening...

Closing this issue as it has been repurposed a few times and it is now hard to follow. The original feature tracked by the issue has been completed. For any other issues around the feature, please open new issues.

Was this page helpful?
0 / 5 - 0 ratings