Wednesday, 20 May 2015

(Trying to) use ADAL from Mono

For a recent project I needed to consume a Web API secured using Azure Active Directory from a Linux file server. I had an existing Windows program that did the same thing so I decided to try running it under Mono, the Linux port of .NET.


I hadn't used Mono before and was pleasantly surprised to discover that most things just worked (except for a few quirks with HttpWebRequest which I will describe in another post).  The only problem came when I tried to call Web API methods secured with Azure Active Directory. In the Windows program I was using the excellent Active Directory Authentication Library for .NET (ADAL). This wasn't working in the Mono version and the "Break on Exception" functionality in MonoDevelop isn't great so I created a simple test app for this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net;

namespace BearerAuthTest
{
 class MainClass
 {
  public const string DownloadTarget = "https://yourapp.com/";
  public const string Authority = "https://login.windows.net/common";
  public const string ClientId = "e74f20b9-d3b3-4b68-8a54-088ea85b55a8";
  public const string ServerApplicationIdentifier = "https://your-azure-ad.com/your-server-app";
  public const string ClientRedirectUri = "https://your-azure-ad.com/your-client-app";

  public static void Main (string[] args)
  {
   AuthenticationContext context = new AuthenticationContext (Authority);
   AuthenticationResult result = context.AcquireToken(
    ServerApplicationIdentifier,
    ClientId,
    new Uri(ClientRedirectUri)
   );
   WebClient webClient = new WebClient ();
   string header = result.CreateAuthorizationHeader ();
   Console.WriteLine ("Header={0}", header);
   webClient.Headers.Add(HttpRequestHeader.Authorization, header);
   string downloaded = webClient.DownloadString(DownloadTarget);
   Console.WriteLine ("Downloaded:\n\n {0}", downloaded);
   Console.ReadLine ();
  }
 }
}

I've edited the constants to remove the references to my own application and Azure AD directory, but I'll just explain what they are:

  1. DownloadTarget - this is the Url in our Web API that we are attempting to do a GET from.
  2. Authority - Url of the Azure AD multi tenant app login screen.
  3. ClientId - Guid of the native application; we must have set this up in Azure AD and granted it access to our Web API.
  4. ServerApplicationIdentifier - Uri identifier of the the Azure AD application that the Web API uses for authentication,
  5. ClientRedirectUri - Redirect Uri of the native application.
Just to make that clear, there are two Azure AD applications involved here; the Web Application that hosts the Web API, whose configuration will look like this:


And the native application that is attempting to connect to the Web API, the configuration of which looks like this:


When I run this in MonoDevelop on my Ubuntu dev machine I get this exception when I hit line 18:


An exception occurs when attempting to create a WindowsFormsWebAuthenticationDialog object. If I had to guess I would say this object is probably trying to raise a dialogue box to attempt an authentication operation with a web endpoint (see, if you make the type name long enough you don't need documentation). Getting out ILSpy I can see that the Type Initialiser of the base class of WindowsFormsWebAuthenticationDialog is calling a native method in IEFrame.dll - obviously that's not going to be there on a Linux machine, hence the exception.

I didn't give up hope yet though, because I'd read in a post on on Vittorio Bertocci's blog that it is possible to request an Azure AD authentication token without raising the authentication dialogue, passing the username and password in the request. So I replaced lines 18-22 of my code above with this:


AuthenticationResult result = context.AcquireToken (ServerApplicationIdentifier, ClientId,
 new UserCredential ("user1@your-azure-ad.com", "YourPassword"));

And this worked! I thought I'd cracked it, so I updated my code and deployed it only to find that as soon as I tried to use it I got an "Operation is not Supported" error. After a bit of head scratching I worked out that the only significant difference was that I was trying to log in with a user from a different Azure AD directory, my live directory rather than my test one. And this turns out to be the significant difference because:
  1. The native "Your Client App" app and the test user are both from the "your-azure-ad.com" directory, which means that the users from this domain do not need to grant consent to be able to use the app.
  2. The native app and the live user are from different directories, so when a token is requested using the AcquireToken method the user does need to give their consent.
  3. Re-reading Vittorio's post on the AcquireToken overload that allows you to pass a username and password in the request he says (in the Constraints and Limitations section):
"Users do not have any opportunity of providing consent if username & password are passed directly."
So I'm officially stuffed. Multi-tenant apps will always need to establish consent and that can only be done by showing the UI, which depends on native Windows components.

I'm not without hope that this might work in the future; the pre-release of ADAL 3 includes support for raising the authentication dialogue on Android and iOS platforms, so maybe this will work on other Mono platforms eventually.

In the meantime for my app I have had to pursue other options for authenticating from Mono. I am currently using Client Certificates which seems to be working OK, although as there doesn't seem to be anyone who supplies these commercially I'm generating them myself.

No comments:

Post a Comment