Recently I was asked to look into integrating our systems with an external application via the third-party’s single sign-on system. The way it works is simple enough. We would have an application that authenticates the user through our system and then sends a request to the third-party asking for a token to sign the user into their system. The third-party would return the token that I then give to the end-user and redirect them to the third-party web site. This token is how the third-party would authenticate the user into their system. (The token is nothing more than a long string of characters that is passed on the URL of the redirect.)
The method of obtaining the token is also fairly simple. The application submits a SOAP request over an SSL session to the third-party’s authentication server and that server would respond with the token. SOAP, for all the technical specs and other crap, is very straightforward. It’s a simple XML document consisting of a root element called ENVELOPE which contains two children called HEADER and BODY. The header isn’t always required and the body typically contains elements with the names of various fields the SOAP function your calling requires with each element containing the value of that parameter. Very straightforward.
A SOAP request in ColdFusion couldn’t be simpler, especially with the CFSAVECONTENT tag. Simply construct your envelope inside a CFSAVECONTENT tag and then use CFHTTP to submit the request. It looks a little something like this:
<cfsavecontent variable="variables.soap"> <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetCurrentTime xmlns="http://ws.historicaloptiondata.com/" /> </soap:Body> </soap:Envelope> </cfsavecontent> <cfhttp url="http://ws.historicaloptiondata.com/Service.asmx" method="POST" > <cfhttpparam type="header" name="SOAPAction" value="""http://ws.historicaloptiondata.com/GetCurrentTime""" /> <cfhttpparam type="header" name="Content-Length" value="#Len( Trim( variables.soap ))#" /> <cfhttpparam type="xml" value="#Trim( variables.soap )#" /> </cfhttp> <cfdump var="#xmlParse( cfhttp.filecontent )#" />
You should be able to plug this code into a CFM file and run it without having to touch a thing and you should see the CFDUMP of an XML object. (I say “should” because xmlParse() seems to first try and open a file with the name of the content of the passed variable and, when that fails, treat the passed value as an XML document itself. This can trigger errors and make it unusable if you employ any sort of file operation restrictions on your server. In which case modify the code to remove the xmlParse() call and just dump the cfhttp.filecontent.)
A few notes about this code SOAP in general.
- This is an example of a SOAP 1.1 request. There is another, slightly different format known as SOAP 1.2. The major differences between the two are that the content-type for 1.1 is text/xml, but for 1.2 it is application/soap+xml. Also the SOAPAction HTTP header is no longer needed in 1.2.
- The SOAPAction HTTP header’s value must be wrapped in double-quotation marks. And sometimes the first character after the open quotes will need to be a pound (#) symbol. This means ColdFusion programmers will need to be certain they escape these special characters in their values.
- If the CFHTTPPARAM type “xml” is present, CFHTTP automatically sets the content-type to text/xml. I am not sure if it’s possible to override this, but I believe not, therefore you’re almost always going to have to stick with SOAP 1.1 if you’re using CFHTTP for your SOAP requests.
- You must Trim() the variables.soap variable! The newline at the beginning of the value, which exists because there is a newline immediately after after CFSAVEDCONTENT tag (for visual formatting purposes) will make the XML document you’re sending an invalid XML document and result in errors.
Now comes the WSDL file. A WSDL file is an XML documents that describes the functions and parameters of said functions available through a SOAP service. The CreateObject() function has a “webservice” object type which will consume a WSDL file and create an object with all the available functions offered by the SOAP service. All the stuff with XML and ENVELOPES and CFHTTP becomes transparent and, as it turns out, SOAP can be simpler than my previous example. The above code can be reduced to the following using CreateObject():
<cfset ws = CreateObject( "webservice", "http://ws.historicaloptiondata.com/Service.asmx?WSDL" ) /> <cfset ws.getCurrentTime() /> <cfdump var="#GetSOAPResponse( ws )#" />
This makes this life much simpler for ColdFusion programmers. No need to worry about what version of SOAP you’re using or what URLs you need to submit your request to, the formatting of your SOAP envelope, the SOAPAction HTTP header variable, etc. It’s all taken care of for you by ColdFusion.
So this integration I was asked to do should be a piece of cake, right?
Simple SOAP is simple. Complex SOAP… well, you’ll see in Part 2.