Using SOAP Header and SOAP Extensions in a Web Service

Objective

We will develop a web service that would authenticate users for web sites. The web site needs to pass the user ID as well as the password for the user. Apart from this the web site also needs to pass the sites ID and password. This information is needed so that the web service can validate the site that have requested for the user authentication.

The user ID and the password will be passed through the parameters of an authenticate method exposed by the web service, while the sites ID and password will be passed using SOAP header. Since the data is very sensitive SOAP extension will be used to encrypt the data and decrypt them.

Flow

The site that wants to authenticate its user will pass a soap message. This soap message will have the sites user id and password in its header and it will have the method call with the user ID and password in its body. This soap message is intercepted by the SOAP extension to encrypt the messages.
On the server side the SOAP extension will intercept the message and decrypt it before the method is called.
The Authenticate method will validate the site ID and the password and then it will validate the user ID and password of the user.
On its way back the SOAP extension will again encrypt the response.
In the clients side the SOAP extension will decrypt the response.

Participants

1. A web service that will authenticate the ID and password.
2. A soap extension that will intercept the messages and encrypt them.
3. A web site, which will pass the ID as well as the password of the user and the site.

Create an empty solution call it the UserWebSite. To it we need to add the following projects: -

  • Create a class library project, which will have the SOAP extension.
  • Create a web service project.
  • Create a web application project.

To create the SOAP extension project: -

1. To the solution add a new project of type class library name it WebServiceHeaderExtension.
2. To this project add two class files. Name them WebServiceSOAPExtension.cs and EncryptClass.cs.
3. In the WebServiceSOAPExtension.cs add the following line of code at the top

using System;
using System.Web.Services.Protocols;
using System.IO;
using System.Text;
using System.Xml;

4. Add a reference to System.Web.Services to the project.

5. To this add a class and name it WebServcSoapExt. Derive it from SoapExtension as shown.

public class WebServcSoapExt : SoapExtension
{
}

6.To this class add the following line of code. This create two streams

private Stream inwardStream;
private Stream outwardStream;

7. Implement the ChainStream method in the WebServcSoapExt class as given.

public override Stream ChainStream(Stream stream)
{
outwardStream = stream;
inwardStream =
new MemoryStream();
return inwardStream;
}

This method is used to get a reference to the SOAP message that we are trying to modify. The stream that is passed to the ChainStream method is where the SOAP Extension reads from and the stream that is returned is where the SOAP extension writes. So we must maintain the reference to both.

8. Implement the GetInitializer methods. The initialize methods are used to initialize data that might be needed in the web service. Implement the initialize method as follows.

public override object GetInitializer(Type serviceType)
{
return null;
}
public override object GetInitializer(LogicalMethodInfo
methodInfo,SoapExtensionAttribute attribute)
{
return null;
}

For our case we dont need to initialize any data. Hence it returns null. The methods are called if it is the first time this SOAP extension was executed with this XML Web service.

9. Implement the Initialize method. This method is called every time a request is made to any XML web service method. It receives the object initialized in the GetInitializer method as its input parameter.

public override void Initialize(object initializer)
{
return;
}

For out case, we can keep it as simple as possible as it doesnt need to do anything.

10. After this we will write the method that intercepts the messages for encryption and decryption, the ProcessMessage method. This method is called at every stage of the ProcessMessage enumeration

public override void ProcessMessage(System.Web.Services.Protocols.SoapMessage message)
{
string soapMsg1;
StreamReader readStr;
StreamWriter writeStr;
XmlDocument xDoc =
new XmlDocument();
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterDeserialize:
break;
case SoapMessageStage.BeforeDeserialize:
break;
case SoapMessageStage.AfterSerialize:
break;
}
}

11. We need to intercept the message only in the following stage SoapMessageStage.BeforeDeserialize and SoapMessageStage.AfterSerialize. The message is modified as follows: -

  • In the client side after the message is serialized the SOAP extension encrypts it.
  • In the server side before the message deserialized the SOAP extension decrypts it.
  • In the server side before the message is sent, it is encrypted after serialize stage.
  • In the client side after the message is received it is decrypted by the SOAP extension before it is deserialized.

12. Now we will change the SoapMessageStage.BeforeDeserialize case in the ProcessMessage method as below. In the client side the portion of the code within

