Exposing Azure Function web API to native & web clients through Azure AD authentication

Problem

Azure AD writeups are prevalent but I was really struggling to find examples of calling the same Azure Function API, secured by Azure AD Authentication, by both Native as well as Web clients (since we can only select one app type in the Azure AD App registration, not both).

TL;DR

The kicker solution for me was having both a web and native App registration (i.e. two Client Id’s) and providing the WEB App registration’s Application Id as the “RESOURCE” parameter to the AuthenticationContext.AcquireTokenAsync() call in the Native app (see code sample below).

So the web registration is tied directly to the Azure Function… and then we’re piggybacking the web registration by requesting the web as the resource parameter in the native client call … i haven’t seen this documented yet so i can’t say whether this is an officially preferred solution.

Basic Steps

This is a good getting started guide guide, in parity with current landscape.

  1. get your Azure Function working as a web api… probably doesn’t matter whether web or native comes first but it seems like the web is more “trusted” from an OAuth standpoint and more clearly documented… OAuth refers to native clients as “public” and requiring a couple more OAuth contortions than web clients.
  2. create a Web type entry for your Function under New Portal > Azure Active Directory > App registrations… all the defaults are good, except you’ll need to create the Reply URLs that are valid for you… reply url is a parameter to your ADAL.js client call… in the end this entry provides the crucial Application Id aka Client Id
  3. now configure this web registration for AD Auth via New Portal > App Services > {your Function app} > Function app settings > Configure authentication > Authentication Providers > Azure AD > Express >
    1. Azure AD App = the Web App registration name you gave above
  4. Now create another Azure AD > App registration as Native type and (HERE’S THE KICKER) > Settings > Required Permissions > Add > Select an API > TYPE IN YOUR web App registration name in the search box and it’ll show up to be selected
  5. finally, use the Application Id guid from your web app as the RESOURCE parameter to the AcquireTokenAsync() call in your native app

Working ADAL.js web client code sample

function adalResultHandler(err, token) {
  if (err) {
    self.userName(null);
    lobibox.notify("error", { size: "mini", title: "Azure AD Auth", msg: err });
    return false;
  } else {
    self.userName(adal.getCachedUser().profile.name);
    //lobibox.notify("info", { size: "mini", msg: "login successful\nuser: " + user.profile.name + "\ntoken: " + token });
    return true;
  }
}

var adal = new AuthenticationContext({
  instance: "https://login.microsoftonline.com/",
  tenant: "{your domain}.onmicrosoft.com",
  clientId: "{your web guid}", // your Azure AD > App registrationS > {your web api} > APPLICATION ID
  //NUGGET: these "reply URLs" are set under Azure Portal > AD > App registrations > {your App Service} > Settings > Reply URLs
  //NOT under {your App Service} > Settings > (Manage) Auth > AD > Redirect URLs !!!
  redirectUri: window.location.href, //REPLACE WITH YOUR REDIRECT URL
  popUp: true
});

adal.callback = function (err, token) { if (adalResultHandler(err, token)) doSomething(); }

adal.login();

Working Xamarin Native iOS app client code sample

private const string Instance = "https://login.microsoftonline.com";
private const string Tenant = "{your domain}.onmicrosoft.com"; //common //COMMON OR YOUR TENANT ID // "hfcazure.com", //"4be68759-0968-4760-b716-f82711a28fcb", //http://stackoverflow.com/questions/26384034/how-to-get-the-azure-account-tenant-id
private const string ClientId = "{your native guid}"; //from your Azure AD > App registrations > {your ***NATIVE*** api} > APPLICATION ID
private const string RedirectUri = "https://{your azure function api name}.azurewebsites.net";
private const string ResourceId = "{your web guid}"; //take this from your Azure AD > App registrations > {your ***WEB*** api} > APPLICATION ID // **isn't that interesting, we're requesting another API as the "resource" of this api**

var Azure_OAuth2_Authority_Url = $"{Instance}/{Tenant}/oauth2/authorize");
var authContext = new AuthenticationContext(Azure_OAuth2_Authority_Url);

var authResult = await authContext.AcquireTokenAsync(ResourceId, ClientId, new Uri(RedirectUri), await _platformParameters.GetAsync()); //_platformParameters is something i whipped up special
CurrentUser = new HfcUserAuth
{
  FirstName = authResult.UserInfo?.GivenName,
  LastName = authResult.UserInfo?.FamilyName,
  AccessToken = authResult.AccessToken,
  IdToken = authResult.IdToken
};

