10 May

Decoding SAML responses in form post data using PowerShell

If you are ever inspecting a network trace when diagnosing SAML issues and the computer you are using is restricted enough that you cannot install useful SAML specific tools, you can always use PowerShell to help you demystify the network trace.

The techniques in this post can be used for decoding any application/x-www-form-urlencoded data however I will describe fully the procedure for decoding the SAMLResponse HTTP POST data and displaying the resultant XML in a nicely indented format.

In your network trace you will see HTTP POST data similar to the following:


SAMLResponse=PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2
wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0Y
zFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0
OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJ
PTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij48c2FtbDpJc3N1ZXI%2baHR0cDovL2lk
cC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1scDpTdGF0dXM%2bPHNhbWxwOlN0YXR1c0NvZGUgV
mFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIgLz48L3NhbWxwOlN0YXR1cz48c2FtbDpB
c3NlcnRpb24geG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeG1sbnM6eHM9Imh
0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiBJRD0iX2Q3MWEzYThlOWZjYzQ1YzllOWQyNDhlZjcwNDkzOTNmYzhmMD
RlNWY3NSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDctMTdUMDE6MDE6NDhaIj48c2FtbDpJc3N1ZXI%2baHR
0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1sOlN1YmplY3Q%2bPHNhbWw6TmFtZUlE
IFNQTmFtZVF1YWxpZmllcj0iaHR0cDovL3NwLmV4YW1wbGUuY29tL2RlbW8xL21ldGFkYXRhLnBocCIgRm9ybWF0PSJ1cm46b2F
zaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDp0cmFuc2llbnQiPl9jZTNkMjk0OGI0Y2YyMDE0NmRlZTBhMGIzZG
Q2ZjY5YjZjZjg2ZjYyZDc8L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6b
mFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIw
MjQtMDEtMThUMDY6MjE6NDhaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA%2fYWNzI
iBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4OTAwYjUyNzNkNTY2ODUiIC8%2bPC9zYW
1sOlN1YmplY3RDb25maXJtYXRpb24%2bPC9zYW1sOlN1YmplY3Q%2bPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTQtM
DctMTdUMDE6MDE6MThaIiBOb3RPbk9yQWZ0ZXI9IjIwMjQtMDEtMThUMDY6MjE6NDhaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0
aW9uPjxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U
%2bPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24%2bPC9zYW1sOkNvbmRpdGlvbnM%2bPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0
aG5JbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyNC0wNy0xN1QwOTowMTo0OFo
iIFNlc3Npb25JbmRleD0iX2JlOTk2N2FiZDkwNGRkY2FlM2MwZWI0MTg5YWRiZTNmNzFlMzI3Y2Y5MyI%2bPHNhbWw6QXV0aG5D
b250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlB
hc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ%2bPC9zYW1sOkF1dGhuU3RhdGVt
ZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2F
zaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT
0ieHM6c3RyaW5nIj50ZXN0PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2bPHNhbWw6QXR0cmlidXRlIE5
hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPj
xzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3RAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlV
mFsdWU%2bPC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3
JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhb
HVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXJzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhz
aTp0eXBlPSJ4czpzdHJpbmciPmV4YW1wbGVyb2xlMTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2F
tbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ%2bPC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg%3d%3d

To decode this, we need to initially interpret this data as application/x-www-form-urlencoded data and for this we will use the HttpUtility.ParseQueryString available in System.Web.dll.

First off we need to load the System.Web assembly:

[System.Reflection.Assembly]::LoadWithPartialName("System.Web")

If it is successfull we will see output similar to the following:


GAC Version Location
--- ------- --------
True v4.0.30319 C:\WINDOWS\Microsoft.Net\assembly\GAC_64\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll

Now let’s assign our form data to a variable and parse it:

$urlencodedformdata = @"
SAMLResponse=PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2
wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0Y
zFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0
...................................................................................................
...................................................................................................
...................................................................................................
JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhb
HVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXJzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhz
aTp0eXBlPSJ4czpzdHJpbmciPmV4YW1wbGVyb2xlMTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2F
tbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ%2bPC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg%3d%3d
"@
$parsedformdata = [System.Web.HttpUtility]::ParseQueryString($urlencodedformdata)

If everything was successful we should get pure base64 encoded data as the value of the SAMLResponse entry in $parsedformdata

$parsedformdata["SAMLResponse"]
PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2
......................................................................................
......................................................................................
......................................................................................
tbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==

