Search code examples
c#wcfwcf-securitywcf-authentication

Reading ClientCredentials from SOAP Request body node and verifying it against a Custom Validator?


I have a method GetColors which takes a GetColorIdsRQ as a parameter and returns a GetColorIdsRS. GetColorIdsRQ is the SOAP Request and GetColorIdsRS is the SOAP Resposne. Here are the implementation details of each one:

GetColorIdsRQ:

[DataContract]
public class GetColorIdsRQ
{
    [DataMember(Name="UserCredentials",Order=0,IsRequired=true)]
    public UserCredentials UserCredentials { get; set; }
}

UserCredentials:

[DataContract(Name="UserCredentials")]
public class UserCredentials
{
    [DataMember(Name="Username",Order=0,IsRequired=true)]
    public string UserName { get; set; }
    [DataMember(Name="Password",Order=1,IsRequired=true)]
    public string Password { get; set; }
}

GetColorIdsRS:

[DataContract]
public class GetColorIdsRS
{
    [DataMember(Name = "ColorsIds", IsRequired = true, Order = 0)]
    public List<Color> ColorIds { get; set; }
}

Color:

[DataContract(Name="Color")]
public class Color    
{
   [DataMember(Name="Code",IsRequired=true,Order=0)]
   public string Code { get; set; }
   [DataMember(Name="Name",IsRequired=true,Order=1)]
   public string Name { get; set; }  
}

This is how the method is declaration in the interface:

[OperationContract(Name = "GetColorIds")]
GetColorIdsRS GetColorsIds(GetColorIdsRQ req);

This is the implementation of GetColorIds:

public GetColorIdsRS GetColors(GetColorIdsRQ req)
{
    GetColorIdsRS getColorIdsRS = new GetColorIdsRS();
    List<Color> colorIds = new List<Color>();

    req.UserCredentials.UserName = "test";
    req.UserCredentials.Password = "test";

    //Validate Username and Password

    DataTable dtColorIds = Data.GetDataTable("GetColors");

    foreach (DataRow item in dtColorIds.Rows)
    {
        colorIds.Add(new Color { Name = item["Name"].ToString(), 
                                 Code = item["ColorId"].ToString() });
    }

    getColorIdsRS.ColorIds = colorIds;

    return getColorIdsRS;
}

When I invoke GetColors from the WCF Test client, the Request Body is:

<s:Body>
    <GetColors xmlns="http://test.com/2011/05">
      <req xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
       <UserCredentials>
         <Username>test</Username>
         <Password>test2</Password>
       </UserCredentials>
     </req>
   </GetColors>
 </s:Body>

The problem with the above is that I want to use the Username and Password nodes for the CustomUserNameValidator. I am not sure how to get the CustomUserNameValidator to recoginize GetColorIdsRQ Username and Password nodes so it can validate against that. If you notice above in the GetColors method, I am setting:

req.UserCredentials.UserName = "test";
req.UserCredentials.Password = "test";

but this doesn't get Validated obviously. Here is the implementation of my CustomUserNamePasswordValidator:

public class CustomUserNameValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        // check if the user is not test
        if (userName != "test" || password != "test")
            throw new FaultException("Username and Password Failed");
    }
}

So essentially if I pass:

req.UserCredentials.UserName = "test";
req.UserCredentials.Password = "test2";

The CustomUserNameValidator should throw the FaultException.

Notice also in the GetColors method, I have a comment "//Validate Username and Password", I know I can do:

CustomUserNameValidator val = new CustomUserNameValidator();
val.Validate(req.UserCredentials.UserName,req.UserCredentials.Password)

and the above would call the Validate method, but I was made aware that it should be automatic and then I would have to do this in every single method.

Is it true that the only way for the CustomUserNameValidator to get called is to set the ClientCredentials in proxy code such as:

proxy.ClientCredentials.UserName.UserName = "test;
proxy.ClientCredentials.UserName.Password = "test;

The above would cause the Validate method to be called, but if I am unable to do the above and I set the Username and Password as properties of the Request object, then Validate won't be called, so my other option is to just have my own Validate method within each operation that requires it. An operation will not be called if the ClientCredentials fail, but in my case, I need an operation to call and then for it to return an SOAP Response with an error node with something like "Invalid Username and/or Password" instead of a throwing a FaultException.

Based on the above, is it best to avoid using the CustomUserNamePasswordValidator in my case?

In the event that the username and password can't be set through the proxy's client credentials, but is rather sent through the body of the soap request, then it appears that the way to handle this is to handle validation on an operational basis and does this affect the security settings (for example, is there a point to have a clientCredentialType="Username")


Solution

  • That is not possible unless you dive deeply into WCF security pipeline and implement custom security token (even then you can find that it is not possible) and all the stuff related to this implementation - it is a lot of work.

    Why don't you use standard UserName authentication?

    Edit:

    If you want to pass credentials in custom element in message body (and you will not change security pipeline), the validation is up to you - it will not be routed to user name validator. You must do it either in operation or you can build custom message inspector (implement IMessageInspector) and wrap it with custom endpoint behavior (IEndpointBehavior) to have validation centralized.