Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it Possible to Debug Azure Functions Locally with Azure Active Directory Authentication?

I have been spending the day learning how Azure Functions work. I have gotten my Azure Function registered with my Azure Active Directory, and have registered this application to be secured by way of my Azure Active Directory provide in my Azure Portal.

I have deployed this to Azure and everything works as expected, asking for my Azure AD User account, and once I sign in it shows me my HelloWorld Azure Function as I expect.

Additionally, I have been able to debug my Azure Function locally. However, locally it is using the AuthorizationLevel as configured on my HttpTriggerAttribute (AuthorizationLevel.Anonymous).

This, of course, is ignored when deployed in Azure, but locally now I have lost my user identity as it is configured to be anonymous and not using Azure Active Directory.

Is there a way to enable Azure Active Directory authentication on my locally deployed Azure Function?

To be clear here, I would like to sign in with my Azure Function locally just as I do with my deployed Azure Function (so I will be redirected to login.microsoftonline.com to login), but have that same identity be available to my local Azure Function development environment.

Thank you for any assistance you can provide!

like image 766
Mike-E Avatar asked Mar 20 '18 17:03

Mike-E


2 Answers

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-E-angelo/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. :(");
    }
}
like image 124
Mike-E Avatar answered Nov 15 '22 08:11

Mike-E


Here is another alternative if you are developing a SPA with JWT tokens that uses Azure-AD or Azure B2C via Easy Auth.

like image 40
David Yates Avatar answered Nov 15 '22 07:11

David Yates