Wednesday, February 23, 2022

SharePoint provider hosted app certificate trust issues

 It is important to configure certificates correctly for a provider hosted app in SharePoint website since the authentication requires communication between Azure AD, SharePoint, and our app, which is hosted on a different IIS server.

I have added this method to TokenHelper class and call it in CSOM webpart methods to trust certificates

public class TokenHelper

    {

            #region public methods

 

            /// <summary>

            /// Configures .Net to trust all certificates when making network calls.  This is used so that calls

            /// to an https SharePoint server without a valid certificate are not rejected.  This should only be used during

            /// testing, and should never be used in a production app.

            /// </summary>

            public static void TrustAllCertificates()

            {

                //Trust all certificates

                System.Net.ServicePointManager.ServerCertificateValidationCallback =

                    ((sender, certificate, chain, sslPolicyErrors) => true);

            }

}

Also there are some articles which describe certificates trust configuration on a sharepoint farm

https://docs.microsoft.com/en-us/sharepoint/troubleshoot/sharing-and-permissions/ssl-certificate-authentication

https://docs.microsoft.com/en-us/sharepoint/administration/exchange-trust-certificates-between-farms

https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/create-high-trust-sharepoint-add-ins


SharePoint CSOM get absolute url from FileRef

 This one liner can return absolute url of an item using its FileRef property

var absoluteUrl = new Uri(context.Url).GetLeftPart(UriPartial.Authority) + serverRelativeUrl;



SharePoint add in part - postback error localhost refused to connect

 While developing an addin part using CSOM I was able to use a button to postback the data, but the addin part would show the error 'Localhost refused to connect'. So I was trying to find out why the addin part work on initial load but fails to load when postback method return the view.

After investingating the forms collection I noticed that there are some tokens added to the forms collection by sharepoint in the inital part load request. These tokens are used by IIS to validate the user request, thus they need to be sent with every request. The code below solved this issue:


In the controller I added the method CopyTokens

    private void CopyTokens(AddInViewmodel viewModel)

        {

            if (Request.Form["SPAppToken"] != null)

            {

                viewModel.SPAppToken = Request.Form["SPAppToken"];

            }

            if (Request.Form["SPSiteUrl"] != null)

            {

                viewModel.SPSiteUrl = Request.Form["SPSiteUrl"];

            }

            if (Request.Form["SPSiteTitle"] != null)

            {

                viewModel.SPSiteTitle = Request.Form["SPSiteTitle"];

            }

            if (Request.Form["SPSiteLogoUrl"] != null)

            {

                viewModel.SPSiteLogoUrl = Request.Form["SPSiteLogoUrl"];

            }

            if (Request.Form["SPSiteLanguage"] != null)

            {

                viewModel.SPSiteLanguage = Request.Form["SPSiteLanguage"];

            }

            if (Request.Form["SPSiteCulture"] != null)

            {

                viewModel.SPSiteCulture = Request.Form["SPSiteCulture"];

            }

            if (Request.Form["SPRedirectMessage"] != null)

            {

                viewModel.SPRedirectMessage = Request.Form["SPRedirectMessage"];

            }

            if (Request.Form["SPCorrelationId"] != null)

            {

                viewModel.SPCorrelationId = Request.Form["SPCorrelationId"];

            }

            if (Request.Form["SPErrorCorrelationId"] != null)

            {

                viewModel.SPErrorCorrelationId = Request.Form["SPErrorCorrelationId"];

            }

            if (Request.Form["SPErrorInfo"] != null)

            {

                viewModel.SPErrorInfo = Request.Form["SPErrorInfo"];

            }

        }

I called this method just before completing the postback handler

            public ActionResult Index()

        {

            var viewModel = new AddInViewmodel();

           

            TokenHelper.TrustAllCertificates();

            string contextTokenString = TokenHelper.GetContextTokenFromRequest(Request);

 

            if (contextTokenString != null)

            {

                // Get context token

                var contextToken = TokenHelper.ReadAndValidateContextToken(contextTokenString, Request.Url.Authority);

 

                // Get access token

                Uri sharepointUrl = null;

                if (Request.QueryString["SPAppWebUrl"] != null)

                {

                    sharepointUrl = new Uri(Request.QueryString["SPAppWebUrl"]);

                    var accessToken = TokenHelper.GetAccessToken(contextToken, sharepointUrl.Authority).AccessToken;

                }

               

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

                if (spContext != null)

                {

                    using (var clientContext = spContext.CreateUserClientContextForSPHost())

                    {

                        if (clientContext != null)

                        {

                            // Code to fetch data from SharePoint site into view model

 

                        }

                    }

                }

            }

            CopyTokens(viewModel);

 

            return View(viewModel);

        }

Lastly, I have added some hidden controls in the view to persist and send these tokens in each new postback request, also note the querystring parameters added to reqeust.


@using (Html.BeginForm(actionName: "Index", controllerName: "MyAddInWebpart",

    routeValues: new

    {

        SPHostUrl = Request.QueryString["SPHostUrl"],

        SPHostTitle = Request.QueryString["SPHostTitle"],

        SPAppWebUrl = Request.QueryString["SPAppWebUrl"],

        SPLanguage = Request.QueryString["SPLanguage"],

        SPClientTag = Request.QueryString["SPClientTag"],

        SPProductNumber = Request.QueryString["SPProductNumber"],

        SenderId = Request.QueryString["SenderId"]

    }, method: FormMethod.Post, htmlAttributes: new { }))

    {

        @*@Html.AntiForgeryToken()*@

        @Html.HiddenFor(model => model.SPAppToken)

        @Html.HiddenFor(model => model.SPSiteUrl)

        @Html.HiddenFor(model => model.SPSiteTitle)

        @Html.HiddenFor(model => model.SPSiteLogoUrl)

        @Html.HiddenFor(model => model.SPSiteLanguage)

        @Html.HiddenFor(model => model.SPSiteCulture)

        @Html.HiddenFor(model => model.SPRedirectMessage)

        @Html.HiddenFor(model => model.SPCorrelationId)

        @Html.HiddenFor(model => model.SPErrorCorrelationId)

        @Html.HiddenFor(model => model.SPErrorInfo)

<div class="form"><div class="form-group"><div class="form-row">

       <input type="submit" class="btn btn-default" value="Save" />

</div></div></div>

}


c# httpclient The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch

 If we get this error while trying to get http reponse using HttpClient object, it could mean that certificate validation fails for the remo...