Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
Delphi Prism Web Services and SOAP Security
In this article, I'll demonstrate how to use SOAP Headers as security technique for ASP.NET Web Service projects using Delphi Prism (extending the example from last month by adding a security layer to it).
SOAP Headers
In order to work with SOAP Headers in an ASP.NET Web Service, we have to add System.Web.Services.Protocols to the uses clause, and define a new class derived from the SoapHeader class found in the aforementioned namespace.The new class can be called anything, so I've decided to call it TCredentials (I know that in the .NET Framework you're not supposed to prefix everything with a "T", but since I'm a proud Delphi developer, I always use a T prefix for my types).
It must be a public class, since we need to export it so any client importing our ASP.NET web service can also know about it.
Adding a Username and Password field to the TCredentials class, this could lead to the following definition.
type TCredentials = public class(System.Web.Services.Protocols.SoapHeader) public Username: String; Password: String; end;
With this class definition in mind, we can add a public field (for example called token) to the public class Service, as well as a private method (for example called CheckCredentials) to check if the credentials have been received and contain the correct username/password information.This leads to the following modification of the Service class:
Service = public class(System.Web.Services.WebService) public var token: TCredentials; private method CheckCredentials: Boolean;
In order to specify that the token has to be used for certain web method (from the original set), we have to add the attribute SoapHeader to the web method, specifying the token as well as the direction in which the token is assigned (in our case only on input, so only the web service client will be writing information in the token, and the Service web service "engine" can check the token for valid credentials by looking at its value).
Using the example from last month, we can modify some of the web methods as follows:
public
[WebMethod(EnableSession := true)]
[SoapHeader('token', Direction := SoapHeaderDirection.&In)]
method Remember(const Name,Value: String);
[WebMethod(EnableSession := true)]
[SoapHeader('token', Direction := SoapHeaderDirection.&In)]
method Recall(const Name: String): String;
[WebMethod(EnableSession := true)]
method Forget(const Name: String);
[WebMethod(EnableSession := true)]
method Amnesia; // forget all
end;
Note that I only added the SoapHeader attribute to the Remember and Recall web methods, and not to the Forget or Amnesia methods.
In other words: you are free to forget, but in order to remember or recall something, you need security clearance.
Inside that Remember and Recall methods, we will have to call the CheckCredentials method before doing their actual work, in order to verify the value of the token.
Which leads us to the implementation of CheckCredentials, which is shown below (using a hard-coded check for the values of the Username and Password properties of the token - you should obviously implement a stronger check here!).
Note that apart from returning False is the Username and Password are incorrect, we can also raise an exception, for example if the token is not found in the SOAP header (which might happen if the web service is consumed and used by web service client applications who do not add the token to the SOAP header in the first place).
method Service.CheckCredentials: Boolean; begin Result := False; if not Assigned(token) then raise ApplicationException.Create('No token in SOAP Header!'); if (token.Username.ToLower = 'bob') and (token.Password.ToLower = 'swart') then Result := True end;
The implementation of Remember and Recall can simple be extended by calling the CheckCredentials method before doing anything:
method Service.Remember(const Name,Value: String); begin if CheckCredentials then Session[Name] := Value end; method Service.Recall(const Name: String): String; begin if CheckCredentials then if Assigned(Session[Name]) then Result := Session[Name].ToString else Result := '' end;
Note that Forget and Amnesia remain unchanged, since you are always allowed to forget:
method Service.Forget(const Name: String); begin Session.Remove(Name) // always allowed end; method Service.Amnesia; begin Session.Abandon // always allowed end;
Before deploying and consuming this new secure web service, there's one thing to keep in mind about using SOAP Headers: the contents are sent in plain text over the network.
So passing a username and password inside a SOAP Header is adding authorization capabilities, but still lacks a little security.
For that reason, I always recommend to use SOAP Headers in combination with HTTPS and SSL.
That way, the traffic between the web service and client is encrypted, and nobody else should be able to decypher the information sent by the client to the server (since it's encrypted with the public key from the server, and can only be decrypted by the private key of the server, so anyone sniffing the network only gets an encrypted package).
Implementing HTTPS and SSL requires two things: first, you should purchase a certificate and install it on your web server (or ask your provider if a certificate is already available for use by your web service), and second, all URLs that are used to communicate with the web service should use https:// and not http://.
For this example, I give you the option of exploring the difference, so I've deployed the secure web service on two different web servers: one with HTTPS and one without.
On one server, you can access the web service via http://www.bobswart.net/MyWebService/Service.asmx and on the other (secure) web server you have to use HTTPS and the URL https://www.bobswart.nl/MyWebService/Service.asmx instead.
Both will work the same (the MyWebService.dll code behind assembly is exactly the same on both machines), but one connection is more secure than the other, which is important when using SOAP Headers.
SOAP Header Client
With the new secure web service deployed (in two locations), it's time to import and consume it.Again, feel free to play along, as I'm just extending the client from last month again.
First of all, we must update the import unit.This can be done by right-clicking on the localhost node under the Web References node:
Alternately (or if this doesn't update the reference.pas), you can just remove the web references node from the project.This is a better solution anyway, since I now want to import from http://www.bobswart.net (or the more secure https://www.bobswart.nl), which can be done by adding a new web reference (right-click on References and select "Add Web Reference"):
Click on Add Reference, and once the web references has been created edit Main.pas again and add the WindowsClientApplication.nl.bobswart.www unit to the uses clause (or WindowsClientApplication.net.bobswart.www if you import from www.bobswart.net instead).
Now, if you only re-import the web service engine, without actually modifying the client code (more details in a minute), you will get an error when trying to call the Remember or Recall methods (since no SOAP Header with correct credentials was passed).
The error is shown as an exception dialog, as follows:
The original client project creates the instance of the Service proxy class inside the MainForm constructor, and assigns a value to the CookieContainer property (for the session cookie, as explained last time).
This time, we must add three more lines, assigning a value to the TCredentialsValue property (a new property, generated as a result of the new import of the secure web service - if you don't see this property, then you should ensure that the import of the new web service has succeeded).The TCredentialsValue needs to be created, and we need to set the values for the Username and Password properties.In the code below, I assign some hardcoded values to these properties, and I leave it as exercise for the reader to change this to a dyanmic way, reading the username/password from a configuration file or obtaining these values from a dialog where the user can enter them.The idea remains that without these credential values, you cannot call Remember or Recall, and with the correct TCredentialsValue, you can call them.
constructor MainForm; begin
//
// Required for Windows Form Designer support
//
InitializeComponent();
WS := new Service();
//WS.Url := 'http://www.bobswart.net/MyWebService/Service.asmx';
WS.Url := 'https://www.bobswart.nl/MyWebService/Service.asmx';
WS.CookieContainer := new CookieContainer();
WS.TCredentialsValue := new TCredentials();
WS.TCredentialsValue.Username := 'Bob';
WS.TCredentialsValue.Password := 'Swart';
end;
The resulting client application will now be able to call the secured web methods Remember and Recall.
And remember that the SOAP request and responses will be encrypted for extra security if you use the HTTPS protocol.
The result is the working client again:
Summary
In this article, I've demonstrated how to use SOAP Headers as security technique for ASP.NET Web Service projects using Delphi Prism (extending the example from last month by adding a security layer to it).
Feel free to create and deploy your own ASP.NET Web Services with Delphi Prism, or consume my test secure Web Services at my website to send messages back and forth (you know the built-in password now).
For more information on SOAP and Web Services with Delphi, you can check out my Delphi 2006 XML, SOAP and Web Services on Lulu.com, or my RAD Studio 2007 XML, SOAP and Web Services courseware manual in PDF format (with free updates and e-mail support), or wait for the Delphi Prism Development Essentials manual to become available which will also include ASP.NET Web Service coverage.