At this point we can get the XML by base64 decoding the data and interpreting it as a UTF8 string:

$base64encodedxml = $parsedformdata["SAMLResponse"]
$xml = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($base64encodedxml))

At this point $xml should look like the following:

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"><saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status><saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z"><saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><saml:Subject><saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685" /></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z"><saml:AudienceRestriction><saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue></saml:Attribute><saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue><saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>

Now we can use the XmlTextWriter class to indent and format the XML for us.

Jeffrey Snover has already given a sample snippet on how this can be done over at the PowerShell Team Blog. I have repeated their code here:

function Format-XML ($xml, $indent=2) 
{ 
    $StringWriter = New-Object System.IO.StringWriter 
    $XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter 
    $xmlWriter.Formatting = “indented” 
    $xmlWriter.Indentation = $Indent 
    $xml.WriteContentTo($XmlWriter) 
    $XmlWriter.Flush() 
    $StringWriter.Flush() 
    Write-Output $StringWriter.ToString() 
}

We finally use this function to format our XML data nicely on screen:

Format-Xml -xml $xml -indent 4

Our final output should look like this:

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
    <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
    </samlp:Status>
    <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
        <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
        <saml:Subject>
            <saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685" />
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
            <saml:AudienceRestriction>
                <saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
        <saml:AttributeStatement>
            <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
                <saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>

This is a lot more readable than what we had to begin with. Hopefully if you ever find yourself in this situation you will remember that PowerShell can come to your rescue.

10 Nov

Calling a back end WCF Service from SharePoint using the Claims Based Security Token used to Logon when using a third party STS

We currently use AD FS to provide SSO to a few ASP.NET MVC web sites and a SharePoint Web Application. We also have a dozen or so back end WCF services, which are also secured by AD FS. The normal way to do this would be to use the Security Token (usually a SAML token) that was issued by AD FS to logon to the web site. For normal ASP.NET web sites the usual way to access the logon token (also called the bootstrap token) is to access the BootstrapToken property of the IClaimsIdentity instance, which is a member of the ClaimsPrincipal object which is set as the current user in the HttpContext. That is (HttpContext.Current.User.Identity as IClaimsIdentity).BootstrapToken.

Once you have the original logon security token you contact your STS requesting a new security token for the destination back end web service.

Once you have a security token for access to the back end WCF service, it is as straight forward as calling the back end service using a channel created with the issued token.

The problem with SharePoint is that it gets rid of the original logon token almost immediately and replaces the bootstrap token with it’s own token issued by the internal SharePoint STS.

The following image shows the issuer of the bootstrap token when running in the context of a SharePoint web application

Bootstrap token issuer is SharePoint

Bootstrap token issuer is SharePoint

The security token issued by the internal SharePoint STS is used all over the farm for all manner of tasks however this token is useless for authenticating to a back end WCF service secured by AD FS. It’s possible to tell AD FS to trust SharePoint tokens to allow AD FS to issue new tokens for back end WCF services based on a SharePoint token but I don’t like this approach as it means a SharePoint administrator could theoretically create tokens for any back end WCF service.

What I wanted is the original token issued by AD FS so I could use this for delegating credentials to back end WCF services. The challenge was to work out at what point SharePoint gets rid of the original token to see if there was any way to persist it for later use.

I thought the easiest way to track this down would be to prevent SharePoint swapping the token for a new internal SharePoint one. This is as simple as stopping the SharePoint STS application pool, SecurityTokenServiceApplicationPool, and then try to log in whilst monitoring the ULS logs.

The exception in the ULS logs was:

