Category Archives: ADAL

Developing Yammer apps for iOS/OS X with ADAL, REST API and Swift 2.0

You’re interested to develop applications on top of Yammer (Microsoft’s Enterprise Social Network) but you’re currently working on iOS/OS X devices ? No problem ūüėČ

Yammer offer to developers a REST API to allow them build applications on top of the product. Microsoft has also started to move the authentication mechanism to Office 365, to let people to use the same credentials as for other services in their company (Outlook, SharePoint, Skype for Business…).

In this article, we’ll discover what are the main steps to create our first application which will use the Yammer REST API and Office 365 authentication, in conjunction with Swift 2.0, the new programming language of Apple platforms, updated during the WWDC 2015.

Declare your application in Azure Active Directory

The first step to accomplish to be able to develop an application which will call the Yammer REST API with the Office 365 authentication (based on Azure AD), is to declare your application in Azure.

To do this, you need to be a global administrator of your tenant, and you can achieve this by using the Azure Management Portal : https://manage.windowsazure.com

On the Azure Management Portal, go to Active Directory section, select the appropriate directory, select applications and create a new native application.

After you fill the requested information (a name and a redirect URL), you have to declare for each service (SharePoint, Exchange, Active Directory…) what permissions are needed by your application as you can see below.

Few weeks ago, Microsoft has released a new permission which allows you to use the Yammer REST API (it’s a preview for now). So just add it to your application (as you can see below) and save your modifications.

Office 365 Yammer - Azure AD Permissions

Authenticate with ADAL

The second step to develop your application is to acquire an authentication token from Azure AD and to pass it to the Yammer REST API when you want to retrieve data.

The simplest way to acquire an authentication token from Azure AD is to use the Azure Active Directory Library (aka. ADAL) which can be downloaded for free on GitHub : https://github.com/AzureAD/azure-activedirectory-library-for-objc

After downloading the library and integrating it in your application (cf. documentation on GitHub on how to achieve this goal), you can acquire a token with just few lines of code.

var authError: ADAuthenticationError?
var authContext = ADAuthenticationContext(authority: "https://login.windows.net/common", error: &authError)
var bearerToken: String!

authContext.acquireTokenWithResource("https://www.yammer.com/", clientId: "...", redirectUri: "...", completionBlock: { (result: ADAuthenticationResult!) in
    guard (authError == nil) && (result.accessToken != nil) else {
        // Authentication Error
        return
    }

    bearerToken = result.accessToken
}

The Swift code above is pretty simple to understand :

  • We declare an authentication context
  • We call the acquireTokenWithResource method with required parameters (clientId and redirectUri are the same you filled in step #1)
  • If the token was successfully acquired, we store it in a variable to use it later

Use the Yammer REST API

Now that we have a valid authentication token, we are now able to use the Yammer REST API. If you need some information about all the capabilities available in that API, you can refer to the official documentation : https://developer.yammer.com/v1.0/docs/userscurrentjson

In Swift, we are able to use all the frameworks (Foundation, AppKit, UIKit…) available on iOS/OS X and which allow us to perform advanced operations in a simple manner.

To perform HTTP communications, we can use the NSURLSession class. With just few lines of code, we are able to send a request to a server (synchronously or asynchronously) and to retrieve the response.

let URLSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSMutableURLRequest(URL: NSURL(string: "https://www.yammer.com/api/v1/users/current")!)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue(bearerToken, forHTTPHeaderField: "Auhtorization")
let task = URLSession.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) in
    guard (data != nil) else {
        // No data returned
        return
    }
    processResult(data)
}
task?.resume()

The most important part in the code above, to authenticate the request, concerns the addition of an Authorization header to the request. The header is a concatenation of the token retrieved in step 2 with the Bearer keyword such as “Authorization: Bearer our_authentication_token“.

If the request is successfully executed (it returns data), then we call the processResult method by passing the retrieved data as parameter.

Deserialize JSON response and catch errors

The last step to perform in our application is to parse the retrieved data. Because we have passed an Accept header to the previous request to indicate that we want to retrieve the data as JSON, we just have to deserialize and to use them.

