ADAL Plugin for Apache Cordova: Deep Dive
I am super happy to finally be able to talk about this! Today our friends in MS open Tech are releasing the first developer preview of an Apache Cordova plugin for ADAL, the result of few months of merry collaboration between out teams. This plugin will be yet another arrow in your developer quiver for adding the power of Azure AD to your multi-platform applications – namely, the ones targeting iOS, Android, Windows Store and Windows Phone. You can find the announcement posts here and here. And if you want to get started quickly, instead of sifting through all my words salad below, head straight to the sample repo and follow the detailed readme! In this post I am going to dig a bit deeper on the role that the plugin plays in the ADAL franchise, mention intended usage and take a peek under the hood of the plugin itself.
No compromises: JavaScript apps with the power of native ADALs
Since we announced ADAL JS, we had a constant stream of questions about using it in Cordova applications: how to do it, why it was not optimized for that use case, and so on. Technically it is possible to use ADAL JS in Cordova apps – I know of people who do it. However ADAL JS is designed to operate in a different environment, SPA apps coming from a server, and assumes constraints that are simply not present in Cordova apps: browser sandboxing, absence of refresh token in the implicit flow, and so on. The Cordova plugin for ADAL does not have to cope with such limitations, and it grants you far more access to the advanced authentication capabilities of the devices themselves. How? Perhaps I should start from getting on the same page on what Cordova is. Quoting from its about page:
Apache Cordova is a set of device APIs that allow a mobile app developer to access native device function such as the camera or accelerometer from JavaScript. Combined with a UI framework such as jQuery Mobile or Dojo Mobile or Sencha Touch, this allows a smartphone app to be developed with just HTML, CSS, and JavaScript. When using the Cordova APIs, an app can be built without any native code (Java, Objective-C, etc) from the app developer. Instead, web technologies are used, and they are hosted in the app itself locally (generally not on a remote http server). And because these JavaScript APIs are consistent across multiple device platforms and built on web standards, the app should be portable to other device platforms with minimal to no changes.
That is a very, very neat trick. As our first sample shows, it is amazingly simple to whip together one app – and run it on many different platforms without a single change. Of course our sample is a toy, as samples demonstrating API usage ought to be – IRL you’d likely at least add some CSS to comply to the look & feel of the targeted platform. But even taking that into account, I am amazed by how succinct the app code turns out to be. Cordova achieves its tricks by exposing native platform capabilities by plugins: JavaScript façades which route calls to fragments of native code – native code that the plugin must supply for each of the platforms it wants to support. That is also how the ADAL plugin came to be: we decided a JavaScript API to use for exposing the most basic ADAL capabilities, then the valiant developers at MS open Tech created a bridge between that and the native ADALs on iOS, Android and .NET. (specifically, the two Windows Runtime Components in the ADAL .NET NuGet targeting Windows Store and Windows Phone 8.1 store apps). Concretely: say that you write a Cordova app and you deploy it to an iOS device, real or emulated. When in your JavaScript you invoke one of the ADAL Cordova plugin methods, say the classic acquireTokenAsyc, what actually happens is that the parameters will be dispatched down and the logic will be executed by the Objective-C flavor of ADAL: the cached tokens will be looked up from the Keychain, for example. Take the same application, and deploy it to a Windows device: the exact same JavaScript call will end up being executed by the corresponding .winmd component, and the tokens will be looked up from the Windows Store isolated storage. None of that would be possible with ADAL JS, of course: the storage on the actual device would be completely unreachable. The same holds for any other capability you get when you use the native ADALs.
ADALs’ Rosetta Stone
This isn’t the first time we work on an ADAL deliverable that can target multiple targets at once: ADAL .NET 3.0 preview uses PCLs and Xamarin technology to target the same platforms discussed here. Apart from the obvious audience difference between the two libraries (one is aimed at C# developers, the other at JavaScript ones) the main characteristic that sets those apart is how deep they need to drill in the platform layers to achieve their goals. For ADAL .NET, it’s the .NET Framework itself that is now available on every platform. The differences between platforms do exist, and we do need to take them into account in our programming model, but those all still live at the .NET level: we do need to change the component that shows the web authentication experience on every platform, but on every platform there’s a .NET API for it. Those differences surface all the way to you, the application developer: your Visual Studio solution typically has projects for each platform, where you write platform specific code (though that’s still .NET). That basically means that we are only limited by what makes sense for the target platform, but as a baseline we can expose whatever is in ADAL .NET. In ADAL for Cordova things are different. The JavaScript layer is just a façade and all the hard work is delegated to actual platform bits. We can only execute on platforms where we have an ADAL flavor available. For example: Cordova can run on Ubuntu, but we don’t have an ADAL that would run natively on it. That has two main consequences:
- The JavaScript façade we expose must utilize features that are available on ALL of the ADAL libraries used by the plugin.
- Corollary: if there are differences in the way in which ADALs on different platforms handle things, the plugin should try to normalize those as much as possible
- If there are platform specific features that MUST be surfaced to support mandatory functionalities, they have to be done in ways that won’t interfere with the platform-neutral programming model
That’s quite a tall order! To avoid the analysis paralysis that was very likely to ensue, we deliberately kept things very simple:
- We picked two AcquireTokenAsync and AcquireTokenSilentAsync overloads and ensured they were well mapped. Those two methods are enough to implement the main ADAL pattern on a native client
- We tool all the surface those two entail – AuthenticationContext properties and constructors, AuthenticationResult, cache and cache items – and ensured that we could map those back and forth from the JS façade while guaranteeing coherent results
That approach makes it possible for you to write something like
authenticate: function (authCompletedCallback) { app.context = new Microsoft.ADAL.AuthenticationContext(authority); app.context.acquireTokenSilentAsync(resourceUri, clientId) .then(authCompletedCallback, function () { app.context.acquireTokenAsync(resourceUri, clientId, redirectUri) .then(authCompletedCallback, function (err) { app.error("Failed to authenticate: " + err); }); }); }); },
Which is pretty much the base of all native flows – try to get the token I need without showing any UX, and if it fails – prompt. That forced us to ensure that what we return from all libraries is consistent. That is mostly the case – the ADAL dev team makes semantic (!=syntactic) consistency across platforms a priority, but there are few things here and there that for a reason or another diverge. For example, not all ADALs agree about what should be used as user ID in the AuthenticationResult: some use a human readable identifier, others do not. A more serious difference is in how platforms handle the common endpoint. The plugin normalized what was easy to normalize, but in general you can expect the consistency between native ADALs to increase with new releases. Anyhow: I personally really like the minimal interface this plugin offers. I am hoping that you guys will like it – I am all for lightweight, and if we see few important apps built on top of this doing their auth stuff just fine, we might be able to spread the approach back to other ADALs
The Plugin
The plugin in itself has a pretty interesting architecture, dictated by how Cordova organizes things in a plugin. You don’t need to know any of the below in order to use the plugin in your app, I am reporting it just because it’s cool – and who knows. maybe I’ll entice you to contribute to it! Here there’s the screenshot of the structure of the plugin repo:
The JavaScript façade is in the www folder. That’s super convenient for figuring out the development surface offered by the plugin. All the files there are artifact exposed by the OM, apart from CordovaBridge.js. That file contains the main dispatcher (executeNativeMethod) to route JS calls to their native counterparts (see this for more details). For example, if you taker a look at AuthenticitonContext.js you’ll find that a call to acquireTokenAsync actually boils down to
bridge.executeNativeMethod('acquireTokenAsync', [this.authority, resourceUrl, clientId, redirectUrl])
The native action is all under /src. Here, every platform is represented by a subfolder (with the exception of Windows Store and Windows Phone, which are bundled). Every platform folder follows the same logical structure.
- It includes the bits of the corresponding native ADAL (or in the Android case, the Maven reference to it).
- It includes a proxy of some sort, establishing the interface used by the dispatcher: ADALProxy.js in Windows, CordovaAdalPlugin.* for iOS, CordovaAdalPlugin.java for Android.
- The rest of the files are mostly impedance mismatch fixers
The Scripts folder is also interesting, but before I get in the details of it I have to mention how one actually sets up the plugin in one application. Remember, we have detailed instructions on the readmes of both the library and the sample – the below is only for explaining the plugin’s architecture. Let’s say that you wrote your JS app and you are now ready to give it a spin. Here there’s the ceremony you follow if you use the Cordova command line tools:
1: cordova create MySample --copy-from="sample"
2: cd MySample
3: cordova platform add https://github.com/apache/cordova-android.git
4: cordova platform add ios
5: cordova platform add windows
6: cordova plugin add android@97718a0a25ec50fedf7b023ae63bfcffbcfafb4b
7: cordova run
(Note that if you are on Mac you can’t run a Windows emulator, and on Windows you can’t emulate iOS) The first line takes your code and creates a new local repository based on that. It will be used to host both your code and whatever it is necessary to support the platforms you’ll choose to support. The lines 3 to 5 tell Cordova to set your sample app to include all the artifacts necessary for supporting the platforms specified. Finally – line 6 sets up the ADAL plugin in your project. That’s where the files in the /Script folder come in – they contain logic that needs to be executed as the plugin code is added to each platforms, and in some cases at app build time . For example: if in Windows you want to be able to authenticate against one ADFS in your intranet, the Windows Runtime expects lots of settings to be set; iOS requires specific entitlements for code singing; and so on. All in all, I have to say that Cordova offers one of the cleanest and easier to understand plugin structure I’ve seen. Navigating through the ADAL plugin repo is a joy, as everything is nicely readable and just makes sense. Again, you don’t need to see what’s inside the plugin to use it – but I find it interesting and instructive
Feedback!
This is a preview, and as usual the reason we put previews out is to give you the change of giving it a spin and letting us know what you like, dislike and what does not work for you. I am personally very excited about this plugin, I just love the simplicity and power it offers – and I know that lots of you were searching for a solution for using Azure AD and call the API it protects (Office 365, Azure, Graph API, etc) from Cordova applications. Please do not hesitate to hit us with your feedback directly on github. Happy coding!!