Claims Saml Sign-In: Could not get local token for trusted third party token. Exception: 'System.ServiceModel.ServerTooBusyException: The HTTP service located
at http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc is unavailable. This could be because the service is too busy or because no endpoint was found listening
at the specified address. Please ensure that the address is correct and try accessing the service again later. ---&gt; System.Net.WebException: The remote server returned an error: (503) Server Unavailable.
at System.Net.HttpWebRequest.GetResponse()
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout) -
-- End of inner exception stack trace --- Server stack trace:
at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown
at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData&amp; msgData, Int32 type)
at Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustContract.Issue(Message message)
at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst, RequestSecurityTokenResponse&amp; rstr)
at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst)
at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForContext(Uri context, Boolean bearerToken, SecurityToken onBehalfOf, SecurityToken actAs, SecurityToken delegateTo, SPRequestSecurityTokenProperties properties)
at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForOnBehalfOfContext(Uri context, SecurityToken onBehalfOf)
at Microsoft.SharePoint.IdentityModel.SPFederationAuthenticationModule.ExchangeArgumentTrustedThirdPartySessionSecurityTokenForLocalToken(SecurityToken thirdPartyToken, SessionSecurityTokenCreatedEventArgs arguments)'. Stack: ' Server stack trace:
at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown
at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData&amp; msgData, Int32 type)
at Microsoft.IdentityModel.Protocols.WSTrust.IWSTrustContract.Issue(Message message)
at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst, RequestSecurityTokenResponse&amp; rstr)
at Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel.Issue(RequestSecurityToken rst)
at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForContext(Uri context, Boolean bearerToken, SecurityToken onBehalfOf, SecurityToken actAs, SecurityToken delegateTo, SPRequestSecurityTokenProperties properties)
at Microsoft.SharePoint.SPSecurityContext.SecurityTokenForOnBehalfOfContext(Uri context, SecurityToken onBehalfOf)
at Microsoft.SharePoint.IdentityModel.SPFederationAuthenticationModule.ExchangeArgumentTrustedThirdPartySessionSecurityTokenForLocalToken(SecurityToken thirdPartyToken, SessionSecurityTokenCreatedEventArgs arguments)'.

From the stack trace you can see the call originates in the SPFederationAuthenticationModule class. This is derived from the WSFederationAuthenticationModule HTTP Module which handles logging on with a security token in normal ASP.NET web sites. It looks like this is the module which is getting an internal SharePoint STS token and replacing the logon token with it and storing this in the BootstrapToken property of the IClaimsIdentity.

Looking in reflector at this class you can see all the SharePoint magic happens inside the OnSessionSecurityTokenCreated method override. So creating our own HTTP module derived from the SharePoint one and providing are own override we should be able to persist the AD FS security token.

What ever we do we must make sure that the BootstrapToken property on the IClaimsIdentity remains as the SharePoint internal STS token as this is used by SharePoint and would break functionality if it was not there.

To make sure everything looks correct as far as SharePoint is concerned I decided to wrap the IClaimsIdentity inside a wrapper class that implements an interface derived from IClaimsIdentity. The only purpose to this derived interface is to expose an extra property of the original bootstrap token from AD FS. The interface is defined as follows:

public interface IExternalSTSClaimsIdentity : Microsoft.IdentityModel.Claims.IClaimsIdentity
{
System.IdentityModel.Tokens.SecurityToken ExternalSTSBootstrapToken { get; set; }
}

My implementation of this interface is just a wrapper for an existing IClaimsIdentity. All IClaimsIdentity interface members just forward on the request to the wrapped IClaimsIdentity. The extra ExternalSTSBootstrapToken member just returns a security token taken in during the objects construction. The definition is as follows:

public class ClaimsIdentityWrapper : IExternalSTSClaimsIdentity
{
private Microsoft.IdentityModel.Claims.IClaimsIdentity wrappedIdentity;

private System.IdentityModel.Tokens.SecurityToken externalSTSbootstrapToken;

public ClaimsIdentityWrapper(Microsoft.IdentityModel.Claims.IClaimsIdentity identityToWrap, System.IdentityModel.Tokens.SecurityToken externalSTSBootstrapToken)
{
this.wrappedIdentity = identityToWrap;
this.externalSTSbootstrapToken = externalSTSBootstrapToken;
}

public System.IdentityModel.Tokens.SecurityToken ExternalSTSBootstrapToken
{
get { return this.externalSTSbootstrapToken; }
set { this.externalSTSbootstrapToken = value; }
}

public Microsoft.IdentityModel.Claims.IClaimsIdentity Actor
{
get { return this.wrappedIdentity.Actor; }
set { this.wrappedIdentity.Actor = value; }
}

public System.IdentityModel.Tokens.SecurityToken BootstrapToken
{
get { return this.wrappedIdentity.BootstrapToken; }
set { this.wrappedIdentity.BootstrapToken = value; }
}

public Microsoft.IdentityModel.Claims.ClaimCollection Claims
{
get { return this.wrappedIdentity.Claims; }
}

...
}

It is all well and good having this class with the extra ExternalSTSBootstrapToken property but how do we go about populating it you might ask?

