Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

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:

  1. AfterDeserialize
  2. AfterSerialize
  3. BeforeDeserialize
  4. 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. 

Like this article?  Then kick it on DotNetKicks.com

posted @ Tuesday, January 09, 2007 7:21 PM

Print

Comments on this entry:

# Securing Web Services With a Custom SoapExtension

Left by DotNetKicks.com at 1/9/2007 9:06 PM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# Securing Web Services With a Custom SoapExtension

Left by SecurityKicks.com at 1/9/2007 9:17 PM

You've been kicked (a good thing) - Trackback from SecurityKicks.com

 re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Hogne Fossland at 1/30/2007 6:39 AM
Gravatar

First, thanks for a great article. It has certainly given me some ideas.

After your previous article you said:

"Not only does doing a SoapExtension provide automatic authentication, but it could also allow you to hook into a config file or database to validate each method differently dependent upon permissions."

Now, I'd really be interested in something that would let me distinguish between different user groups, so that e.g. 'admin' has access to all functions, while 'average-joe' is limited to a certain subset, i.e.:

[PermissionsAttribute("admin")]
void SetValue(int value);

[PermissionsAttribute("all")]
int GetValue();

Any idea how to achieve this?

# re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Keith Elder at 1/30/2007 5:05 PM
Gravatar

Sure, it isn't that bad really just takes a bit of wiring up. For starters look at the last sample above starting at line 48. This method takes the SoapMessage as a parmeter.

From the SoapMessage you have a property called SoapMessage.MessageName. Instead of attributing the methods as you did like this:

[PermissionsAttribute("admin")]
void SetValue(int value);

I would simply use this property to determine the Method the user is requesting, and then do a lookup in the database to map their permissions. I'll try to write up an example on how I would do this and get it posted, it is a good follow up article. Stay tuned!

 re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Hogne Fossland at 1/31/2007 7:18 AM
Gravatar

Thanks for your reply. Looking forward to reading your follow-up article. Hope it'll be available soon...!

 re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by quynh at 7/11/2007 1:13 AM
Gravatar

SOAP extension is never going to fire.

I decided to deploy it on real server for test SOAP extension. Running the client from my computer and it still pass the authentication with wrong password.

Please help me!

# re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Keith Elder at 7/11/2007 7:50 AM
Gravatar

Your problem is probably simple. When you debug locally in VS, and you click on the submit form, you are in fact not doing a true Soap call, rather you are doing a straight post which does in fact by pass the soap authentication. Test it with code instead of the browser.

 re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Bryce G. at 9/26/2007 8:23 PM
Gravatar

If you really wanted to pass some type of access level for the method into the attribute, you could do something like this:

[MySecuritySoapExtension(AccessLevel=MyAccessLevel.Administrator)]

By doing something like this in your SoapExtension class:

private MyAccessLevel mAccessLevel = MyAccessLevel.User;

public override object GetInitializer(Type serviceType)
{
return mAccessLevel;
}

public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return (MyAccessLevel)((AuthenticatonSoapExtensionAttribute)attribute).AccessLevel;
}

public override void Initialize(object initializer)
{
mAccessLevel = (MyAccessLevel)initializer;
}


And in your SoapExtensionAttribute class add a public property like so:
private MyAccessLevel mAccessLevel = MyAccessLevel.RegisteredUser;

public MyAccessLevel AccessLevel
{
get { return mAccessLevel; }
set { mAccessLevel = value; }
}

MyAccessLevel being an enum with all the levels you desire:
public enum MyAccessLevel
{
Administrator,
User
}

Then in your ProcessMessage you could pass in the access level as a parameter to do your validation during Authentication or whatever you like.

 re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Arul at 2/6/2008 2:11 AM
Gravatar

Hi, I am trying to use soapextension to intercept incoming request and append with Action header. Currently java client not sending the action along with the request so due to that getting an error "Action for Ultimate recipient is required not present". So using soap extension to add that along with request but soap extension is not executed at all when i invoked the web service through XML Spy and other soap tools. I am not sure why.

 re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Ghanshyam at 2/6/2008 2:47 AM
Gravatar

Hi, Is it possible to send in the credentials from the clientside by attaching them to the WebService call by SoapExtension? I have tried inserting header dynamically in BeforeSerialize stage in ProcessMessage, but the header shows up as SoapUnknownHeader on the WebService side in the AfterDeserialize stage of ProcessMessage. What I mean is that caller's of the webservices need not attach the authheader value everytime themselves, rather the SoapExtension do it. Another thing, is it possible to send in also some other dynamic data like which is dependent on the current session using SoapExtension class. I could not find anything apart from using CurrentPrincipal. Please advise.

# re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Keith Elder at 2/6/2008 7:37 AM
Gravatar

@Arul

By you stating that Java is not sending the action header that tells me you are using Soap 1.1 protocol. I would highly suggest that you use Soap 1.2 protocol with the service and disable the 1.1 version. I have found this to be much easier for a vast majority of clients to work with than 1.1 since 1.1 requires the clients to send an action header.

I think that may help you. Hope that helps.

# re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by Keith Elder at 2/6/2008 7:43 AM
Gravatar

@Ghanshyam

Clarification real quick. You can attach credentials from the client side by adding them to the SoapHeader object in the client. The web service processes the soap header via the SoapExtension.

I wanted to clarify your first question because it didn't make any sense to me the way it was worded.

In the client you would have code like this for your service:

MyService proxy = new MyService();
MyService.AuthHeader header = new MyService.AuthHeader();
header.Username = "foo";
header.Password = "bar";
proxy.AuthHeader = header;

proxy.CallSomeMethod();

This is what I mean by in the client you attach the credentials from the client side.

Hope that helps.

-Keith

 re: Take Securing Web Services With Username and Password One Step Further With a Custom SoapExtension

Left by KS Prabu at 2/21/2008 3:40 AM
Gravatar

Thank you so much for Nice concepts.

Your comment:






 
 
 
Please add 8 and 1 and type the answer here:
 

Live Comment Preview:

 
«July»
SunMonTueWedThuFriSat
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789