Typical error responses

Various attempts at sussing out a valid resource value for the AcquireTokenAsync() in my Xamarin Forms native iOS app would yield the following error:
AADSTS65005: The client application has requested access to resource <xyz>. This request has failed because the client has not specified this resource in its requiredResourceAccess list

i was also getting these where {app} was the resource i was passing when i had the ClientId parameter wrong
AADSTS50001: The application named {app} was not found in the tenant named {tentant}.

Helpful references

What is my Tenant Id or “Authority” URL ???

Wanted to mention this in closing since “Tentant” is currently so ambiguously referred to in the documentation i ran into…
New Portal > Azure Active Directory > App registrations > Endpoints is where you pull the “Authority” Url from the “OAUTH 2.0 AUTHORIZATION ENDPOINT” slot – the main argument for new AuthenticationContext()

for example:
https://login.windows.net/9198d419-6ce5-4229-a457-8c38421f7466/oauth2/authorize
this “9198…” guid is your Tenant Id (don’t worry this one is made up)

our tenant appears to be simply our azure ad domain name, at least in typical configurations… so this works here as well:
https://login.windows.net/XYZ.onmicrosoft.com/oauth2/authorize

image

Lighter Spin on ADAL in Xamarin Forms

tl;dr

new-up the elusive “PlatformParameters” in your AppDeligate.cs::FinishedLoading / MainActivity.cs::OnCreate
 

ts;wm (too short; want more ; )

thankfully we have solid writeups on ADAL with XF… this post is just me trying to boil it down to essence and PCL as much as possible…
(BTW: ADAL = Active Directory Auth Lib… i needed it for PowerBI embedding)

  1. http://www.appzinside.com/2016/02/22/implement-adal-for-cross-platform-xamarin-applications/
  2. https://blog.xamarin.com/authenticate-mobile-apps-using-microsoft-authentication-library/

the first post keeps the platform specific surface area pretty minimal but also winds up wrappering the stock ADAL classes quite a bit…
the second post seems pretty minimal and leverages CustomRenderers for the right timing to grab this context… seems like a good general trick to tuck away…
 

the approach i came to is grabbing this context right up front in app initialization and then providing it through dependency injection later…
both pieces of that are basically one liners which feels nice
also it’s now conveniently available to other services should needs arise…
and theoretically we’ve kept things clean for TDD but honestly i don’t readily see how to test this flow since it requires interactive auth… i’ll have to read up on how people generally recommend mocking this kind of thing
 

iOS AppDeligate.cs::FinishedLoading()

  public partial class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
  {
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
      Xamarin.Forms.Forms.Init();

      var prismApp = new App(new iOSInitializer());
      LoadApplication(prismApp);

      var finishedLaunchingResult = base.FinishedLaunching(app, options);
      //KeyWindow won't be populated until after FinishedLaunching
      prismApp.Container.RegisterInstance(typeof(IPlatformParameters), new PlatformParameters(UIApplication.SharedApplication.KeyWindow.RootViewController)); // ** here's the beef **

      return finishedLaunchingResult;
      }
  }

Android MainActivity.cs::OnCreate

  public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
  {
    protected override void OnCreate(Bundle bundle)
    {

      base.OnCreate(bundle);

      Xamarin.Forms.Forms.Init(this, bundle);
      var prismApp = new App(new AndroidInitializer());
      LoadApplication(prismApp);

      prismApp.Container.RegisterInstance(typeof(IPlatformParameters), new PlatformParameters(this)); // ** here's the beef **
    }
  }

then later in calling code just reference via DI

    public PowerBIService(IPlatformParameters platformParameters)
    {
      _platformParameters = platformParameters;
    }

List all your Azure RDP’s

Get-AzureVM | #this first one gets the entire list of VMs in subscription
    Get-AzureVM | # this one gets the detailed object for each specific VM
    %{
        $port = ($_ | Get-AzureEndpoint | ? {$_.name -like "Remote*"})[0].Port;
        $null = $_.DNSName -match 'http://(.*?)/'
        write-host "$($_.Name) - $($matches[1]):$($port)"
    }