Debugging Azure Active Directory Authentication with Azure Mobile Service .NET Backend When Running Locally

In a recent project one of the more challenging things was to debug the Azure Mobile Services (AMS) when running locally. For the most part it was pretty straight forward as a lot of the requests, whilst they required a user to be authenticated, the returned data wasn’t contingent on who the user was. When you run Azure Mobile Services locally, which you can with the .NET backend but not with the Node.js backend, by default it doesn’t enforce any of the security requirements. This makes it easy to debug scenarios where it doesn’t matter who the user is but very hard for those scenarios where the data needs to be altered based on who the user is, particularly if the user is being authenticated using Azure Active Directory (AAD). In this post I’ll walk through getting debugging to run locally.

The first thing I need to do is to configure the Mobile Service to enforce the same security when running locally, as if it were running in Azure. I do this by adding the following immediately following the ServiceConfig.Initialize line:

// This tells the local mobile service project to run as if it is being hosted in Azure,
// including honoring the AuthorizeLevel settings. Without this setting, all HTTP requests to
// localhost are permitted without authentication despite the AuthorizeLevel setting.
config.SetIsHosted(true);

Side Note (feel free to skip over this section if you’re not interested on how I attempted to get this to work)

My initial approach was to use what I new about the way AAD connects with AMS which involves an application within AAD that defines the relationship between the native client (which is authenticating) and the AMS (which is the resource it is requesting access to). As my AMS is running locally it is hosted at http://localhost:51539/ I thought that I needed to create another AAD Web application pointing at http://localhost:51539/login/aad eg

image

And then in the Native Client AAD application I had to delegate permissions to the new AAD Web application eg

image

In the native application, in this case my WPF application, I changed both the resource url for the AAD authentication and the mobile services url to use http://localhost:51539/ ie http://localhost:51539/login/aad for the resource url and just http://localhost:51539/ for the mobile services url. This seemed to work correctly initially as it displayed the sign in prompt, I could sign in and get back an AAD access token. Unfortunately when this was passed to the AMS it failed miserably with an unauthorised exception.

Note: I actually feel what I did here was correct that that the Mobile Services team are incorrectly validating the claims based on the illusion that the local services is being hosted in the cloud. This means you actually need to authenticate with AAD as if you were requesting access to the resource https://realestateinspector.azure-mobile.net/login/aad and then actually connect to the locally running mobile service.

End of Side Note – Here’s how you really do it

Ok, so you DON’T need to change the url that is being specified as the resource when signing into AAD. However, you DO need to change the url that is used when creating the instance of the mobile service client. My constants file now looks like this:

#if DEBUG
#define DEBUGLOCAL
#endif

public static class Constants
{
    public const string ADTenant = “realestateinspector.onmicrosoft.com”;
    public const string ADAuthority=”
https://login.windows.net/” + ADTenant;

    public const string ADNativeClientApplicationClientId = “a5a10ee9-f871-4bde-997f-3f1c323fefa5”;

    public const string ADRedirectUri = “http://builttoroam.com”;

#if DEBUGLOCAL
    public const string ActualMobileServiceRootUri = “
https://realestateinspector.azure-mobile.net/”;
    public const string MobileServiceRootUri = “http://localhost:51539/”;
    public const string MobileServiceAppIdUri = ActualMobileServiceRootUri + “login/aad”;
#else
    public const string MobileServiceRootUri = “
https://realestateinspector.azure-mobile.net/”;
    public const string MobileServiceAppIdUri = MobileServiceRootUri + “login/aad”;
#endif
}

Note that I’ve used an inline compilation constant to allow me to switch from debugging against the local services versus those hosted in the cloud.

If you attempt to run this, the user will be directed to authenticate using the AAD signin with a resource https://realestateinspector.azure-mobile.net/login/aad and then the application will attempt to login to the Mobile Service running locally at http://localhost:51539/ (Note – I typically set both the native client project (in this case the WPF application) and the Mobile Service project as start projects). However, you’ll still get an unauthorized exception because the Mobile Service actual does verify that the claims in the access token returned from AAD match the settings of the Mobile Services (excluding the base url that the service is being run from – see my early argument why this imho is the wrong approach). Let’s first take a look at the access token using the site http://jwt.io.

image 

If you look in the Web.config file for the Mobile service you’ll see that there is a list of Mobile Service settings in the appSettings section. The first of which is typically the MS_MobileServiceName and I’m guessing it’s this setting that is being used to validate that the claim in the token is for the right mobile serivce – in the token you can see that the resource is https://realestateinspector.azure-mobile.net/login/aad where the only piece that varies between Mobile Services is the service name.

<add key=”MS_MobileServiceName” value=”realestateinspector” />
The next thing to look at is in the actual login request that the mobile service client library makes:

