Using SharePoint Apps with SAML and FBA Sites in SharePoint 2013

Hopefully by now everyone has heard about the new app model in SharePoint 2013. There’s a lot of documentation out there about it so I won’t go into exactly what it is. What you should know is that it’s the preferred model for developing applications going forward. If you’ve done any development with the new app model so far though, one of the things that you may have noticed is that out of the box, we only ship a framework in Visual Studio 2012 for working with SharePoint sites that use Windows authentication. A frequent request I’ve heard already is how you can build apps that work with sites that use SAML or forms based authentication. In this post I’m going to walk through exactly how to do that. 

The first thing to understand is that you need to distinguish between the authentication method used in SharePoint, and the authentication method used for your hosted application. To provide a very specific example, at RTM, SharePoint apps will not work for the scenario where SharePoint is using SAML authentication and the application itself is also hosted in SharePoint. However it WILL work if the SharePoint site is using SAML authentication and the application is hosted in Azure or provider-hosted (i.e. running on some non-SharePoint web server elsewhere in your organization). It’s this latter scenario that this post focuses on – your SharePoint site is using either SAML or FBA authentication, and you are using a SAML or FBA provider-hosted application. 

For reasons I’ll explain a little later, there actually is a very loose coupling between the authentication method you use in your SharePoint site and the authentication you use in your provider-hosted application. What I mean by that is that I can have a SharePoint site that uses SAML authentication for example, but my provider-hosted application could be using FBA. I can still get those two to integrate and work because of how the OAuth authentication and authorization works between them. I’ll try and illustrate this point a little later in this post.

So let’s start with my original scenario – SAML secured SharePoint and provider-hosted application. As we walk through this you’ll see that everything works almost exactly the same for FBA, and then hopefully you’ll see how the two can interoperate irrespective of whether both SharePoint and the provider-hosted app use the same authentication model or not. 

For this scenario then, I’ve created an SPTrustedIdentityTokenIssuer in SharePoint. The claims mappings for it include email, UPN, and SIP claims, and email is the identity claim. I’ve created a new web application in SharePoint that is using only that identity token issuer, and authentication happens in ADFS. For my provider-hosted application, I’ve just created a simple ASP.NET application that also uses SAML claims. I’ve also created a separate relying party for it in ADFS and that’s where users for the application authenticate. I also return the email, UPN and SIP claim for users that authentication to my ASP.NET application. I first test both the SharePoint and ASP.NET application separately to make sure that the SAML configuration is configured correctly and I’m getting the expected claim values in both applications. Once that’s squared away then I can move onto the next step.

The next thing I’m going to do is to create an SPAppPrincipal for my new application. I’m not going to go into great detail on this step because I’ve already discussed it in spots and there’s a fair amount of coverage on this topic on TechNet. If you aren’t sure about how to do this you can look at this post to get you started: https://blogs.technet.com/b/speschka/archive/2012/11/01/more-troubleshooting-tips-for-high-trust-apps-on-sharepoint-2013.aspx. For simplicity’s sake, I created my applications as high trust apps, which means that I created a certificate that I will use to sign my OAuth tokens, and I’ve configured it SharePoint by created a new SPTrustedSecurityTokenIssuer. With that in place, I just need to run a little PowerShell to create my new SPAppPrincipal:

#*********************************************************************************

#TO CREATE A NEW APP

#*********************************************************************************

#1. Create a new GUID. Let's say it is 09E62669-0C55-47C0-B7FD-6645D13E1D1F.

#2. Paste it into the ClientId element in the AppManifest.xml file.

#3. Paste it into the ClientId element in the web.config file for your hosted app.

#4. Run the following PowerShell to create a new appPrincipal:

#*********************************************************************************

$clientId = "1b7e2733-9d25-485b-bf67-2479691374dc"

$spurl ="https://samlapps.vbtoys.com"

$spweb = Get-SPWeb $spurl

$realm = Get-SPAuthenticationRealm -ServiceContext $spweb.Site

$fullAppIdentifier = $clientId + '@' + $realm

$appPrincipal = Register-SPAppPrincipal -NameIdentifier $fullAppIdentifier -Site $spweb -DisplayName "SAML App Model"

