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.

Monday, 11 May 2015

NuGet Package Restore and Unit Test filtering in TFS Build Online

In a previous post I blogged about how to set up a project so that the NuGet packages aren't added to source control. This post is a sort-of sequel to that one, looking at basic steps in getting the same project building in TFS Build and having its unit tests run by TFS Build. In my case I'm using TFS Build Online, but the steps should be pretty similar in an on premise installation of TFS Build.

We're aiming to end up with this:



This is what we do:

1. Create a new Build Definition using an appropriate build process file

New Build Definitions are created in Visual Studio 2013 in Team Explorer, Builds, click on the "New Build Definition" link.

You are shown a tabbed properties sheet. Most of the tabs are fairly self explanatory but the most complicated one is the "Process" tab. The "Build process file" which you select at the top, controls which "Build process parameters" you are shown at the bottom.

The key here is that we need to select a build process file that will restore NuGet packages before building as we are not checking our NuGet packages into TFS. The default build process file in my version of TFS is "DefaultTemplate.11.1.xaml"; this will not restore NuGet packages so it's no good for our purposes. In TFS Build Online there is a build process file called "TfvcTemplate.12.xaml" available which does restore NuGet packages before build, so I use this.

(In the end the build process file is simply a WF 4 workflow; if you want to you can customise it and even add WF activities that you have defined yourself)

2. Specify the solution(s) to build and unit test filters

This is where we set the Build process parameters:



In the "2.Build" section set the solutions to build as shown above.

Now, the test filters.  You may not want to use test filters of course, you may want to run all your unit tests. In my case I have an Azure Cloud Services application that uses Azure table storage for most of the backing storage. I therefore have (roughly) three kinds of unit tests:

  1. Tests that test the repository layer and therefore need a running Azure Storage Emulator to work.
  2. Tests that have some kind of dependency upon the environment being configured in a certain way (in production this is achieved by using Cloud Services Startup tasks).
  3. Tests that use a mock implementation for the repository layer and do not have any dependency on the environment other than using file system directories available through the TestContext object.

Tests of types (1) and (2) will always fail if run by TFS Build; how do I tell TFS Build that I only want to run tests of type (3)?  Answer: using the Test case filter property in my build process parameters (if you're using a different build process file the property may be called something different, but it will almost certainly be there somewhere). I indicate the tests that are of type (1) or (2) by decorating them with TestCategory attributes, as shown below:


        [TestMethod, TestCategory("RequiresStorage")]
        public void CreateAndRetrieveUser()

I give tests of type (1) a TestCategory of "RequiresStorage" and tests of type (2) a TestCategory of "RequiresInfrastructure". Then I set the Test case filter to:


TestCategory!=RequiresStorage&TestCategory!=RequiresInfrastructure

as shown above.  That's all!

Tuesday, 5 May 2015

Keeping NuGet packages out of TFS

When you start working with Visual Studio 2013 and TFS, by default Visual Studio adds your NuGet packages to TFS Source Control. This works OK but it's a bit backward; the whole point of having NuGet is that it acts as the repository for these packages so your source control system doesn't have to. And for one of my projects packages\ now contains 163 MB which is a lot of lard for source control to handle. So I decided to try and find out how to exclude the packages from TFS and keep them out.

I've read a few posts on how to do this:
So this is a step-by-step process of how to create a new solution and have (almost) all the packages kept out of TFS.

1. Create a .tfignore file

In the TFS folder that is the parent folder of all my projects I create a ".tfignore" file containing the following:


1
2
3
4
5
*.user
*.suo
bin
obj
packages

This instructs TFS to ignore any folder called "packages". Add the file to source control so that all your projects on all your dev machines get the benefit of this.

You would have thought that that is it, but it's not...

2. Create my new project by initially creating an empty solution file

The option to create a Blank Solution is in the "Other Project Types" section:


Close Visual Studio (if you don't do this, it won't work).

3. Add a "nuget.config" in a ".nuget" folder within the new solution

Create a ".nuget" folder within the solution folder and add a "nuget.config" file to it that contains the following:


1
2
3
4
5
<configuration>
    <solution>
        <add key="disableSourceControlIntegration" value="true" />
    </solution>
</configuration>

This is needed to stop NuGet.exe from trying to add packages to source control (it ignores the .tfignore file, a known issue). We want to do this before we add any projects so that NuGet uses our new settings for the packages in the initial project.

4. NOW add your projects to the solution...

In my case I added an MVC project and a Tests project.  I'm hoping that in the Pending Changes window I should see NOTHING being added from the "packages" folder even though my MVC project uses loads of packages.

And this is what I see:


Almost nothing. I can't seem to stop nuget / TFS (whichever one it is that is doing it) from adding the repositories.config to source control.  Fortunately this file is only about 200 bytes as opposed to the 163 MB I was putting in source control before.

If you use Git rather than TFS for source control then the only difference to this process would be that you would need to create a ".gitignore" instead of a ".tfignore" file.