Sunday, March 05, 2006

I am not an expert in WSE3 by anyone's definition. So when I went looking for a way to add authentication requirements to my web service that tied into the ASP.NET 2.0 Membership Provider model class that I had already written for my web site, I was sorely disappointed when I could not find any articles on that exact topic. One very good MSDN article came close, but just like all the articles I saw, it seemed to focus more on using Active Directory authentication, or heavy message-level encryption, or some other advanced feature I did not need or want.

My primary focus was to write a web service that required authentication while preserving the ability to serve to PHP and Java clients that have not yet been graced by WSE3. I would use SSL for encryption to simplify the process. But since no one covered the topic of what I would do, I finally managed to adapt the article mentioned above to what I was doing and I share what I have learned with you. I still have not managed to get a PHP web service client to call my WSE3 web service though.

It seems like a very common situation to want to have your web services simple to access, yet require authentication through the same mechanism that your ASP.NET Forms Authentication uses. So I hope my findings will help you.

Right-click on your web project, and click WSE Settings 3.0. If this is your first time clicking this, a wizard will start. The rest of these steps assume you've been through the wizard, and that the regular settings box appears. If you get the wizard, try to figure out how to apply these next steps to the wizard, and then come back to the settings box afterward to make sure all is well. Sorry for the confusion here.

Under the General tab, check both the Enable this project for Web Services Enhancements and Enable Microsoft Web Services Enhancement Soap Protocol Factory.

Under the Security tab, in the Security Tokens Managers area, click Add.

Add an section if you would like to require your web service consumers to belong to a specific role or roles. It is very important that you put the tag above your tag. When you're done, it may look something like this:

As a reminder, I am assuming you already have ASP.NET 2.0 Membership and Roles providers set up in your web site and included in your Web.config file. If not, you might as well stop here and get that working first. And an explanation of how to do so is beyond the scope of this particular blog.

Make sure that the following segments appear in your Web.config file. Pay attention to detail. I'm pretty sure one of the problems I struggled with was that just one of these lines were not automatically put in by the WSE Settings dialog, and this only works if it's all there.

Now open up each of your web service classes (either your .asmx files or associated code-behind files) and add the following class attribute to your web service class:

[Microsoft.Web.Services3.Policy("usernameTokenSecurity")]

You are now done configuring you web service to authenticate each web service request via a WSE 3 SOAP header, relying only on encryption provided by your transport (HTTPS, for example).

Now to configure a client to test your new web service's authentication feature:

Configure your client project for WSE.

Right-click on your client project in Visual Studio and click WSE Settings 3.0. If this is your first time clicking this, a wizard will start. The rest of these steps assume you've been through the wizard, and that the regular settings box appears. If you get the wizard, try to figure out how to apply these next steps to the wizard, and then come back to the settings box afterward to make sure all is well. Sorry for the confusion here.

Under the General tab, check "Enable this project for Web Service Enhancements".

Under the Policy tab, check "Enable Policy" and add a policy called "usernameTokenSecurity".

The wse3policyCache.config file should look like this:

Add a couple of using statements to the file that you will be scripting your client in:

using Microsoft.Web.Services3.Security;using Microsoft.Web.Services3.Security.Tokens;

And if your WSE-enabled web service was called RIRegistration, you would use the following code to call its ValidateLogin method:

RIRegistrationWse wse = new RIRegistrationWse();UsernameToken token = new UsernameToken(username, password, PasswordOption.SendPlainText);wse.SetClientCredential(token);wse.SetPolicy("usernameTokenSecurity");// Now you can call methods repeatedly, and the authentication// is automatically passed to the server each time.Debug.Assert(user.Username, wse.ValidateLogin());

Notice how we call ValidateLogin without passing any parameters, yet it returns the username of the user you are logging in with. Let's see how this ValidateLogin method might be implemented:

[WebMethod(Description = "SOAP header test. If successful, it will return the username of the logged in user.")]publicstring ValidateLogin(){return Microsoft.Web.Services3.RequestSoapContext.Current.IdentityToken.Identity.Name;}

Run your web client. You should get the username you passed to authenticate back from the web method.

Try changing to an invalid credentials. Your ASP.NET Membership provider should reject the login and an exception will be thrown to the client before your web method is ever called.

If you added an section to your wse3policyCache.config file, try changing to credentials that are valid but do not belong to a required role. Verify that your web service likewise rejects the invocation.

I hope this is helpful to you. Feel free to ask questions, if your project is pretty close to the situation I describe. I am still pretty new to this myself, so if your project deviates from this path much, I'm afraid I won't be able to help you.

I followed it as closely as possible, but am having some problems I hope you can respond to.

- Under the client section step 1.4 there is no wse3policyCache.config example code.

- Under the client section step 3 I need to add a web reference to my service but when I went to use the class I had to further quailfy the new statement, ie: RIRegistration.RIRegistrationWse wse = new RIRegistration.RIRegistrationWse()

- Although you do not mention it, I believe I am forced to select X509 certificates on the client and server. When running the client I get a WSE600, WSE593 error stating the server is unable to decrypt the certificate.