Domanda

I have developed a Provider Hosted MVC App for SP2013. Everything works in my development environment; however, I am struggling getting it to work in our production (on-premises) environment.

Anything with the [SharePointContextFilter] attribute will not load (i.e. Home/Index aka start page), but About or Contact pages will as they do not have the attribute. The homepage redirects to my 'Error' page; however, other actions give the unexpected error page: "An unexpected error has occurred. Please try again by launching the app installed on your site."

When navigating to the app I see, via Fiddler, the following error: SPAppToken=&SPSiteUrl=http%3A%2F%2Ftest.thrive&SPSiteTitle=Test+Thrive&SPSiteLogoUrl=&SPSiteLanguage=en-US&SPSiteCulture=en-US&SPRedirectMessage=EndpointAuthorityMatches&SPErrorCorrelationId=b237159f-1886-7076-971c-96c26a739eb2&SPErrorInfo=The+Azure+Access+Control+service+is+unavailable.

Which appears to be a red-hearing and seems exactly like this post: SharePoint 2013 Provider-Hosted App "Azure Access Control Service is unavailable"

I have looked at the suggested blog posts by Russ (Part 1 & Part 2).

I have my provider hosted web hosted on a seperate server from the SP ones which is accessible via https://apps.mycompany.on.ca/appname and is using our company wildcard cert issued by GoDaddy.

Because of this on the SharePoint side I have added 4 Trusted Root Authorities to SharePoint as my understanding is the entire certificate chain needs to be a trusted authority:

  • Company Wildcard cert
  • GoDaddy CA - G2
  • GoDaddy Root CA
  • GoDaddy Class 2 CA

I have also registered the company wildcard as a trusted token issuer:

$certPath = "C:\wildcard\wc.cer"
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath)
$realm = Get-SPAuthenticationRealm
$specificIssuerId = [guid]::NewGuid()  # Make sure to take note of this
$fullIssuerIdentifier = $($specificIssuerId.toString() + '@' + $realm)
New-SPTrustedSecurityTokenIssuer -Name "Company High Trust Apps Cert" -Certificate $certificate -RegisteredIssuerName $fullIssuerIdentifier -IsTrustBroker

I thought this would have allowed things to work properly. It is worth noting that our web app (HNSCs) is not SSL, nor is our add-in domain (however, other apps are working, i.e. ones bought from the store).

I have also seen mention of modifying the TokenHelper.cs file which I have done to the following:

#if DEBUG
    private static readonly string ClientSigningCertificatePath = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePath");
    private static readonly string ClientSigningCertificatePassword = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePassword");
    private static readonly X509Certificate2 ClientCertificate = (string.IsNullOrEmpty(ClientSigningCertificatePath) || string.IsNullOrEmpty(ClientSigningCertificatePassword)) ? null : new X509Certificate2(ClientSigningCertificatePath, ClientSigningCertificatePassword);
    private static readonly X509SigningCredentials SigningCredentials = (ClientCertificate == null) ? null : new X509SigningCredentials(ClientCertificate, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest);
#else
    private static readonly string ClientSigningCertificateSerialNumber = WebConfigurationManager.AppSettings.Get("ClientSigningCertificateSerialNumber");
    private static readonly X509SigningCredentials SigningCredentials = GetSigningCredentials(GetCertificateFromStore());
#endif

#if !DEBUG
    private static X509SigningCredentials GetSigningCredentials(X509Certificate2 cert)
    {
        return (cert == null) ? null
                            : new X509SigningCredentials(cert,
                                                        SecurityAlgorithms.RsaSha256Signature,
                                                        SecurityAlgorithms.Sha256Digest);
    }

    private static X509Certificate2 GetCertificateFromStore()
    {
        if (string.IsNullOrEmpty(ClientSigningCertificateSerialNumber))
        {
            return null;
        }

        // Get the machine's personal store
        X509Certificate2 storedCert;
        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

        try
        {
            // Open for read-only access                 
            store.Open(OpenFlags.ReadOnly);

            // Find the cert
            storedCert = store.Certificates.Find(X509FindType.FindBySerialNumber,
                                                ClientSigningCertificateSerialNumber,
                                                true)
                        .OfType<X509Certificate2>().SingleOrDefault();
        }
        finally
        {
            store.Close();
        }

        return storedCert;
    }
