I'm finishing off one of the last chapters which is on Mobile Services for my new book Learning Microsoft Azure for Packt Publishing. The book is based on an enterprise case study spanning multiple business domains and uses Azure Active Directory for authenticating the internal (used by company staff rather than the public) systems.
Using AD authentication is slightly different to the other OAuth providers such as Twitter and Facebook as the MobileServiceClient.LoginAsync method requires an access token obtained from the AuthenticationContext.AcquireTokenAsync method which calls the mobile service /login/aad endpoint. There are some good examples of doing this, however there is no detail on debugging the service locally.
In order to call the /login/aad endpoint locally we need to enable HTTPS on web server (IIS Express) and use the login URL like this https://localhost:12345/login/aad with the AuthenticationContext. The Windows Store Application doesn't like this endpoint as it's got a temporarly SSL certificate without a trusted root and we don't have the option to absorb untrusted certificates like we can in .Net clients using the ServicePointManager.ServiceCertificationValidationCallback like this:
ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => true;
So we get an exception with message: The underlying connection was closed. Could not establish trust relationship for the SSL/TLS secure channel when we call AuthenticationContext.AcquireTokenAsync.
I had a play about with the capabilities in the Package.appxmanifest to see if anything could be done to allow the certificate. I enabled "Shared User Certificate" which got rid of the exception, but then came back with an 401 Unauthorized exception on LoginAsync which I think is because this setting is for using an SSL cert for securing comms between the client and custom web services.
I don't think there is an easy way of easily getting this to work locally. We could apply a trusted SSL certificate to our local service, however this requires purchasing one in the first place and second requires additional configuration. The other option would be to create a temporary certicate and configure that on the local web server and configure certificates in the Package.appxmanifest which would need removing on publish.
This was bugging me for a while and I was close to giving up and just debugging against Azure, but I suddenly though we might be able to authenticate against a published service on Azure, then use the credentials with a client pointing at our local service...well, it works!
First thing to do is publish the Mobil Service, then modify the web.config app settings. Use the same keys as the published service to save switching them in code:
<add key="MS_MasterKey" value="YYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxx" /> <add key="MS_ApplicationKey" value="YYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxx" />
These settings aren't well documented, but add these with the MSAadClient id being the AD application for the Azure service (see this example) and the MSAadTenants setting the AD tenant domain:
<!-- Manually add these --> <add key="MS_AadClientID" value="XXXXXXXXXXXyyyyyyyyyyy" /> <add key="MS_AadTenants" value="myservice.onmicrosoft.com" />
In my auth base class I have something like this at the top:
private const string authority = "https://login.windows.net/myapplication.onmicrosoft.com"; private const string clientID = "xxxxxxxxxxxxxYYYYYYYYYYYYY"; private const string azureUri = "https://myservice.azure-mobile.net"; private const string resourceURI = "https://myservice.azure-mobile.net/login/aad"; #if DEBUG protected readonly static MobileServiceClient _mobileService = new MobileServiceClient( "http://localhost:58932/", "XXXXXXXXXXXXXXXyyyyyyyyyyyyyyyyy" ); #else protected readonly static MobileServiceClient _mobileService = new MobileServiceClient( "https://myservice.azure-mobile.net", "XXXXXXXXXXXXXXXyyyyyyyyyyyyyyyyy" ); #endif
When we're in DEBUG mode, we're using the local URL (HTTP not HTTPS) which will allow us to debug the service once we've logged into out AD Tenant.
Not at the login step, if we're in DEBUG mode, we quickly new-up a MobileServiceClient pointing to the published Mobile Service, login, the copy the user object into our static MobileServiceClient to be used by all classes implementing the base class:
var ac = new AuthenticationContext(authority); var ar = await ac.AcquireTokenAsync(resourceURI, clientID); var payload = new JObject(); payload["access_token"] = ar.AccessToken; MobileServiceUser user = null; #if DEBUG // Create temp client to loging to azure service, then pass user to local client var client = new MobileServiceClient(azureUri, _mobileService.ApplicationKey); user = await client.LoginAsync(_provider, payload); // Swap user _mobileService.CurrentUser = user; #else // Login user = await _mobileService.LoginAsync(_provider, payload); #endif
This workaround is really simple to implement and works really well, it's not really exploiting a security loophole either because even though we're getting an authentication token from a different service, the local service is still validating the token against the AD application with the same identifiers as the published version.