3

I'm a complete newbie to SharePoint Provider-Hosted Solutions and their authentication logic. My App is an ASP MVC Solution. I run many AJAX-Calls from Client-Scripts to my Controller to interact with the SharePoint Host asynchronously. Many of my Task-Handlers in the Controller require the ClientContext to handle UserData.

I analyzed the default procedures which provider-hosted Apps use to get a ClientContext. I found out that the GetContextTokenFromRequest(HttpRequestBase request) method of the TokenHelper is used to get a Context Token from the Request. This method searches for the following Parameters and returns it's Value, if one of them was found in the Request:

  • AppContext
  • AppContextToken
  • AccessToken
  • SPAppToken

The result of that Method is later used in the method GetClientContextWithAccessToken(string targetUrl, string accessToken) together with the HostWeb-Url to finally receive a ClientContext.

This means that the SharePoint App Redirection System must send either one of those Parameters to the provider-hosted App. In my case the Request contained the Parameter SPAppToken

The problem is that the AJAX Calls from my App-Client are obviously not coming from the SharePoint Host and do not contain any Token Parameters.

I used to store the Access-Token (received through the GetContextTokenFromRequest(HttpRequestBase request) method) in the Session of the user which is currently using my App and use it on every Call to the Controller. That works for the start but after some time of using the app, without any redirection through the SharePoint, the Access Token seems to expire, as I get 401 Unauthorized Exeptions everywhere.

I'm searching for a way to properly cache an Access-Token inside my provider-hosted App in order to get a ClientContext to interact with the SharePoint Host.

Is this the right approach, or are there better ways of using the CSOM in such environments? If that is a valid approach, is there any way to validate the Token or to refresh it in case of an exception?

  • Did you see Navigating to new page loses querystring? – eirikb Nov 21 '14 at 13:54
  • I find it a bit scary to save an Access Token in a Clients Cookie :/ And that approach would not help, when the Access-Token expires! – Vinz Nov 21 '14 at 13:58
  • 1
    Sorry I misunderstood the question. The link I provided is just about storing the URL – eirikb Nov 21 '14 at 14:01
  • Beside ajax the default app setup should redirect user to SharePoint to refresh the token (and then be automatically redirected back to your app) - so can't you do the same? If your Web API controller fails to auth with ClientContext simply tell your client script to refresh the browser, which again will trigger the whole refresh-token-cycles – eirikb Nov 21 '14 at 14:06
1

I recently ran into the similar scenario and after some research I got a solution to this problem. In my case user keeps on updating data on my page for hours and then hit Save button but till the time the access token was getting expired. To handle this situation I performed following steps

  1. Create a empty RefreshToken View in my MVC app.

Views/RefreshTokenView.cshtml

@{
        Layout = null;
    }

    @{
        ViewBag.Title = "RefreshToken";
    }

    <!DOCTYPE html>
    <html>
    <head>
    </head>
    <body>
        <h2>Refreshing Token...</h2>
    </body>
    </html>
  1. Add one more .cs file as ContextFilter class in your Filters

Here in this code we are using the same code as SharePointContextFilterAttribute.cs file but only difference is we return 401- Unauthorized Error when server receives an http request but the token has already expired.

Filters/ApiSharePointContextFilterAttribute.cs

public class ApiSharePointContextFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");}

        Uri redirectUrl;
        string statusText = string.Empty;
        switch (SharePointContextProvider.CheckRedirectionStatus(filterContext.HttpContext, out redirectUrl))
        {

                case RedirectionStatus.Ok:
                    statusText = "Authenticated";
                    return;
                case RedirectionStatus.ShouldRedirect:
                    statusText = "NotAuthenticated";
                    //filterContext.Result = new RedirectResult(redirectUrl.AbsoluteUri);
                    break;
                case RedirectionStatus.CanNotRedirect:
                    statusText = "NotAuthenticatedError";
                    //filterContext.Result = new ViewResult { ViewName = "Error" };
                    break;
            }

            if (statusText != "Authenticated")
            {
                // if (statusText == "NotAuthenticatedError") ExceptionLogger.LogError(new LPException("User " + email + " is not authenticated", Severity.Information, HttpStatusCode.Unauthorized));

                filterContext.HttpContext.Response.Clear();
                filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                filterContext.HttpContext.Response.End();
            }
        }
}
  1. Write Following code in your controller file

HomeController.cs

/// <summary>
    /// This view is a temporary view created to handle user context token expiration issue.
    /// </summary>
    /// <returns></returns>
    [SharePointContextFilter]
    public ActionResult RefreshTokenView()
    {
        return View();
    }



    [ApiSharePointContextFilter]
     public ActionResult YourMethodName(){
your custom code here
    }

Observe that we are using ApiSharePointContextFilter for all the endpoints in your controller except the Index and the RefreshTokenView

  1. Finally in your javascript code use following error handler to refresh the token. We receive errorThrown == "Unauthorized" from your ApiSharePointContextFilterAttribute.cs when it receives http request with expired token. Then we create a dummy Iframe of widht :1px; height:1px virtually invisible on page and call our RefreshTokenView from there. Then retry YourMethodName Operation again refreshed new token. Make sure you add reference to spcontext.js in your html file

    error: function (xhr, textStatus, errorThrown) {

            if (errorThrown && errorThrown == "Unauthorized") {
                $('<iframe class="dummyTokenRefreshIframe"  name="myFrame1" style="height: 1px; width:1px;" >').appendTo('body');
                var refreshTokenUrl = UrlPointingToRefreshTokenView // e.g . Home/RefreshTokenView
                console.log(refreshTokenUrl);
    
                $('.dummyTokenRefreshiFarmeMyTask').attr('src', refreshTokenUrl);
                setTimeout(function () { callYourMethodAgainToRetry(); }, 10000);  
    
            } else {
                // window.location.reload(true);
                alert("UPDATE_FAILED: Due to an unexpected error, your changes have not been saved on the server.");
        }
    

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.