Compartilhar via


OAuth, o365 APIs and Azure Service Management APIs – Using Them All Together

I’ve been spending some time lately fooling around the o365 API’s. Frankly, it has been a hugely frustrating experience; I’ve never seen so many documentation gaps and examples and sample code that didn’t work, to flat out wouldn’t compile in a long time. So, once I finally stumbled up on the “right” collection of code that worked to get me the all important access token for the o365 APIs I decided to take a quick detour to see if I could use the same approach to manage my Azure subscription with the Service Management APIs. Turns out you can, and actually do so in a way that leverages both of the APIs together in kind of a weird way. Here’s the story of how it works (with a special twist at the end, you’ll have to read all the way down to see it).

The first step in getting any of this working is to create an application in Azure. In that app, you can grant the rights that it is going to require – to things like Office 365 and the Azure Service APIs. The easiest way to do this scenario is to create the application using the Microsoft Office 365 API Tools for Visual Studio, which as of this writing you can find here: https://visualstudiogallery.msdn.microsoft.com/a15b85e6-69a7-4fdf-adda-a38066bb5155

Once you have those installed, the next step is to create a new project – whatever kind of project type is appropriate for what you are trying to accomplish. Once you’ve done that you want right-click on your project name and select “Add Connected Service”, like this:

 

 

When you add your Connected Service the first thing you’ll do is click on the Register your app link. It will first ask you to log in using your Azure organizational account. This account needs to be an admin account – one that has rights to create a new application in an Azure Active Directory:

 

 

After entering your credentials Visual Studio will take care of registering the app with Azure. It then presents you with a list of permission scopes, and for each one you can click on the scope to select which permissions in that scope you want your app to have. Remember that when a user installs your application it will ask if it’s okay for the app to have access to these things you’re defining in the permissions list. 

In this case I’m just going to select the Users and Groups scope and select the permissions to Read directory data and Access your organization’s directory:

 

 

That should be enough permissions to use the o365 Discovery Service API; I’ll explain why we want to do that in a bit. For now just click the Apply button, then click the OK button to save your changes. When you do that Visual Studio will begin modifying the application with the permissions you requested. In the Output window in fact you should see it add the Discovery Service NuGet package to your project (“ServiceApi” is the name of my project in Visual Studio): 

Adding 'Microsoft.Office365.Discovery' to ServiceApi.

... 

When it’s done with that it should open up a page in the Visual Studio browser window that includes the interesting information about what you need to do next, and of course, no details on how to actually do it. That’s why you’re reading this blog post now, right?? :-) 

The next thing we’re going to do is to go into the Azure Management Portal and add the other rights we want for our application, which is to use the Service Management API. So open up your browser and log into the Azure Management Portal at https://manage.windowsazure.com. Scroll down and click on the Active Directory icon in the left navigation, then click on your Azure Active Directory domain in the right pane. When you go into the details for your directory, click on the Applications tab, then find the application you just created. Click on it and then click on the Configure link.  

One of the things you’ll see at the top of the page is the CLIENT ID field. If you look in Visual Studio at your project, you should have had something like an app.config file added to it when you set up the application (app.config is added to winforms project; the config file that is added will vary based on your project type). Open up the app.config file and you will see an appSettings section and an entry for ida:ClientId. The value for it should be the same as the CLIENT ID value you see for your application in the Azure portal. 

Scroll down to the bottom of the page for the application to the “permissions to other applications” section. You should see one entry in there already, and that’s for the permission we requested for working with the directory data. Now click on the drop down in that section that says “Select application”, and select Windows Azure Service Management API. On the drop down next to it that says “Delegated Permissions: 0”, click on it and check the box next to the item that says “Access Azure Service Management”. 

 

Once you’ve done that click the SAVE button at the bottom of the page. You’re done now with all of the configuration you need to do in Azure, so let’s shift gears back into our project. 

The next thing we need to do is plug the application key values we need into our application. That includes the client ID, redirect URI, the Discovery Service resource ID and the Azure Service Management resource ID. You can get the client ID and redirect URI values from the app.config file that was added to your project. If for some reason you can’t find it in your project, you can also find it in the Configure tab for the application in the Azure management portal. The client ID value is in the CLIENT ID field, and the redirect URI value is in the REDIRECT URIS list. Finally, we are also going to use what’s called the “Common Authority” for getting an authentication context – an ADAL class (Active Directory Authentication Library that is used under the covers to get an access token to the o365 and Azure resources). 