POST: https://realestateinspector.azure-mobile.net/login/aad
X-ZUMO-INSTALLATION-ID: c2ac49d1-af97-472b-af61-bfb06663f137
X-ZUMO-APPLICATION: wpxaIplpeXtknXQhqXiVlZAPYQEBcg12
X-ZUMO-AUTH: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ1cm46bWljcm9zb2Z0OndpbmRvd3MtYXp1cmU6enVtbyIsImF1ZCI6InVybjptaWNyb3NvZnQ6d2luZG93cy1henVyZTp6dW1vIiwibmJmIjoxNDIyMDkxOTY3LCJleHAiOjE0MjIwOTU0OTYsInVybjptaWNyb3NvZnQ6Y3JlZGVudGlhbHMiOiJ7XCJ0ZW5hbnRJZFwiOlwiZTY4OGQ1OTQtODY0My00YmRmLTllNGMtMGJlOGJjYmM2NDVmXCIsXCJvYmplY3RJZFwiOlwiYWJkNzc5MjgtNDFkMS00YzNjLThmNmYtMmE1NmQ4NDRkMDVlXCIsXCJhY2Nlc3NUb2tlblwiOlwiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKU1V6STFOaUlzSW5nMWRDSTZJbXR5YVUxUVpHMUNkbmcyT0hOclZEZ3RiVkJCUWpOQ2MyVmxRU0o5LmV5SmhkV1FpT2lKb2RIUndjem92TDNKbFlXeGxjM1JoZEdWcGJuTndaV04wYjNJdVlYcDFjbVV0Ylc5aWFXeGxMbTVsZEM5c2IyZHBiaTloWVdRaUxDSnBjM01pT2lKb2RIUndjem92TDNOMGN5NTNhVzVrYjNkekxtNWxkQzlsTmpnNFpEVTVOQzA0TmpRekxUUmlaR1l0T1dVMFl5MHdZbVU0WW1OaVl6WTBOV1l2SWl3aWFXRjBJam94TkRJeU1Ea3hOalUyTENKdVltWWlPakUwTWpJd09URTJOVFlzSW1WNGNDSTZNVFF5TWpBNU5UVTFOaXdpZG1WeUlqb2lNUzR3SWl3aWRHbGtJam9pWlRZNE9HUTFPVFF0T0RZME15MDBZbVJtTFRsbE5HTXRNR0psT0dKalltTTJORFZtSWl3aVlXMXlJanBiSW5CM1pDSmRMQ0p2YVdRaU9pSmhZbVEzTnpreU9DMDBNV1F4TFRSak0yTXRPR1kyWmkweVlUVTJaRGcwTkdRd05XVWlMQ0oxY0c0aU9pSnBibk53WldOMGIzSkFjbVZoYkdWemRHRjBaV2x1YzNCbFkzUnZjaTV2Ym0xcFkzSnZjMjltZEM1amIyMGlMQ0p6ZFdJaU9pSm5WMlJUY0VwbllqSnBUbkpyWm5wbVlUQlViWGhPTFZGeU0yMXFhamhoY0ZoZlYxbG5lVGRrVDNOUklpd2libUZ0WlNJNklrbHVjM0JsWTNSdmNpSXNJblZ1YVhGMVpWOXVZVzFsSWpvaWFXNXpjR1ZqZEc5eVFISmxZV3hsYzNSaGRHVnBibk53WldOMGIzSXViMjV0YVdOeWIzTnZablF1WTI5dElpd2lZWEJ3YVdRaU9pSmhOV0V4TUdWbE9TMW1PRGN4TFRSaVpHVXRPVGszWmkwelpqRmpNekl6Wm1WbVlUVWlMQ0poY0hCcFpHRmpjaUk2SWpBaUxDSnpZM0FpT2lKMWMyVnlYMmx0Y0dWeWMyOXVZWFJwYjI0aUxDSmhZM0lpT2lJeEluMC5IVEhlUEFEcW5XYjE2ZTdwYTQ1VkppdFlTV2pvanZzaEwyZ3I4cTR3S1k2QkpWNF9INXBoQklmQ1p3RUtGeU05MlpQWkpza1NDVkl5QlNvNnU0eTgyUlJKcWxmVW9NN3hjRG1zZ0dOVlNYZzhoOGhJWm56X1ZIcGg4ZUp4TXRvZ0VrR2tZWXFld0R5Wi1zWlVTaVZ5ckhpZWlCd2Qzd2M4T0hQaDRXeGUwbUh6OHItekZoOGtmY2lQWW9KRkJ3aGczVGFaZ3hMQWJnaWVMUG83V1ZBUFpIUjV5SG1UaW02RXhQOGdUejQ0WmFiYTI3QnpISUI1dV9oa1F2QWlmU2M2blBXcVZwcC1CamZQM25mbXQwai0xemkwb1RHMVlvMjZNQzNXSHhCTUdKbGpKcVg3UmhBenV4anZESDFyRG5PeE9YaldSVk1qV2gyNzRXUjdXa0YwM0FcIn0iLCJ1aWQiOiJBYWQ6Z1dkU3BKZ2IyaU5ya2Z6ZmEwVG14Ti1RcjNtamo4YXBYX1dZZ3k3ZE9zUSIsInZlciI6IjIifQ.s6fjFOzFhdvvYgL3yD3lUEiUcxALg-avOvwJb1gILDU
Accept: application/json
User-Agent: ZUMO/1.3 (lang=Managed; os=Windows; os_version=6.2.0.9200; arch=Win32NT; version=1.3.21121.0)
X-ZUMO-VERSION: ZUMO/1.3 (lang=Managed; os=Windows; os_version=6.2.0.9200; arch=Win32NT; version=1.3.21121.0)
Content-Type: application/json; charset=utf-8
Host: localhost:51539
Cookie: ARRAffinity=0289d9a2e779a2431db31b4a154e84828a77f89dbbe1fe391d5fe9794f54f970
Content-Length: 1237
Expect: 100-continue
Accept-Encoding: gzip
Connection: Keep-Alive

