0

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.

1 Answer 1

0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.