Using ADAL Access Tokens with o365 REST APIs and CSOM

Many fine folks were generous enough to point out to me this week that you can now (actually since June’ish I’m told) use an access token you get from ADAL in conjunction with the o365 APIs to use ALSO with the SharePoint REST API as well CSOM.  Shocking!!  This may be what they call “asleep at the wheel” (or taking the red pill), but now that I’m wide awake again and aware of my surroundings it seemed like a good time to augment and update some of the content that’s out there.

Jeremy Thake did a nice write up on this concept originally at https://www.jeremythake.com/2014/06/using-the-sharepoint-csom-and-rest-api-with-office-365-api-via-azure-ad/.  While much of it still applies, I decided to revisit it myself so a) I would appear to know what the hell I’m talking about and b) I could update the really awful goo that he was forced to use to get an access token.  So in this little sample I’m going to use a standard flow to get an access token using ADAL (Active Directory Authentication Library).  I’m also going to be a little verbose and do a few things that you can do “less manually” with some tools for Visual Studio, just to make sure you are clear on what those tools are actually doing for you (especially if you’re <gasp> not using Visual Studio).  Here we go.

Add a New Application

You can use the Office 365 Tools add in for Visual Studio, or just create the application yourself manually in the Azure portal.  That’s what I’m doing in this case.  I begin by opening the portal and selecting my Azure Active Directory instance that’s used with my Office 365 tenant.  Then I click on the Applications tab, and then click on the ADD button at the bottom of the page to create a new application.  Here’s how we do it:

Click the check button and you’ve completed the first step – your application has been created.  Now go ahead and click on the Configure link so you can add the permissions you need to access your o365 content. 

Click the Add Application button at the bottom of the page and a Permission to Other Applications dialog pops up.  Click on the Office 365 SharePoint Online application and then click on the + sign next to it.  When you’ve completed that click the check button to save your changes:

Now you’ll have an item in the list of applications that your application wants permissions to, and you can click on the Permissions drop down for it to configure the permissions you want your application to have in the Office 365 tenant where it’s used.  Here’s what that looks like:

After you’ve made all of these changes, click the SAVE button at the bottom of the page but DON’T close the application configuration page just yet, there’s some info we need to grab out of there for connecting to it.  Scroll up to the top of the page and copy the a) CLIENT ID and b) REDIRECT URIS values.  You are going to use this when you ask a user to consent to having your application access his or her SharePoint content:

Okay, now we’re going to create a little winforms app that uses ADAL to get an access token, and then CSOM and REST to get data out of SharePoint.  So to begin the next step I’m going to create a new winforms application, and dive right into the code-behind in my form.  Once in there I’m going to plug in my Client ID and Redirect URI that I got from my application configuration in Azure.

Now…if you follow my last o365 API blog post here:  https://blogs.technet.com/b/speschka/archive/2014/12/08/oauth-o365-apis-and-azure-service-management-apis-using-them-all-together.aspx, you’ll see I’m going to use nearly identical techniques that I described there to get my access token from Azure AD.  To begin with, here are the standard set of application details I’m adding to my app:

Now I’m going to use NuGet to add in support for ADAL and o365; when I’m done it looks like this:

I’m also going to add references to Microsoft.SharePoint.Client and Microsoft.SharePoint.Client.Runtime so I can use CSOM from my application.  Next I’m going to add that same helper method I described in my previous blog post to get the access token, so let’s use this code here:

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

       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

       {

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

       }

       catch (Exception acquireEx)

       {

          //utter failure here, we need let the user know we just can't do it

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

             acquireEx.Message);

       }

   }

 

   return ar;

}

Okay, now we have the most important part of the guts done, let’s go back and look at the basics of what Jeremy was showing in his post:

Retrieving Data via REST

This one is virtually identical to Jeremy’s code, it just a) uses my updated code for obtaining an access token and b) shows how to create the Resource ID that you want to use for getting content from your SharePoint sites.  I tested this code against a couple of different o365 tenants and I can happily <small tear in eye> say that it worked in both.

private async void RestListBtn_Click(object sender, EventArgs e)

{

   try

   {

       AuthenticationResult ar = await AcquireTokenAsync(CommonAuthority,

          GetSharePointHost(SiteUrlTxt.Text));

 

       if (ar != null)

       {

          string requestUrl = SiteUrlTxt.Text + "/_api/Web/Lists";

 

          HttpClient hc = new HttpClient();

 

          //add the header with the access token

          hc.DefaultRequestHeaders.Authorization = new

             System.Net.Http.Headers.AuthenticationHeaderValue(

             "Bearer", ar.AccessToken);

 

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

 

          if (hrm.IsSuccessStatusCode)

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

          else

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

       }

   }

   catch (Exception ex)

   {

       MessageBox.Show("Error: " + ex.Message);

   }

}

 

private string GetSharePointHost(string url)

{

   Uri theHost = new Uri(url);

   return theHost.Scheme + "://" + theHost.Host + "/";

}

The main thing to call out here really is the GetSharePointHost method that creates the Url that is used as the Resource ID when getting an access token.  The net of this advice is that no matter where your SharePoint site lives, you really want to use the root site as the Resource ID.  So if your o365 site is https://steve.sharepoint.com/sites/blazers, the Resource ID to get an access token should be https://steve.sharepoint.com.  Doesn’t matter where the site is in that hierarchy – from the root all the way down as far as you want to go – you want to make sure you use the root site as the Resource ID.  About a billion thanks to Dan K. for clearing that up for me; your Christmas card is in the mail!  The other thing worth noting is that you MUST include a trailing slash on the Url that you use as the Resource ID.  It cause ADAL no end of pain and suffering when you leave that out.  Both of these little gems are captured in those two lines of code above.  Yay!

Retrieving Data via CSOM

This one is also quite similar to Jeremy’s…again the main differences are that it uses my standard method to get the access token, and for some reason I get a somewhat different class naming structure for my WebRequestEventArgs.  Here’s the relevant code:

private void CsomListBtn_Click(object sender, EventArgs e)

{

   try

   {

       ClientContext ctx = new ClientContext(SiteUrlTxt.Text);

       ctx.ExecutingWebRequest += ctx_ExecutingWebRequest;

 

       ctx.Load(ctx.Web.Lists);

 

       ctx.ExecuteQuery();

 

       string theLists = string.Empty;

 

       foreach (List lst in ctx.Web.Lists)

       {

          theLists += lst.Title + Environment.NewLine;

       }

 

       ResultsTxt.Text = theLists;

   }

   catch (Exception ex)

   {

       MessageBox.Show("Error: " + ex.Message);

   }

}

 

async void ctx_ExecutingWebRequest(object sender, WebRequestEventArgs e)

{

   AuthenticationResult ar = await AcquireTokenAsync(CommonAuthority,

       GetSharePointHost(SiteUrlTxt.Text));

 

   if (ar != null)

   {

       e.WebRequestExecutor.RequestHeaders["Authorization"] =

          "Bearer " + ar.AccessToken;

   }

}

So there you have it.  I must say, I find this ridiculously cool.  Many thanks to Jeremy for his original post and for the loads of people who basked in the glory of pointing out to me that I missed it.  :-)   Just kidding of course, I love having a legion of friends that can read on my behalf and get me pointed in the right direction as needed.  Hopefully this post will help one of you in the same way.  I’ve attached the complete source code to this post, so just change the ClientID and ReturnUri variables after you create your application and you should be good to go.  As usual, I've also included the Word document from which this sorry mess of Notepad-like content was created.

AdalCsom_BlogPost.zip