I have a webservice written in c#/.NET that redirects unauthenticated users to a WS Federation identity provider, which then redirects back to my webservice with a SAML token which has the roles of that user. This is as per the passive WS federation specification - http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#_Toc223175008
Having got this, I get a request which has the wresult set to be the token. In my code I've got a string wresult which is the string for the xml document. What I know is the realm im on, the thumbprint of the identity provider, the wctx (if it was sent).
The security token is a standard WS-Trust token described here: http://specs.xmlsoap.org/ws/2005/02/trust/WS-Trust.pdf
What I want to get is the SecurityToken and eventually the IPrincipal for that user just from that string which is the XML document/security token.
An example of the string would be (with a few things obfuscated).
<?xml version="1.0"?>
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime>
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-09-14T13:40:25.164Z</wsu:Created>
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2018-09-14T14:40:25.164Z</wsu:Expires>
</t:Lifetime>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>https://localhost:44366/</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken>
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" MajorVersion="1" MinorVersion="1" AssertionID="_e1580903-02ac-453d-a157-ae27c8614cc9" Issuer="http://adfs.ORGANISATION.com/adfs/services/trust" IssueInstant="2018-09-14T13:40:25.164Z">
<saml:Conditions NotBefore="2018-09-14T13:40:25.164Z" NotOnOrAfter="2018-09-14T14:40:25.164Z">
<saml:AudienceRestrictionCondition>
<saml:Audience>https://localhost:44366/</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Attribute AttributeName="emailaddress" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>[email protected]</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="givenname" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Jeff</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="surname" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Mandelson</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="windowsaccountname" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>jeff.mandelson</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="role" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>Stuff\Domain Users</saml:AttributeValue>
<saml:AttributeValue>Stuff\DevTeam</saml:AttributeValue>
<saml:AttributeValue>Stuff\RDS-MSSQLDEV-RW</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="upn" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>[email protected]</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Jeff Mandelson</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" AuthenticationInstant="2018-09-14T11:59:16.147Z">
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
</saml:AuthenticationStatement>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#_e1580903-02ac-453d-a157-ae27c8614cc9">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>a_digest_value_removed</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>signature</ds:SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>certificate</X509Certificate>
</X509Data>
</KeyInfo>
</ds:Signature>
</saml:Assertion>
</t:RequestedSecurityToken>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>
I've tried using the inbuilt methods such as WSFederationAuthenticationModule, however, this seems to have problems unless you're using System.Web.Request. An inbuilt .NET/C# function would be preferable!
The solution is to think of the token as it was a regular XMLDsig signed XML - the assertion node is signed and the signature's reference points back to it. The code is rather simple, what's interesting however is that the
SignedXml
class has to be inherited to have the signature validator that follows theAssertionID
attribute (the default convention is that the signed node's id attribute is called justID
and the default validator just won't find the node that has the id attribute called differently).Note that the XPath assumes the token has the
RequestSecurityTokenResponseCollection
in the root, make sure your tokens follow this convention (in case of a single token, the collection node can be missing and the token's root could be justRequestSecurityTokenResponse
, update the code accordingly).The validation code is then
With some minor tweaks, you should be able to do what you want.
BTW. Most of the answer is copied from my blog entry
https://www.wiktorzychla.com/2018/09/parsing-saml-11-ws-federation-tokens.html
that documents the approach we are using internally in one of our apps. I planned to make this entry for some time and your question was just a trigger I needed.