Implementing admin consent in multi-tenant Office 365 applications using implicit OAuth flow


If you’re building a SaaS application on the Office 365 platform, there is a chance you might need to implement admin consent. Here is how to do it for applications built using Angular and ADAL JS.

Building multi-tenant SaaS applications on the Office 365 platform

The new Office 365 APIs simplify building web applications and add-ins on the Office platform. Using tools such as the Yeoman Office Generator, Angular and ADAL JS you can pretty quickly build your first solution connected to Office 365 without a single line of server-side code.

Once you get comfortable with the new APIs and OAuth you might start thinking about building solutions for your customers. You might even consider building a SaaS application that you would host for your customers. And this is exactly where things get slightly more complicated.

With new APIs you can build a fully client-side based solution connected to Office 365 using for example Angular and ADAL JS. The only requirement for it to work is to enable the implicit OAuth flow. This will allow your client-side application to complete the OAuth flow without any server-side code.

If you have built a SaaS application for your customers, depending on its functionality, you might need organization’s administrator to approve your application before it can be used. One such example is if your application is to perform search queries on behalf of the current user.

The process of approving the application for the whole organization by the administrator is referred to as admin consent and is a part of the Azure AD OAuth implementation. And while ADAL JS simplifies working with OAuth in Angular applications, it doesn’t provide a turnkey solution for implementing admin consent.  There are however some pieces in the underlying ADAL JS library that you can use to implement admin consent in your application.

Building client-side add-ins and applications connected to Office 365 isn’t overly complex. If you are new to it, you can either use the step-by-step manual of how to connect your solution to Office 365 or you can have the Yeoman Office Generator scaffold it for you.

Once you’re done, you will end up having an empty application that requires you to authenticate using your organization account.

Organization account loging prompt displayed as a part of the application startup flow

Assuming that your application uses permissions that require admin consent, such as the Run search queries as user permission, if you were to try and use the application as a user from another Active Directory, you would get an error stating that the consent cannot be completed due to lack of permissions.

Error in the authentication flow when trying to use an application that requires admin consent before it has been approved by the admin

Before this application can be used by users from other directories, their administrators have to approve it first. They do this by completing the admin consent flow. So how do you start the admin consent flow in your application?

A sample application showing the end result is available on GitHub at https://github.com/waldekmastykarz/sample-azure-adminconsent.

As you have just seen, if a regular user tries to start an application that requires admin consent, and which hasn’t been approved yet, an error is shown. If that user is however the administrator, the application will work as expected.

Leveraging the fact that administrators are allowed to use new applications that haven’t been approved for the whole organization yet, a nice way to allow them to sign up their organization for using that application could be to provide them with an admin page that includes the sign up link. Since only administrators can complete the admin consent only they should be able to see the link in the application.

Let’s start by extending our data service with a function that will allow us to check if the current user is an administrator or not:

function isAdmin() {
  var deferred = $q.defer();
  
  $http({
    url: 'https://graph.windows.net/me/memberOf?api-version=1.6',
    method: 'GET',
    headers: {
      'Accept': 'application/json;odata=nometadata'
    }
  }).success(function(data) {
    var isAdmin = false;
    
    for (var i = 0; i < data.value.length; i++) {
      var obj = data.value[i];
      
      if (obj.objectType === 'Role' &&
        obj.isSystem === true &&
        obj.displayName === 'Company Administrator') {
        isAdmin = true;
        break;
      }
    }
    
    deferred.resolve(isAdmin);
  }).error(function(err) {
    deferred.reject(err);
  });
  
  return deferred.promise;
}

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/services/data.service.js#L33-L62

When loading the home page we would call this function in the controller and store the result in the view model:

function homeController(dataService){
  var vm = this;  // jshint ignore:line
  vm.title = 'home controller';
  vm.isAdmin = false;

  activate();
  
  function activate() {
    dataService.isAdmin().then(function(isAdmin) {
      vm.isAdmin = isAdmin;
    }, function(err) {
      console.error(err);
    });
  }
}

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/home/home.controller.js#L10-L25

Finally, in the view we would include the link to the admin page which would be visible only to administrators:

{{::vm.title}}<br />
<br />
<a href="#/admin" ng-show="vm.isAdmin">admin</a>

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/home/home.html#L1-L3

Admin link visible for administrators on the home page

