Blog

SharePoint High Trust Apps and ADFS/SAML


by Tobias Lekman, 01 February, 2015

A high trust app requires several things in order to work: an S2S trust configuration that generates the OAuth token, a private certificate for decrypting the OAuth token and a registered issuer ID and client ID. If this is in place, then the OAuth token will contain the claim that allows an app to talk to SharePoint will be sent to the app. However, the user portion of the token is not sent by SharePoint – it must be generated on the app side. This is why a high trust app needs both OAuth and Windows authentication.

But when your farm is configured with ADFS and SAML, you cannot use Windows authentication for your app web. If you do that, then the app will try to authenticate again and you will need to supply valid Windows credentials. You must therefore get ADFS to authenticate directly on the ASP.NET application level.

Existing solutions

Steve Pescha wrote a helper class that allows you to cast the SAML claim and resolve the claim as a proper SharePoint user and therefore be able to create the ClientContext. This did however force you to rewrite the code.

Wictor Wilen then rewrote this solution and managed to overwrite the standard classes so that you only needed to change the SharePoint context and TokenHelper objects and not rewrite any of your custom code.

The problem was that this only worked for user contexts and not for App-only context. Steve Pescha then released an updated code that allowed app only calls as well.

The modifications to allow app-only calls

i needed to merge these solutions together, so I created a simple replacement for TokenHelper and SharePointContext. The addition was to overwrite the CreateAppOnlyClientContextForSPHost and CreateAppOnlyClientContextForSPAppWeb methods and not pass the client portion, as described in Steve Pescha’s updated code. You can also add this to Wictor’s code as long as you declare the original methods as virtual, so that you can overwrite them.

/// <summary>
/// Creates app only ClientContext for the SharePoint host.
/// </summary>
/// <returns>A ClientContext instance.</returns>
public override ClientContext CreateAppOnlyClientContextForSPHost()
{
return TokenHelper.GetS2SClientContextWithClaimsIdentity(this.SPHostUrl,
logonUserIdentity, TokenHelper.DefaultIdentityClaimType,
TokenHelper.DefaultClaimProviderType,
true);
}
/// <summary>
/// Creates an app only ClientContext for the SharePoint app web.
/// </summary>
/// <returns>A ClientContext instance.</returns>
public override ClientContext CreateAppOnlyClientContextForSPAppWeb()
{
return TokenHelper.GetS2SClientContextWithClaimsIdentity(this.SPAppWebUrl,
logonUserIdentity, TokenHelper.DefaultIdentityClaimType,
TokenHelper.DefaultClaimProviderType,
true);
}

You can download the complete modified versions from my CodePlex site.

Steps to allow high trust ADFS applications

I take it that you have already got a working High Trust App. Make sure it works with Windows first before adding ADFS to the mix as well!

You then need to create the relaying party in ADFS as outlined in Wictor’s article.

Replace the SharePointContext.cs and TokenHelper.cs files with my versions and change the namespaces to match your application.

Run NuGet Package Manager console and execute the following commands:

      Install-Package Microsoft.AspNet.Identity.Core -Version 2.1.0

      Install-Package System.IdentityModel.Tokens.ValidatingIssuerNameRegistry

Then, modify the web.config by adding new configSections elements under configuration/configSections.

<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>

Then, add the following element under configuration/system.web

<authentication mode="None" />

Then, add the following sections under configuration, replacing all the URL values with your real ADFS endpoints and realms.

<system.webServer>
<modules>
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>
</system.webServer>
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="urn:sharepoint:apps" />
</audienceUris>
<securityTokenHandlers>
<add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</securityTokenHandlers>
<certificateValidation certificateValidationMode="None" />
<issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
<authority name="apps.lekman.com">
<keys>
<add thumbprint="12a1238263675aaa61e848527c1a1a86579a907a" />
</keys>
<validIssuers>
<add name="http://apps.lekman.com/adfs/services/trust" />
</validIssuers>
</authority>
</issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="true" name="SP" />
<wsFederation passiveRedirectEnabled="true" issuer="https://adfs.lekman.com.com/adfs/ls/" realm="urn:sharepoint:apps" reply="https://simpleapp.lekman.com" requireHttps="true" />
</federationConfiguration>
</system.identityModel.services>

Note that the cookieHandler value “name” may need to be removed or changed to match the name of your SAML token cookie. Also, the wsFederation value “reply” was added to enforce redirection to the right domain in my ADFS server.

Note that if you are debugging this and need to enable just Windows authentication, then comment out the authentication, system.webServer, system.identityModel and system.identityModel.services sections.

Common troubleshooting

You get the error “The value of the 'type' property could not be parsed.Verify that the type attribute of 'issuerNameRegistry x element is correct.”

Although you added the NuGet references, the DLL System.IdentityModel.Tokens.ValidatingIssuerNameRegistry is not directly refered to in your project. Manually add a reference for the DLL which can be found under Packages/System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.4.5.1\lib\net45.

You get a 401 Access Denied when creating User contexts

The user is matched using the email address claim from ADFS. Make sure the user has the email address populated in the User Profile Service, otherwise you cannot do this. To make sure that this is the case, and not your entire app, try making an app only call by using CreateAppOnlyClientContextForSPAppWeb. If not, switch to Windows authentication and make sure it works with Windows auth before attempting anything else.

You get an error similar to expected SecurityToken, received X

You need to specify the name of the SAML cookie under system.identityModel.services/federationConfiguration/cookieHandler[name] so that the decoding works.

You get redirected back to the wrong server after ADFS has authenticated you

Add the URL of your app web under system.identityMode.services/wsFederation[reply] to force ADFS to redirect to the correct web application.