ADAL JavaScript and AngularJS – Deep Dive
Many web apps are structured as “single page apps”, or SPA: they have a JavaScript-heavy frontend and a Web API backend. Notable examples: Outlook Web App, Gmail.
Properly securing SPA’s traffic between its JS frontend and its Web API backend requires an OAuth2 flow, the implicit grant, that Azure AD did not expose… until today’s developer preview.
Today we are turning on the ability for you to enable a preview of our OAuth2 implicit grant support on any web app you choose.
At the same time, we are releasing the developer preview of a JavaScript flavor of ADAL, which will make it super easy for you to take advantage of Azure AD for handling sign in in your AngularJS based SPAs.
Last week I visited Cory on his Web Camp TV show, where I had the opportunity of introducing ADAL JS and demonstrating it in action; you can head to the recording if you want a quick overview.
In this post I will dig a bit deeper in the library and its basic usage. Remember, this is a preview and nothing is set in stone. We really want your feedback on this one – please let us know what you like and what you want us to do differently!
That said, without further ado…
The Basics
As usual, using ADAL JS does not require any protocol knowledge. In fact, operations here are even easier (though more tightly scoped) than with any other ADAL flavor. You just need to add a reference to the library, initialize it with the coordinates of your app, specify which routes should be protected by Azure AD – and you’re good to go.
Let’s flesh out the details. I will use our basic SPA sample as a reference.
Set up the OAuth2 Implicit grant for your test app
By default, applications provisioned in Azure AD are not enabled to use the OAuth2 implicit grant. In order to try the OAuth2 implicit grant preview, you need to explicitly opt in for each app you want to experiment with. In the current developer preview the process unfolds as in the following.
- Navigate to the the Azure management portal. Go to your directory, head to the Applications tab, and select the app you want to enable.
- Using the Manage Manifest button in the drawer, download the manifest file for the application and save it to disk.
- Open the manifest file with a text editor. Search for the
oauth2AllowImplicitFlow
property. You will find that it is set tofalse
; change it totrue
and save the file. - Using the Manage Manifest button, upload the updated manifest file. Save the configuration of the app
Add a reference to the library
As it is customary for J S libraries, you can either include the file (adal.js) in your local Scripts library, or you can pull it in via CDN (for this preview, the minified version lives at https://secure.aadcdn.microsoftonline-p.com/lib/0.0.1/js/adal.min.js). Either way, you’ll want to pull adal.js in your main html page:
<script src="App/Scripts/adal.js"></script>
Initialize
The rest all takes place in your main app.js file, where you’d normally configure your route. In our sample we use the OOB ngRoute, but in fact you don’t have to stick to it. First, we need to include the ADAL module:
var app = angular.module('TodoSPA', ['ngRoute','AdalAngular']);
We also need to make sure that we pass the corresponding provider to the app.config:
app.config(['$routeProvider', '$httpProvider', 'adalAuthenticationServiceProvider',
function ($routeProvider, $httpProvider, adalAuthenticationServiceProvider) {
Then, as we define the routes as usual, we can specify if there are some that we want to protect with Azure AD:
$routeProvider.when("/Home", { controller: "HomeController", templateUrl: "/App/Views/Home.html",
}).when("/TodoList", { controller: "TodoListController", templateUrl: "/App/Views/TodoList.html", requireADLogin: true,
There are other ways of kicking off the authentication dance, but this is the one with the least amout of code involved.
Finally, the initialization proper. We have to pass to ADAL the coordinates that describe our app in Azure AD:
adalAuthenticationServiceProvider.init( { tenant: 'contoso.onmicrosoft.com', clientId: 'e9a5a8b6-8af7-4719-9821-0deef255f68e' }, $httpProvider );
On the client, that’s all you have to do. For what concerns the API backed, you just configure it in the same way in which you configure Web API with Azure AD authentication: with the OAuth bearer OWIN middleware. The only difference is that in the audience you’ll need to use the clientid of the app, the same value you just passed in Init() above.
Run!
Hit F5. You’ll land on the home view. Click on Todo list: you’ll be bounced to the familiar Azure AD authentication screen. Enter your credentials, and you’ll end up on the screen below.
♪Ta dah!♪ Barely 3 lines of JavaScript strategically placed, and your SPA gets to partake on one of the biggest identity systems on the planet.
Deep Dive
Well, that was pretty smooth. Aren’t you curious about how that happened?
Protocol Flows
Traditional web apps reply on roundtrips both for executing business logic and for driving the user experience. The classic way of securing those entail an initial leg where the user is shipped elsewhere via a full redirect, typically to the authority that the app trusts as identity provider. This initial leg results in a token being sent to the app. The app (or usually some form of middleware sitting in front of it) will validate the token and, upon successful verification, will issue to itself a session cookie. The session cookie will be present in all subsequent requests: the app will interpret its presence (and validity) as a sign that the request comes from an authenticated user. Rinse and repeat until the cookie expires. And once it does, the middleware will simply redirect the browser to the authority again – and the dance will continue.
SPAs don’t work too well with the same model. The cookie can be used for securing calls to your own backed, and in fact lots of people do so; but this does not work if you want to call API on a different backend (cookies can only go to their own domain). Also, web API calls aren’t too conducive of renewing cookies – when some JavaScript is trying to get data for populating an individual DIV and the session cookie it uses is expired, getting back a 302 isn’t the most actionable thing. Finally, having to support cookies on your Web API is just odd: that would not help at all when the same API are invoked by a native client, forcing you to maintain multiple validation stacks and ensure that they don’t step on each others’ toes. ADAL JS does not use cookies, or at least not directly.
Just as traditional web apps and SPAs have evolved different mechanisms for managing security and user authentication, the process of buying medication like Strattera online has also adapted to modern consumer needs. Online pharmacies have streamlined the process of purchasing medications by utilizing secure, direct platforms that bypass traditional prescription refill methods. This allows consumers to conveniently order Strattera from the comfort of their homes, ensuring privacy and ease of access much like how modern web applications manage user sessions and authentication without traditional cookies. Moreover, just as ADAL JS provides a more seamless integration without the use of cookies, online pharmacies optimize their user experience by ensuring that all transactions are secure and that patient information is protected, thereby simplifying the process of obtaining necessary medications.
Signing In
Signing in is kind of a misnomer here, but only if you know what’s really going on under the cover – and most don’t need to. Rather than forcing everybody to grok what happen in the name of accuracy, we just went with the flow J
Signing in for ADAL JS actually means obtaining a token for your app’s backend API. As simple as that.
Given that from Azure AD’s perspective your JS frontend and your web API backend are actually different manifestations of the same app, you will be asking a token for yourself: which is exactly the semantic for an id token. We already do that in the OpenID Connect middleware, however there our goal is to deliver the token to the server side of the web app. Here we want the JavaScript frontend to obtain the token, so that it can later use it whenever it needs to invoke its own Web API.
Here there’s how the request looks like:
GET https://login.windows.net/developertenant.onmicrosoft.com/oauth2/authorize?response_type=id_token&client_id=aeadda0b-4350-4668-a457-359c60427122&redirect_uri=https%3A%2F%2Flocalhost%3A44326%2F&state=8f0f4eff-360f-4c50-acf0-99cf8174a58b&nonce=8b8385b9-26d3-42a1-a506-a8162bc8dc63 HTTP/1.1
Notice that we are asking for an id_token, and that we do inject state and nonce for the usual security reasons.
The response is the interesting part:
HTTP/1.1 302 Found
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: text/html; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
The id_token is delivered as a fragment, so that only the client is able to access it.
Now that we have the token on the client we can store it and retrieve it whenever we are about to send a request to our backed API. We’ll detail how ADAL JS does it a little later.
Calling APIs
Once you have a token, on the wire a call to an API looks exactly the same as what you’d see from any other client type: you’ll find an authorization header containing a bearer token, just like OAuth2 prescribes.
In this particular case, however, we know a lot about the circumstances in which the call will be made. Whereas a native client can use all sorts of classes to compose the request, here we know it’s going to use what the browser (and JS in general) makes available. Thanks to that knowledge, ADAL JS can go further than any other ADAL flavor and automatically attach the right token whenever you make an API call. In fact, it can even renew it or request it ex novo contextually to the call! That’s why in the sample you don’t see any code referencing AuthenticationContext, AcquireToken and the like: those primitives are there but unless you want to do something custom you don’t actually need to use them
Keeping the session alive
One of the other shortcomings of cookie based sessions lies in the fact that renewing them is kind of a pain, which leads devs to decouple the session validity from the validity of the token itself. That typically delivers a smoother experience (popping a redirect once per hour isn’t fun for the user) but makes it much harder to revoke a session when something goes south.
In this flow we are not working with cookies, but tokens. In native applications, tokens are refreshed using refresh tokens. However here we run in a user agent, hence delivering something as autonomous as a refresh token is a dangerous proposition; not an option either.
We do have another option to renew tokens without perturbing the user experience AND without introducing hard to revoke session artifacts. The trick is to go back to the authority asking for a token, like we’d do in the roundtrip apps scenario, but doing so in a hidden iframe. If there is still an existing session with the authority (which might be represented by a cookie – but it is a cookie in the domain of the authority, NOT the app’s) we will be able to get a new token without any UX. There is even a specific parameter, prompt=none, which lets Azure AD know that we want to get the token without a UX, and if it can’t be done we want to get an error back. Here there’s the request.
If the session is gone (maybe somebody signed out) then we will not be able to extend the session further without user interaction, but that will be because that’s what the authority wants and not for any limitation of the flow. Pretty neat, eh? ADAL JS does all this behind the scenes for you. More details later.
Getting Tokens for Other Services
The trick described above can do much more than renewing the tokens for your own Web API backend; it can be used for obtaining tokens for other Web API, too. However this time we will be requesting regular access tokens, and we’ll have to specify the resourceID of the API we want. Furthermore, those API will need to support CORS.
Signing out
Signing out is pretty much as usual. Note that we don’t need to let our backend know that we’re signing ouit, vigen that all session artifacts are under the JS frontend’s control.
GET https://login.windows.net/developertenant.onmicrosoft.com/oauth2/logout?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44326%2F HTTP/1.1
Components
Now that we understand the basic flows behind ADAL JS, we can dig a bit deeper in how the library makes all that happen.
ADAL is exposed to your apps as an AngularJS module, “adalAngular”.
It does not have to be that way, and in fact you could use the lower layer in any other JS stack if you’d so choose, but for this preview if you use AngularJS you’ll have the best experience.
All of the interesting logic takes place in a service, “adalAuthenticationService”, which surfaces selected primitives to be used at init time and from the app’s controllers. The main methods and properties you’ll work with are:
Init(configOptions, httpProvider)
This method initializes the entire ADAL JS engine. You have seen it in action in the sample with as configOptions the only two mandatory parameters:
Tenant – tenantID or domain of the target Azure AD tenant .
clientId – client ID of the application.
The values passed in configOptions end up being exposed afterwards in the config property.
There are many more optional parameters you can use for modifying the default behavior.
redirectUri – in case you want to use anything different from the root path of the application when getting back tokens.
postLogoutRedirectUri– in case you want to use anything different from the root path of the application when signing out.
localLoginUrl – this parameter allows you to specify a specific Url to be used in lieu of the default redirect to Azure AD that would take place when an unauthenticated user attempts to access a protected route. You can use that page for creating any auth experience you like, such as for example something that gives users a choice between authenticating with Azure AD (see below n how to implement that option) or with a local provider.
Instance – by default, ADAL JS addresses requests to login.windows.net. If you want to override that, you can enter the desired hostname here.
expireOffsetSeconds – this value is used to determine with much advance an access token should be considered expired. Every time ADAL fetches a token from the cache, before it it assesses whether the token is less than this value (the default is 120 secs) from expiring. If it is, ADAL triggers a renew flow before performing the call.
extraQueryParameter – this has the same function as its homonym in the other ADALs, it allows you to pass any extra value you want to send to Azure AD in the querystring of authorization requests.
Endpoints – this is a very important parameter. ADAL can always tell what token to use when you’re calling your own backend, but without this property it would have no clue on how to handle other API. Endpoints is an array of couples (URL, resourceID). Every time a call to an API is made, ADAL’s interceptor looks up the requested URL in endpoint, then searches the cache for a token with a corresponding resourceID. If it finds it, it uses it; otherwise, it requests a token for that resource, caches it and then uses it. Very handy!
UserInfo
This property holds the main info about the currently signed in user and signed in status. Sub-properties:
isAuthenticated – Boolean indicating whether there is a currently signed in user.
userName – UPN or email of the currently signed in user
profile – the claims found in the id token, decoded and exposed as properties.
loginError – login erros, if any.
login and logOut
Those methods can be used for driving login and logout directly, as opposed to side effects of requesting a protected route. They are pretty easy to use, all you need to do is set them up in the controller backing the view where you want to place your sign in/out gestures:
app.controller('HomeController', ['$scope', 'adalAuthenticationService','$location',
function ($scope, adalAuthenticationService, $location) { $scope.Login = function () { adalAuthenticationService.login(); }; $scope.Logout = function () { adalAuthenticationService.logOut(); }; }]);
$On
ADAL JS also offers you the opportunity of handling events associated to the login outcome. That’s super important for error handling. For example:
$scope.$on("adal:loginSuccess", function () { $scope.testMessage = "loginSuccess"; }); $scope.$on("adal:loginFailure", function () { $scope.testMessage = "loginFailure"; $location.path("/login"); });
Cache
You can take a look at the cache logic by inspecting the code. As mentioned, ADAL JS cache is based on localStorage. It allows for interesting feats, like the ability of staying signed in even if you close the browser. Below you can see a screenshot of ADAL’s cache content (and what ADA JS keeps in localStorage in general).
VERY IMPORTANT. The use of localStorage does have security implications, given that other apps in the same domain will have access to it (kind of what happens with cookies in the default case) and it is prone to all the same attacks that localStorage have to deal with. Please exercise caution when using this feature and ensure you do all the due diligence you’d normally do for protecting data in localStorage. Also, remember: this is a developer preview. That means that it should not used for anything but experimenting, given that we are still just gathering feedback… please don’t use those bits with anything critical
Next
Kudos to Omer Cansizoglu, the awesome developer who put together ADAL JS! Also, thanks to Mads Kristensen and Damian Edwards for their invaluable feedback.
Those are very early bits, but we know that this is a scenario of great interest to you – we wanted to put the bits in your hands as son as possible, so that you experiment and let us know what to do. We are looking forward for your feedback!