The XML
I'm going to start with the base XML (before adding in the ColdFusion processing) to give an idea of what I'm going to generate. Here it is below with the items you need to replace marked off as ColdFusion variable calls:
<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response Destination="#SPService#" ID="#ssouser.uuid#" IssueInstant="#nowDateTime#" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">#IDPDomain#</saml:Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
<saml:Assertion ID="#ssouser.uuid#" IssueInstant="#nowDateTime#" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:Issuer>#IDPDomain#</saml:Issuer>
<saml:Subject>
<saml:NameID>#ssouser.email#</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:2.0:cm:bearer">
<saml:SubjectConfirmationData InResponseTo="#ssouser.uuid#" NotOnOrAfter="#nowDateTimePlus1#" Recipient="#SPService#"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="#nowDateTime#" NotOnOrAfter="#nowDateTimePlus1#">
<saml:AudienceRestriction>
<saml:Audience>#SPDomain#</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="#nowDateTime#">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="screenName">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">#ssouser.name#</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
- #IDPDomain# = The base URL of the IDP
- #SPService# = The full URL of the SP's authentication system
- #ssouser.uuid# = The user ID from the IDP that will be used as a unique identifier in several parts of this file
- #nowDateTime# = The current date/time to mark the initiation of the assertion
- #nowDateTimePlus1# = A date/time stamp after the current date/time to limit the time frame an assertion can occur. This prevents a user from trying to simulate/mimic one. Generally a 1 min differential is good. Assertions will always occur behind the scenes through one or more redirects.
- #ssouser.name# = Username/Display Name
- #SPDomain# = The base URL of the SP
The #SPDomain# and #SPService# will come from the SP and can just be hardcoded into the XML template. The #ssouser.uuid# and #ssouser.name# will be pulled from the IDP database or cookie. The date/time stamps will be pulled from #Now()#.
Now here's that same XML wrapped in ColdFusion:
<cfoutput>
<cfset nowDateTime = "#DateFormat(DateConvert('local2utc', Now()),'YYYY-MM-DDT')# & #TimeFormat(DateConvert('local2utc',Now()),'HH:mm:SS.LZ')#">
<cfset nowDateTimePlus1 = "#DateFormat(DateConvert('local2utc', Now()),'YYYY-MM-DDT')# & #TimeFormat(DateConvert('local2utc', DateAdd('n',1,Now())),'HH:mm:SS.LZ')#">
<cfsavecontent variable="responseOpen">
<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response Destination="#SPService#" ID="#ssouser.uuid#" IssueInstant="#nowDateTime#" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">#IDPDomain#</saml:Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
</cfsavecontent>
<cfxml variable="samlAssertionXML">
<saml:Assertion ID="#ssouser.uuid#" IssueInstant="#nowDateTime#" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:Issuer>#IDPDomain#</saml:Issuer>
<saml:Subject>
<saml:NameID>#ssouser.email#</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:2.0:cm:bearer">
<saml:SubjectConfirmationData InResponseTo="#ssouser.uuid#" NotOnOrAfter="#nowDateTimePlus1#" Recipient="#SPService#"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="#nowDateTime#" NotOnOrAfter="#nowDateTimePlus1#">
<saml:AudienceRestriction>
<saml:Audience>#SPDomain#</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="#nowDateTime#">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="screenName">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">#ssouser.name#</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</cfxml>
<cfsavecontent variable="responseClose">
</samlp:Response>
</cfsavecontent>
</cfoutput>
The immediate question is why do I have the XML split up into 3 variable? The whole thing could be enclosed as 1 inside <CFXML>. The reason is that I ran into a bug (to come in Part 3) where I had trouble traversing the DOM beyond the first level of depth. So by cutting out the opening and closing <SAMLP:RESPONSE> tags, I can deal with the middle piece by itself, where <SAML:ASSERTION> is the root level. It might not make sense yet, but I promise it will be cleared up once you read Part 3!
So on to the other ColdFusion pieces:
1) The 2 <CFSET>s in the beginning handle the 2 date/time stamps (now & 1 minute later). I'm not sure if they actually need to be formatted this way, but the other sites I studied had it like this and it worked for me as well.
2) The 1st and 3rd variables I'm saving out use <CFSAVECONTENT> because I'm going to be adding these back in after the signing process and they don't need to be recognized as XML yet.
3) The 2nd variable needs to use <CFXML> rather than <CFSAVECONTENT> because it needs to retain the XML DOM. Later on, when I create and insert the signature piece, it will need to reference this 2nd variable as if it were a valid XML document.
While your at this step, I encourage you to dump out these variables right on the document you're creating so you can get a visual of the unsigned XML to compare with the signed version later on. Just be sure to delete it when it's finalized.