Now that my App Principal is created, I can create my application in Visual Studio 2012. In Visual Studio I open my project that has my ASP.NET SAML application and then I add a new application to it. I select App for SharePoint 2013, then I use the wizard and configure my app to be a Provider-hosted application. By the way, this is based on using the Office Developer RTM Preview tools; these tools get a necessary update with Preview 2, which I’ll explain a little later. When the wizard is complete it will add a new SharePoint app project as well as an ASP.NET project. In this case I’m not going to use the ASP.NET project it added because I already have my SAML site app that I’m going to use. You can delete the ASP.NET project it adds if you wish, but it doesn’t hurt anything to leave it there.

Now I need to configure the SharePoint app. To do that I right-click on the AppManifest.xml file and select View Code. There are two things I need to change here:

  1. Change the ClientId value from * to the $clientid you used in your PowerShell illustrated above.
  2. Change the Start Page for the application to point to the ASP.NET SAML application you created. You’ll also want to change the query string to add another value you’ll need to include the SharePoint chrome in your ASP.NET application. Here’s an example of what I used: https://saml.vbtoys.com/SamlSite/default.aspx?{StandardTokens}&SPHostTitle={HostTitle}. In this case then my ASP.NET SAML site is at the Url https://saml.vbtoys.com/SamlSite, and the page I want to display is default.aspx.

You can close the xml file now, but then double-click on it to open it in the designer. I do this so I can set the permissions I want to use for the application. In my demo case here I’m just going to pull back the title of the web using CSOM, but I’ve gone ahead and asked for Read rights to the Site Collection scope. Once I do that, the configuration of my SharePoint app is complete.

The next thing I need to do is work with my ASP.NET SAML application. The first thing I’m going to do is to add the web.config entries that I need to use the TokenHelper.cs class that is used to create the OAuth token for SharePoint. To begin with, here’s the entries that I’ve added to my web.config:

  <appSettings>

    <add key="ClientId" value="6a9fef7d-42d4-4fcd-8bef-ac852dfeb3dd"/>

    <add key="ClientSecret" value="9LsXrzcHXn4QJl8lImyYTUOffI9CbxA1cpikqgCA2Ug="/>

    <add key="ClientSigningCertificatePath" value="C:\HighTrustCert\spapps.pfx"/>

    <add key="ClientSigningCertificatePassword" value="foobar"/>

    <add key="IssuerId" value="e9134021-0180-4b05-9e7e-0a9e5a524965"/>

    <add key="TrustedProviderName" value="ADFS"/>

    <add key="MembershipProviderName" value="FbaMember"/>

                … (other entries here that are used for SAML auth)

  </appSettings>

Let’s talk about these entries. The ClientId and ClientSecret you should be familiar with – these are used by all SharePoint apps. The ClientSigningCertificatePath and ClientSigningCertificatePassword are used when you are using high trust apps. In my case I’ve created a certificate and SharePoint trusts it because I’ve created my SPTrustedSecurityTokenIssuer. In order to sign my OAuth tokens with that certificate, I need to let the TokenHelper class know where the PFX is and the password for it. The next three tags are ones that you probably have not seen before. 

IssuerId is a value that is used starting with the Preview 2 bits version of the TokenHelper class. This value should be the ID of the SPTrustedSecurityTokenIssuer that you created. As noted in my other blogs, you MUST use the -IsTrustBroker flag when you create the SPTrustedSecurityTokenIssuer in order to use it with multiple applications like I am here. If you don’t remember what the ID is, you can just run the Get-SPTrustedSecurityTokenIssuer cmdlet. The confusing part of this is that you do NOT want the Id value that it displays! Instead you want just the first part of the RegisteredIssuerName property, up to but not including the ampersand. For example, my RegisteredIssuerName is e9134021-0180-4b05-9e7e-0a9e5a524965@72ddc737-72e5-4102-8e1e-91cecbc9884c, so my IssuerId as you see above is e9134021-0180-4b05-9e7e-0a9e5a524965.

The next two attributes – TrustedProviderName and MembershipProviderName – are properties that I’ve added to my TokenHelper helper class. The TrustedProviderName is used when you’re SharePoint site is using SAML authentication; in that case you need to put in the name of your SPTrustedIdentityTokenIssuer. The MembershipProviderName is used when your SharePoint site is using FBA. In that case you need to put the name of the “ASP.NET Membership provider name” you set up in the authentication providers dialog for your web application.

Once the web.config is configured, then I need to add the TokenHelper.cs file to my ASP.NET SAML web application. There’s three distinct steps here:

  1. Add the TokenHelper.cs class that comes with the Preview 2 bits. I’ve attached a copy of it to this posting to help simplify things.
  2. Change the TokenHelper.cs class by doing these two things (if you are using the one that comes with Preview 2; I’ve already made these changes in the version I attached to this post):
    1. Delete the “namespace” declaration for the class.
    2. Change the class from “public class TokenHelper” to “public partial class TokenHelper”. This was the least invasive way I could come up with to add the additional functionality for SAML and FBA.
  3. Add the ClaimsTokenHelper.cs class to the App_Code directory too.

In short, what I’ve done here is I’ve added some additional functionality to support TokenHelper with FBA and SAML sites. To minimize the amount of changes to the TokenHelper.cs class, I created another partial class. ASP.NET is stupendously cool about taking partial classes and merging them all together to make one class at runtime. I did this so that when / if TokenHelper.cs changes in the future, you won’t have to try and sync code changes into it for FBA and SAML support. Instead you can just remove the namespace and change it to a partial class again, and you should be good to go. That takes care of getting all your helper classes into your ASP.NET SAML application. 

Now that everything’s there, you can write some code. This fortunately is the really simple part I think. In this case we’re just going to add this code to the default.aspx code-behind:

        try

        {

            TokenHelper.TrustAllCertificates();

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

            var clientContext = TokenHelper.GetS2SClientContextWithClaimsIdentity(sharepointUrl,

                HttpContext.Current.User, TokenHelper.IdentityClaimType.SMTP, TokenHelper.ClaimProviderType.SAML);

            if (clientContext == null)

            {

                Debug.WriteLine("couldn't get a client context");

            }

            else

            {

                clientContext.Load(clientContext.Web);

                clientContext.ExecuteQuery();

                HelloLit.Text = "<h2>Web title retrieved using the managed client object model</h2>" +

                    "<p>" + clientContext.Web.Title + "</p>";

                clientContext.Dispose();

            }

        }

        catch (Exception ex)

        {

            HelloLit.Text = "There was an error: " + ex.Message;

        }

A few things to note here. First, the code should be very much like other code on TechNet that describes how to use a high trust application. Second, the mystery magic here is in this line of code:

var clientContext = TokenHelper.GetS2SClientContextWithClaimsIdentity(sharepointUrl, HttpContext.Current.User, TokenHelper.IdentityClaimType.SMTP, TokenHelper.ClaimProviderType.SAML);

The GetS2SClientContextWithClaimsIdentity is one of the helper methods. The only two things that are going to vary in your code are the last two parameters. The IdentityClaimType.SMTP indicates to the helper that I want to use the SMTP claim to identify the user to SharePoint. The ClaimProviderType.SAML tells the helper class that my ASP.NET site is using SAML authentication; if it were using FBA then the parameter would be TokenHelper.ClaimProviderType.FBA. Again – this refers to the authentication being used in my provider-hosted site – NOT the SharePoint site.

Once I get the clientContext, then I can make my CSOM call into SharePoint. I have a Literal control in my default.aspx page called HelloLit and I just plugging in the data I get into that so that is what will be displayed on my app page. Pretty simple app obviously. 

Now let’s talk a little bit more about the claims piece of this and how it all works. I mentioned above that in my call to get a clientContext I configured it to retrieve the SMTP claim to identify the user to SharePoint. What that means is that I’m going to get the SMTP address from the SMTP claim that I receive in my ASP.NET application – for example, darrins@contoso.com. I’m going to add that to my OAuth token that I send to SharePoint. When SharePoint gets the OAuth token it’s going to grab that SMTP value and it’s going to do a lookup in the user profile application for a user who has that SMTP address – darrins@contoso.com. Assuming it finds a match, it will then figure out all of the claims for that user as described here: https://blogs.technet.com/b/speschka/archive/2012/08/15/oauth-and-the-rehydrated-user-in-sharepoint-2013-how-d-they-do-that-and-what-do-i-need-to-know.aspx. It will take all of the claims for that user and see if any grant access to the resource being requested – in this case the title of a particular web. If it does, and if the app itself also has rights to that resource, then SharePoint returns the data we requested in our CSOM call.

