Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension
Posted by Keith Elder | Posted in Asp.Net, Programming | Posted on 09-01-2007
22
In a previous posts I showed how to secure your web services with a username and a password. One of the readers added a comment and mentioned using an extension to provide authentication so I thought I would expand on how that is done. If you haven’t read the previous posts do that first, then pickup with this. And before we get started, thanks for the idea Kevin!
Have you ever wanted to intercept a web service method because you wanted to maybe log it or even as we are about to do authenticate a user? Did you know you can intercept any incoming SoapMessage that is sent to your web service? This is all possible because of the SoapExtension class in .Net. Not only can you intercept the incoming message but you can do it within one of four stages:
- AfterDeserialize
- AfterSerialize
- BeforeDeserialize
- BeforeSerialize
This allows us a lot of flexibility obviously. In our example we are going to work with the AfterDeserialize stage which is after the message has been sent across the wire and serialized into a SoapMessage object. Since we will have a full blown SoapMessage object, we can inspect the headers of the SoapMessage and take care of authentication then. Our end goal with taking this approach is to allow us to authenticate a user with a WebMethod simply by adding an authentication attribute to the WebMethod like this highlighted in bold.
1 [WebMethod]
2 [SoapHeader(“CustomSoapHeader”)]
3 [AuthenticatonSoapExtensionAttribute] (magic here)
4 public int AddTwoNumbers(int x, int y)
5 {
6 return x + y;
7 }
You’ll notice in this example we do not have to remember to call the code to authenticate the user manually as we saw in the previous article. Instead by adding the attribute to the method it knows to call the authentication method. To do this we need to first create a custom object that extends SoapExtensionAttribute and then another that extends SoapExtension.
Create a Custom SoapExtensionAttribute
In order to have the method call our custom SoapExtension we need to create an object that extends SoapExtensionAttribute. It is a farily simple class with two overridden properties. Here’s the code:
1 [AttributeUsage(AttributeTargets.Method)]
2 public class AuthenticatonSoapExtensionAttribute : SoapExtensionAttribute
3 {
4 private int _priority;
5
6 public override int Priority
7 {
8 get { return _priority; }
9 set { _priority = value; }
10 }
11
12 public override Type ExtensionType
13 {
14 get { return typeof(AuthenticatonSoapExtension); }
15 }
16 }
You’ll notice about the only thing of real substance is the Extension type property which simply returns to us our custom extension.
Create a Custom SoapExtension
The last piece to pull this all together is a custom class which extends the SoapExtension class. In this class we are going to write the code that does the actual authentication. We are going to check for the AfterDeserialize stage and then first make sure we have a valid SoapHeader. Once we do that we are going to call the static validation method and pass in the SoapHeader as we did above.
1 /// <summary>
2 /// Custom SoapExtension that authenticates the method being called.
3 /// </summary>
4 public class AuthenticatonSoapExtension : SoapExtension
5 {
6
7 /// <summary>
8 /// When overridden in a derived class, allows a SOAP extension to initialize data specific to an XML Web service method using an attribute applied to the XML Web service method at a one time performance cost.
9 /// </summary>
10 /// <param name=”methodInfo”></param>
11 /// <param name=”attrib”></param>
12 public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attrib)
13 {
14 return null;
15 }
16
17 /// <summary>
18 /// When overridden in a derived class, allows a SOAP extension to initialize data specific to a class implementing an XML Web service at a one time performance cost.
19 /// </summary>
20 /// <param name=”WebServiceType”></param>
21 public override object GetInitializer(Type WebServiceType)
22 {
23 return null;
24 }
25
26 /// <summary>
27 /// When overridden in a derived class, allows a SOAP extension to initialize itself using the data cached in the GetInitializer method.
28 /// </summary>
29 /// <param name=”initializer”></param>
30 public override void Initialize(object initializer)
31 {
32
33 }
34
35 /// <summary>
36 /// After the message is deserialized we authenticate it.
37 /// </summary>
38 /// <param name=”message”></param>
39 public override void ProcessMessage(SoapMessage message)
40 {
41
42 if (message.Stage == SoapMessageStage.AfterDeserialize)
43 {
44 Authenticate(message);
45 }
46 }
47
48 public void Authenticate(SoapMessage message)
49 {
50 ServiceAuthHeader header = message.Headers[0] as ServiceAuthHeader;
51 if (header != null)
52 {
53 ServiceAuthHeaderValidation.Validate(header);
54 }
55 else
56 {
57 throw new ArgumentNullException(“No ServiceAuthHeader was specified in SoapMessage.”);
58 }
59 }
60 }
The method that we are really concerned with is the ProcessMessage which checks for the stage and then calls the Authenticate method. This in turn calls our static validation method which checks for authentication. At this point light bulbs should be going off! Since we have a SoapMessage object do we not know which method is being called? Yes! Could we modify the ServiceAuthHeaderValidation to check for a database instead of hard coding things? Yes! Now you are starting to see where this could really go. SoapExtensions are powerful and only limited to your imagination.
When I Test It, It Doesn’t Work, Why?
Once you get your SoapExtension in your solution setup and press F5 to debug it within Visual Studio it will launch a new web server on a random port and bring you to your service. You enter the parameters and submit the form and it by passes your validation. Why!?
This is suppose to happen and here is why. If you go to the service invoked from VS through the browser interface it will not invoke the authentication and it isn’t suppose to either. The reason is you are not invoking the service via SOAP but rather just a standard POST from a form. Therefore, the SOAP extension is never going to fire. This should be disabled when you publish your web service to production as only SOAP messages should be allowed. If you have a case where you need to allow GET and POST calls then the method of a custom SoapExtension isn’t going to work. Go back to the previous way outlined that I talked about.
As a benefit, Visual Studio builds the form for you automatically when you press F5 and allows you to pass parameters to the web method, but it does it via POST. If you invoke the web method from a console application or a real client making a SOAP call, you have to pass in the username and password. I actually consider this behavior a feature. If we didn’t use the SoapExtension to secure the method, we’d be forced to pass in username and password all the time which would mean we’d have to always call the secured web method from a test client. Speaking from experience this isn’t fun. Of course you should have Unit Tests for each web method anyway but it is really easy to pass in the params to a web form while debugging.
I hope you find this useful and now don’t feel so daunted because your team leader asked you how you were going to authenticate web service methods via a database. The only thing left is for you to implement your required features. Of course, if you are on an Intranet, instead of using username and password as we did in the previous post you could at the point of Authenticate(SoapMessage message) use the user’s integrated credentials and check for various groups in Active Directory or even using Enterprise Library Security Block.