Programming with the Office 365 APIs General Availability Release

Scot Hillier

by Scot Hillier on 11/3/2014

Share this:
Print

Article Details

Date Revised:
11/24/2014


Sponsored by


Last week, the Office 365 APIs have moved from preview status to general availability. I posted a blog article about the release and noted some changes I found along with resources to get started. The biggest change between preview and general availability, however, has to do with authentication. In the preview bits, the Office 365 APIs libraries handled some authentication work for you. For example, you would get a RedirectRequiredException if the user was not properly authenticated. From there, you could get the URL for authentication and redirect the user. In the general availability, you must handle all the OAuth authentication yourself.

Handling the OAuth flow yourself is good because it means that your applications will follow a consistent pattern whether or not they use the Office 365 APIs. The drawback, of course, is that you must now handle the OAuth authentication flow yourself! Furthermore, we still haven’t reached a state of perfect consistency because SharePoint apps that use OAuth are still making use of a slightly different model involving Azure Access Control (ACS) and relying on the SharePointContext class.

When it comes to handling the OAuth flow for your applications, the best resource for samples and guidance is the GitHub repository for Azure Active Directory samples. Here you will find samples for two basic approaches to authentication: OpenID Connect ASP.NET OWIN Middleware, or Active Directory Authentication Library (ADAL). Although both approaches are valid, Microsoft recommends using the OpenID Connect ASP.NET OWIN Middleware pattern.

I’ll dedicate the rest of this article to getting you started with the OpenID Connect ASP.NET OWIN Middleware pattern. My work here is based on a fusion of articles and samples along with a great discussion with @chakkaradeep that cleared up some issues for me. Here is a list of my references for the article:

OpenID Connect is an authentication protocol based on OAuth 2.0 that makes it easier for developers to write security code in their web-based applications. OpenID Connect is an alternative to SAML for single sign-on and is attractive because relying parties are not tightly bound to identity providers. Microsoft made OpenID Connect available to web developers through the OWIN security components in ASP.NET. So, to get started, you’ll need to create a web application that uses these components.

Start by creating a new MVC web application with No Authentication.

Next, add the required Connected Services to your application. You must grant the application Enable Sign On and Read Users’ Profiles. After that, you can grant whatever permissions the app needs. For this example, I’m just asking for permission to read the user’s contacts.

Next, add the required OWIN security components by running the following commands in the Package Manager Console.

Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Microsoft.Owin.Security
Install-Package Microsoft.Owin.Security.OpenIdConnect
Install-Package Microsoft.Owin.Security.Cookies

One of the key aspects of implementing the single sign-on process is to utilize a cache that can hold onto key data like the authorization codes. In this way, you can reuse codes without having to make round trips back to the authorization server. The cache you create must implement the Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache interface. In production code, this cache should be implemented using a permanent store like a database. For a learning sample, you can implement it using the session cache. The simplest way to get started, is to use the NaiveSessionCache class from starter project mentioned in the references.

Next, Add a new class named Startup.cs to the root of your project and replace all the code with the following. This is the hook into the OWIN security components that will allow us to create a single sign-on experience for the application and the requested connected services. Don’t worry if you see a compile error at this point, that will be cleaned up shortly.

using Microsoft.Owin;

using Owin;

 

[assembly: OwinStartup(typeof([[YOUR_NAMESPACE]].Startup))]

 

namespace [[YOUR_NAMESPACE]]

{

    public partial class Startup

    {

        public void Configuration(IAppBuilder app)

        {

            ConfigureAuth(app);

        }

    }

}

 

 

Next, add a class named Startup.Auth.cs to the App_Start folder. This class is going to handle signing into the application. Here is a simplified version designed to focus on the most important aspects of the code.

public partial class Startup
{
    private const string CLIENT_ID = "YOUR CLIENT ID";
    private const string CLIENT_SECRET = "YOUR CLIENT SECRET";
    private const string AUTHORITY = "https://login.windows.net/[YOUR TENANCY]";
    private string GRAPH_RESOURCE_ID = "https://graph.windows.net";
    
    public void ConfigureAuth(IAppBuilder app)
    {
 
        app.SetDefaultSignInAsAuthenticationType(
            CookieAuthenticationDefaults.AuthenticationType);
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = CLIENT_ID,
                Authority = AUTHORITY,
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    AuthorizationCodeReceived = (context) =>
                        {
                            string code = context.Code;
                            ClientCredential creds =
                                new ClientCredential(CLIENT_ID, CLIENT_SECRET);
                            string UserObjectId =
                                context.AuthenticationTicket.Identity.FindFirst(
                                ClaimTypes.NameIdentifier).Value;
 
                            AuthenticationContext authContext =
                                new AuthenticationContext(
                                    AUTHORITY, new NaiveSessionCache(UserObjectId));
 
                            AuthenticationResult result =
                                authContext.AcquireTokenByAuthorizationCode(
                                    code, 
                                    new Uri(
                                      HttpContext.Current.Request.Url.GetLeftPart(
                                      UriPartial.Path)), 
                                creds,
                                GRAPH_RESOURCE_ID);
 
                            return Task.FromResult(0);
                        }
                }
            });
    }
}
 

