Sharepoint 2013 MVC 5 provider-hosted app. Fails to authenticate on HttpPost using [SharePointContextFilter]

Lys picture Lys · May 23, 2014 · Viewed 10.3k times · Source

I have been banging my head for the past week unable to resolve some issues with proper authentication for sharepoint provider-hosted app.

I am currently developing a sharepoint app for a company's Sharepoint online. I am using Visual Studio 2013. I deploy the app as a Cloud-service on the company's Windows Azure portal. Everything goes smooth up to the point when i need to make a HttpPost, then the app fails to authenticate. The design of the Conroller is as it follows:

    [SharePointContextFilter]
    public ActionResult Index()
    {
        UserSingleton user_temp = UserSingleton.GetInstance();

        User spUser = null;

        SharePointContext spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);

        using (var clientContext = spContext.CreateUserClientContextForSPHost())
        {
            if (clientContext != null)
            {
                spUser = clientContext.Web.CurrentUser;

                clientContext.Load(spUser, user => user.Title, user => user.Email);

                clientContext.ExecuteQuery();

                ....code....

            }
        }

           ....code....

        return View();
    }

Loading the index page goes fine, the the action creates user context and it's all good. The problem comes when i try to submit a HttpPost as it follows:

    [HttpPost]
    [ValidateAntiForgeryToken]
    [SharePointContextFilter]
    public ActionResult GetService(System.Web.Mvc.FormCollection fc)
    {
        PlannedHours ph = this.PopulateModel(fc);

        if (ph == null)
            return View("NoInfoFound");

        ViewData["PlannedHours"] = ph;

        return View("Index");
    }

When I call this via the post button, i get a "Unable to determine your identity. Please try again by launching the app installed on your site." The Shared/Error.cshtml view. The thing is that when i remove the [SharePointContextFilter] then it works, but that means that the request doesn't pass through[SharePointContextFilter] thus it is not properly authenticated? Or is it? Because it fails to validate the user's legitimacy.

One thing that i noticed when i don't remove [SharePointContextFilter] and invoke the post, then the url ends up without the {StandardTokens} query. Is it suppose to be like that - i mean it is smth like hostname.com/Home/GetService, however when i use actionlink the spcontext.js always appends the {StandardTokens} query to the base url - smth like hostname.com/Home/ActionNAme/?SPHostUrl=https%3A%2F%2FSHAREPOINTPAGEURL....

What i notice is that i call hostname.com/Home/ActionNAme/ without appending the query it fails to pass the [SharePointContextFilter].

I am fairly new to sharepoint 2013 and MVC 5 ( Razor ) so please if you know why my HttpPost fails to pass the [SharePointContextFilter] try to explain me or give any suggestion. I have tried using HttpGet However, when I Invoke the HttpGet having the [SharePointContextFilter] and appending the SPHostUrl= token it works. But then i cannot use the [ValidateAntiForgeryToken]. Is [ValidateAntiForgeryToken] even needed in such an app since the [SharePointContextFilter] always checks the legitimacy of the user? I am quire confused now. There is tons of material to read on the net and nothing is close to explain when to append these Standard tokens, when to use the [SharePointContextFilter] etc. The matter of fact is that I am developing a sharepoint app for the first time in my life and i've been researching and coding only for the past 3 weeks. So my knowledge is yet pretty limited, have that in mind when answering. Thanks in advance, I hope that i get some clarification about what is happening!

-----------------------------UPDATE----------------------------------------

Ok, a quick update. I have found out something rather weird. The SharePointContextFilterAttribute.cs

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

        Uri redirectUrl;
        switch (SharePointContextProvider.CheckRedirectionStatus(filterContext.HttpContext, out redirectUrl))
        {
            case RedirectionStatus.Ok:
                return;
            case RedirectionStatus.ShouldRedirect:
                filterContext.Result = new RedirectResult(redirectUrl.AbsoluteUri);
                break;
            case RedirectionStatus.CanNotRedirect:
                filterContext.Result = new ViewResult { ViewName = "Error" };
                break;
        }
    }
}

Always returns the last case ( RedirectionStatus.CanNotRedirect ) because the method SharePointContextProvider.CheckRedirectionStatus(filterContext.HttpContext, out redirectUrl) contains something that I cannot wrap my head around.

First of all:

     Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);

        if (spHostUrl == null)
        {
            return RedirectionStatus.CanNotRedirect;
        }

Ok i understand that - if the httpContext.Request does no contain the spHostUrl it will fail to redirect. That for some reason has to be there.

But the following:

    if (StringComparer.OrdinalIgnoreCase.Equals(httpContext.Request.HttpMethod,                    "POST"))
        {
            return RedirectionStatus.CanNotRedirect;
        }

Wait WHAAAT?!? No POST allowed?!!? What is going on here? I really don't know if I am doing something totally wrong or what? Am I even allowed to play around with the SharePointContext.cs ? I really need someone to clarify what exactly is going on... I'd appreciate!

Answer

Libin M picture Libin M · Aug 5, 2014

Above solution didn't work for me. I had the same problem with post, but for me it was the

return RedirectToAction("Index");

causing the error.

I changed it to:

return RedirectToAction("Index", new {SPHostUrl = SharePointContext.GetSPHostUrl(HttpContext.Request).AbsoluteUri});

and it worked.

I am not sure this is the solution for your problem as you are doing return view, but it may help someone :)