Exchanging Data Using SOAP Headers


One final topic to look at in this chapter is using SOAP headers to exchange information, rather than including information in method parameters. The reason for covering this is that it is a very nice system to use for maintaining a user login. This section won’t go into detail about setting up your server for SSL connections, or the various methods of authentication that can be configured using IIS, because these do not affect the Web service code you need to get this behavior.

Say that you have a service that contains a simple authentication method with a signature as follows:

  AuthenticationToken AuthenticateUser(string userName, string password); 

where AuthenticationToken is a type you define that can be used by the user in later method calls, for example:

  void DoSomething(AuthenticationToken token, OtherParamType param); 

After logging in, the user has access to other methods using the token received from AuthenticateUser(). This technique is typical of secure Web systems, although it is often implemented in a far more complex way.

You can simplify this process further by using a SOAP header to exchange tokens (or any other data). You can restrict methods so that they can only be called if a specified SOAP header is included in the method call. This simplifies their structure as follows:

  void DoSomething(OtherParamType param); 

The advantage here is that, after you have set the header on the client, it persists. After an initial bit of setting up, you can ignore authentication tokens in all further Web method calls.

To see this in action, create a new Web service project called PCSWebService3 in the directory C:\ProCSharp\Chapter36\, and add a new class to the App_Code directory called AuthenticationToken, as follows:

  using System; using System.Web.Services.Protocols; public class AuthenticationToken : SoapHeader {    public Guid InnerToken; } 

You’ll use a GUID to identify the token, a common procedure, because you can be sure that it is unique.