{
  “access_token”: “eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImtyaU1QZG1Cdng2OHNrVDgtbVBBQjNCc2VlQSJ9.eyJhdWQiOiJodHRwczovL3JlYWxlc3RhdGVpbnNwZWN0b3IuYXp1cmUtbW9iaWxlLm5ldC9sb2dpbi9hYWQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9lNjg4ZDU5NC04NjQzLTRiZGYtOWU0Yy0wYmU4YmNiYzY0NWYvIiwiaWF0IjoxNDIyMDkxNjU2LCJuYmYiOjE0MjIwOTE2NTYsImV4cCI6MTQyMjA5NTU1NiwidmVyIjoiMS4wIiwidGlkIjoiZTY4OGQ1OTQtODY0My00YmRmLTllNGMtMGJlOGJjYmM2NDVmIiwiYW1yIjpbInB3ZCJdLCJvaWQiOiJhYmQ3NzkyOC00MWQxLTRjM2MtOGY2Zi0yYTU2ZDg0NGQwNWUiLCJ1cG4iOiJpbnNwZWN0b3JAcmVhbGVzdGF0ZWluc3BlY3Rvci5vbm1pY3Jvc29mdC5jb20iLCJzdWIiOiJnV2RTcEpnYjJpTnJrZnpmYTBUbXhOLVFyM21qajhhcFhfV1lneTdkT3NRIiwibmFtZSI6Ikluc3BlY3RvciIsInVuaXF1ZV9uYW1lIjoiaW5zcGVjdG9yQHJlYWxlc3RhdGVpbnNwZWN0b3Iub25taWNyb3NvZnQuY29tIiwiYXBwaWQiOiJhNWExMGVlOS1mODcxLTRiZGUtOTk3Zi0zZjFjMzIzZmVmYTUiLCJhcHBpZGFjciI6IjAiLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLCJhY3IiOiIxIn0.HTHePADqnWb16e7pa45VJitYSWjojvshL2gr8q4wKY6BJV4_H5phBIfCZwEKFyM92ZPZJskSCVIyBSo6u4y82RRJqlfUoM7xcDmsgGNVSXg8h8hIZnz_VHph8eJxMtogEkGkYYqewDyZ-sZUSiVyrHieiBwd3wc8OHPh4Wxe0mHz8r-zFh8kfciPYoJFBwhg3TaZgxLAbgieLPo7WVAPZHR5yHmTim6ExP8gTz44Zaba27BzHIB5u_hkQvAifSc6nPWqVpp-BjfP3nfmt0j-1zi0oTG1Yo26MC3WHxBMGJljJqX7RhAzuxjvDH1rDnOxOXjWRVMjWh274WR7WkF03A”
}

The X-ZUMO-APPLICATION header specified in the request needs to match the MS_ApplicationKey setting specified in the Web.config file. Most of these settings have a default value of “Overridden by portal settings” which, as it implies, is because they are overridden when the Mobile Service is hosted in the cloud. However, when running locally some of these settings need to be specified in order to get authentication to work. In this case it’s both the MS_ApplicationKey and MS_AadTenants:

<add key=”MS_ApplicationKey” value=”wpxaIplpeXtknXQhqXiVlZAPYQEBcg12″ />
<add key=”MS_AadTenants” value=”realestateinspector.onmicrosoft.com” />

With this in place, running the native application again and you can login, retrieve the AAD access token, and then when the mobile service client attempts to login it will do successfully. When you then make a call to one of the controllers, you can set a break point and access the User property to retrieve information about the current user and their claims. This image shows the AAD claim for this user.

 

image

Leave a comment