locked-gate.jpg

SharePoint Online Authentication for Hybrid Environments

SharePoint Online Authentication

The flow chart below illustrates how we are authenticating applications to SharePoint Online from an on-prem context.

Our SharePoint Online environment is configured to support authentication from ADFS on-prem. In this configuration, users will usually login using their standard domain credentials, e.g. contosoeric or [email protected].

As custom app developers, we need our applications to authenticate to SharePoint Online as the domain user who is running the application.  The user should not have to re-enter their credentials in order for the application to access SharePoint Online.

If you browse the web, you’ll find several solutions and code samples:

  • MSOnlineClaimsHelper:  There are several copies of this class floating around.  This blog post includes the MSOnlineClaimsHelper and also includes an important CookieBehavior that we used for authentication when using a WCF client.
  • Microsoft.SharePoint.Client.SharePointOnlineCredentials:  This class is available from the SharePoint Server 2013 Client Components SDK .  This is a great way to authenticate both C# and PowerShell apps to SharePoint Online.  But, it requires that your app is running against .net 4.0.  This .net requirement is a problem if you need to use it within the context of a SharePoint 2010 environment because SharePoint 2010 uses .net framework 3.5.

Both of these solutions are great, but they require the username and password.  They will not authenticate to SharePoint Online using the domain login of the current user.

So, we modified the MSOnlineClaimsHelper to support this requirement.  The flow chart below illustrates the authentication flow for an MVC 4 Web API service which was created to retrieve resources from SharePoint Online on behalf of the logged in user.

In summary, the flow chart below illustrates that we must first retrieve an appropriate SAML assertion from on-prem ADFS.  Next, we must submit the resulting SAML assertion to Microsoft Online STS.

Step 3 in the diagram below, submitting a SAML assertion to Microsoft Online STS, is the hard to find ‘secret sauce.’

MSOnlineAuthentication

Now for some code…

Request a SAML assertion from ADFS.  This example is intending to use kerberos for authentication to the on-prem ADFS endpoint, 2005/windowstransport.  This is the first step of the authentication flow.

///
/// Retrieve binary login token from O365, via ADFS
///

///Url to the adfs endpoint e.g. /2005/windowstransport ///The id of the relying party trust in ADFS e.g. urn:home:sharepoint ///The logon credential of the endpoint. Use the mex endpoint in ADFS to get this credential or SPN. ///SAML assertion retrieved from ADFS ///Expire date/time of the SAML assertion private void getO365BinaryTokenFromADFS(string stsUrl, string realm, string adfsEndPointAuthority, out string binaryToken, out DateTime expires)
{
expires = DateTime.MinValue;
binaryToken = "";

Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel channel = null;

Uri u = new Uri(stsUrl);

WindowsWSTrustBinding windowsBinding = new WindowsWSTrustBinding(SecurityMode.Transport);
windowsBinding.TrustVersion = TrustVersion.WSTrustFeb2005;

Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory trustChannelFactory2 =
new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(
windowsBinding,
new EndpointAddress(u, EndpointIdentity.CreateUpnIdentity(adfsEndPointAuthority), new AddressHeaderCollection()));

trustChannelFactory2.TrustVersion = TrustVersion.WSTrustFeb2005;

trustChannelFactory2.Credentials.Windows.AllowNtlm = false;
trustChannelFactory2.Credentials.SupportInteractive = false;

trustChannelFactory2.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Delegation;
trustChannelFactory2.Credentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;

GenericXmlSecurityToken token = null;

try
{
RequestSecurityToken rst = new RequestSecurityToken(WSTrustFeb2005Constants.RequestTypes.Issue, WSTrustFeb2005Constants.KeyTypes.Bearer);
rst.AppliesTo = new EndpointAddress(_realm);

rst.TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.Saml11TokenProfile11;

channel = (Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel)trustChannelFactory2.CreateChannel();

RequestSecurityTokenResponse rstr = null;

token = channel.Issue(rst, out rstr) as GenericXmlSecurityToken;

GenericXmlSecurityToken token2 = GetO365BinaryTokenFromToken(token, new EndpointAddress(u), new EndpointAddress(u));

expires = token2.ValidTo;
binaryToken = token2.TokenXml.InnerXml;
}
catch (WebException wex)
{
Trace.TraceWarning("WebException in getO365BinaryTokenFromADFS: " + wex.ToString());
throw;
}
catch (Exception ex)
{
Trace.TraceWarning("WebException in getO365BinaryTokenFromADFS: " + ex.ToString());
throw;
}
finally
{
if (null != channel)
{
channel.Abort();
}
trustChannelFactory2.Abort();
}
}

Next, the code below will authenticate to Microsoft Online sts using the SAML assertion retrieved above, and it will return to us a SAML assertion that can be used to authenticate to SharePoint Online.

///
/// Retrieve a security login token from O365 given an auth token issued by ADFS
///

///The security token we previously retrieved from on-prem ADFS ///The url to Microsoft Online STS. ///The url to Microsoft Online STS. /// Security token which can be posted to SharePoint Online as the current domain user.
private GenericXmlSecurityToken GetO365BinaryTokenFromToken(SecurityToken FromSwapnatoken, EndpointAddress issuerAddress, EndpointAddress mexAddress)
{

Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel channel = null;

UriBuilder u = new UriBuilder(office365STS);

var un = new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential);

var iss = new IssuedTokenWSTrustBinding(un, issuerAddress, SecurityMode.TransportWithMessageCredential, TrustVersion.WSTrustFeb2005, mexAddress)
{
EnableRsaProofKeys = false,
KeyType = SecurityKeyType.BearerKey
};

Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory trustChannelFactory2 = new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(iss, new EndpointAddress(u.Uri.AbsoluteUri));
trustChannelFactory2.TrustVersion = TrustVersion.WSTrustFeb2005;
trustChannelFactory2.ConfigureChannelFactory();
if (trustChannelFactory2.Credentials != null) trustChannelFactory2.Credentials.SupportInteractive = false;

trustChannelFactory2.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
trustChannelFactory2.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;

GenericXmlSecurityToken token = null;

try
{
RequestSecurityToken rst = new RequestSecurityToken(WSTrustFeb2005Constants.RequestTypes.Issue, WSTrustFeb2005Constants.KeyTypes.Bearer);
rst.AppliesTo = new EndpointAddress(_hostUri.GetLeftPart(UriPartial.Authority));
rst.SignatureAlgorithm = SecurityAlgorithms.RsaSha1Signature;

channel = (Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel)trustChannelFactory2.CreateChannelWithIssuedToken(FromSwapnatoken);

RequestSecurityTokenResponse rstr = null;
token = channel.Issue(rst, out rstr) as GenericXmlSecurityToken;

}
catch (Exception ex)
{
Trace.TraceWarning("Exception in GetO365BinaryTokenFromToken:" + ex.Message);
throw;
}
finally
{
if (null != channel)
{
channel.Abort();
}
trustChannelFactory2.Abort();
}
return token;
}

Eventually, I expect Microsoft or another vendor to create a version of this code that we can all use. Until then, this will be a very high value bit of code for SharePoint Online Hybrid environments. Let me know if you run into authentication issues with SharePoint Online, I’ve learned quite a bit recently through the school of hard knocks.


Related Posts

Eric BowdenSharePoint Online Authentication for Hybrid Environments