Sdílet prostřednictvím


Calling O365 APIs from your Web API on behalf of a user

This post will show how to create a custom Web API that calls the Office 365 APIs on behalf of a user.

Background

How about that title?!?  That’s pretty geeky.

I’ve been working with a customer who is interested in the new Office 365 APIs that were announced at SharePoint Conference.  They are building multiple client applications that will consume their custom Web API endpoints.  They did not want to reference the O365 APIs and perform all of the work from the client applications, but rather centralize that logic into a service that they control. 

clip_image002

In this scenario, the user opens an application and logs in using their Azure Active Directory credentials.  Once the user is authenticated, we obtain an access token used to call the custom Web API endpoint.  The Web API endpoint, in turn, obtains an access token for O365 using the user’s token.  This means when the O365 API is called, it is called using an app+user context on behalf of the currently logged in user.  The current user must have permissions to write to the list, as does the app, and if one of them is missing permission then the operation fails.

Yeah, even after editing that paragraph several times over it’s hard to understand.  Let’s walk through an example.

The code I am going to show is based on a sample posted by the Azure Active Directory team, Web API OnBehalfOf DotNet available on GitHub.  I am not going to provide a code download for this post, I strongly urge you to go download their sample and become familiar with it.  Instead of using the O365 API, that sample shows how to use the Graph API.  This post details how to leverage some of the code in that example to call the O365 API.  There are many things I omitted that you should spend time in that sample understanding.

There’s also a great post by Johan Danforth, Secure ASP.NET Web API with Windows Azure AD, that illustrates some of the same concepts that I show here.  His example is well formatted and might help explain some of the settings that I am going to gloss over here.  If you need help understanding the various parts (client ID, client secret, callback URL, app ID URI for the web API) that is a great resource as well.

Manage O365 Tenant from Azure Management Portal