The resource IDs are fixed – meaning they are always the same, no matter what application you are using them from. So with that in mind, here’s what the values look like once I’ve added them to the code behind for my winforms application: 

 

Okay, we’re getting awfully close to actually writing some code…the last thing we need to do before we start doing that though is to add the ADAL NuGet package to our application. So in Visual Studio open up the NuGet package manager, search find and add the ADAL package and add it. When you’re done you should see it AND the Microsoft Office 365 Discovery Library for .NET, which was added when we configured our application previously:

 

 

Now we’ll add the last couple of helper variables we need – one which is the base Url that can be used to talk to any specific Azure tenant, and the other is a property to track our AuthenticationContext: 

private const string BASE_TENANT_URL = https://login.windows.net/{0};

public static AuthenticationContext AuthContext { get; set; }

 

Okay, now let’s add the code that will get the access token for us, and then we’ll walk through it a little bit: 

private async Task<AuthenticationResult> AcquireTokenAsync(string authContextUrl, string resourceId)

{

   AuthenticationResult ar = null;

 

   try

   {

       //create a new authentication context for our app

       AuthContext = new AuthenticationContext(authContextUrl);

 

       //look to see if we have an authentication context in cache already

       //we would have gotten this when we authenticated previously

       if (AuthContext.TokenCache.ReadItems().Count() > 0)

       {

 

          //re-bind AuthenticationContext to the authority source of the cached token.

          //this is needed for the cache to work when asking for a

          //token from that authority.

          string cachedAuthority =

              AuthContext.TokenCache.ReadItems().First().Authority;

 

          AuthContext = new AuthenticationContext(cachedAuthority);

       }

 

       //try to get the AccessToken silently using the resourceId that was passed in

       //and the client ID of the application.

       ar = (await AuthContext.AcquireTokenSilentAsync(resourceId, ClientID)); 

   }

   catch (Exception)

   {

       //not in cache; we'll get it with the full oauth flow

   }

 

   if (ar == null)

   {

       try

       {

          //request the token using the standard oauth flow

          ar = AuthContext.AcquireToken(resourceId, ClientID, ReturnUri);

       }

       catch (Exception acquireEx)

       {

          //let the user know we just can't do it

          MessageBox.Show("Error trying to acquire authentication result: " +

              acquireEx.Message);

       }

   }

 

   return ar;

The first thing we’re doing is creating a new AuthenticationContext for a particular authority. We use an authority for working with an AuthenticationContext so that we can manage the cache of AuthenticationResults for an authority. ADAL provides a cache out of the box, so once we get an AuthenticationResult from an AuthenticationContext, we can just pull it from that cache without having to go through the whole oAuth flow all over again. The AuthenticationResult by the way is where we get the things we need to access a resource – an access token and a refresh token. 

Once we’ve created our AuthenticationContext again then we can try and acquire the AuthenticationResult out of the cache, which is done with the call to AcquireTokenSilentAsync. If there’s an AuthenticationResult in cache then it will be returned to you. If not then it throws an exception, which we catch directly below that call. 

Once we get through that part of the code we look to see if we were able to get an AuthenticationResult. If not, we’ll go ahead and use the oAuth flow to obtain one. That means a dialog will pop up and the user will have to enter their credentials and approve our application to access the content we said we needed when we configured the permissions for our application. 

Now that we’ve got the code out of the way to get an AuthenticationResult, we can write the code to actually go work with our data. This is where you can see this kind of interesting intersection between the o365 APIs and the Azure Service Management API that I was describing at the start of this post. As I alluded to earlier, I really put this code together and wrote this post for two reasons: 1) there are SO MANY o365 API examples that either don’t include clear instructions on how to obtain an access token, or the code they have either does not even compile or does not work. This is proof I guess that APIs change, right? Now you have an example that works (at least for today). 2) the Service Management API requires the tenant ID to work with it. Well, as it turns out, when you get your AuthenticationResult from a call to the DisoveryService through the o365 APIs, you will get the tenant ID back.  

So you can use this pattern – call the DiscoveryService, use the CommonAuthority for the authContextUrl and authenticate, then you will have the tenant ID. Then take the tenant ID, use the login Url for the specific tenant as the authContextUrl and get an AuthenticationResult to use with the Service Management APIs (we’ll end up getting the access token silently). Once you have that you can take the access token from it to do whatever you’re going to do with Service Management APIs. 

