SharePoint 2013 with SAML Claims and Provider Hosted Apps

Tags: SharePoint 2013, AD FS, Claims, Windows Server 2012 R2, Apps

Introduction

The other week I posted an article about how to use SharePoint Hosted Apps when using SAML Claims, I did not expect that amount of feedback I had on that blog post, in e-mail, comments, tweets etc. Some of that feedback was how do you do it with Provider Hosted apps. Well you’re about to find out. It took me a while to get it properly done and there are some things that you should be aware of. In this post I will walk you through the simplest scenario and you will notice that there are a couple of moving parts. But, since I am such an influencer I thought I should make it easier for you. I will show you how to do this without the minimal changes to your current provider hosted apps – you only have to add an extension file to your solution, make a small modification to the helper files that Visual Studio gives you and a couple of web.config modifications! All the code you need will be published in a Github repository (https://github.com/wictorwilen/SharePointContextSaml) for you to consume and do all the fancy gitty stuff that you code dweebs out there like.

Let’s get started, bare with me this is a long post and for some of you the first parts might be just a repeat of what you’ve seen/done previously…

Creating a Provider Hosted High Trust App

The first thing we need is to create us a High Trust Provider Hosted App. That is an app that does not run in SharePoint and is using the Server to Server (S2S) protocol for authorization. In my case (and it should be yours as well if you want to use my helper code) is to use Visual Studio 2013 and create a SharePoint App, choose a provider hosted app using the ASP.NET MVC template. To create a High Trust App we then need to specify a certificate for the S2S trust. This is how you can create a cert using makecert.exe and some PowerShell. In the script below all you need to do is modify the four properties; domain, name and password of cert and the location where we will store the certificate files:

#ALIASES
set-alias makecert "C:\Program Files\Microsoft Office Servers\15.0\Tools\makecert.exe"

# CREATE SELF SIGNED DEV CERTS
$domain = "localhost"
$certName = "HightTrustSAMLDemo" 
$password = "Pass@word1"
$dir = "c:\HighTrustCerts\"

New-Item $dir -ItemType Directory -Force -Confirm:$false | Out-Null

$publicPath = $dir + $certName + ".cer"
$privatePath = $dir + $certName + ".pfx"

makecert -r -pe -n "CN=$domain" -b 01/01/2012 -e 01/01/2022 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine -sky exchange -sy 12 -sp "Microsoft RSA SChannel Cryptographic Provider" $publicPath 

$publicCert = Get-PfxCertificate -FilePath $publicPath
$publicThumbprint = $publicCert.Thumbprint

Get-ChildItem Cert:\LocalMachine\My | 
    Where-Object {$_.Thumbprint -eq $publicThumbprint} | 
    ForEach-Object {
        $_.Export("PFX",$password) |Set-Content $privatePath -Encoding Byte        
    }

Once we have the certificate we need to install the certificate in the SharePoint certificate store as well as register a Trusted Security Token Issuer. And to do this we need a Guid! You can use any preferred tool to generate your Guid or generate and register your very own unique Guid at www.vanityguid.com. Here’s a script the does the trick; give it your very own unique Guid/Issuer Id and a unique display name:

$issuerId = '17721805-67c2-46ba-92d1-fd1fdf91bb01' # MUST BE UNIQUE
$displayName = "High Trust SAML Demo" # MUST BE UNIQUE

Add-PSSnapin Microsoft.SharePoint.PowerShell -EA 0
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($publicCert)

New-SPTrustedRootAuthority -Name $certName -Certificate $certificate
$realm = Get-SPAuthenticationRealm
$fullIssuerId = $issuerId + "@" + $realm

New-SPTrustedSecurityTokenIssuer -Name $displayName -Certificate $certificate -RegisteredIssuerName $fullIssuerId –IsTrustBroker
iisreset 

Yes I trust Wictor!Ok, this was standard High Trust App procedure. Copy and paste the issuer Id into the Visual Studio wizard and enter the certificate details to complete the app creation. What you have right now is a standard High Trust app ready to work with Windows (claims) users. Before you start hacking in some code into this app – just make sure that it properly deploys and executes (ie press F5), so that you haven’t messed up any Guids or anything else. This app should now run using IISExpress and it uses Windows Authentication (remove http(s)://localhost from Local Intranet sites in Internet Explorer if you want to test different users).

What about them SAML users?

So let’s see what happens if we log into our SharePoint Developer site, where we deployed our High Trust app, as a SAML user (using for instance AD FS 3.0). Of course you need to setup a trusted identity token issuer before doing that, how that’s done is out of scope of this post. When logged in as a SAML user and I click on the High Trust App I either get logged into the app as the currently logged on Windows user or I get the Windows login dialog (if http(s)://localhost was removed from Local Intranet sites). So, it doesn’t really work! We don’t even get the chance of authentication with our AD FS (or similar federation service).

Configuring the app for federated Authentication

ASP.NET devs get all the new shiny stuffWhat we need to do is modify our web application (the provider hosted web) to use federated authentication. Unfortunately we are SharePoint developers, and are not given all the love that ordinary ASP.NET developers are from the Visual Studio team. When creating a standard ASP.NET project we get the really awesome option of specifying which authentication method we would like to use and we can point out our federation service and Visual Studio fixes everything for us.

We can do this in two different ways. Either we add a new ASP.NET MVC project and remove the default web project and convert that ASP.NET MVC project into a SharePoint app project (which is the easiest way) or we start fiddling with web.config and configure it ourselves – which is exactly what we’re going to do here!

The first thing we need to do is to use NuGet and add the Microsoft ASP.NET Identity Core package and the Microsoft Token Validation Extension for .NET 4.5. These package will give us all the assemblies and references we need to run the app.

NuGet FTW!

In your web application project open up web.config, now we’re going to do some copy-paste work! First of all we need to add a configSections element for the System.IdentityModel. Insert the snippet below directly after the configuration element:

<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 head on over to the system.web element and below the authorization element add the following.

<authentication mode="None" />  

This means that we’re not letting ASP.NET do the Authentication, but instead handling it over to a new module which we’re going to add. Directly after the system.web element copy and paste the following, which will add two new modules to the ASP.NET pipeline:

<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>
These two modules are called WSFAM and SAM (two more acronyms for you JB). WSFAM is responsible for the login/logout process and SAM takes care of session management by using cookies.

Then we need to add the configuration for our Relying Party. This is done by adding the system.identityModel element after the system.serviceModel element. It’s a handful of configuration and we need to modify it in a few places to match our app and our federation service.

<system.identityModel>
  <identityConfiguration>
    <audienceUris>
      <add value="uri:SharePoint:App:SAML" />
    </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="fs.contoso.com">
        <keys>
          <add thumbprint="EF66CEF27067637D296C403691DBBE2A8B5C7BAA" />
        </keys>
        <validIssuers>
          <add name="http://fs.contoso.com/adfs/services/trust" />
        </validIssuers>
      </authority>
    </issuerNameRegistry>
  </identityConfiguration>
</system.identityModel>

First of all on line #4 – that row must contain the relying party identifier for our app registered in AD FS. I prefer using a URI, since that makes it easier when I need to deploy this into production, but you could use the URL of your web application (https://localhost:44305/ for instance). I’ll show you in just a minute how to configure this in AD FS. Line 12 should contain the name of our authority and line 17 must have the Federation Service Identifier of AD FS (found under Federation Service Properties in the AD FS Console). On line 14 we need to specify the thumbprint of the AD FS Token-signing certificate (you’ll find that easy in the AD FS Console under Service/Certificates).

Ok, only one more thing to fiddle with in the web.config.

<system.identityModel.services>
  <federationConfiguration>
    <cookieHandler requireSsl="true" />
    <wsFederation passiveRedirectEnabled="true"
      issuer="https://fs.contoso.com/adfs/ls/"  
      realm="uri:SharePoint:App:SAML" 
      requireHttps="true" />
  </federationConfiguration>
</system.identityModel.services>

Here there are two things we need to modify. First of all we need to point to the SAM/WS-Federation endpoint of AD FS, the login page, that Url is set in the issuer property of the wsFederation element. Secondly we must add the realm of our app – which should be the same as the audienceUri specified above. As I said earlier, I use a URI instead of the web application Url.

A quick note here as well, notice that I use HTTPS and SSL all the way. For my SharePoint web application and for my app web application. If you prefer to be sloppy you can still use HTTP but that requires some extra work in SharePoint and the app web application config.

Let’s take our High Trust App for a ride! Press F5 to get the app up and running. Doesn’t work…well some of it do. You should now be taken to the AD FS login page, but that one will just tell you an error occurred and the details doesn’t give much of a hint. You have to take a look in the AD FS server and the event log for AD FS. The error message is pretty clear on what we haven’t done yet:

MSIS7007: The requested relying party trust 'uri:SharePoint:App:SAML' is unspecified or unsupported. 
If a relying party trust was specified, it is possible that you do not have permission to 
access the trust relying party. 
Contact your administrator for details.

Yes, we haven’t created our Relying Party Trust yet.

Creating the Relying Party Trust in AD FS

Claims rulesNow we need to create our Relying Part Trust in AD FS, it’s pretty straightforward, but comes with a couple of caveats. Add a new Relying Party Trust and choose to enter data manually, then give the trust a display name (I prefer to use the realm here). Continue by choosing to use the AD FS Profile, skip the certificate page in the wizard, and then select to enable support for the WS-Federation Passive protocol and enter the URL of your App. On the next page add a new identifier and now use the realm of your app, then just keep on clicking Next until you’re done. Once you’re done with creating the Relying Party Trust a new dialog will open up in AD FS in which you need to create Claims Rules for this new trust. This is a very important step, you need to get the rules correct and include the correct claims. In this case I will add two claims. One which augments the E-Mail claim from Active Directory (LDAP) for my Active Directory users and one which will just pass through the E-Mail claim from my third party Identity Provider (Thinktecture Identity Server). You have to pass either E-Mail, SIP address or UPN to get this to work. SharePoint needs at least one of those claims to be able to rehydrate the user from the User Profile Service Application.

Once that is done, go back to your App and start it again using F5. Now logon using one of the Active Directory, use some other account than the one you are currently logged on to your machine as (otherwise you’ll think you are home safe already). You should see the default ASP.NET MVC SharePoint App page which says “Welcome Someone”, where Someone is the name of the logged in user. But what! That’s not the name of the account you logged in using? You can easy check that you are actually properly logged in using AD FS by setting a break point in the HomeController.cs in the Index action and take a look at some watches. You can clearly see that we are authenticated through federation and you’ll see the e-mail claim for that user who authenticated through AD FS, but the identity shown is the wrong one!

Visual Studio Baby!

Well, it turns out that the SharePointContext.cs and TokenHelper.cs classes that the Visual Studio and Office Dev team so kindly wrote for us only works with Windows Claims for High Trust apps. Bummer!

Steve Peschka to the rescue!

I barely gave up when I hit this, but my Bing-fu found a single blog post by Steve Peschka (who knew!) called “Using SharePoint Apps with SAML and FBA Sites in SharePoint 2013” who solved my problems, almost. This post of Steve was written some time ago when the TokenHelper.cs class looked very different and it forced you to make modifications to your app to support federated users. So I took his example code and modified it to fit the new helper classes provided by Visual Studio 2013 and made them in such way that you do not have to re-write your apps and packaged it into a single .cs file; SharePointContextSaml.cs. I will NOT post the entire thing here, since it is a lot of code. But if you’re like me you should read through it, it gives you tons of knowledge on how the app authorization actually works beneath the hood.

So you just download the .cs extension file and add to your app web application project and then you have to do a couple of tweaks to your app configuration and to the default TokenHelper.cs and SharePointContext.cs files. This is how you do it.

1) After you copied the SharePointContextSaml.cs file into your project, modify the namespace of that file to match the namespace of your project.

2) Modify the default TokenHelper.cs class declaration so that the TokenHelper class is a partial class declaration, we need this to be able to extend the TokenHelper class. (This declaration should have been there by default imho!)

public static partial class TokenHelper {...}

3) In the SharePointContext.cs file locate the static constructor for the SharePointContextProvider class and modify it so it looks like below. This is needed so that we can use our custom context provider if the user is authenticated through federation.

