The main objective
The goal here is to avoid the redundant transmission of the authentication parameters for each request. Well, lets precise that we want to avoid the user to have to deal with that other than the initial call to the Login() method. Thus, by the same, we can simplify the process on the server application as well.
How it works?
The concept is to make use of a SOAP header class which can read specific parameters the server application can define in it. Thus, that becomes part of an HTTP header transmission. It is kind of similar to a HTTP cookie. We initially ask the user to authenticate to a Web site, the server application stores a cookie in the browser's memory and that is being transmitted to the Web server for every page access.
The server application, in such implementation, should refuse access to any method as long as the Login() method is not executed with success. The server application, as it is the case for a Web server application, is responsible to set up the authentication schema so whenever the user will send any other requests, the authentication parameters will be sent as well.
The SOAP client implementation should benefit of a binding to a SOAP header class in order to be compliant with that approach. Thus, just instantiating the SOAP client object and making a call will not be sufficient, in this case, to gain access to the available Web Service methods. In such implementations, the Web Service provider normally provides a basic code you can use to get started.
The Web Service
The Web Service should include a login method. We will call it Login(). That method receives a username and a password as the only two parameters. Base on the result of the authentication, it returns a boolean to represent its status. Here is a basic login method approach:
DEFINE CLASS WebService AS SESSION OLEPUBLIC FUNCTION Login(tcUsername AS STRING,tcPassword AS STRING) AS BOOLEAN LOCAL llResult llResult=LoginAuthenticate() RETURN llResult ENDFUNC ENDDEFINE
In order for the Web Service to be able to store the authentication schema in the SOAP header, we need to define SOAP header class. When a SOAP server class is bind to your Web Service, it can interact with its content. Thus, you can define a property in it which is sent to the SOAP client header class for every request. A simple DLL can be created on the server for such a need. That DLL benefits of the Microsoft SOAP client header handler. There is a basic structure we have to respect. Basically, we need to provide the basic methods to read and write to the header. The write method benefits of the Microsoft SOAP Serializer object to create the header as the first parameter following by the Web Service object as the second parameter. The read method benefits of the Microsoft XMLDomNode object as the first parameter to read the header followed by the Web Service object as the second parameter. Thus, when we create the header, the Web Service object is passed as one of the parameter. So, if we create a property in it which contains the user authentication, we can have the write method to create a structure which will contain a user property. As for the read method, it stores the user property value in the Web Service user property. So, when we get in the Web Service application, we already have access to that property value.
The following code describes the structure of the DLL which supports the creation of a User property in the SOAP header and the ability to read it for every request and store its value in the Web Service User property.
DEFINE CLASS ServerHeaderHandler AS Session OLEPUBLIC Implements IHeaderHandler IN 'mssoap.soapclient' * Read the login information PROTECTED FUNCTION IHeaderHandler_readHeader(loHeaderNode As MSXML2.IXMLDOMNode, loObject As Object) AS Boolean IF loHeaderNode.baseName<>'SessionInfo' OR loHeaderNode.namespaceURI<>'http://tempuri.org/SessionInfoHeader' RETURN .F. ENDIF loObject.cUser=loHeaderNode.selectSingleNode('User').Text RETURN .T. ENDFUNC * Needed PROTECTED FUNCTION IHeaderHandler_willWriteHeaders() AS Boolean RETURN .T. ENDFUNC * Write the login information * expC1 Login information PROTECTED FUNCTION IHeaderHandler_writeHeaders(loSerializer As MSSOAPLib.ISoapSerializer, loObject As Object) loSerializer.startHeaderElement('SessionInfo','http://tempuri.org/SessionInfoHeader') loSerializer.startElement('User') loSerializer.writeString(loObject.cUser) loSerializer.endElement loSerializer.endHeaderElement ENDFUNC ENDDEFINE
DEFINE CLASS WebService AS SESSION OLEPUBLIC cUser='' FUNCTION Login(tcUsername AS STRING,tcPassword AS STRING) AS BOOLEAN LOCAL llResult llResult=LoginAuthenticate() IF llResult This.cUser="My authentication" ENDIF RETURN llResult ENDFUNC ENDDEFINE
<?xml version='1.0' encoding='UTF-8' ?> <!-- Generated 11/29/01 by Microsoft SOAP Toolkit WSDL File Generator, Version 1.01.707.0 --> <servicemapping name='UniversalThread'> <service name='UniversalThread'> <using PROGID='Web Service.WebService' cachable='0' ID='WebServiceObject' /> <port name='WebServiceSoapPort'> <operation name='GetUserGroupMeeting'> <execute uses='WebServiceObject' method='GetUserGroupMeeting' dispID='25'> <parameter callIndex='-1' name='retval' elementName='Result' /> </execute> </operation> <operation name='GetAvailablePosition'> ...
<?xml version='1.0' encoding='UTF-8' ?> <!-- Generated 11/29/01 by Microsoft SOAP Toolkit WSDL File Generator, Version 1.01.707.0 --> <servicemapping name='UniversalThread'> <service name='UniversalThread'> <using PROGID='Web Service.WebService' cachable='0' ID='WebServiceObject' /> <using PROGID='SessionInfo.ServerHeaderHandler' cachable='0' ID='ServerHeaderHandler' /> <port name='WebServiceSoapPort' headerHandler="ServerHeaderHandler"> <operation name='GetUserGroupMeeting'> <execute uses='WebServiceObject' method='GetUserGroupMeeting' dispID='25'> <parameter callIndex='-1' name='retval' elementName='Result' /> </execute> </operation> <operation name='GetAvailablePosition'> ...
The SOAP client
So, at this point, you should have everything you need on the server side to handle the SOAP header. On the client side, we need to implement a compliant structure which returns the required header for each request. Lets see first what we need to do to initialize the SOAP client object to connect to our Web Service:
* Create the SOAP object loUniversalThread=Createobject("mssoap.soapclient") loUniversalThread.mssoapinit("http://www.universalthread.com/universalthread.wsdl")
llLogin=loUniversalThread.Login(lcUsername,lcPassword)
So, a binding mecanism should be in place as well on the client to support a SOAP header class. The following code will do:
* This is the SOAP header handler class * This should remain as is. If you change something in it, it won't work with the UT Web Service. DEFINE CLASS ClientHeaderHandler AS Session OLEPUBLIC Implements IHeaderHandler IN 'mssoap.soapclient' cUser='' * Read the login information PROTECTED FUNCTION IHeaderHandler_readHeader(loHeaderNode As MSXML2.IXMLDOMNode, loObject As Object) AS Boolean IF loHeaderNode.baseName<>'SessionInfo' OR loHeaderNode.namespaceURI<>'http://tempuri.org/SessionInfoHeader' RETURN .F. ENDIF This.cUser=loHeaderNode.selectSingleNode('User').Text RETURN .T. ENDFUNC * Needed PROTECTED FUNCTION IHeaderHandler_willWriteHeaders() AS Boolean RETURN .T. ENDFUNC * Write the login information * expC1 Login information PROTECTED FUNCTION IHeaderHandler_writeHeaders(loSerializer As MSSOAPLib.ISoapSerializer, pObject As Object) loSerializer.startHeaderElement('SessionInfo','http://tempuri.org/SessionInfoHeader') loSerializer.startElement('User') loSerializer.writeString(This.cUser) loSerializer.endElement loSerializer.endHeaderElement ENDFUNC ENDDEFINE
Authentication at the methods level
At this point, the SOAP client is sending the proper header to the Web Service. So, after the Login() has succeeded, the first request to any method will send the proper header. All is required now on the Web Service is to establish the standard authentication for every method request. This simple code can handle it:
FUNCTION GetMessage(tdDate AS Date) as STRING IF NOT Authenticate(This.cUser) RETURN 'You need to authenticate prior to use any method.' ENDIF RETURN GetMessageFromUniversalThread(tdDate) ENDFUNC
From now on
This article has described the use of the SOAP header class for the purpose of authentication. That can be used for other types of content. You can also define several other properties. So, as you can see, this is pretty simple. I have to say, as this is brand new, that the information required to put that in place was extremly difficult to find and to put it all together. I hope this article can shade some light on the topic and help you in that direction if you need it.