The first part of this exercise requires that you manage your Office 365 tenant from the Azure Management Portal.  A blog post with detailed instructions and an accompanying video are available at Using a existing Windows Azure AD Tenant with Windows Azure.  I have an existing Office 365 tenant (https://kirke.sharepoint.com) that I log into with the domain “kirke.onmicrosoft.com”, and I want to add that account into Azure AD.  To do this was as simple as logging into my Azure subscription using a Live ID and adding an existing directory in the Azure Management Portal.

image

Next, I log in using my Office 365 tenant administrator, and then the directory shows up in the Azure Management portal.

image

I can now open the directory and see all of my users from my O365 tenant.

image 

Make sure you start In Private browsing before doing this, and it should work smoothly.

Create the Web API Project

Create a new ASP.NET Application in Visual Studio 2013 and then choose Web API. 

image

Click the button to Change Authentication.  We’ll use organizational accounts in Azure AD to authenticate, using the same domain that our O365 tenancy was in.

image

Leave the default App ID URI value, which is your tenant + “/ListService”.  When you click OK, you are prompted to sign in as a Global Administrator for the tenant.  This is because Visual Studio is going to do several nice things for you, including registering the app and setting up the configuration settings for you.

image

Sign in, click OK, then click OK again, and the project is created.  If you inspect the web.config, you’ll see that several settings were added for you.

image

Now go look at your Azure Active Directory tenant and click on the Applications tab.  Notice that an application was added for you.

image

Click the ListService application to see its settings, then go to the Configure tab.  In the section “keys”, create a new key:

image

Click Save, and the key is then displayed for you, and you can now copy its value.

image

Go back to web.config and add a new key “ida:AppKey” with the value of the key you just created.  Add a key “ida:Resource” which points to your SharePoint Online tenant (note: this need not be the site collection you are accessing).  Finally, add a key “ida:SiteURL” that points to your site collection.

image

Give the Web API Permission

The Web API has been registered with Azure AD, we now need to give it some permissions.  In Azure AD, select your Web API application and choose Manage Manifest / Download Manifest.

image

We are prompted to download and save a .JSON file. 

image

Save it, and then open it with Visual Studio 2013.  If you have the Visual Studio 2013 Update 2 RC installed, you will be delighted to see color-coding while editing the JSON.  There’s a property called appPermissions that we need to edit.

image

We’ll replace this with the following:

Code Snippet

  1. "appPermissions": [
  2. {
  3.     "claimValue": "user_impersonation",
  4.     "description": "Allow full access to the To Do List service on behalf of the signed-in user",
  5.     "directAccessGrantTypes": [],
  6.     "displayName": "Have full access to the To Do List service",
  7.     "impersonationAccessGrantTypes": [
  8.         {
  9.             "impersonated": "User",
  10.             "impersonator": "Application"
  11.         }
  12.     ],
  13.     "isDisabled": false,
  14.     "origin": "Application",
  15.     "permissionId": "b69ee3c9-c40d-4f2a-ac80-961cd1534e40",
  16.     "resourceScopeType": "Personal",
  17.     "userConsentDescription": "Allow full access to the To Do service on your behalf",
  18.     "userConsentDisplayName": "Have full access to the To Do service"
  19. }
  20. ],

The result looks like this:

image

Save it, then upload it to Azure by going to Manage Manifest and Upload Manifest.

The last thing we need to do is to grant our Web API application permission to call the O365 APIs.  In Azure AD, go to the Configure tab for your application.  We will grant the Web API permission to “Read items in all site collections” and “Create or delete items and lists in all site collections”.

image 

Check for User Impersonation

In the previous step, we added the appPermission claim “user_impersonation”.  We will check for the user_impersonation scope claim, making sure it was registered for the Web API application in Azure AD.  We need to perform this check every time an action is executed for our controller, so we create a filter.  It doesn’t matter where you put the class, but I prefer to put this in a new folder named Filters in my Web API project.

Code Snippet

  1. using System.Net;
  2. using System.Net.Http;
  3. using System.Security.Claims;
  4. using System.Web.Http.Filters;
  5.  
  6. namespace ListService.Filters
  7. {
  8.     public class ImpersonationScopeFilterAttribute : ActionFilterAttribute
  9.     {
  10.         // The Scope claim tells you what permissions the client application has in the service.
  11.         // In this case we look for a scope value of user_impersonation, or full access to the service as the user.       
  12.         public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
  13.         {
  14.             if (ClaimsPrincipal.Current.FindFirst("https://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
  15.             {
  16.                 HttpResponseMessage message = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "The Scope claim does not contain 'user_impersonation' or scope claim not found");
  17.             }
  18.         }
  19.     }
  20. }

To use the filter, we apply it to the top of our controller class.  Yes, I am going to be lazy and just use the ValuesController class that Visual Studio generates.

image

Using ADAL

We’re now ready to start doing the Azure Active Directory coding.  Right-click the Web API project and choose Manage NuGet Packages.  Search for “adal” to find the Active Directory Authentication Library, and use the drop-down that says “Include Prerelease” to find version 2.6.1-alpha (prerelease).

image

Install and accept the EULA.  Next, add a class “SharePointOnlineRepository” to the Web API project.  We borrow some code from the GitHub project mentioned previously to obtain the user token.  This class will use the SharePoint REST API, passing an access token in the header.  Here I give you two different ways to accomplish this: using XML (as in the GetAnnouncements method) and JSON (as in the AddAnnouncement method). 

Code Snippet

  1. using Microsoft.IdentityModel.Clients.ActiveDirectory;
  2. using System;
  3. using System.Globalization;
  4. using System.Net.Http;
  5. using System.Net.Http.Headers;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using System.Web;
  9. using System.Xml.Linq;
  10.  
  11. namespace ListService.Models
  12. {
  13.     public class SharePointOnlineRepository
  14.     {
  15.         /// <summary>
  16.         /// Get the access token
  17.         /// </summary>
  18.         /// <param name="clientId">Client ID of the Web API app</param>
  19.         /// <param name="appKey">Client secret for the Web API app</param>
  20.         /// <param name="aadInstance">The login URL for AAD</param>
  21.         /// <param name="tenant">Your tenant (eg kirke.onmicrosoft.com)</param>
  22.         /// <param name="resource">The resource being accessed
  23.            ///(eg., https://kirke.sharepoint.com)
  24.         /// </param>
  25.         /// <returns>string containing the access token</returns>
  26.         public static async Task<string> GetAccessToken(
  27.             string clientId,
  28.             string appKey,
  29.             string aadInstance,
  30.             string tenant,
  31.             string resource)
  32.         {
  33.             string accessToken = null;
  34.             AuthenticationResult result = null;
  35.  
  36.  
  37.             ClientCredential clientCred = new ClientCredential(clientId, appKey);
  38.             string authHeader = HttpContext.Current.Request.Headers["Authorization"];
  39.  
  40.             string userAccessToken = authHeader.Substring(authHeader.LastIndexOf(' ')).Trim();
  41.             UserAssertion userAssertion = new UserAssertion(userAccessToken);
  42.  
  43.             string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
  44.  
  45.             AuthenticationContext authContext = new AuthenticationContext(authority);
  46.  
  47.             result = await authContext.AcquireTokenAsync(resource, userAssertion, clientCred);
  48.             accessToken = result.AccessToken;
  49.  
  50.             return accessToken;
  51.         }
  52.  
  53.         /// <summary>
  54.         /// Gets list items from a list named Announcements
  55.         /// </summary>
  56.         /// <param name="siteURL">The URL of the SharePoint site</param>
  57.         /// <param name="accessToken">The access token</param>
  58.         /// <returns>string containing response from SharePoint</returns>
  59.         public static async Task<string> GetAnnouncements(
  60.             string siteURL,
  61.             string accessToken)
  62.         {
  63.             //
  64.             // Call the O365 API and retrieve the user's profile.
  65.             //
  66.             string requestUrl = siteURL + "/_api/Web/Lists/GetByTitle('Announcements')/Items?$select=Title";
  67.  
  68.             HttpClient client = new HttpClient();
  69.             HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
  70.             request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/atom+xml"));
  71.             request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
  72.             HttpResponseMessage response = await client.SendAsync(request);
  73.  
  74.             if (response.IsSuccessStatusCode)
  75.             {
  76.                 string responseString = await response.Content.ReadAsStringAsync();
  77.  
  78.                 return responseString;
  79.             }
  80.  
  81.             // An unexpected error occurred calling the O365 API.  Return a null value.
  82.             return (null);
  83.         }
  84.  
  85.         /// <summary>
  86.         /// Gets the form digest value, required for modifying
  87.         /// data in SharePoint.  This is not needed for bearer authentication and
  88.         /// can be safely removed in this scenario, but is left here for posterity.
  89.         /// </summary>
  90.         /// <param name="siteURL">The URL of the SharePoint site</param>
  91.         /// <param name="accessToken">The access token</param>
  92.         /// <returns>string containing the form digest</returns>
  93.         private static async Task<string> GetFormDigest(
  94.             string siteURL,
  95.             string accessToken)
  96.         {
  97.             //Get the form digest value in order to write data
  98.             HttpClient client = new HttpClient();
  99.             HttpRequestMessage request = new HttpRequestMessage(
  100.                   HttpMethod.Post, siteURL + "/_api/contextinfo");
  101.             request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
  102.             request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
  103.             HttpResponseMessage response = await client.SendAsync(request);
  104.  
  105.             string responseString = await response.Content.ReadAsStringAsync();
  106.  
  107.             XNamespace d = "https://schemas.microsoft.com/ado/2007/08/dataservices";
  108.             var root = XElement.Parse(responseString);
  109.             var formDigestValue = root.Element(d + "FormDigestValue").Value;
  110.  
  111.             return formDigestValue;
  112.         }
  113.  
  114.         /// <summary>
  115.         /// Adds an announcement to a SharePoint list
  116.         /// named Announcements
  117.         /// </summary>
  118.                 /// <param name="title">The title of the announcement to add</param>
  119.         /// <param name="siteURL">The URL of the SharePoint site</param>
  120.         /// <param name="accessToken">The access token</param>
  121.         /// <returns></returns>
  122.         public static async Task<string> AddAnnouncement(
  123.             string title,
  124.             string siteURL,
  125.             string accessToken)
  126.         {
  127.             //
  128.             // Call the O365 API and retrieve the user's profile.
  129.             //
  130.             string requestUrl =
  131.                 siteURL + "/_api/Web/Lists/GetByTitle('Announcements')/Items";
  132.  
  133.             title = title.Replace('\'', ' ');
  134.             //get the form digest, required for SharePoint list item modifications
  135.             var formDigest = await GetFormDigest(siteURL, accessToken);
  136.  
  137.             HttpClient client = new HttpClient();
  138.             client.DefaultRequestHeaders.Add(
  139.                 "Accept",
  140.                 "application/json;odata=verbose");
  141.  
  142.             HttpRequestMessage request =
  143.                 new HttpRequestMessage(HttpMethod.Post, requestUrl);
  144.             request.Headers.Authorization =
  145.                 new AuthenticationHeaderValue("Bearer", accessToken);
  146.  
  147.             // Note that the form digest is not needed for bearer authentication.  This can
  148.             //safely be removed, but left here for posterity.            
  149.             request.Headers.Add("X-RequestDigest", formDigest);
  150.  
  151.             var requestContent = new StringContent(
  152.               "{ '__metadata': { 'type': 'SP.Data.AnnouncementsListItem' }, 'Title': '" + title + "'}");
  153.             requestContent.Headers.ContentType =
  154.                System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json;odata=verbose");
  155.  
  156.             request.Content = requestContent;
  157.  
  158.             HttpResponseMessage response = await client.SendAsync(request);
  159.  
  160.             if (response.IsSuccessStatusCode)
  161.             {
  162.                 string responseString = await response.Content.ReadAsStringAsync();
  163.                 return responseString;
  164.             }
  165.  
  166.             // An unexpected error occurred calling the O365 API.  Return a null value.
  167.             return (null);
  168.         }
  169.     }
  170. }

We will accept an HTTP POST from the caller to insert new announcements into the list.  The easiest way to do model this is to create a class with the values that will be posted.  I add a new class “Announcement” to the Models folder in my Web API project.

Code Snippet

  1. namespace ListService.Models
  2. {
  3.     public class Announcement
  4.     {
  5.         public string Title { get; set; }
  6.     }
  7. }

Now that we have the repository class that interacts with SharePoint, and we have our new model class, we update the ValuesController class.  The Get action will simply return the entire string of data back from SharePoint.  The Post action will read data from the form data, add an announcement to SharePoint, and then return the SPList data for the item.  I leave it as an exercise to the reader to do something more meaningful with the data such as deserializing it into a model object.

Code Snippet

  1. using ListService.Filters;
  2. using System.Collections.Generic;
  3. using System.Configuration;
  4. using System.Threading.Tasks;
  5. using System.Web.Http;
  6.  
  7. namespace ListService.Controllers
  8. {
  9.     [Authorize]
  10.     [ImpersonationScopeFilter]
  11.     public class ValuesController : ApiController
  12.     {
  13.         // GET api/values
  14.         public async Task<string> Get()
  15.         {
  16.             string clientID = ConfigurationManager.AppSettings["ida:ClientID"];
  17.             string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
  18.  
  19.             string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
  20.             string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
  21.             string resource = "https://kirke.sharepoint.com";
  22.  
  23.             string accessToken = await Models.SharePointOnlineRepository.GetAccessToken(
  24.                 clientID,
  25.                 appKey,
  26.                 aadInstance,
  27.                 tenant,
  28.                 resource);
  29.             var ret = await Models.SharePointOnlineRepository.GetAnnouncements(
  30.                 "https://kirke.sharepoint.com/sites/dev",
  31.                 accessToken);
  32.             return ret;
  33.         }
  34.  
  35.         // POST api/values        
  36.         public async Task<string> Post(Models.Announcement announcement)
  37.         {
  38.             string clientID = ConfigurationManager.AppSettings["ida:ClientID"];
  39.             string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
  40.  
  41.             string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
  42.             string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
  43.             string resource = "https://kirke.sharepoint.com";
  44.  
  45.             string accessToken = await Models.SharePointOnlineRepository.GetAccessToken(
  46.                 clientID,
  47.                 appKey,
  48.                 aadInstance,
  49.                 tenant,
  50.                 resource);
  51.             var ret = await Models.SharePointOnlineRepository.AddAnnouncement(
  52.                 announcement.Title,
  53.                 "https://kirke.sharepoint.com/sites/dev",
  54.                 accessToken);
  55.  
  56.             return ret;
  57.         }
  58.     }
  59. }

Register the Client Application

In Azure AD, go to your directory and add a new application.  Choose “Add an application my organization is developing”. 

image

Next, choose “Native Client Application” and give it a name.

image

In the next screen, we provide a URL.  Any URL is fine here.  If we were building a Windows 8 application, then we’d provide the ms-app URL obtained from the store.  To save some digital ink, this is why we chose a WPF application… any URL will do here as long as it is a valid URL (but it does not to have to actually resolve to anything).  I used “https://OnBehalfOf/Client".

image

In the Update Your Code section, you see that a client ID was generated for you.  Copy that, you’ll need it later.

image

In the Configure Access to Web APIs in Other Applications section, click Configure It Now.  Under Permission to Other Applications, find the ListService that we modified the manifest for earlier in this process.  When we modified the manifest, we modified the appPermissions section and provided a new permission.  We now use this to grant the client application permission to the Web API.  Choose ListService, and choose “Have full access to the To Do List service”.

image

Notice that we are configuring the permission for the client application here, and the only permission it needs is to access our Web API.  We do not have to grant the client application access to O365 because it never talks directly to O365… it only communicates with our custom Web API.

And a reminder to self… you can change the text of the permission by downloading the manifest, editing the appPermission section with the proper text, and uploading it again.  Now make sure to click Save!

image

Create the Client Application

Create a new WPF application.  This is going to be the easiest way to get started as it is the most forgiving in terms of callback URL.  If we used a Windows 8 app, we’d need to register it with the store and obtain the app ID (the one that starts with ms-app).  For now, a WPF application is fine.

image

Add the Active Directory Authentication Library pre-release version 2.6.1-alpha NuGet package.

image

The UI for the application is simple, two buttons and a text box.

Code Snippet

  1. <Window x:Class="Client.MainWindow"
  2.         xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  4.         Title="MainWindow"
  5.         Height="350"
  6.         Width="525">
  7.     <Grid>
  8.         <Button Content="Add Announcement"
  9.                 HorizontalAlignment="Left"
  10.                 Margin="330,80,0,0"
  11.                 VerticalAlignment="Top"
  12.                 Width="142"
  13.                 Click="Button_Click" />
  14.         <Button Content="Get Announcements"
  15.                 HorizontalAlignment="Left"
  16.                 Margin="190,147,0,0"
  17.                 VerticalAlignment="Top"
  18.                 Width="136"
  19.                 Click="Button_Click_1" />
  20.         <TextBox HorizontalAlignment="Left"
  21.                  Height="20"
  22.                  Margin="93,81,0,0"
  23.                  TextWrapping="Wrap"
  24.                  Text="TextBox"
  25.                  VerticalAlignment="Top"
  26.                  Width="215"
  27.                  x:Name="announcementTitle" />
  28.  
  29.     </Grid>
  30. </Window>

The code behind the XAML is what’s interesting.  I am using the HttpClient classes to call our custom Web API.  Note how we add the form data to the request using a Dictionary.  I could have moved the settings to app.config instead of hard-coding everything in the GetAuthHeader method, I leave that as an exercise to the reader as well.  The GitHub sample (link at the For More Information section of this post) does a better job of putting things in app.config, I’d suggest using that as a baseline.

Code Snippet

  1. using Microsoft.IdentityModel.Clients.ActiveDirectory;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Net.Http;
  5. using System.Windows;
  6.  
  7. namespace Client
  8. {
  9.     /// <summary>
  10.     /// Interaction logic for MainWindow.xaml
  11.     /// </summary>
  12.     public partial class MainWindow : Window
  13.     {
  14.         public MainWindow()
  15.         {
  16.             InitializeComponent();
  17.         }
  18.  
  19.         private async void Button_Click(object sender, RoutedEventArgs e)
  20.         {
  21.             //POST to the Web API to create a new announcement
  22.             HttpClient client = new HttpClient();
  23.  
  24.             HttpRequestMessage request = new HttpRequestMessage(
  25.               HttpMethod.Post, "https://localhost:44307/api/Values");
  26.  
  27.             //Create the form POST data
  28.             var postData = new Dictionary<string, string>
  29.             {
  30.                 {"Title",announcementTitle.Text}
  31.             };
  32.             request.Content = new FormUrlEncodedContent(postData);
  33.  
  34.             //Add the authorization Bearer header
  35.             string authHeader = GetAuthHeaderValue();
  36.             request.Headers.TryAddWithoutValidation("Authorization", authHeader);
  37.             HttpResponseMessage response = await client.SendAsync(request);
  38.             string responseString = await response.Content.ReadAsStringAsync();
  39.  
  40.             MessageBox.Show(responseString);
  41.         }
  42.  
  43.         private async void Button_Click_1(object sender, RoutedEventArgs e)
  44.         {
  45.             // Get the list of announcements          
  46.             HttpClient client = new HttpClient();
  47.             HttpRequestMessage request = new HttpRequestMessage(
  48.               HttpMethod.Get, "https://localhost:44307/api/Values");
  49.             string authHeader = GetAuthHeaderValue();
  50.             request.Headers.TryAddWithoutValidation("Authorization", authHeader);
  51.             HttpResponseMessage response = await client.SendAsync(request);
  52.             string responseString = await response.Content.ReadAsStringAsync();
  53.             MessageBox.Show(responseString);
  54.  
  55.         }
  56.  
  57.         private string GetAuthHeaderValue()
  58.         {
  59.             string aadInstance = "https://login.windows.net/{0}";
  60.             string tenant = "kirke.onmicrosoft.com";
  61.  
  62.             AuthenticationContext ac = new AuthenticationContext(
  63.              string.Format(aadInstance, tenant));
  64.  
  65.             string webAPIAppID = "https://kirke.onmicrosoft.com/ListService";
  66.             string clientID = "67070e10-9540-444f-883b-d090d7b7be18";
  67.             string callbackUrl = "https://OnBehalfOf/Client";
  68.  
  69.             AuthenticationResult ar =
  70.               ac.AcquireToken(webAPIAppID,
  71.               clientID,
  72.               new Uri(callbackUrl));
  73.  
  74.             // Call Web API
  75.             string authHeader = ar.CreateAuthorizationHeader();
  76.  
  77.             return authHeader;
  78.         }
  79.     }
  80. }

Cross Your Fingers

This is a lot of configuration, and a lot of code.  There are a lot of opportunities for errors here, so we’re going to cross our fingers, set the start-up projects, and run the application.

image

We run the application and click the button.  A login screen shows itself (this is a good thing!)

image

I wait for a little bit and… darn it, I see this screen.

image

That’s not right, I should see the Announcements list data.  I fire up Fiddler and watch the traffic.  Looking through the requests, I notice that one of them does not have an access token value.

image

I double-check the code, yep… we’re providing it.  What did I miss?  I debug through the code and see that I do not get an access token.  Hmm… maybe permissions?  Let’s go double-check the Web API permissions in Azure AD. 

image

Yep… that’s the problem.  Earlier in the post I told you to add two permissions for Office 365 SharePoint Online, but apparently I forgot to hit Save.  Do this step again, and this time, hit Save!

image

Run the application again, this time we see a different response… we get the items from our Announcements list!

image

We then try our operation to write an announcement to the list, and it works.  The UI is horrible, but serves my purpose for now.

image

We then check the Announcements list in SharePoint and see the new announcement.  Notice that the item was added by the Web API application on behalf of the current user!

image

Think about how incredibly cool that is.  We just used constrained delegation to a downstream resource, all using OAuth.

image

For More Information

Using a existing Windows Azure AD Tenant with Windows Azure

Web API OnBehalfOf DotNet 

Secure ASP.NET Web API with Windows Azure AD

Comments

  • Anonymous
    June 02, 2014
    Awesome article Kirk!  Is there any documentation for the Manifest schema?  Trying to get a little deeper into the permissions, and what is possible.  Any help or pointers appreciated.

  • Anonymous
    June 02, 2014
    Thanks, Pete.  About the only documentation available at the moment is at msdn.microsoft.com/.../dn132599.aspx.  What you see is pretty much the set of options at the moment.  See github.com/AzureADSamples for more samples that demonstrate the capabilities.

  • Anonymous
    June 17, 2014
    Thanks Kirk, I will look into the links!

  • Anonymous
    August 26, 2014
    Is it possible that I get access to the Mails, Files, Folders of a user using Admin credentials? Let's just say it is one form of impersonation where the Admin plays the role of a user. Is this achievable?I have changed my manifest, run Powershell commands for impersonation assignment, but nothing helps.The REST APIs give changelogs only but when I try to fetch the Item that is changed it says, "Item does not exist. It may have been deleted by another user."But the item is there, so I guess it is purely an access issue. If it is, then it is strange because what good is the Admin account then if it can't monitor such information!Much appreciated

  • Anonymous
    May 03, 2015
    Hi Kirk,I cannot get the token with the code shown above (ref function GetaccessToken).While debugging I observed that the string authHeader is always empty, so I presume this causes the token not to be generated and then to get an Authorization Denied message (302) to my client application.Any hints on this?Thanks a lot in advance, nice post!Alex Varia

  • Anonymous
    May 06, 2015
    @Alex - I don't usually update past posts.  The techniques have changed somewhat, the appPermission is no longer needed as impersonation is added by default.  See blogs.msdn.com/.../a-sample-sharepoint-app-that-calls-a-custom-web-api.aspx for a more recent version of how to do this.  That link points to a GitHub repository where you can download a sample.  Note that you have to change the appSettings values (client ID, client secret, realm, etc) with values from your environment.

  • Anonymous
    June 29, 2015
    Really nice article Kirk. So just to be clear, form digest is no longer required, when using Bearer authentication? cheers

  • Anonymous
    June 29, 2015
    @Nigel - that is correct, form digest is not required when using Bearer authentication.

  • Anonymous
    June 29, 2015
    AWESOME!!!!

  • Anonymous
    December 07, 2016
    The comment has been removed