3

We are developing an low-trust provider hosted add-in which will be hosted in the cloud. We use Web API to retrieve data from the add-in. To ensure that only authenticated users can call the Web API services, we use WebAPIContextFilterAttribute (from OfficeDevPnP like in this example).

Additionally, we have a number of Web API services which are hosted in the local network like e.g. a print service to use a specific printer.

We are facing the following scenario:
When a user clicks the print button in the add-in, web api will be called in the add-in. The add-in creates the report and pass it to the print service hosted in the local network. The print service will print the report on a local installed printer. (It's not an option to install the printer on the client.)

Now we are facing the problem that we need to ensure that only users can call this service who are authenticated to SharePoint. We also need to identify the user who calls the service and get the client context to check additional infos in the host web.

2

You can solve this issue in different ways.
Since you are using authorization filters (like WebAPIContextFilter) I'm assuming you are not using Owin middleware for ASP.NET.

This approach I'm proposing is not using Owin. I will give you a general idea, implementation is for you. The idea is to pass your context token from cloud API to local network API and validate that token later on network API.

In your cloud API controller put the following method:

private string GetCacheKeyValue()
{
    CookieHeaderValue cookie = Request.Headers.GetCookies(WebAPIHelper.SERVICES_TOKEN).FirstOrDefault();
    if (cookie != null)
    {
        return cookie[WebAPIHelper.SERVICES_TOKEN].Value;
    }
    else
    {
        NameValueCollection queryParams = Request.RequestUri.ParseQueryString();
        return queryParams.Get(WebAPIHelper.SERVICES_TOKEN);
    }
}

In your cloud API, in controller, right after report generation logic put this code:

var cacheKey = GetCacheKeyValue();
var spContext = WebAPIContextCache.Instance.Get(cacheKey);

HttpClient client = new HttpClient();
.....
client.DefaultRequestHeaders.Add("Authorization", spContext.SharePointServiceContext.Token);
//send request eventually 

PnP's implementation stores sharepoint context information inside in-memory cache on the server. Cache key stored inside cookie, we need to read cache key from cookie, then get context information.
It's more convenient to use WebAPIHelper.GetCacheKeyValue(ControllerContext);, but unforetunatuly GetCacheKeyValue is private method, so I just "clone" it. Then I'm sending request to network API and attaching sharepoint context token as authorization header.

Next, inside network API, you need to create a custom authorization filter, something like this one:

public class MyCustomAuthorizationAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        base.OnAuthorization(actionContext);

        var authorization = actionContext.Request.Headers.FirstOrDefault(h => h.Key.Equals("Authorization"));

        if (string.IsNullOrEmpty(authorization.Value.FirstOrDefault()))
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "You are not authorized to use this resource");
        }

        try
        {
            var sharePointContextToken = TokenHelper.ReadAndValidateContextToken(authorization.Value.Single());
            if(sharePointContextToken == null) {
               actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "You are not authorized to use this resource");
            }
        }
        catch (Exception)
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "You are not authorized to use this resource");
        }
    }
} 

Decorate your network API with filter and only authenticated users will have an access to action methods. All others will receive 401 Unauthorized.

In order to create client context, inside network API controller you can do:

var authorization = Request.Headers.FirstOrDefault(h => h.Key.Equals("Authorization"));
var sharePointContextToken = TokenHelper.ReadAndValidateContextToken(authorization.Value.Single());
var accessToken = TokenHelper.GetAccessToken(sharePointContextToken, new Uri(hostUrl).Authority).AccessToken;
var clientContext = TokenHelper.GetClientContextWithAccessToken(hostUrl, accessToken); 

NOTE: in your network API's web.config you need ClientId and ClientSecret, also you need to pass hostUrl param to your network API.

  • 1
    Thanks very much Kai. Your Solution worked for me. As you pointed out, its important to have the same ClientId and ClientSecret in Add-In's and network API's config. Also you need to pass the Add-Ins hostname to TokenHelper.ReadAndValidteContextToken or add HostedAddpHostName to the config of network API. Otherwise the validation of the context token failed. – Jan-Bernd Dec 3 '16 at 15:57
  • Good point, I forgot about this. – Sergei Sergeev Dec 3 '16 at 16:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.