case SoapMessageStage.BeforeDeserialize:
readStr =
new StreamReader(outwardStream);
writeStr =
new StreamWriter(inwardStream);
soapMsg1 = readStr.ReadToEnd();
if ( message is System.Web.Services.Protocols.SoapClientMessage)
{
// this is executed at client side
xDoc.LoadXml(soapMsg1);
XmlNodeList xResult = xDoc.GetElementsByTagName("AuthenticateResult");
xResult[0].InnerXml = decrypt(xResult[0].InnerXml);
}
else if( message is System.Web.Services.Protocols.SoapServerMessage)
{
// this is executed at server side
xDoc.LoadXml(soapMsg1);
XmlNodeList xSiteID = xDoc.GetElementsByTagName("siteID");
xSiteID[0].InnerXml = decrypt(xSiteID[0].InnerXml);
XmlNodeList xSitePwd = xDoc.GetElementsByTagName("sitePwd");
xSitePwd[0].InnerXml = decrypt(xSitePwd[0].InnerXml);
XmlNodeList xUserID = xDoc.GetElementsByTagName("UserID");
xUserID[0].InnerXml = decrypt(xUserID[0].InnerXml);
XmlNodeList xPwd = xDoc.GetElementsByTagName("Password");
xPwd[0].InnerXml = decrypt(xPwd[0].InnerXml);
}
soapMsg1 = xDoc.InnerXml;
writeStr.Write(soapMsg1);
writeStr.Flush();
inwardStream.Position = 0;
break;

13. Now we will change the SoapMessageStage.AfterSerialize case in the ProcessMessage method as below.

case SoapMessageStage.AfterSerialize:
inwardStream.Position = 0;
readStr =
new StreamReader(inwardStream);
writeStr =
new StreamWriter(outwardStream);
soapMsg1 = readStr.ReadToEnd();
if ( message is System.Web.Services.Protocols.SoapClientMessage)
{
// this is executed at client side
xDoc.LoadXml(soapMsg1);
XmlNodeList xSiteID = xDoc.GetElementsByTagName("siteID");
xSiteID[0].InnerXml = encrypt(xSiteID[0].InnerXml);
XmlNodeList xSitePwd = xDoc.GetElementsByTagName("sitePwd");
xSitePwd[0].InnerXml = encrypt(xSitePwd[0].InnerXml);
XmlNodeList xUserID = xDoc.GetElementsByTagName("UserID");
xUserID[0].InnerXml = encrypt(xUserID[0].InnerXml);
XmlNodeList xPwd = xDoc.GetElementsByTagName("Password");
xPwd[0].InnerXml = encrypt(xPwd[0].InnerXml);
}
else if( message is System.Web.Services.Protocols.SoapServerMessage)
{
// this is executed at server side
xDoc.LoadXml(soapMsg1);
XmlNodeList xResult = xDoc.GetElementsByTagName("AuthenticateResult");
xResult[0].InnerXml = encrypt(xResult[0].InnerXml);
}
soapMsg1 = xDoc.InnerXml;
writeStr.Write(soapMsg1);
writeStr.Flush();
break;

14. We write two methods for encryption and decryption. These methods call encrypt or decrypt methods in the EncryptClass class. These methods are as below

private string encrypt(string message)
{
EncryptClass ec =
new EncryptClass();
string encryStr = ec.custEncrypt(message);
return encryStr;
}
private string decrypt(string message)
{
EncryptClass ec =
new EncryptClass();
string decryptStr = message;
return ec.custDecrypt(decryptStr);
}

15. The EncryptClass.cs file will have methods to encrypt or decrypt a string. To this file add the following code at the top.

using System;
uing System.Security.Cryptography;
using System.IO;
using System.Text;
using System.Xml;

16. To this file add a class call it EncryptClass. The code to add this class is as follows.

namespace WebServiceHeaderExtension
{
public class EncryptClass
{
DESCryptoServiceProvider des;
private Byte[] key = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
private Byte[] IV = {0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef};
public EncryptClass()
{
des =
new DESCryptoServiceProvider();
}
// for encryption
public string custEncrypt(string message)
{
}
// for decryption
public string custDecrypt(string message)
{
}
}
}

17. We will modify the custEncrypt method so that it encrypts the passed message.

// for encryption
public string custEncrypt(string message)
{
//create a memory stream
MemoryStream memStrm = new MemoryStream();
//create a crypto stream in write mode
CryptoStream crystm = new CryptoStream(memStrm, des.CreateEncryptor
key,IV),CryptoStreamMode.Write);
//Encode the passed plain text string into Unicode byte stream
Byte[] plaintextbyte = new UnicodeEncoding().GetBytes(message);
//Write the plaintext byte stream to CryptoStream
crystm.Write(plaintextbyte,0,plaintextbyte.Length);
//don't forget to close the stream
crystm.Close();
//Extract the ciphertext byte stream and close the MemoryStream
Byte[] ciphertextbyte = memStrm.ToArray();
memStrm.Close();
//Encode the ciphertext byte into Unicode string
string ciphertext = new UnicodeEncoding().GetString(ciphertextbyte);
return XmlConvert.EncodeName(ciphertext)}