Building the admin page

The next step is to build the admin page. This page will provide administrators with the link to sign up their organization for using the application by completing the admin consent.

Let’s start by checking if the user accessing the admin page is in fact an administrator. If they are not, they should be redirected to the home page.

function activate() {      
  dataService.isAdmin().then(function(isAdmin) {
    if (!isAdmin) {
      $location.href = '#/';
    }
  }, function(err) {
    console.error(err);
  });
}

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/admin/admin.controller.js#L18-L26

Next, let’s extend the controller with the ability to start the admin consent flow. As mentioned earlier this is not available through the ADAL Angular library and has to be done by directly interacting with the underlying ADAL JS library.

In order to interact with the ADAL JS library, we have to pass it as a dependency to our controller:

angular.module('office365app')
    .controller('adminController', ['$location', 'dataService', 'adalAuthenticationService', adminController]);

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/admin/admin.controller.js#L4-L5

Now we can add a function that will start the admin consent flow to our controller:

function signupOrganization() {
  var adal = new AuthenticationContext();
  adal.config.displayCall = function adminFlowDisplayCall(urlNavigate) {
    urlNavigate += '&prompt=admin_consent';
    adal.promptUser(urlNavigate);
  };
  adal.login();
  adal.config.displayCall = null;
}

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/admin/admin.controller.js#L28-L36

Although it seems like we are creating a new instance of AuthenticationContext, it’s actually a singleton and calling the constructor will return a reference to the existing instance created during the startup of our application.

In order to start the admin consent flow we have to add the prompt=admin_consent query string parameter to the login URL. We can do this by providing our own handler for the login process. We do this by assigning a function to the AuthenticationContext.prototype.config.displayCall property.

adal.config.displayCall = function adminFlowDisplayCall(urlNavigate) {
  urlNavigate += '&prompt=admin_consent';
  adal.promptUser(urlNavigate);
};

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/admin/admin.controller.js#L30-L33

Next, we start the admin consent flow by calling the AuthenticationContext.prototype.login function.

Because we’re working with a singleton and we only want the admin consent flow to be started once, we need to remove our custom login handler:

adal.config.displayCall = null;

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/admin/admin.controller.js#L35

Finally let’s extend the UI of the admin page with a button that administrators can use to start the admin consent:

<div>
    <button ng-click="vm.signupOrganization()">Signup organization</button>
</div>

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/admin/admin.html#L2-L4

Signup organization button displayed on the admin page

By clicking the Signup button, administrators start the admin consent flow for the application. Once completed, all users within the organization will be allowed to use the application.

Admin consent flow in Azure Active Directory

The only thing left to do, is to notify the administrator that the admin consent flow has been completed and that the organization is signed up for using the application.

When the admin consent flow is completed, the Azure AD OAuth flow redirects the administrator back to the application including the admin_consent=True fragment in the URL’s hash. We can use this information to display a notification in our application.

First let’s extend the app.module.js file and check if the admin consent has been completed:

var organizationSubscribed = window.location.hash.indexOf('admin_consent=True') > -1;
office365app.value('organizationSubscribed', organizationSubscribed);

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/app.module.js#L19-L20

By storing the value as an Angular variable we can easily access it in other parts of our application.

Next, let’s extend the home page controller to include the value of that variable in the view model:

angular.module('office365app')
    .controller('homeController', ['dataService', 'organizationSubscribed', homeController]);

function homeController(dataService, organizationSubscribed){
    var vm = this;  // jshint ignore:line
    vm.organizationSubscribed = organizationSubscribed;
}

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/home/home.controller.js#L4-L14

Now on the home page view we can display a notification to the administrator to confirm the completion of the admin consent:

<div ng-show="vm.organizationSubscribed">
    Organization successfully subscribed
</div>

https://github.com/waldekmastykarz/sample-azure-adminconsent/blob/master/app/home/home.html#L4-L6

Notification of a successfully completed admin consent displayed in the application

Congratulations: your application now has support for the admin consent flow!

Summary

The new Office 365 APIs simplify building rich client applications that interact with the Office platform. These applications might be built for specific customers or as multi-tenant SaaS applications. The latter, depending on their functionality, might require admin consent before they can be used by an organization. If you are building your applications using Angular, you can benefit of the ADAL JS library to implement admin consent in your application.

Others found also helpful: