Kirk Evans Blog

.NET From a Markup Perspective

High Trust SharePoint Apps on Non-Microsoft Platforms

High Trust SharePoint Apps on Non-Microsoft Platforms

Rate This
  • Comments 8

This post will discuss some options for building high-trust apps for SharePoint when the app is not running on a Microsoft platform. 

Background

Let me start this post by saying I don’t have code to announce or a solution to provide.  I am merely pointing out that building both low-trust and high-trust apps on non-Microsoft platforms is completely feasible.  I am a Microsoft platforms developer, it’s been a long time since I wrote Java or PHP code and I don’t have the bandwidth to re-learn enough to implement the guidance in this post.  I am writing this post because lately I have seen questions from customers about running high-trust apps for SharePoint on non-Microsoft platforms.  The purpose of this post is show that it is completely feasible to build apps, either high-trust or low-trust, on non-Microsoft platforms.

When you build a provider-hosted app for SharePoint in Visual Studio 2013, you choose between using Azure ACS or a certificate for your app.

image

This decision is asking about how the target SharePoint farm has its trust configured and generates configuration settings in Web.config.  Either it configures a client ID and client secret, or it configures a client ID, certificate path, and certificate password.  The former uses Azure ACS, which is a “low-trust app”, the latter uses an X.509 certificate, which is a “high-trust” app.  The difference between “low-trust” and “high-trust” is explained in the next section.

Understanding OAuth and Trust

In the podcast, Office 365 Developer Podcast: Episode 002 with Radi Atanassov, Radi describes how SharePoint uses OAuth and describes “three-legged authentication.”  When you configure an app to use Azure ACS, the app communicates to Azure ACS to provide a refresh token and obtain an access token.  The refresh token is provided from SharePoint, which obtains the refresh token from Azure ACS as well. In this mode, both the app and SharePoint mutually trust Azure ACS but not each other, which is why we call this model “low-trust”.  If you want to understand the “OAuth dance”, then go watch my Build 2013 session, Understanding Authentication and Permissions with Apps for SharePoint and Office, where I walk through each step and what SharePoint and Azure ACS are doing under the covers. 

image

The low-trust model can be used with both Office 365 and SharePoint 2013 on-premises.

Realizing that many customers would not be able to configure their on-premises farm with a trust to Azure ACS, the product team made it possible to use apps without a trust to Azure ACS through the S2S protocol.  The S2S protocol is still standard OAuth 2.0, it just specifies a different way of signing the access token using an X.509 certificate to sign the token.  In this model, there is a trust configured between an X.509 certificate and SharePoint, and the app uses the X.509 certificate to sign the token (see the section “Configuring High-Trust On-Premises” for configuration details).  This is called “high trust” because the app is trusted to assert the identity of the current user.  The term “high trust” does not imply the app has permission to do whatever it wants, it’s called “high trust” not “full trust”.  The term “high trust” simply means the app itself will create the OAuth2 access token, which makes sense because Azure ACS is not there to create it for you.  The app can only create the access token because a SharePoint farm administrator explicitly registered an SPTrustedSecurityTokenIssuer using the public-key portion of the certificate, and the app signs the access token with the private-key portion of the certificate.  SharePoint trusts the request because the access token is signed with the X.509 certificate, and signing can only be done if you possess the private key. 

  OAuth Flow using High Trust

SharePoint 2013 on-premises can use either the low-trust or high-trust model.  Office 365 does not provide the ability to register high-trust apps, which makes sense because registering the public-key portion of the certificate requires farm administrator privileges (you do not have farm administrator privileges in O365).

Side note: It now makes perfect sense that apps obtained from the SharePoint Store can only be used when the app uses an Azure ACS trust.  The store trusts ACS, SharePoint trusts ACS, and your app trusts ACS.  This is how end users can obtain and install apps in a SharePoint farm without requiring a farm administrator.

Low-Trust Apps on Non-Microsoft Platforms

For low-trust apps, it’s really straightforward and easy when using O365 because there is no configuration necessary, you just create a Client ID and Client Secret and use them with your app.  There are multiple samples and conference presentations that demonstrate using SharePoint apps on non-Microsoft platforms when using an ACS trust.  For instance, Todd Baginski did a fantastic session at SharePoint Conference on using Node.js for SharePoint apps, and authored a sample SharePoint 2013: Perform operations on SharePoint Document Library from PHP site.  In this model, you only need to use a JWT library to extract the context token that SharePoint sends to you (again, see my Fiddler post above to show how it’s not that hard to crack one of these open), get the Azure ACS URL, and pass it the refresh token to obtain an access token.

It’s also completely possible to create low-trust apps for your on-premises SharePoint 2013 farm following the directions in the article, How to: Use an Office 365 SharePoint site to authorize provider-hosted apps on an on-premises SharePoint site.  In this model, you create an O365 tenant and then establish a trust from your on-premises SharePoint farm to O365.  You don’t have to do anything with the O365 tenant, you just need it in order to access the Azure ACS namespace that is provisioned for you.  Once configured, you simply obtain a Client ID and Client Secret and use them from your app, whether it’s on the Microsoft platform or not.  After configuring your on-premises farm for low-trust apps, you no longer have to worry about managing X.509 certificates or SPTrustedSecurityTokenIssuer registrations for multiple environments (dev, QA, prod).  You no longer have to worry about developers having access to the password for the X.509 certificate and how to manage that.  As you might tell, I argue that this is the right way to go and that you do not want to create high trust apps. 

On-premises provider-hosted trust with ACS 

  1. Create an ACS proxy in your on-premises SharePoint 2013 farm.

  2. Install the signing certificate of your on-premises server to your Office 365 tenancy.

  3. Add the fully qualified domain names of the sites on your SharePoint 2013 farm where you want to run apps to the service principal name collection in your Office 365 tenancy.

  4. Create an app management proxy on your SharePoint 2013 farm.

You can see that your app and all of your data remains on-premises, you’re just using Azure ACS for app registration and authorization.  From the developer’s point of view, you now simply use a client ID and client secret for your apps, enabling you to use various JWT libraries with little or no customization.  This is why I think this is such a great opportunity to reduce the management complexity of high-trust app solutions by instead using low-trust apps.

Configuring High-Trust On-Premises

Whether you are building an app using Visual Studio and ASP.NET MVC running on IIS or Azure Web Sites, or you are building an app using Eclipse and Tomcat that will run on Apache and Linux, this registration step for high-trust apps is the exact same and can only be done by a member of the SharePoint Farm Administrators group. Configuring SharePoint to trust an X.509 certificate to configure high-trust is done through PowerShell.

PowerShell for High Trust Apps
  1. #Tell SharePoint to trust the certificate
  2. $publicCertPath = "C:\HighTrust.cer"
  3. $certificate = Get-PfxCertificate $publicCertPath
  4. New-SPTrustedRootAuthority -Name "HighTrust" -Certificate $certificate
  5.  
  6. #Get the tenant ID.  For on-premises default installations,
  7. #this will be the same as the SharePoint farm ID
  8. $spweb = Get-SPWeb "https://mysite.contoso.com"
  9. $realm = Get-SPAuthenticationRealm -ServiceContext $spweb.Site
  10.  
  11. #Specify the issuer ID
  12. $issuerID = "b77a601b-3133-4567-bb37-f147f61dd332"
  13. $fullIssuerIdentifier = $issuerId + '@' + $realm
  14.  
  15. #Create a trusted security token issuer based on the certificate
  16. New-SPTrustedSecurityTokenIssuer -Name "Contoso S2S HighTrust Apps" -Certificate $certificate -RegisteredIssuerName $fullIssuerIdentifier IsTrustBroker
  17.  
  18. #IISRESET is needed, otherwise settings won't be applied for 24 hours
  19. iisreset

The full configuration steps on Windows are documented in the topic, “How to: Create high-trust apps for SharePoint 2013 (advanced topic)”.   The app will use the certificate, certificate password, and issuer ID to form the access token.  Visual Studio provides a wizard to populate these values. 

Visual Studio provider-hosted app wizard

As we will see below, apps on non-Microsoft platforms will need to manage these values in order to form the access token. 

JWT Tokens

SharePoint uses JWT tokens for its OAuth implementation.  Don’t let the term “token” scare you into thinking this is some complex security mumbo-jumbo.  JWT tokens are just JSON objects, which are just name-value pairs.  The JWT specification draft is very easy to read.

To demonstrate a SharePoint access token (again, it’s just a JWT token), I show an example of an access token for an on-premises high-trust app in my blog post, “Creating a Fiddler Extension for SharePoint 2013 App Tokens”.  Here is a screen shot from that post.

Fiddler extension to show OAuth tokens

You can download the source and see how I decoded the token to display it.  The token is encoded, meaning you can decode it just as I did in the Fiddler screen shot above.  This is important for security reasons, it’s not safe to transport these tokens without protecting them using SSL.  The token is good for 12 hours, meaning I could obtain the token during that 12 hour window and use it to do whatever the app has permissions to do.  You must use SSL for apps.

Here are the relevant bits of the access token:

aud Audience.  The value is 00000003-0000-0ff1-ce00-00000000/<hostname>@<realm*>  .    The hostname is the FQDN of the web application root or the host-header site collection root.  The realm is the GUID that represents the SharePoint tenant.
iss Issuer. <IssuerID>@<realm*>. The issuer ID is obtained when you register the SPTrustedIdentityTokenIssuer. The realm is the GUID that represents the SharePoint tenant.
nbf Not before. The Unix epoch time upon which the token started being valid.
exp Expires. The Unix epoch time upon which the token expires.
nameid The identifier for the user (more info below)
nii The identity provider used to rehydrate the user.  One of the values:
urn:office:idp:activedirectory
urn:office:idp:forms:membershipprovidername
trusted:samlprovidername (as noted in Steve Peschka’s example below, this is what is actually configured as opposed to the documentation)
actortoken The token for the application.

* In reality, when you install SharePoint 2013 on-premises, there is only one tenant ID (unless you have configured additional tenants explicitly, which is unlikely).  In single-tenancy mode (the default), the realm is the same as the SharePoint farm ID.  The Farm ID can be obtained by using PowerShell Get-SPFarm | select ID.

Now we pay special attention to the actortoken.  As opposed to the outer token, the actortoken identifies the app.

aud Audience.  The value is 00000003-0000-0ff1-ce00-00000000/<hostname>@<realm*>  .    The hostname is the FQDN of the web application root or the host-header site collection root.  The realm is the GUID that represents the SharePoint tenant.
iss Issuer.  <IssuerID>@<realm*>.  The issuer ID is obtained when you register the SPTrustedIdentityTokenIssuer.  The realm is the GUID that represents the SharePoint tenant.
nbf Not before.  The Unix epoch time upon which the token started being valid.
exp Expires. The Unix epoch time upon which the token expires.
nameid Identifier for the app.  <Client ID>@<realm>.  The client ID uniquely identifies your app, this is provided by using AppRegNew.aspx or provided when registering an app in the Office Marketplace.

Inner token, outer token… kind of weird to describe.  Let’s look at an example.  We will use the following values, making it easier to find in the code snippet following.  These are the same values as shown in our PowerShell script above.

SharePoint site mysite.contoso.com
SharePoint realm ID 6305dc22-8cb8-4da3-8e76-8d0bbc0499a5
SPTrustedSecurityTokenIssuer issuer ID b77a601b-3133-4567-bb37-f147f61dd332
Client ID 06d847ca-011f-4965-ac1f-5ad14740ad89

And finally, a sample decoded token using our hypothetical values.

Sample Access Token Body
  1. {
  2.     aud:    00000003-0000-0ff1-ce00-000000000000/mysite.contoso.com@6305dc22-8cb8-4da3-8e76-8d0bbc0499a5,
  3.     iss:    b77a601b-3133-4567-bb37-f147f61dd332@6305dc22-8cb8-4da3-8e76-8d0bbc0499a5,     
  4.     nameid: s-1-5-21-3304015898-3601453682-3711364722-500,
  5.     nii:    urn:office:idp:activedirectory,        
  6.     nbf:    1320176785,     
  7.     exp:    1320219985,             
  8.     actortoken:     
  9.     {         
  10.         aud:        00000003-0000-0ff1-ce00-000000000000/mysite.contoso.com@6305dc22-8cb8-4da3-8e76-8d0bbc0499a5,
  11.         iss:        b77a601b-3133-4567-bb37-f147f61dd332@6305dc22-8cb8-4da3-8e76-8d0bbc0499a5,         
  12.         nameid:     06d847ca-011f-4965-ac1f-5ad14740ad89@6305dc22-8cb8-4da3-8e76-8d0bbc0499a5,                        
  13.         nbf:        1320176785,         
  14.         exp:        1320219985,                          
  15.           trustedfordelegation: true
  16.     }
  17. }

As you can see, there are a few research items required prior to implementing this, but it is completely feasible to implement this on a non-Microsoft platform once you deconstruct the message.

The S2S implementation of OAuth 2.0 for SharePoint 2013 is documented as part of the Open Specification Promise at [MS-SPS2SAUTH]: OAuth 2.0 Authentication Protocol: SharePoint Profile.

High-Trust Apps on Non-Microsoft Platforms

If, despite my guidance to the contrary, you still insist on building high-trust apps, things will be a little more involved.  The issue for non-Microsoft developers who want to build high-trust apps for SharePoint 2013 comes down to the lack of an available implementation of TokenHelper.cs for anything but .NET.  The thing is, TokenHelper is really just helping you encode and decode the JWT token, and that is readily available on non-Microsoft platforms, but there are a few key details to understand for high-trust apps.

When you create a new provider-hosted app using Visual Studio, it generates this incredible set of classes to make developing an app easy by abstracting away all the OAuth stuff.  The main class that does the heavy lifting is generated in your app and is called TokenHelper.cs.  It has all kinds of goodies in it such as calling Azure ACS for low-trust apps.  It also has a method to create an access token based on the current user, used for high-trust apps:

Windows Identity
  1. public static string GetS2SAccessTokenWithWindowsIdentity(
  2.     Uri targetApplicationUri,
  3.     WindowsIdentity identity)
  4. {
  5.     string realm = string.IsNullOrEmpty(Realm) ? GetRealmFromTargetUrl(targetApplicationUri) : Realm;
  6.  
  7.     JsonWebTokenClaim[] claims = identity != null ? GetClaimsWithWindowsIdentity(identity) : null;
  8.  
  9.     return GetS2SAccessTokenWithClaims(targetApplicationUri.Authority, realm, claims);
  10. }

This method will accept a WindowsIdentity and create the access token based off of it.  Because this method accepts a WindowsIdentity, it may seem like this cannot be done on anything except the Windows platform.  However, if we dive into the GetClaimsWithWindowsIdentity method that is called within its body, we can see that it’s doing something rather simple.

Claims Identity
  1. private static JsonWebTokenClaim[] GetClaimsWithWindowsIdentity(WindowsIdentity identity)
  2. {
  3.     JsonWebTokenClaim[] claims = new JsonWebTokenClaim[]
  4.     {
  5.         new JsonWebTokenClaim(NameIdentifierClaimType, identity.User.Value.ToLower()),
  6.         new JsonWebTokenClaim("nii", "urn:office:idp:activedirectory")
  7.     };
  8.     return claims;
  9. }

All it is doing is adding two claims.  The first claim is “nameid”, and its value is a SID.  The second value, “nii”, tells SharePoint how to map the “nameid” value to an identity provider.  In this case, the “nii” value is “urn:office:idp:activedirectory”.   The “nameid” and “nii” claims are specific to SharePoint, so you have to add those claims to the JWT token.

Where Do the Values Come From?

TokenHelper.cs is generated by Visual Studio.  If you are using another platform, then you will need to implement your own version of TokenHelper.  We showed in the previous section where all the appropriate values go, but now comes the question of how to obtain the values.

issuerid

The issuer ID is created when you register the SPTrustedSecurityTokenIssuer.  This is a GUID that you generate and is converted to all lowercase.

Client ID

The client ID is obtained when you register the app using AppRegNew.aspx or the Office Marketplace.

realm

The realm is very easy to obtain, just go to the SharePoint site and append “/_vti_bin/client.svc” to the host name, adding an HTTP header "Authorization: Bearer ".  You can see the logic to parse this in TokenHelper.cs:

Obtaining the Realm
  1. public static string GetRealmFromTargetUrl(Uri targetApplicationUri)
  2. {
  3.     WebRequest request = WebRequest.Create(targetApplicationUri + "/_vti_bin/client.svc");
  4.     request.Headers.Add("Authorization: Bearer ");
  5.  
  6.     try
  7.     {
  8.         using (request.GetResponse())
  9.         {
  10.         }
  11.     }
  12.     catch (WebException e)
  13.     {
  14.         if (e.Response == null)
  15.         {
  16.             return null;
  17.         }
  18.  
  19.         string bearerResponseHeader = e.Response.Headers["WWW-Authenticate"];
  20.         if (string.IsNullOrEmpty(bearerResponseHeader))
  21.         {
  22.             return null;
  23.         }
  24.  
  25.         const string bearer = "Bearer realm=\"";
  26.         int bearerIndex = bearerResponseHeader.IndexOf(bearer, StringComparison.Ordinal);
  27.         if (bearerIndex < 0)
  28.         {
  29.             return null;
  30.         }
  31.  
  32.         int realmIndex = bearerIndex + bearer.Length;
  33.  
  34.         if (bearerResponseHeader.Length >= realmIndex + 36)
  35.         {
  36.             string targetRealm = bearerResponseHeader.Substring(realmIndex, 36);
  37.  
  38.             Guid realmGuid;
  39.  
  40.             if (Guid.TryParse(targetRealm, out realmGuid))
  41.             {
  42.                 return targetRealm;
  43.             }
  44.         }
  45.     }
  46.     return null;
  47. }

Simply convert this to your platform of choice’s code and you have an example of how to obtain the realm, which happens to be the same as the SharePoint farm ID in a default installation on-premises.  For O365, which is configured for many tenants, the realm will be different for each tenant. 

Side note:  It’s interesting that the realm will change from your on-premises tenant ID to become the O365 tenant ID if you configure a trust between your farm and O365 as shown in the section on low-trust apps. 

nameid and nii

When configuring high-trust apps, the MSDN documentation states that the web application must use Windows Authentication.  Remember that the only security communication between your app and SharePoint is a JWT token in the Authentication: Bearer header which is sent over SSL.  While the code in TokenHelper uses a WindowsIdentity, the only thing it’s doing with that object is obtaining the SID, it’s not used for any other purpose.  The only reason the app’s web application is required to run using Windows Authentication is because of how the implementation of TokenHelper.cs uses a WindowsIdentity object to obtain the user’s SID.  You could change the implementation of TokenHelper.cs to obtain the user’s SID through another means and get the exact same result.

The preceding section titled “JWT Tokens” introduced the nameid for the outer token and showed that the value is a SID, obtained from a WindowsIdentity object.  How do you, then, get the user’s SID in a non-Microsoft environment?  One way would be to make an LDAP call to the Active Directory where the user resides.  You can see an example of accessing the objectsid attribute based on the samAccountName using PHP in the post PHP - Get users SID from Active Directory via LDAP.  Using that technique, you can figure out how to look up the current user with an LDAP query to Active Directory, obtain the SID, and then add the SID (using lower-case) as the nameid value.  The nice thing about using an LDAP query is that it can be done from any platform (as the PHP example demonstrates). 

But what if our users are not in Active Directory, we use ADFS or Ping or some other SAML provider with SharePoint?  Something I absolutely love about this implementation is that you can use this same approach, passing the nameid for the user, with FBA and SAML claims as well.  Steve Peschka shows in his blog post, Using SharePoint Apps with SAML and FBA Sites in SharePoint 2013, how to augment TokenHelper.cs with some new logic that does something just like the Windows implementation does but with SAML or FBA users.

Assert the user identity
  1. private static JsonWebTokenClaim[] GetClaimsWithClaimsIdentity(
  2.     System.Security.Principal.IPrincipal UserPrincipal,
  3.     IdentityClaimType SamlIdentityClaimType, TokenHelper.ClaimsUserIdClaim id,
  4.     ClaimProviderType IdentityClaimProviderType)
  5. {
  6.  
  7.     //if an identity claim was not found, then exit
  8.     if (string.IsNullOrEmpty(id.ClaimsIdClaimValue))
  9.         return null;
  10.  
  11.     Hashtable claimSet = new Hashtable();
  12.  
  13.     //you always need nii claim, so add that
  14.     claimSet.Add("nii", "temp");
  15.  
  16.     //set up the nii claim and then add the smtp or sip claim separately
  17.     if (IdentityClaimProviderType == ClaimProviderType.SAML)
  18.         claimSet["nii"] = "trusted:" + TrustedProviderName.ToLower();  //was urn:office:idp:trusted:, but this does not seem to align with what SPIdentityClaimMapper uses
  19.     else
  20.         claimSet["nii"] = "urn:office:idp:forms:" + MembershipProviderName.ToLower();
  21.  
  22.     //plug in UPN claim if we're using that
  23.     if (id.ClaimsIdClaimType == CLAIMS_ID_TYPE_UPN)
  24.         claimSet.Add("upn", id.ClaimsIdClaimValue.ToLower());
  25.  
  26.     //now create the JsonWebTokenClaim array
  27.     List<JsonWebTokenClaim> claimList = new List<JsonWebTokenClaim>();
  28.  
  29.     foreach (string key in claimSet.Keys)
  30.     {
  31.         claimList.Add(new JsonWebTokenClaim(key, (string)claimSet[key]));
  32.     }
  33.  
  34.     return claimList.ToArray();
  35. }

 

In this example, Steve is adding the nameid and nii claims, in which case the nameid will be mapped to the UPN, email, or SIP.  He explains the importance of these three attributes in his blog post.  If you are working with FBA or SAML claims users in SharePoint, you definitely need to read Steve’s posts on this.  The most important thing to understand is how the User Profile Service Application in SharePoint is used with high-trust apps.  I offer the following two resources as additional reading to understand the impact on the User Profile Service Application.

SharePoint 2013 User Profile Sync for Claims Users

OAuth and the Rehydrated User in SharePoint 2013 – How’d They do That and What do I Need to Know

Another reason why I love this approach is because your app can run on any platform, even using a different authentication scheme, and can still work with a SharePoint farm that relies on Windows authentication. Your app only needs to map the user’s SID for Active Directory.  In the case where your SharePoint 2013 farm supports more authentication providers, you can support those, too.

What About the X.509 Certificate?

When you create a high-trust app using Visual Studio, you have to provide a path to a certificate and the certificate’s password.  The X.509 certificate is used to sign the access token.  Again, you can only sign using the certificate if you possess the private key, protected by a password.  For this, I don’t see public details about the implementation, but the specifications are public so I’ll just describe in generalities how it happens. 

The X.509 signature is used just like a client secret is used for OAuth2, the token is signed and formed in the very same way. At a very high level, a JSON object is formed and serialized to a string, the string is base64 URL encoded, and then the signature is applied.  You use the same process when you use a client secret instead of an X.509 certificate, so you should be able to find a library that forms the JWT token and extend the signing capability to include an X.509 certificate.

When you create a new App for SharePoint provider-hosted project, it automatically adds a reference to a .NET assembly called Microsoft.IdentityModel.Extensions.  I used Telerik JustDecompile (a free tool) to decompile the Microsoft.IdentityModel.Extensions library into a project.  I then used Visual Studio 2013 to create a new SharePoint provider-hosted project using a certificate, removed the binary reference to Microsoft.IdentityModel.Extensions, and added a project reference to my newly generated Microsoft.IdentityModel.Extensions project.  That lets me step through all the code down to the point that the JWT token is constructed and signed.  Of course, I am not providing legal guidance here on if you should do this or not (this library isn’t open-source), I simply stated what I did.

The implementation looks like this:

  1. The JWT token has two parts, a header and a body.  The header indicates the token type (JWT) and the algorithm used, while the body looks like the sample access token we provided earlier.  The header and body are separated by a “.”
  2. The header is encoded to JSON, then base64UrlEncoded.
  3. The body is encoded to JSON, then base64UrlEncoded.
  4. The two base64UrlEncoded values are joined together by a “.”
  5. The resulting value is then signed using an X.509 certificate using RSA SHA256 signature algorithm and a SHA256 digest algorithm.
  6. The result from step 5 is base64URLEncoded.
  7. The value of step 4 is concatenated with the result of step 6, joined together by a “.”

Here is some pseudo-code to help you understand the format.

Code Snippet
  1. IDictionary<string, string> headerClaims = jwt.CreateHeaderClaims();
  2. IDictionary<string, string> payloadClaims = jwt.CreatePayloadClaims();
  3.                         
  4. string encodedHeader = Base64UrlEncoder.Encode(headerClaims.EncodeToJson());
  5. string encodedBody = Base64UrlEncoder.Encode(payloadClaims.EncodeToJson());
  6.             
  7. string formattedClaims = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", encodedHeader, encodedBody);
  8. string encodedSignature = this.Sign(formattedClaims, jwt.SigningCredentials);
  9.  
  10. return string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", encodedHeader, encodedBody, encodedSignature);

What is “jwt.SigningCredentials” here?  If you look in TokenHelper.cs, you’ll see that it’s the X.509 certificate.  In your platform of choice, you’ll need to have the private key portion of the certificate and its password in order to generate the signature.  The public key portion is what is registered with SharePoint using PowerShell.

Code Snippet
  1. private static readonly string ClientSigningCertificatePath = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePath");
  2. private static readonly string ClientSigningCertificatePassword = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePassword");
  3. private static readonly X509Certificate2 ClientCertificate = (string.IsNullOrEmpty(ClientSigningCertificatePath) || string.IsNullOrEmpty(ClientSigningCertificatePassword)) ? null : new X509Certificate2(ClientSigningCertificatePath, ClientSigningCertificatePassword);
  4. private static readonly X509SigningCredentials SigningCredentials = (ClientCertificate == null) ? null : new X509SigningCredentials(ClientCertificate, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest);

It might help to add a picture of the result:

image

The X.509 certificate that you sign the token with is the same one used when you create the app, it’s the same one that you export the public key to SharePoint, it’s the same one that the farm administrator uses in SharePoint to register the SPTrustedSecurityTokenIssuer.  To make “high trust” work with SharePoint in non-Microsoft platforms, you need to form the JWT token using the correct aud and iss claims, inject two claims for nii and nameid, and sign the token with the X.509 cert.

Are there JWT Libraries in Other Platforms

Of course, you don’t want to have to write all of these details yourself, you want to leverage others’ work as much as possible.  There are plenty of JWT libraries for other platforms.  This is not an exhaustive list, just a few to point out.


As I started poking around, I noticed that few or none of the libraries implemented S2S, they only implement using a shared secret (aka, “client secret”).  To make this abundantly clear, I don’t see that any of them provide you the ability to use an X.509 certificate to sign the token.  This is the part that’s really up to you to figure out.  I’ve given you the various bits and pieces here, as well as a tool in Fiddler that you can inspect and reverse-engineer tokens to make your code generate the correct format.

There is a bit of elbow grease (interpretation: a bit of hard work) needed to make this work.  Like I said, if you are using a low-trust app, things are drastically simpler.

O365 APIs

If O365 is an option for you, another option to consider is using the O365 APIs.  The O365 API is a set of REST endpoints that extends the platform by adding the ability for both websites and native applications to consume Office 365 data.  This is powerful. Now, both web applications as well as native applications running on Windows 8, iOS, Android, and other device platforms can consume Office 365 data by using REST APIs and standard OAuth flows.  Just as we described previously that you could consume low-trust apps easily using any JWT library, the same goes for the O365 APIs.

Authentication is performed against Azure Active Directory using standard OAuth flows.  Similar to the goals of TokenHelper.cs, Microsoft introduced a library called Active Directory Authentication Library (ADAL) to make working with JWT and Azure Active Directory easy.  The ADAL library provides methods to obtain an access token, which you then add as an HTTP header “Authorization: Bearer “.  Using this approach you do not have to worry about X.509 certificates or modifying an existing library, you just configure your app with Azure Active Directory and call the O365 API. 

As an example, I wrote a post, Call Multiple Services with One Login Prompt Using ADAL, that uses the ADAL library to call the O365 API, among other APIs registered with Azure Active Directory.  You can see that it’s just a REST call, quite easy to convert into your platform of choice.

The Microsoft Open Technologies group has built several implementations of the Active Directory Authentication Library (ADAL) for non-Microsoft platforms. 

If O365 is an application target, you might consider going this route.  Simply convert some of the ADAL samples to one of the platforms above using ADAL and call it a day.

Summary

I’ve given you the most important parts of the solution, namely decoding a token to obtain bits of information (such as the Azure ACS URL) and how the X.509 signature works.  I showed you the nameid and nii claims, and provided you information on how to use these with FBA and SAML users. 

For More Information

JWT Specification Draft

Understanding Authentication and Permissions with Apps for SharePoint and Office

SharePoint 2013 User Profile Sync for Claims Users

OAuth and the Rehydrated User in SharePoint 2013 – How’d They do That and What do I Need to Know

PHP - Get users SID from Active Directory via LDAP

SharePoint 2013: Perform operations on SharePoint Document Library from PHP site

Using SharePoint Apps with SAML and FBA Sites in SharePoint 2013

https://github.com/auth0/java-jwt – A Java JWT library

https://github.com/firebase/php-jwt – A PHP JWT library

https://github.com/michaelrhanson/jwt-js – JWT implemented in JavaScript, used with Node.js

ADAL library for Java

ADAL library for Android

ADAL library for iOS

ADAL library for Node.js

  • Kirk

    Good post. Does this work with O365  or only on-perm?

    Thanks.

  • O365 is low-trust only.  On-premises can be low-trust (as stated in the section "Low-Trust Apps on Non-Microsoft Platforms") or high-trust.  My personal recommendation is to use low-trust.

  • Kirk, VERY helpful article.  I'm trying to prove out implementing app-only calls in Java using S2S trusts. Based on inspecting an S2S token on a working example in .net, the cert thumbprint (x5t) is also included in the JWT header. In the working example, this value is "fAtmc82bWkCSKI0hV3PbH_-3cuY" and I can plug that in on the Java side and the call works.  When I look at the cert in IIS, it says the thumbprint is "7c0b6673cd9b5a4092288d215773db1fffb772e6".  I can get this value via Java code.  However, I can't seem to find any documentation on where the "fAtm..." value is coming from or how it is calculated.  Can you point me in the right direction?  

  • @JasonMc - the x5t header is the base64 URL-encoded hash of the certificate.

    headers.Add("x5t", Base64UrlEncoder.Encode(issuerToken.Certificate.GetCertHash()));

    The GetCertHash method is documented here:

    msdn.microsoft.com/.../system.security.cryptography.x509certificates.x509certificate.getcerthash(v=vs.110).aspx

  • Thank you, Kirk!  This really got me over the hump.  For what it's worth, I'd love to see this more explicitly laid out in the [MS-SPS2SAUTH] spec but for the benefit of the readers of your blog, the key is that SharePoint expects the x5t header to be the Base64URL encoding of the byte[] of the thumbprint (also may be referred to as the digest) NOT the more prominent string value of the thumbprint which has a format similar to "7c0b6673cd9b5a4092288d215773db1fffb772e6" and may be readily available via "certificate.getThumbprint()" type methods on other platforms. This string value is returned in .NET via X509Certificate2.Thumbprint and X509Certificate.GetCertHashString() as well.  The string value is also typically displayed when inspecting the certificate using tools like IIS Manager.  So non-Microsoft platform app developers need to remember to disregard the string thumbprint and look for a way to access the SHA-1 digest (a byte array) of the DER encoded certificate.  I will try to post a follow-up comment containing a code example of how to do this in Java.  Hopefully, this helps bring additional clarity to this type of development.  Thanks again!    

  • Below is some Java code to get the Base64URL encoded (use the encoding library of your choice) value of a certificate.  All of the classes are are from java.io and java.security packages.

    String certPath = "C:\\Certs\\HighTrustSampleCert.cer";

    File pubCertFile = new File(certPath);

    BufferedInputStream bis = null;

    try {

       bis = new BufferedInputStream(new FileInputStream(pubCertFile));

    } catch(FileNotFoundException e) {

       throw new Exception("Could not locate certfile at '" + certPath + "'", e);

    }

    CertificateFactory certFact = null;

    Certificate cert = null;

    try {

       certFact = CertificateFactory.getInstance("X.509");

       cert = certFact.generateCertificate(bis);    

    } catch(CertificateException e) {

       throw new Exception("Could not instantiate cert", e);

    }

    bis.close();

    MessageDigest md = MessageDigest.getInstance("SHA-1");

    byte[] der = cert.getEncoded();

    md.update(der);

    byte[] digest = md.digest();

    System.out.println("The thumbprint for the x5t header is: " + Base64URL.encode(digest));  

  • @JasonMc - you've got to let me know when you get the first successful call through!  It would be great if you could share the solution to GitHub.  

  • I was able to successfully integrate our Java application with SharePoint using S2S trust.  I will try to put together a distilled solution and post it to GitHub.  I'll be sure to post a link to that here when it's available.  

Page 1 of 1 (8 items)
Leave a Comment
  • Please add 8 and 8 and type the answer here:
  • Post
Translate This Page
Search
Archive
Archives