Now, let me explain one other behavior that you may not be aware of, and then explain how that impacts the code I described above. If you read my description of the pattern above, you may notice this: in the first call to create an AuthenticationContext I use the CommonAuthority for the authContextUrl, which is https://login.windows.net/Common. In the next call I use an authContextUrl of https://login.windows.net/myTenantIdGuid. So if I’m using two different Urls to create my AuthenticationContext, then how could there be something in the cache for me to use when as I say above, on the second call I’m going to get the AuthenticationResult out of the cache? Well, it turns out that when you create the AuthenticationContext with the CommonAuthority, after the user actually authenticates ADAL changes the Authority property of the AuthenticationContext from CommonAuthority to https://login.windows.net/myTenantIdGuid. This is actually pretty cool. The net result of all this is – I actually don’t need to authenticate into the DiscoveryService at all to get the tenant ID for the tenant being used. So what’s the net of everything I’ve given you so far? Well as I explained at the beginning, you now have some nice code that actually does work with the o365 APIs. However now that you understand how it works you can see you really don’t need to call into the DiscoveryService to get the tenant ID, so you learned a little something about how the AuthenticationContext works in the process. 

Okay, so now that we know that, let’s take a look at the code to get our subscription data from Azure. You’ll see that it makes just a single call to get an AuthenticationResult for working with the Service Management APIs. Note, this code uses the new HttpClient library, which is installed as a NuGet package; here’s the package I added: 

 

The code looks like this: 

try

{

   //base Url to get subscription info from

   string subscriptionUrl = "https://management.core.windows.net/subscriptions";

 

   AuthenticationResult ar =

       await AcquireTokenAsync(CommonAuthority, AzureManagementResourceId);

 

   //yeah this is me just being lazy; should really do something here

   if (ar == null)

       return;

 

   string accessToken = ar.AccessToken;

 

   if (!string.IsNullOrEmpty(accessToken))

   {

       //create an HTTP request for the subscription info

       HttpClient hc = new HttpClient();

 

       //add the header with the access token

       hc.DefaultRequestHeaders.Authorization = new

          System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

 

       //add the header with the version (REQ'D)

       hc.DefaultRequestHeaders.Add("x-ms-version", "2014-06-01");

 

       HttpResponseMessage hrm = await hc.GetAsync(new Uri(subscriptionUrl));

 

       if (hrm.IsSuccessStatusCode)

          SubscriptionsTxt.Text = await hrm.Content.ReadAsStringAsync();

       else

          MessageBox.Show("Unable to get subscription information.");

   }

}

catch (Exception ex)

{

   MessageBox.Show("Error trying to acquire access token: " + ex.Message);

So just to recap what we’ve been talking about…the first thing I do is get an AuthenticationResult using the CommonAuthority. If the person that signs in does not have rights to use the Service Management APIs for the tenant they sign into, then they won’t get the chance to grant the app permissions to do what it wants. Otherwise they person will ostensibly grant rights to the app, and you’ll get your AuthenticationResult. From that we take our access token and we add two headers to our request to the subscriptions REST endpoint: the authorization header with our access token, and a special Microsoft version header, which is configured per the Microsoft SDK. Then we just make our request, hopefully get some data back, and if we do we stick it in the text box in our application. Voila – mission accomplished! Here’s an example of the XML for a subscription (and yes, the GUIDs shown here are not the actual ones from my tenant):

 

My biggest regret here is that even though in some ways this is “simplified”, it still takes me nine pages of a Word document to explain it. However I do think you have some good code that you can go out and run with today to start working with both the o365 APIs as well as the Azure Service Management APIs, so I think it’s worth it. Just bookmark this post for a rainy day, and then when you’re ready to start developing on either SDK you know where to find the info to get you started. I've also attached to this post the complete source code of the application I wrote here. You'll just need to update the client ID and return URI in order to use it. I've also included the original Word document from which this somewhat ugly post was pasted.

ServiceApi.zip

Comments

  • Anonymous
    December 17, 2014
    Many fine folks were generous enough to point out to me this week that you can now (actually since June’ish
  • Anonymous
    January 07, 2015
    One of the things you frequently want to do in your custom applications is send out emails. Historically