To declare that the Web service can have a custom SOAP header, you simply add a public member to the service class of your new type:

 public class Service : System.Web.Services.WebService {    public AuthenticationToken AuthenticationTokenHeader; 

You will also need to use the System.Web.Services.Protocols.SoapHeaderAttribute attribute to mark those Web methods that require the extra SOAP header in order work. However, before you add such a method, you can add a very simple Login() method that clients can use to obtain an authentication token:

  [WebMethod(true)] public Guid Login(string userName, string password) {    if ((userName == "Karli") && (password == "Cheese"))    {       Guid currentUser = Guid.NewGuid();       Session["currentUser"] = currentUser;       return currentUser;    }    else    {       Session["currentUser"] = null;       return Guid.Empty;    } } 

If the correct username and password are used, then a new Guid object is generated, stored in a session-level variable, and returned to the user. If authentication fails, an empty Guid instance is returned and stored at the session level. The true parameter enables session state for this Web method, because it is disabled by default in Web services and it is required for this functionality.

Next, you have a method that accepts the header, as specified by the SoapHeaderAttribute attribute:

  [WebMethod(true)] [SoapHeaderAttribute("AuthenticationTokenHeader",                       Direction = SoapHeaderDirection.In)] public string DoSomething() {    if (Session["currentUser"] != null &&        AuthenticationTokenHeader != null &&        AuthenticationTokenHeader.InnerToken        == (Guid)Session["currentUser"])    {       return "Authentication OK.";    }    else    {       return "Authentication failed.";    } } 

This returns one of two strings, depending on whether the AuthenticationTokenHeader header exists, isn’t an empty Guid, and matches the one stored in Session[“currentUser”] (if this Session variable exists).

Next, you must create a quick client to test this service. Add a new Web site called PCSWebClient2 to the solution, with the following simple code for the user interface:

 <form  runat="server">   <div>     User Name:     <asp:TextBox Runat="server"  /><br />     Password:     <asp:TextBox Runat="server"  /><br />     <asp:Button Runat="server"  Text="Log in" /><br />     <asp:Label Runat="server"  /><br />     <asp:Button Runat="server"        Text="Invoke DoSomething()" /><br />     <asp:Label Runat="server"  /><br />   </div> </form>

Add the PCSWebService3 service as a Web reference (because the Web service is local to the solution you can click on the Web Services in This Solution link to get a reference quickly) with the name authenticateService, and add the following using statements to Default.aspx.cs:

  using System.Net; using authenticateService; 

You need to use the System.Net namespace because it includes the CookieContainer class. This is used to store a reference to a cookie, and you require this if you are working with Web services that use session state. This is because you need some way for the Web service to retrieve the correct session state across multiple calls from the client, where the proxy for the service is recreated on each postback. By retrieving the cookie used by the Web service to store session state, storing it between Web service calls, and then using it in later calls, you can maintain the correct session state in the Web service. Without doing this, the Web service would lose its session state, and therefore the login information required in this scenario.

Back to the code, where you use a protected member to store the Web reference proxy, and another to store a Boolean value indicating whether the user is authenticated or not:

 public partial class _Default : System.Web.UI.Page {    protected Service myService;    protected bool authenticated; 

Page_Load() starts by initializing the myService service, as well as preparing a CookieContainer instance for use with the service:

 protected void Page_Load(object sender, EventArgs e) {    myService = new Service();    myService.Credentials = CredentialCache.DefaultCredentials;    CookieContainer serviceCookie; 

Next, you check for a stored CookieContainer instance or create a new one. Either way you assign the CookieContainer to the Web service proxy, ready to receive the cookie information from the Web service after a call is made. The storage used here is the ViewState collection of the form (a useful way to persist information between postbacks, which works in a similar way to storing information at the application or session level):

  if (ViewState["serviceCookie"] == null) {    serviceCookie = new CookieContainer(); } else {    serviceCookie = (CookieContainer)ViewState["serviceCookie"]; } myService.CookieContainer = serviceCookie; 

Page_Load() then looks to see if there is a stored header and assigns the header to the proxy accordingly (assigning the header in this way is the only step you must take for the data to be sent as a SOAP header). This way any event handlers that are being called (such as the one for the Web method–invoking button) don’t have to assign a header - that step has already been taken:

    AuthenticationToken header = new AuthenticationToken();    if (ViewState["AuthenticationTokenHeader"] != null)    {       header.InnerToken = (Guid)ViewState["AuthenticationTokenHeader"];    }    else    {       header.InnerToken = Guid.Empty;    }    myService.AuthenticationTokenValue = header; }

Next, you add an event handler for the Log in button by double-clicking it in the Designer:

 protected void loginButton_Click(object sender, EventArgs e) {    Guid authenticationTokenHeader = myService.Login(userNameBox.Text,                                                     passwordBox.Text);    tokenLabel.Text = authenticationTokenHeader.ToString();    if (ViewState["AuthenticationTokenHeader"] != null)    {       ViewState.Remove("AuthenticationTokenHeader");    }    ViewState.Add("AuthenticationTokenHeader", authenticationTokenHeader);    if (ViewState["serviceCookie"] != null)    {       ViewState.Remove("serviceCookie");    }    ViewState.Add("serviceCookie", myService.CookieContainer); }

This handler uses any data entered in the two text boxes to call Login(), displays the Guid returned, and stores the Guid in the ViewState collection. It also updates the CookieContainer stored in the ViewState collection, ready for reuse.

Finally, you have to add a handler in the same way for the Invoke DoSomething() button:

 protected void invokeButton_Click(object sender, EventArgs e) {    resultLabel.Text = myService.DoSomething();    if (ViewState["serviceCookie"] != null)    {       ViewState.Remove("serviceCookie");    }    ViewState.Add("serviceCookie", myService.CookieContainer); }

This handler simply outputs the text returned by DoSomething() and updates the CookieContainer storage just like loginButton_Click().

When you run this application, you can click the Invoke DoSomething() button straight away, because Page_Load() has assigned a header for you to use (if no header is assigned, an exception will be thrown because you have specified that the header is required for this method). This results in a failure message, returned from DoSomething(), as shown in Figure 36-4.

image from book
Figure 36-4

If you try to log in with any user name and password except “Karli” and “Cheese” you will get the same result. If, on the other hand, you log in using these credentials and then call DoSomething(), you get the success message, as shown in Figure 36-5.

image from book
Figure 36-5

You can also see a string representation of the Guid used for validation.

Of course, applications that use this technique of exchanging data via SOAP headers are likely to be far more complicated. You may decide to store login tokens in a more scalable way than just using session storage, perhaps in a database. For completeness you can also implement your own expiration scheme for these tokens when a certain amount of time has passed and provide the option for users to log out, which would simply mean removing the token. Session state does expire after a certain amount of time (20 minutes by default), but more complicated and powerful schemes are possible. You could even validate the token against the IP address used by the user for further security. The key points here though are that the username and password of the user are only sent once, and that using a SOAP header simplifies later method calls.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net