#endif

And in the Web project Web.config I added the following app settings:

<add key="ClientID" value="12035ef7-6e7f-4d9f-9d6b-2f974fef09c4" />
<add key="ClientSigningCertificateSerialNumber" value="<Company Wildcard Serial Number>" />
<add key="IssuerId" value="de9920bd-b2b8-42a8-b441-cb1e7b7f3636" />

In the AppManifest.xml I have tried hardcoding the ClientId into the file... but thought this unecessary when it should be done via the publishing process? Same ClientId as in the Web.config file.

I do not understand what I am missing to get this to all come together into a working production app (everything works in my development environment via Visual Studio).

I have also checked out this Step by Step Installation Guide – SharePoint 2013 On-Premises Provider Hosted High Trust Configuration for guidance.

Any help/suggestions would be greatly appreciated. Thanks.

È stato utile?

Soluzione

After a long drawn out process of on and off again trial and error I was able to get my app working and obtain a SharePoint Context in production. A summary of what I have done (that lead to working) is as follows (PowerShell scripts from here):

Need to make sure the entire certificate chain (in my case GoDaddy since our SSL cert was issued by them) was added as SharePoint Root Authorties, running the PowerShell script:

./AddSPRootAuthority.ps1 -CertPath "C:\wildcard-hightrust\Go Daddy Class 2 Certification Authority.cer" -CertName "Go Daddy Class 2 Certification Authority"

./AddSPRootAuthority.ps1 -CertPath "C:\wildcard-hightrust\Go Daddy Root Certificate Authority - G2.cer" -CertName "Go Daddy Root Certificate Authority - G2"

./AddSPRootAuthority.ps1 -CertPath "C:\wildcard-hightrust\Go Daddy Secure Certificate Authority - G2.cer" -CertName "Go Daddy Secure Certificate Authority - G2"

Then need to configure a Trusted Security Token Issuer for the Shared Certificate, that is set as the SSL for the SSL binding (and referenced in the web.config) of your provider hosted app:

./HighTrustConfig-ForSharedCertificate.ps1 -CertPath "C:\wildcard-hightrust\wc.cer" -CertName "*.company.on.ca" -TokenIssuerFriendlyName "SharePoint High-Trust Add-ins Token Issuer"

If the script throws an error after the New-SPTrustedRootAuthority line has run successfully, script cannot be rerun until that object has been removed. Used the following cmdlet, where identity parameter is the same as CertName ("*.company.on.ca" above), or to remove my botched attempt:

Remove-SPTrustedRootAuthority -Identity SomeIdentity
Remove-SPTrustedSecurityTokenIssuer -Identity "Company High Trust Apps Cert"

Web.config for Web project set keys to the following in app settings section (client Id & secret generated in by going to ~siteCollection/_layouts/15/appregnew.aspx, client Id = App Id and app domain is IIS site for your provider hosted apps, my idea is to have all as subsites of the main site, so in this case apps.company.on.ca:

<add key="ClientID" value="<App Id>" />
<add key="ClientSigningCertificateSerialNumber" value="<SSL Cert Serial #>" />
<add key="IssuerId" value="<Generated when registering Trusted Security Token Issuer>" />
<add key="ClientSecret" value="<Provided through App Registration>" />

Even after this and repeating the process multiple times I wasn't having any success until I decided to modify the Token Helper based on this stack overflow answer, specifically modifying the TokenHelper GetCertificateFromStore function, changing the boolean value from true to false for:

storedCert = store.Certificates.Find(X509FindType.FindBySerialNumber,
                                                    ClientSigningCertificateSerialNumber,
                                                    false)

The last main piece of the puzzle was then how I generated the SharePoint context, of which I've ended up using the following after doing a bunch of test creating a context approx. 11 different ways, of which some also worked at this point and others still didn't:

var hostUri = new Uri(SharePointContext.GetSPHostUrl(httpContext.Request).AbsoluteUri);
using (var clientContext = TokenHelper.GetS2SClientContextWithWindowsIdentity(hostUri, httpContext.Request.LogonUserIdentity))
{
 // Doing SharePointy things here with clientContext
}

I believe at this point I have a functional production app as far as SharePoint interaction / authentication goes.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a sharepoint.stackexchange
scroll top