6

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!

Lys
  • 568
  • 1
  • 4
  • 18
  • Did you ever figure this out? There have been no updates from Microsoft on how this approach is supposed to work. – Rex Whitten Jan 05 '15 at 16:25
  • Hi, Unfortunately I haven't figured out yet, since I had to deliver my project and after discussion with some of my colleagues I just used HTTP GET instead of POST. It violates the protocol but since MS are so quick at fixing their own problems in new technologies we had no choice. I will get back to it, only after there is some proper update from MS side ... You can also solve it via Post/Redirect/Get pattern, but it really is still more or less like just using GET instead. – Lys Jan 15 '15 at 15:06
  • I am not sure if I follow. SharePoint is doing the POST at my app. Are you saying that my app has to implement a POST Controller method ? I have no control over how sharePoint is redirecting to me. – Rex Whitten Jan 23 '15 at 20:46
  • I found another work around here [link](https://social.technet.microsoft.com/Forums/lync/en-US/d3d70290-520a-4975-848e-fd55e770b42f/sharepoint-2013-mvc-providerhosted-app-fails-to-pass-sharepointcontextfilter-on-httppost?forum=sharepointgeneral) by Nicolae Anghel – otaku Oct 27 '16 at 18:50

5 Answers5

7

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 :)

Irf
  • 4,285
  • 3
  • 36
  • 49
Libin M
  • 481
  • 1
  • 6
  • 16
  • Hey, yes this is something I have also run into, but it is a different problem from the one that I am asking for help : ) But, you are correct, you always need to pass the SpHostUrl token when redirecting. – Lys Aug 07 '14 at 13:09
  • 1
    I wish I could give you more than 1 point! This saved me after hunting for hours. Thank you! – BrianLegg Jun 11 '15 at 18:55
1

I had the same issue and spent a few days to solve that. I don't know why SharePointContextFilter didn't work properly and didn't redirect. So my app was started via POST method and I had resubmission form confirm dialog when refreshed page. I also got "Unable to determine your identity. Please try again by launching the app installed on your site.".

To fix that I used Post/Redirect/Get pattern. I changed my Index action to do RedirectToAction to another IndexGet action. But you should have two IndexGet methods — for post and get.

In the end I got three actions for that. Index action redirected to POST IndexGet Action:

    public ActionResult Index()
    {            
        return RedirectToAction("IndexGet");
    }

Two IndexGet methods for Post and Get have the same code:

    [HttpPost]
    public ActionResult IndexGet(string tmp)
    {
        //your code from Index action
        return View();
    }

    [HttpGet]
    public ActionResult IndexGet()
    {
        //your code from Index action
        return View();
    }

So it works in the such way: on the start the Index action is called via POST. It redirects to POST IndexGet action and in this place SharePointContextFilter works properly and calls IndexGet via GET. This pattern solved my issue.

Oleg Kyrylchuk
  • 1,199
  • 1
  • 11
  • 21
  • Hi, I am not sure that you understood my problem correctly. Plus I don't see you using [SharepointContextFilter] data annotation. That makes all of you actions in the controller public, thus non-sharepoint authenticated users can access them. That's crucial in my case. – Lys Jun 19 '14 at 14:08
  • @Lys, sorry about forgetting [SharePointContextFilterAttribute]. I use it in the base controller. So all my actions use it. In your update you wrote that SharePointContextFilterAttribute always returns RedirectionStatus.CanNotRedirect. I had the same. I don't know why Sharepoint online cannot call RedirectionStatus.ShouldRedirect to refresh my app via GET method. So I used PRG pattern to enforce redirect. It worked for me. – Oleg Kyrylchuk Jun 24 '14 at 11:59
1

I Agree with Libin/ user24176!

When navigating from one controller's action to another, the SharePoint refers the SPHostUrl to get the context. But, when I found out the issue is due to missing of SPHostUrl, I tried appending SPHostUrl in RedirectToAction method & everything starts working.

Solution: Append SPHostUrl to RedirectToAction method calls It's a common scenario to redirect the user to another Action using Controller's RedirectToAction method and it's overloads.

By default you can easily redirect the user to a "Another" Action using the following line of code

return RedirectToAction("Another");

Assuming that you're executing this call from the HomeController, your browser will be redirected to www.PHapp.com/Home/Another and again the SPHostUrl is missing

To fix that you can easily pass the SPHostUrl to the RedirectToAction method or you can override the method in your BaseController class (which each and every MVC App should have) and change the actual redirect to something like this

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

This will force your browser to request the Action using the following url www.PHapp.com/Home/Fallback?SPHostUrl=..."

Of course there are other pitfalls when building SharePoint Apps using MVC, but being able to create a SharePoint Context from each and every Controller Method is a kind of critical depending on customer's requirements.

--# Update 2--- Initially I was working in IE & the site loads the controller Action without SPHostURL in IE lower versions, later I have updated _Layouts HTML- header section with force IE to open in latest version:

<meta http-equiv="X-UA-Compatible" content="IE=Edge" />

After this, MVC PH App binds the SPHotsURL in all the Action links.

0

I had the same error. Post request is normal for this case if it doesn't contain Form Data with errors. For example such Post request can contain such error:

SPErrorInfo:The endpoint address '' does not match the app's endpoint ''.

If you see such error in the POST your SharePointContextFilterAttribute class will surely throw the error with POST as you described in your question. But if POST request is a "good" request it will be processed before throwing the error and return redirect OK.

So, the task here is to not change behavior of SharePointContextFilterAttribute, but solve the issue. For example the error above says that I specified wrong domain name for my ClientID. You can generate ClientID in Microsoft Seller Dashboard and it requires to specify domain. If it differs from the domain where provider hosted app is hosted it will throw the error.

It is the same about https... if the redirect URL is not HTTPS it will throw the error also. For some reasons when you start app from Visual Studio it doesn't throw such errors, but if you deploy your app to App Catalog it will throw such errors.

So, I would recommend you to open Fiddler and research your POST requires. Most likely you will see the error, which clearly explains the problem.

-2

You have probably lost the Sharepoint Context in the Redirect Action.

Watch this demonstration from SP Conference from 29 minutes. The error you are describing is intentionally produced and a solution (provide the Sharepoint Context in a new RedirectAction is presented)

https://youtu.be/EnT5FtTeWA4?t=28m57s