18. We will modify the custDecrypt method so that it decrypts the passed message.

// for decryption
public string custDecrypt(string message)
{
message = XmlConvert.DecodeName(message);
//Create a memory stream from which CryptoStream will read the cipher text
MemoryStream memStrm = new MemoryStream(new UnicodeEncoding().GetBytes
message));
//Create a CryptoStream in Read Mode; initialise with the Rijndael's Decryptor
CryptoTransform
CryptoStream crystm = new CryptoStream(memStrm, des.CreateDecryptor
key,IV),CryptoStreamMode.Read);
//Create a temporary memory stream to which we will copy the
//plaintext byte array from CryptoStream
MemoryStream plaintextmem = new MemoryStream();
do
{
//Create a byte array into which we will read the plaintext
//from CryptoStream
Byte[] buf = new Byte[100];
//read the plaintext from CryptoStream
int actualbytesread = crystm.Read(buf,0,100);
//if we have reached the end of stream quit the loop
if (0 == actualbytesread)
break;
//copy the plaintext byte array to MemoryStream
plaintextmem.Write(buf,0,actualbytesread);
}
while(true);
//don't forget to close the streams
crystm.Close();
memStrm.Close();
//Extract the plaintext byte stream and close the MemoryStream
Byte[] plaintextbyte = plaintextmem.ToArray();
plaintextmem.Close();
//Encode the plaintext byte into Unicode string
string plaintext = new UnicodeEncoding().GetString(plaintextbyte);
return plaintext;
}

19. Save the file and compile the project.

To create the web service project: -

1. To the solution add a new C# project of type web service name it AuthneticateUser.
2. To this project add an asmx file call it AuthenticateService.asmx.
3. At the top of the file add the following lines of code

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;

4. Now we will add a class for the SOAP header within the AuthenticateService.asmx file

public class AuthenticateHeader : System.Web.Services.Protocols.SoapHeader
{
public string siteID;
public string sitePwd;
}

5. Now we will add a declaration for the SOAP header within the existing web services class as below

public class AuthenticateService : System.Web.Services.WebService
{
public AuthenticateHeader authHeader;
}

6. After this we will add the authenticate method. This will be added in the AuthenticateService.asmx file.

WebMethod]
[SoapHeader("authHeader", Direction=SoapHeaderDirection.In)]

public string Authenticate(string UserID, string Password)
{
return authHeader.siteID + " " + authHeader.sitePwd + " " + UserID + " " +
password;
}

This method doesnt do much to authenticate a user. However our own custom code can be added.

7. Now we need to add a reference to the WebServiceHeaderExtension project.

8. Now the Web.config file of the web service has to be edited. Within the

<System.Web> tag add the following code

<system.web> tag
<webServices>
soapExtensionTypes>
<add type="WebServiceHeaderExtension.WebServcSoapExt,
WebServiceHeaderExtension"
priority="1" group="0" />
</
soapExtensionTypes>
</
webServices>

9. Compile the project.

To create the web project: -

1. To the solution add a new C# ASP.NET project, name it UserWebSite.

2. To this project add an aspx file or use the default WebForm1.aspx page.

3. To this file add two text boxes one for the user id and other for the pass word.

4. Add a button to this page.

5. Add 3 labels to the page. Label them as User ID, Password and leave the third as blank. Make sure
the Label1  is the one that is blank. As this is where we are going to write the web service response.

6. Now we need to add reference to the web service and the SOAP extension. For adding reference to the web service we add a web reference. While for the SOAP extension we can add a project reference to the WebServiceHeaderExtension project.

7. Once that is done. We will double click the button on the design page and within the event handler we will add the following code.

// create an instance of the web service
localhost.AuthenticateService auth = new localhost.AuthenticateService();
// create an instance of the soap header
localhost.AuthenticateHeader authHeader = new localhost.AuthenticateHeader();
// add the site's ID to the Soap header
authHeader.siteID="MySite";
// add the site's password to the Soap header
authHeader.sitePwd = "MySitePwd";
// attach the soap header to the soap message
auth.AuthenticateHeaderValue = authHeader;
// call the authenticate method in the web service
Label1.Text = auth.Authenticate(TextBox1.Text, TextBox2.Text);

8. We are done with the aspx file. Now we need to modify the Web.config file so that it calls the SOAP extension. We will add the following line of codes within the

<system.web> tag
<webServices>
soapExtensionTypes>
<add type="WebServiceHeaderExtension.WebServcSoapExt,
WebServiceHeaderExtension"
priority="1" group="0" />
</
soapExtensionTypes>
</
webServices>

9. Compile the project.

Now we are all set to test the application. Set the web project as the startup project and execute it. You should see the result in the label in your web page.

Up Next
    Ebook Download
    View all
    Learn
    View all