The key to the above code is the establishment of the Notifications. What’s happening here is that the code is receiving a notification when an authorization code is obtained. The code then creates a new instance of the token cache so the code can be saved. This allows the Office 365 API code to reuse the code instead of cycling back to the authorization server for a new one.

Now, you can finally code the HomeController.cs class to use the Office 365 APIs. To start with, add an [Authorize] attribute to the Home controller so a user login will be forced. Next, you can add the following code to the Index method of the controller.

NOTE: Be sure to read up on this "gotcha" regarding the format of the Discovery Service endpoint.

private const string CLIENT_ID = "YOUR CLIENT ID";
private const string CLIENT_SECRET = "YOUR CLIENT SECRET";
private const string AUTHORITY = "https://login.windows.net/[YOUR TENANCY]";
const string DISCOVERY_ENDPOINT = "https://api.office.com/discovery/v1.0/me/";
const string DISCOVERY_RESOURCE = "https://api.office.com/discovery/";
const string CONTACTS_CAPABILITY = "Contacts";

 

var signInUserId = ClaimsPrincipal.Current.FindFirst(
    ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst(
    "http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
 
 
AuthenticationContext authContext = new AuthenticationContext(
    AUTHORITY,
    new NaiveSessionCache(signInUserId));
 
//Discover contacts endpoint
DiscoveryClient discClient = new DiscoveryClient(new Uri(DISCOVERY_ENDPOINT),
    async () =>
    {
        var authResult =
            await authContext.AcquireTokenSilentAsync(DISCOVERY_RESOURCE,
            new ClientCredential(CLIENT_ID,
            CLIENT_SECRET),
            new UserIdentifier(userObjectId,
            UserIdentifierType.UniqueId));
 
        return authResult.AccessToken;
    });
 
var dcr = await discClient.DiscoverCapabilityAsync(CONTACTS_CAPABILITY);
 
//Create Outlook services client
OutlookServicesClient client = new OutlookServicesClient(dcr.ServiceEndpointUri,
    async () =>
    {
        var authResult =
            await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId,
            new ClientCredential(CLIENT_ID,
            CLIENT_SECRET),
            new UserIdentifier(userObjectId,
            UserIdentifierType.UniqueId));
 
 
        return authResult.AccessToken;
    });
 
//Get the contacts
var contactsResults = await client.Me.Contacts.Take(20).ExecuteAsync();
 
//Show the contacts
return View(contactsResults);
 

The key to understanding the above code is to focus on the creation of the DiscoveryClient and the OutlookServicesClient. The DiscoveryClient is used to discover the appropriate endpoint for the current user’s contacts. This information is then used to create an OutlookServicesClient that can read the contacts. Also notice how the NaiveSessionCache is used, which makes it possible to reuse the authorization code provided by the initial login.

After that, all you have to do is put some appropriate display code in the view associated with the Home controller and start debugging. You should see your contacts appear in the resulting page.

 

 


Topic: Development

Sign in with

Or register

  • The error is "Failed to acquire token silently. Call method AcquireToken"
  • yes, I did it that way initially but it didn't work, notifying me that I need to acquire tokens asynchronously.
  • Hi Dragan,Get the capability like this:
    var dcr = await discClient.DiscoverCapabilityAsync("MyFiles");
    Then you can use the same approach I used for the OutlookServicesClient.
  • What is the proper way of estabilishing SharePoint Context for MyFiles capability?

    I tried this
    SharePointClient client = new SharePointClient(
    dcr.ServiceEndpointUri,
    async () =>
    {
    var authResult = await authContext.AcquireTokenAsync(dcr.ServiceResourceId, new ClientCredential(CLIENT_ID, CLIENT_SECRET));


    return authResult.AccessToken;
    });

    but without any result. I receive "Server Error" as response.
  • Thanks scot, please tweet once done.. :)
  • Consuming Office 365 APIs in a SharePoint app is not trivial and worth and article unto itself. I'm working on that one as we speak!
  • Hi Scott,

    Nice Article. What code changes do i have to make to consume office 365 APIs in an App for sharepoint?