Once again thanks to Foundation which expose a class (NSJSONSerialization) to perform this operation.

If you used Swift 1.x during the last year, focus your attention on the code below. A brand new way to deserialize and catch potential errors has been introduced with Swift 2.0 (do … catch).

func processResult(data: NSData?) {    
    do {
        let JSONObject = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
        print(JSONObject, appendNewline: true)
    } catch let error as NSError {
        print(error, appendNewline: true)
    }
}

In the code above, we just print the deserialize object so it should display something like that :

{
    "activated_at" = "2015/01/01 01:01:00 +0000";
    admin = true;
    "birth_date" = "";
    "can_broadcast" = true;
    "can_browse_external_networks" = 1;
    "can_create_new_network" = 1;
    contact = { ... };
    department = Direction;
    email = "...";
    expertise = "Office 365 REST API, SharePoint, iOS, Objective-C, Swift";
    "external_urls" = ();
    "first_name" = "Stéphane";
    "follow_general_messages" = 0;
    "full_name" = "Stéphane Cordonnier";
    guid = "<null>";
    id = 123456789;
    interests = "<null>";
    "job_title" = "Technology Addict";
    "kids_names" = "<null>";
    "last_name" = Cordonnier;
    location = Paris;
    ...
}

If you want to use and parse the deserialized object, it’s a key/value dictionary so it’s pretty simple to use it.

Use ADAL for iOS/OSX with O365 Discovery REST API

You want to access your Office 365 data from an iOS / OSX application and don’t know how to start ? You are in the right place ūüėČ

In this article, we will see the first step to write an application connected to Office 365 which consists to discover what services are available for the current user and what are the endpoints URLs for each.

When you want to connect to Office 365 from an application and use the REST API to retrieve data, you need to authenticate against Azure Active Directory (each user accounts are stored in AAD).

To help us to achieve this goal, Microsoft has released a library named ADAL (Active Directory Azure Library) which was written in Objective-C and can be used with iOS and OSX.

You can download ADAL for free on GitHub : https://github.com/AzureAD/azure-activedirectory-library-for-objc

The next step to be able to write an application which operates with Office 365 is to declare it in the Azure directory associated to your Office 365 tenant. If you are a global administrator of your tenant, you can achieve this by using the Azure Management Portal : https://manage.windowsazure.com

On the Azure Management Portal, go to Active Directory section, select the appropriate directory, select applications and create a new native application.

After you fill the requested information (a name and a redirect URL), you have to declare for each service (SharePoint, Exchange, Active Directory…) what permissions are needed by your application as you can see below.

Azure Manage Portal - Application Permissions

We are now ready to write code to connect to Office 365 and to discover what services are available for our user account.

In your application (it works in the same way for iOS and OSX), add the ADAL library in your project as usual when you use a third party library/framework.

    NSString *clientId = @"12345678-abcd-1234-abcd-1234567890ab";
    NSURL *redirectUri = [NSURL URLWithString:@"https://beecomedigitaldemo"];

    ADAuthenticationContext *authenticationContext = [ADAuthenticationContext authenticationContextWithAuthority:@"https://login.windows.net/common" error:nil];
    [authenticationContext acquireTokenWithResource:@"https://api.office.com/discovery/" clientId:clientId redirectUri:redirectUri completionBlock:^(ADAuthenticationResult *result) {
        if (result.status == AD_SUCCEEDED) {
            // Authentication succeeded
        }
        else if (result.status == AD_USER_CANCELLED) {
            // Authentication cancelled by the user
        }
        else {
            // Authentication failed
        }
    }];

To authenticate against Azure Active Directory, we have to use the ADAuthenticationContext class available in ADAL.

To instanciate a context, you need to indicate what is the authentication authority you will use. The default authority to logon with Azure Active Directory is https://login.windows.net/common but if it’s applicable for your company, you can specify another authority.

After the instanciation of the context, you have to acquire an authentication token for the resource which you will request later.

