views:

758

answers:

3

Here's the situation. I have a webservice (C# 2.0), which consists of (mainly) a class inheriting from System.Web.Services.WebService. It contains a few methods, which all need to call a method that checks if they're authorized or not.

Basically something like this (pardon the architecture, this is purely as an example ;)):

public class ProductService : WebService
{
    public AuthHeader AuthenticationHeader;

    [WebMethod(Description="Returns true")]
    [SoapHeader("AuthenticationHeader")]        
    public bool MethodWhichReturnsTrue()
    {
        if(Validate(AuthenticationHeader))
            throw new SecurityException("Access Denied");
        return true;
    }

    [WebMethod(Description="Returns false")]
    [SoapHeader("AuthenticationHeader")]        
    public bool MethodWhichReturnsFalse()
    {
        if(Validate(AuthenticationHeader))
            throw new SecurityException("Access Denied");
        return false;
    }

    private bool Validate(AuthHeader authHeader)
    {
        return authHeader.Username == "gooduser" && authHeader.Password == "goodpassword";
    }
}

As you can see, the method 'Validate' has to be called in each method. I'm looking for a way to be able to call that method, while still being able to access the soap headers in a sane way. I've looked at the events in the global.asax, but I don't think I can access the headers in that class... Can I?

+1  A: 

You can implement the so-called SOAP extension by deriving from SoapExtension base class. That way you will be able to inspect an incoming SOAP message and perform validate logic before a particular web method is called.

Greg
+4  A: 

Here is what you need to do to get this to work correctly.

It is possible to create your own custom SoapHeader:

public class ServiceAuthHeader : SoapHeader
{
    public string SiteKey;
    public string Password;

    public ServiceAuthHeader() {}
}

Then you need a SoapExtensionAttribute:

public class AuthenticationSoapExtensionAttribute : SoapExtensionAttribute
{
    private int priority;

    public AuthenticationSoapExtensionAttribute()
    {
    }

    public override Type ExtensionType
    {
        get
        {
            return typeof(AuthenticationSoapExtension);
        }
    }

    public override int Priority
    {
        get
        {
            return priority;
        }
        set
        {
            priority = value;
        }
    }
}

And a custom SoapExtension:

public class AuthenticationSoapExtension : SoapExtension
{
    private ServiceAuthHeader authHeader;

    public AuthenticationSoapExtension()
    {
    }

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

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }

    public override void Initialize(object initializer)
    {        
    }

    public override void ProcessMessage(SoapMessage message)
    {
        if (message.Stage == SoapMessageStage.AfterDeserialize)
        {
            foreach (SoapHeader header in message.Headers)
            {
                if (header is ServiceAuthHeader)
                {
                    authHeader = (ServiceAuthHeader)header;

                    if(authHeader.Password == TheCorrectUserPassword)
                    {
                        return;  //confirmed
                    }
                }
            }

            throw new SoapException("Unauthorized", SoapException.ClientFaultCode);
        }
    }
}

Then, in your web service add the following header to your method:

public ServiceAuthHeader AuthenticationSoapHeader;

[WebMethod]
[SoapHeader("AuthenticationSoapHeader")]
[AuthenticationSoapExtension]
public string GetSomeStuffFromTheCloud(string IdOfWhatYouWant)
{
  return WhatYouWant;
}

When you consume this service, you must instantiate the custom header with the correct values and attach it to the request:

private ServiceAuthHeader header;
private PublicService ps;

header = new ServiceAuthHeader();
header.SiteKey = "Thekey";
header.Password = "Thepassword";
ps.ServiceAuthHeaderValue = header;

string WhatYouWant = ps.GetSomeStuffFromTheCloud(SomeId);
Timothy Lee Russell
Not as straightforward as I assumed it to be. Going to try this now.
Erik van Brakel
It works! Keep in mind that the Visual Studio webbased access doesn't fire the extension, as it doesn't use SOAP to make the request. Got me scratching my head for a few minutes.
Erik van Brakel
+1  A: 

I'd take a look at adding a security aspect to the methods you're looking to secure. Take a look at PostSharp, and in particular the OnMethodBoundryAspect type, and OnEntry method.

Sean Campbell