static SharePointContextProvider()
{
    if (!TokenHelper.IsHighTrustApp())
    {
        SharePointContextProvider.current = new SharePointAcsContextProvider();
    }
    else
    {
        if(HttpContext.Current.User.Identity.AuthenticationType == "Federation") {
            SharePointContextProvider.current = new SharePointHighTrustSamlContextProvider();
        } else {
            SharePointContextProvider.current = new SharePointHighTrustContextProvider();
        }
    }
}

4) Finally we need to add three entries to the appSettings in web.config:

<add key="spsaml:ClaimProviderType" value="SAML"/>
<add key="spsaml:TrustedProviderName" value="High Trust SAML Demo"/>
<add key="spsaml:IdentityClaimType" value="SMTP"/>

The first entry is used to tell our custom context provider that we use SAML claims (there is support for Forms based authentication, value = FBA, in the extensions that Steve originally wrote but I have not yet tested it at all in my edition, and personally I prefer using something like IdentityServer instead of FBA). The second entry must be the name of the Trusted Security Token Issuer that you created using PowerShell for your app. And finally we need to specify what claim we will extract and use as identity claim; possible values are SMTP (e-mail), SIP or UPN.

Yet another hobbit e-mailLet’s try to run the app once again. Most likely you will be able to logon but then you get an exception in the HomeController, an access denied. Then you need to check two things. First make sure that the user that you’re logging in with have permissions on your site. Since we’re talking about SAML claims here the people picker behaves a bit differently, enter the e-mail address of the user and make sure to choose the e-mail claim name (hover the mouse over the suggested options to see the claim type). Secondly you must make sure that the user has the WorkEmail attribute in the user profile store populated. This can be done either by using the User Profile Synchronization, if you’re authenticating with an Active Directory or LDAP source. But if you’re using some other IdP, for instance IdentityServer that I use here, you must add the WorkEmail manually through PowerShell or some other method. Once that is done you should see a nice and warm welcome to your SAML claims user!

