+1  A: 

There's a number of things you could do:

  • add a certificate to each and every machine that's allowed to use your service, and check for that certificate. That only allows you to exclude "unauthorized" machines - you cannot limit it to a specific application
  • same as above, but include the certificate embedded in your winforms app and send it from there (do not store it in the machine's certificate store)
  • require a username / password that only that particular app of yours knows about and can transmit to your service; e.g. someone else would not be able to present the appropriate credentials

EDIT 2: OK, so the username/password approach seems to get out of hand.... what if you just have basic transport security (SSL) for basic protection, and then use the MessageContract to define header and body of your SOAP message, include a specific value in the header, and then just check for that presence of the element in the header in your service?

Something like that:

[DataContract]
class YourRequestData
{
 ...
}

[MessageContract]
public class YourRequest
{
  [MessageBodyMember]
  public YourRequestData bodyData { get; set; }

  [MessageHeader]
  public string AppThumbprint { get; set; }
}

And then on your server in your code just check for the presence and the validity of that AppThumbprint code:

public Stream RequestStream(YourRequest request)
{
  if(AppThumbprintIsValid(request.AppThumbprint))
  {
     .... begin your streaming
  }
}

That might end up being a lot easier than the username/password security scenario.

Marc

marc_s
I'd like to avoid adding certificates to client machines since the users of the application change often. I'd like to implement the username/password method you mentioned but I'm not sure how I would transmit those values to the actual service. The method being exposed by the service has streaming enabled so I wouldn't be able to pass those values as parameters (due to the method signature restrictions in place for a streaming-enabled service); can you recommend a method for passing a un/pw combo to the service and authenticating from within the service?
Steve Dignan
After I modify my service's config as above and run the service I get the error "BasicHttp binding requires that BasicHttpBinding.Security.Message.ClientCredentialType be equivalent to the BasicHttpMessageCredentialType.Certificate credential type for secure messages. Select Transport or TransportWithMessageCredential security for UserName credentials.". I thought a streaming-enabled endpoint required Transport level security?Also, in the <endpoint> tag, I think the binding value be basicHttpBinding instead of basicHttpConfiguration.
Steve Dignan
Thanks for all your help Marc, but unfortunately I don't think this approach (EDIT 2) will work for my specific scenario. The service method being called must both accept and return a Stream. Your answer raises an interesting idea; I suppose I could inherit a Stream and decorate it with some un/pw properties; this Stream could be the input parameter of my service method (not overly secure, mind you). But given the apparent flexibility of WCF, I have to assume there is a better way... hmmmm.
Steve Dignan
A: 

If this is going to be an application that lives on the intranet it might be easiest to just create a new group in your Active Directory and only give members of that group the ability to use the service.

You can add Authentication (using windows credentials) with something like this:

<basicHttpBinding> 
 <security mode="TransportCredentialOnly"> 
  <transport clientCredentialType="Windows" /> 
 </security> 
</basicHttpBinding>

Could then Authorise by decorating the Interface to your services methods:

<PrincipalPermission(SecurityAction.Demand, Role:="MyAppsUsers")> _ 
Public Function MyMethod() As String Implements IService.MyMethod

Heres a good link to Security in WCF. It has lots of How To's at the end (the one titled 'How To - Use basicHttpBinding with Windows Authentication and TransportCreditals' might be of use to you).
Wcf Secruity

[Disclaimer: I'm also new to WCF and haven’t done this exact case before so apologises if this is slightly off!]

Cwoo
Since we are trying to ensure that the WCF service is only accessed from a single application, I don't think using windows credentials will do since those users could potentially call the service from outside of the application (correct me if I'm wrong). Supplying a username/password to the service seems like the way to go (from my limited experience). Thanks for the link to the WCF security guide, I'll also be looking at that to see what I can apply to my scenario.
Steve Dignan
+1  A: 

Well, I found a lot of issues surrounding security/streaming while working on this problem. The hack (er...um...workaround) I finally ended up going with was to create a new DataContract that inherits MemoryStream and decorated it with a BaseStream property (for holding the data I want streamed) along with appropriate properties used for simple authentication.

Here is the resulting DataContract:

[DataContract]
[KnownType( typeof( MemoryStream ) )] 
public class StreamWithCredentials : MemoryStream
{
    [DataMember]
    public Stream BaseStream { get; set; }

    [DataMember]
    public string Username { get; set; }

    [DataMember]
    public string Password { get; set; }
}

The above DataContract ends up being the input parameter of my service's method. The first action my service takes is to authenticate the supplied credentials against known valid values and to continue as appropriate.

Now I do know that this is not the most secure option but my directive was to avoid using SSL (which I'm not even sure is possible anyway - as stated here) for this internal process.

That being said, this was the best solution to the above stated problem I could come up with, hope this helps anyone else stricken with this issue.

Thanks to all who responded.

Steve Dignan
A: 

If you want to use basicHttpBinding (for interop) you can only pass your credential at message level. So you have to set your security confiuguration to TransportWithMessageCredential. To do that you have to create a SSL channel, so you need a certificate at server side, and it's not necesary for a cliente to have one.

Fernando