This process has some important implications. First, this is why I said earlier that the authentication of the provider-hosted application and the SharePoint application don’t have to match. Assume I’m still using SAML on my provider-hosted application and I use that to retrieve an email address. When I send that over to SharePoint it’s just going to look for the user who has that email address; it doesn’t really care how that user authenticates into a SharePoint site. So if the email address darrins@contoso.com maps to a profile for a Windows user called darrins, then it will retrieve the info for that user and present it to SharePoint. If the SharePoint site is using Windows authentication and darrins has rights to that site, then everything will work. (SIDE NOTE: There are some differences in one of claims your provider-hosted app needs to send to SharePoint to let it know whether you want to access a SAML, FBA or Windows site, that is beyond the scope of this post and not really relevant to the user info. For more information see the nii claim documentation here: https://msdn.microsoft.com/en-us/library/hh695226(v=office.12).aspx. That being said – in this version of ClaimsTokenHelper it assumes that if you are asking for SAML info, the SharePoint site is using SAML; if you are asking for FBA info, the SharePoint site is using FBA. If you want to make different assumptions then you will need to modify ClaimsTokenHelper.cs.

Another implication is that means that these identifying properties – really SMTP, UPN and SIP – need to be unique in your entire user profile application. So you can’t have a Windows user and a SAML user that both have the same email address for example. They have to be universally unique or your call may fail. This is important to remember mostly for test environments, where you may have the same values for an SMTP user and a Windows user.

A third implication is that you need to understand how caching works. When you make an OAuth request, SharePoint will look in its cache first for a match. So if you’ve already sent over an OAuth token where SMTP address is darrins@contoso.com, it won’t go back and query the user profile application again; instead it will pull it out of its cache. The reason I bring this up is because if you want to test with different values to see what works and what doesn’t, you should NOT test that by just modifying values in the UPA. Once a profile is cached for a user we aren’t going to go back and re-retrieve it every time you change a value in the UPA. The key here is just to use multiple user identities when you’re testing, so you can play with different values in the SMTP and SIP fields to see what works and what doesn’t.

Another point worth pointing out is regarding the application pool account used by your provider-hosted application. When you’re using Windows authentication on a site I’ve found the default app pool for ASP.NET has no problems retrieving the token signing certificate for the SPTrustedSecurityTokenIssuer. However, when you switch to using SAML or FBA that no longer works. You need to do two things to remedy this. First, use a domain account for the application pool for your provider-trusted application. Second, create a new directory to store the token signing certificate and make sure you grant at least read rights to that directory for the app pool account you use. Otherwise you will errors in the constructor for TokenHelper.cs because the default app pool account doesn’t have rights to read in the token signing certificate.

There’s one last thing to point out here about the sample code supplied with this posting. The ClaimsTokenHelper class also works with FBA users, but how does it do this? In SAML it’s simpler because there are standard claim types that hold values for SMTP and SIP claims. FBA doesn’t have anything like that – it just has roles. Since there is no standard, I came up with my own way of passing this information in the roles collection. For each of my users I created three role claims: one has a value of SMTP:theUsersSmtpAddress, one is UPN:theUsersUpn, and the other is SIP:theUsersSipAddress. In my helper method to extract the SMTP or SIP address of an FBA user I just get the roles collection for that user and look for a role that starts with either SMTP:, UPN: or SIP: and then I do some string parsing to get the actual value. If you want to do it some other way then you will need to modify the ClaimsTokenHelper class accordingly.

That pretty much wraps it up. There should be enough information here to understand how OAuth works and how we can pass information about a user between a provider-hosted application and SharePoint for SharePoint to figure out who the user is and what he or she has access to. Really the only difference between using it when you’re using SAML versus FBA is the last parameter in the GetS2SClientContextWithClaimsIdentity method. I’ve added a lot of detail in this post to try and help you understand some of the moving parts and implications of that, but at the end of the day if you just want to write some code fairly quick and not worry about any of that, for the most part you should be able to do so.

Final note: the attached zip includes a Word version of this posting, along with the token helper class and my claims token helper class that I wrote for this posting. It also includes my provider-hosted FBA and SAML sites that I used in testing this.

UPDATE: I updated the attachment on 5/23/2013. There were two primary changes - 1) was to update the ClaimsTokenHelper.cs so to reflect a change of a constant name used in TokenHelper.cs that was modified for the Preview 2 release. 2) was to add an additional parameter to the main method in ClaimsTokenHelper so that you can configure it to use an App Only Token instead of App + User token. All the samples are updated to reflect these changes.

SamlAndFbaForApps.zip