How to get current user identity in Azure Function with Azure Authentication?

一曲冷凌霜 提交于 2019-11-30 12:44:56

Using the Azure Function runtime v2.0.12309, you can retrieve the authenticated user information from the ClaimsPrincipal instance injected in the Run method:

public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
    HttpRequest httpRequest, 
    ILogger logger, 
    ClaimsPrincipal claimsPrincipal)
 {
            // Explores the authenticated user's claims in claimsPrincipal.
 }

It seems that one can get current user name from global state System.Security.Claims.ClaimsPrincipal.Current.Identity.Name (I didn't know that when I originally posted this question). However, it is unclear if this is a reliable or recommended method of getting logged in user info.

Example:

using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;

namespace FunctionApp
{
    public static class Function1
    {
        [FunctionName("HttpTriggerCSharp")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
        {
            return req.CreateResponse(HttpStatusCode.OK, "Hello " + ClaimsPrincipal.Current.Identity.Name);
        }
    }
}

First class integration with App Service EasyAuth is something that isn't in place yet, but we're tracking in our repo here. We're looking at this now and will likely have some improvements in this area very soon.

As you discovered, you can enable EasyAuth and it will require login, and will flow the authenticated principal through, allowing you to access it via standard .NET APIs like ClaimsPrincipal.Current, etc. However the problem is that you had to mark your method with auth level Anonymous which isn't what you want, and requires you to reject unauthenticated requests in your method (see notes in issue referenced above).

We'll iron all these issues out soon in an upcoming release.

Logically, AuthorizationLevel.Anonymous will not give you a current claims principal.

Unfortunately, just changing to AuthorizationLevel.Function doesn't help either: For me I am finding that ClaimsPrincipal.Current is null, even after authenticating successfully via Azure Active Directory B2C.

Finally, I tried AuthorizationLevel.User, but that isn't currently supported by azure functions see here

I believe you need to follow the steps as per the accepted answer on this SO question (I'm in process of trying it now ...)

Copied from my issue here, which was intended to find how to debug locally with an Azure Active Directory authenticated user.
https://stackoverflow.com/a/49402152/3602057

This will work with any provider that is configured on Azure with your Azure Function, and will work while for both local debugging and deployed scenarios.


Alright after a few more (OK, a LOT) hours, I have figured out a solution for now. This works in both local and deployed scenarios. I have posted a template solution here:

https://github.com/Mike-EEE/Stash/tree/master/AzureV2Authentication/AzureV2Authentication

Here are the steps that outline the overall process:

  1. Sign into your function at https://function-name.azurewebsites.net
  2. CTRL-SHIFT-C in Chrome -> Application -> Cookies -> -> AppServiceAuthSession -> Copy Value
  3. Open local.settings.json and paste value from previous step in AuthenticationToken setting.
  4. While you're there, paste in the URL from first step in AuthenticationBaseAddress
  5. Launch application.
  6. Cross fingers.
  7. Enjoy magic (Hopefully.)

Here is the main event:

public static class AuthenticationExtensions
{
    public static Authentication Authenticate(this HttpRequest @this)
    {
        var handler = new HttpClientHandler();
        var client = new HttpClient(handler) // Will want to make this a singleton.  Do not use in production environment.
        {
            BaseAddress = new Uri(Environment.GetEnvironmentVariable("AuthenticationBaseAddress") ?? new Uri(@this.GetDisplayUrl()).GetLeftPart(UriPartial.Authority))
        };
        handler.CookieContainer.Add(client.BaseAddress, new Cookie("AppServiceAuthSession", @this.Cookies["AppServiceAuthSession"] ?? Environment.GetEnvironmentVariable("AuthenticationToken")));

        var service = RestService.For<IAuthentication>(client);

        var result = service.GetCurrentAuthentication().Result.SingleOrDefault();
        return result;
    }
}

Note that:

  1. An HttpClient is created for each call. This is against best practices.
  2. Sample code is based on EasyAuth sample by @stuartleeks
  3. This solution makes use of the excellent Refit project to get its data.

Here are the remaining classes of interest, for the sake of completeness:

public class Authentication // structure based on sample here: https://cgillum.tech/2016/03/07/app-service-token-store/
{
    [JsonProperty("access_token", NullValueHandling = NullValueHandling.Ignore)]
    public string AccessToken { get; set; }
    [JsonProperty("provider_name", NullValueHandling = NullValueHandling.Ignore)]
    public string ProviderName { get; set; }
    [JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
    public string UserId { get; set; }
    [JsonProperty("user_claims", NullValueHandling = NullValueHandling.Ignore)]
    public AuthenticationClaim[] UserClaims { get; set; }
    [JsonProperty("access_token_secret", NullValueHandling = NullValueHandling.Ignore)]
    public string AccessTokenSecret { get; set; }
    [JsonProperty("authentication_token", NullValueHandling = NullValueHandling.Ignore)]
    public string AuthenticationToken { get; set; }
    [JsonProperty("expires_on", NullValueHandling = NullValueHandling.Ignore)]
    public string ExpiresOn { get; set; }
    [JsonProperty("id_token", NullValueHandling = NullValueHandling.Ignore)]
    public string IdToken { get; set; }
    [JsonProperty("refresh_token", NullValueHandling = NullValueHandling.Ignore)]
    public string RefreshToken { get; set; }
}
public class AuthenticationClaim
{
    [JsonProperty("typ")]
    public string Type { get; set; }
    [JsonProperty("val")]
    public string Value { get; set; }
}
interface IAuthentication
{
    [Get("/.auth/me")]
    Task<Authentication[]> GetCurrentAuthentication();
}
public static class Function1
{
    [FunctionName("Function1")]
    public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
    {
        log.Info("C# HTTP trigger function processed a request.");

        var authentication = req.Authenticate();

        return authentication != null
            ? (ActionResult)new OkObjectResult($"Hello, {authentication.UserId}")
            : new BadRequestObjectResult("Authentication not found. :(");
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!