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

10,570

Solution 1

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

Solution 2

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.

Solution 3

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.

Share:
10,570
Lys
Author by

Lys

Updated on June 18, 2022

Comments

  • Lys
    Lys over 1 year

    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
    Lys over 9 years
    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.
  • Oleg Kyrylchuk
    Oleg Kyrylchuk over 9 years
    @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.
  • Lys
    Lys over 9 years
    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.
  • BrianLegg
    BrianLegg over 8 years
    I wish I could give you more than 1 point! This saved me after hunting for hours. Thank you!