In our case, we want to discover services offered by Office 365. The resource associated is https://api.office.com/discovery/ (be careful to not omit the trailing slash character in the URL otherwise it will not work).

When you want to get a token by calling the acquireTokenWithResource method, you have to set a clientId and a redirectUri. These values are those you have declared in the Azure Management Portal.

This method executes asynchronously so when it’s finished, the completionBlock passed in the last parameter is executed. To ensure that the request was successfull, you can test the result status. If everything was good, status will be equal to AD_SUCCEED. Otherwise it will be equal to AD_FAILED or AD_USER_CANCELLED.

Now that we are successfully authenticated and we have a valid authentication token, we can discover what services are available. To do it, we just have to send a request to the discovery REST API endpoint.

// In-house wrapper of NSURLSession to simplify HTTP communications
BCDHttpClient *httpClient = [[BCDHttpClient alloc] init];

NSURL *serviceURL = [NSURL URLWithString:@"https://api.office.com/discovery/v1.0/me/services"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:serviceURL];
[request setValue:[NSString stringWithFormat:@"Bearer %@", result.accessToken] forHTTPHeaderField:@"Authorization"];

[httpClient dataWithRequest:request completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
    if (error == nil) {
        id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonObject);
    }
}];

As you can see above, we build a NSMutableURLRequest with the URL of the Office 365 discovery endpoint : https://api.office.com/discovery/v1.0/me/services

To call this endpoint and retrieve information associated to our user profile, we need to add an Authorization HTTP header with the following format : “Bearer our_access_token“.

The access token is the one that have been retrieved by ADAL and is accessible in the result parameter of the completion block.

Then we execute the request by using our in-house implementation of a HTTP client (just a lightweight wrapper around NSURLSession). If the request complete successfully, we deserialize the retrieved data (in JSON format) and print them in the console.

You can see below the result printed in our console for all services found for the current authenticated user. In this example, our user have access to SharePoint and Exchange with many different services for each (MyFiles, RootSite, Calendar, Contacts and Mail).

{
    "@odata.context" = "https://api.office.com/discovery/v1.0/me/$metadata#allServices";
    value =     (
                {
            "@odata.editLink" = "services('MyFiles@O365_SHAREPOINT')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('MyFiles@O365_SHAREPOINT')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = MyFiles;
            entityKey = "MyFiles@O365_SHAREPOINT";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://beecomedigitaldemo-my.sharepoint.com/_api/v1.0/me";
            serviceId = "O365_SHAREPOINT";
            serviceName = "Office 365 SharePoint";
            serviceResourceId = "https://beecomedigitaldemo-my.sharepoint.com/";
        },
                {
            "@odata.editLink" = "services('RootSite@O365_SHAREPOINT')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('RootSite@O365_SHAREPOINT')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = RootSite;
            entityKey = "RootSite@O365_SHAREPOINT";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://beecomedigitaldemo.sharepoint.com/_api";
            serviceId = "O365_SHAREPOINT";
            serviceName = "Office 365 SharePoint";
            serviceResourceId = "https://beecomedigitaldemo.sharepoint.com/";
        },
                {
            "@odata.editLink" = "services('Contacts@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Contacts@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Contacts;
            entityKey = "Contacts@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        },
                {
            "@odata.editLink" = "services('Mail@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Mail@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Mail;
            entityKey = "Mail@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        },
                {
            "@odata.editLink" = "services('Calendar@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Calendar@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Calendar;
            entityKey = "Calendar@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        }
    );
}

As mentioned before, we have to declare what permissions are needed by our application. But what happens exactly for the user if he’s not agree with these permissions ?

When ADAL tries to acquire an access token and if the user account is not stored in the same directory as your tenant, he must consent to authorize the application for the permissions you are requested.

ADAL - User ConsentIf the user doesn’t consent to authorize your application to use the required permissions, ADAL returns an AD_FAILED status when you call acquireTokenWithResource. The consent is required only once. For subsequent requests the same consent is used by ADAL.

Now you are theorically able to use ADAL and Office 365 Discovery REST API to authenticate and retrieve information about available services for the current user¬† ūüėČ