That’s it! All you need to do to enable SAML Claims user for your SharePoint 2013 High Trust App.

Show me the codez!

I’ve created a Github repository to host the SharePointContextSaml.cs file, you can find it here: https://github.com/wictorwilen/SharePointContextSaml

I’d really like to get feedback in form of features, bugs, fixes etc. Please use the issue feature on Github, instead of e-mailing me…

Currently it is not documented at all, there are some comments; a mix of mine and Steves original ones.

I’d like to maintain it as long as possible, but my hope is that the Office Dev Team picks this up and do some proper testing and then merges this with the default Visual Studio helper files. Well, one can at least wish!

Summary

Yet another long blog post. I really hope I didn’t miss anything, there’s quite a few step once you do this the first time. I’ve shown you the complete example here; how to create and register a high trust SharePoint 2013 app, how to convert the default MVC project to a Claims based web application, registering a Trusted Relying Party in AD FS and then finally adding the new SharePointContextSaml.cs file and enabling you to use federated users in your Provider hosted app. Good luck to you all and thanks Steve for doing all the hard work!

14 Comments

  • Kain De Luca said

    Hi Wictor,

    First I just wanted to say thank you, your site has been an amazing resource for me and this post is no exception. Unfortunately we are using sAMAccount Name as our primary claim identifier so your modified context class didn't work for me.

    I have modified it to add in SAM as an option for IdentityClaimType bound to the correct claim type schema, with windowsaccountname for the type ID, but it still doesn't seem to work correctly for me, I get 401 unauthorised errors when I use the CreateUserClientContextForSPHost function to hit my dev sharepoint site which I definitely have full access to as the current user.

    Decoded User Access Token:
    ------------------------------------------
    {"typ":"JWT","alg":"none"}
    {
    "aud": "00000003-0000-0ff1-ce00-000000000000/my.host.name@e5b30058-7ba1-4943-b1b3-605996188b7d",
    "iss": "b24406d1-d394-445f-842d-b9608ff5bac2@e5b30058-7ba1-4943-b1b3-605996188b7d",
    "nbf": "1407194445",
    "exp": "1407195045",
    "windowsaccountname": "my.username",
    "nii": "trusted:sharepoint high-trust apps token issuer",
    "actortoken": very long base 64 encode string see below...
    }

    Decoded actor token:
    ----------------------------
    {"typ":"JWT","alg":"RS256","x5t":"zQ6oWFhwYoIZEFZpQfaWhrDYfaA"}
    {
    "aud": "00000003-0000-0ff1-ce00-000000000000/my.host.name@e5b30058-7ba1-4943-b1b3-605996188b7d",
    "iss": "95e5b1ec-8f30-4507-8b39-c744dba9c5b2@e5b30058-7ba1-4943-b1b3-605996188b7d",
    "nbf": "1407194445",
    "exp": "1467194445",
    "nameid": "b24406d1-d394-445f-842d-b9608ff5bac2@e5b30058-7ba1-4943-b1b3-605996188b7d",
    "trustedfordelegation": "true",
    "windowsaccountname": "my.username"
    }

    Does anything stand out that you can see that is obviously wrong or do you know of any resources you can point me to that would help in resolving this? After days of searching and trial and error your blog post was the only valuable resource I was able to find to help with this.

    Any help you can provide would be greatly appreciated.
    Regards,
    Kain

  • Craig said

    Do I have to have my app deployed for that to work correctly? I mean if I hit F5 and it is just running from the local host of the app server will it work?

  • Peter said

    Hi Wictor and Kain De Luca,

    Wictor, thank you so much for this great article.

    I am having the same scenario (sAMAccountName as PrimaryClaimIdentifier) as Kain De Luca, and I am having no clue on how to get this thing done. Can you please provide me some insight into where I need to make the changes and what exactly I need to add.

    Kain, can you please shed some light on what changes you made and where so that I can at least get started.

    Thank you so much.
    Pete

  • Wictor said

    @Peter and @Kain,
    the identifier must be UPN, SIP or e-mail address, that is how the user hydration/dehydration works in SharePoint. If you're using the SAM Account Name as the identifier you need to make sure that at least one of those claims are sent to your App and then you need to modify the code and make sure that that claim is used instead of the SAM account name. Basically you need to create a "translator" in RetrieveIdentityForSamlClaimsUser.
    /WW

  • Pete said

    Hi Wictor,

    Thank you for responding. I am fairly new to creating apps and having very basic/no understanding on how the app authentication works. Please excuse me if this is a dumb question.

    In our environment, I am told that "sAMAccountName " is being used as the primaryidentification claim. Does this mean that we cannot pass either UPN or SMTP or SIP as the identification claim?

    The area where I am getting confused is, does UPN/SMTP/SIP be the primary identification claim or Primary identification claim can be anything like sAMAccountName but UPS should also be populated with UPN/SMTP/SIP values.

    Can you also please throw some pointers at me on how to get the "translator" part done. I googled a bit and found an article that said, I will need to query the AD for getting the claims. Is this what I am required to do?

    Thank you very much for your time, and please excuse me if this is a dumb question.

    Thank you,
    Pete.

  • Kevin Armstrong said

    Well done! Your writing style was threaded context and subject matter complexity with a fine technical needle!

    After reading I went to bookmark you and found I had do so prior!?? Guess I found your link earlier in my head-spinning research of SP2103, APP-WEP,HIGH-TRUST...and a deseperate search for 'perspective'...or link to 'the unifying answer' (but bookmarked 'gut-feeling' links of relevance).

    Now a year down the line I 'get it'...and rereading your link feel's like coming home:)

    I get it (understanding), and am now 'consuming' you/your code...thank you for the offering.

    -MS give some love here!

  • Jan-Pieter Jacobs said

    Hi Wictor,

    I was wondering if we are using saml authentication for both the webapplication and the provider hosted apps.
    Will a user that logs in from a non domain joined pc be asked to authenticate twice, or will ADFS take care of that because both the webapp and the provider hosted app use the same authentication method ?

    Thanks for your answer.

  • Craig Jahnke said

    am try to create an ADFS Claims aware app, but can't quite get it to work.

    I can do a High Trust S2S app that queries SharePoint and pull back a list of all the Lists in my Site Collection.

    I followed this post and could get the app to display claims information similar to what you have pictured.

    My problem is when I try to mix the two and have an ADFS app pull data from SharePoint. I get an error but no real description of it. Have you been able to get that to work? I assume it is something with the context I am trying to use, but haven't been able to figure it out.

    Any help would be appreciated.

  • Tobias Lekman said

    Excellent article, as it's very hard to find a real solution for this out there!

    I had a separate requirement to also allow App Only requests, so I added the following update in the :

    public override ClientContext CreateAppOnlyClientContextForSPHost()
    {
    return TokenHelper.GetS2SClientContextWithClaimsIdentity(this.SPHostUrl,
    logonUserIdentity, TokenHelper.DefaultIdentityClaimType,
    TokenHelper.DefaultClaimProviderType, true);
    }

    public override ClientContext CreateAppOnlyClientContextForSPAppWeb()
    {
    return TokenHelper.GetS2SClientContextWithClaimsIdentity(this.SPAppWebUrl,
    logonUserIdentity, TokenHelper.DefaultIdentityClaimType,
    TokenHelper.DefaultClaimProviderType, true);
    }

    You also need to declare the original methods as virtual to get this to work properly.

    Thanks again, and here is the reference on the updated package.

    http://blog.lekman.com/2015/02/sharepoint-high-trust-apps-and-adfssaml.html

  • Bert Clevering said

    Hello Wictor,

    I'm trying to use ThinkTecture with your solution using WSFED HRD. Only this results in a general error directly after i select the IdentityProvider. If i simply use WSFED then the solution works well.

    Do you have any idea what this problem is?

  • Jiri said

    Hi Wictor,

    thank you for this great article. We have similar scenario i our company, with custom identity provider instead of AD FS. Everything works fine with one identity claim for given user.

    But is is possible to use other claims as user "roles" - i have no idea how to create Access token in the SharePointContextSaml.IssueToken() method. I'm not sure if it is even possible, but it would be very useful in SharePoint environment.

    Thanks for your answer.


Comments have been disabled for this content.

About Wictor...

Wictor Wilén is the Nordic Digital Workplace Lead working at Avanade. Wictor has achieved the Microsoft Certified Architect (MCA) - SharePoint 2010, Microsoft Certified Solutions Master (MCSM) - SharePoint  and Microsoft Certified Master (MCM) - SharePoint 2010 certifications. He has also been awarded Microsoft Most Valuable Professional (MVP) for seven consecutive years.

And a word from our sponsors...