Well as alluded to above we implement our own HTTP module derived from SPFederationAuthenticationModule and override the OnSessionSecurityTokenCreated method. At this point we would still have access to the original bootstrap token from AD FS and it is here we will persist this token. We get a copy of a reference to the security token and then just call into the base class method OnSessionSecurityTokenCreated. After the base class method completes we then repopulate the ClaimsPrincipal produced by this method with our own IClaimsIdentity which wrap the original IClaimsIdentity objects in the ClaimsPrincipal. The code for the module is as follows:

public class OurCustomWSFederationModule : SPFederationAuthenticationModule
{
    protected override void OnSessionSecurityTokenCreated(SessionSecurityTokenCreatedEventArgs eventArgs)
    {
        var sessionSecurityToken = eventArgs.SessionToken;
        SecurityToken bootstrapToken = null;
        
        var identity = sessionSecurityToken != null && sessionSecurityToken.ClaimsPrincipal != null && sessionSecurityToken.ClaimsPrincipal.Identities != null && sessionSecurityToken.ClaimsPrincipal.Identities.Count == 1
                       ? sessionSecurityToken.ClaimsPrincipal.Identity as IClaimsIdentity
                       : null;
                       
        if (identity != null)
        {
            bootstrapToken = identity.BootstrapToken;
        }
        
        base.OnSessionSecurityTokenCreated(eventArgs);
        
        if (!ReferenceEquals(eventArgs.SessionToken, sessionSecurityToken))
        {
            // The external STS session security token has been exchanged for a local one. We will insert the bootstrap token into the session
            // security token so we can call external STS backed WCF services.
            
            IClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(eventArgs.SessionToken.ClaimsPrincipal.Identities.Select(i => new ClaimsIdentityWrapper(i, bootstrapToken)));
            
            var modifiedSessionSecurityToken = new SessionSecurityToken( claimsPrincipal,
                                                                        (string)null,
                                                                        eventArgs.SessionToken.ValidFrom,
                                                                        eventArgs.SessionToken.ValidTo)
                                                {
                                                    IsPersistent = eventArgs.SessionToken.IsPersistent
                                                };
            
            eventArgs.SessionToken = modifiedSessionSecurityToken;
        }
    }
}

All that is required now is to make sure that SharePoint uses our HTTP module instead of it’s own. Modify the SharePoint application’s web.config:

<configuration>
...
<system.webServer>
...
<modules runAllManagedModulesForAllRequests="true">
<remove name="AnonymousIdentification" />
<remove name="FileAuthorization" />
<remove name="Profile" />
<remove name="WebDAVModule" />
<remove name="Session" />
<add name="SPNativeRequestModule" preCondition="integratedMode" />
<add name="SPRequestModule" preCondition="integratedMode" type="Microsoft.SharePoint.ApplicationRuntime.SPRequestModule, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="SharePoint14Module" preCondition="integratedMode" />
<add name="StateServiceModule" type="Microsoft.Office.Server.Administration.StateModule, Microsoft.Office.Server, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="PublishingHttpModule" type="Microsoft.SharePoint.Publishing.PublishingHttpModule, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="DesignHttpModule" preCondition="integratedMode" type="Microsoft.SharePoint.Publishing.Design.DesignHttpModule, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<!--<add name="FederatedAuthentication" type="Microsoft.SharePoint.IdentityModel.SPFederationAuthenticationModule, Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />-->
<add name="FederatedAuthentication" type="TestAssembly.OurCustomWSFederationModule, TestAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bd3bb99cda57749e" />
<add name="SessionAuthentication" type="Microsoft.SharePoint.IdentityModel.SPSessionAuthenticationModule, Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="SPWindowsClaimsAuthentication" type="Microsoft.SharePoint.IdentityModel.SPWindowsClaimsAuthenticationHttpModule, Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="SPApplicationAuthentication" type="Microsoft.SharePoint.IdentityModel.SPApplicationAuthenticationModule, Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
</modules>
...
</system.webServer>
...
</configuration>

Make sure you have deployed the assembly with the custom module to the GAC or the web application’s bin folder.

After an iisreset, if you debug a page now you will see we have access to the original security token from AD FS:

Bootstrap token issuer is ADFS

Bootstrap token issuer is ADFS

Now that we have the original AD FS security token we can call a back end WCF secured by AD FS using the usual process. Just remember to use the ExternalSTSBootstrapToken property and not the BootstrapToken property.

In my next post I will show an example of using this security token to actually call a backend WCF as SharePoint has some quirks to overcome to allow the web application to authenticate itself to AD FS to allow the delegation of credentials.