+1  A: 

Well, you really should not pay so much attention to WinForms side because WCF side is the key.

By the way, did you carefully read these pages?

Concept http://www.codeplex.com/WCFSecurityGuide/Wiki/View.aspx?title=Ch%2005%20-%20Authentication,%20Authorization%20and%20Identities%20in%20WCF&referringTitle=Home

and How to http://www.codeplex.com/WCFSecurity/Wiki/View.aspx?title=How%20Tos&referringTitle=Home

Lex Li
+5  A: 

Well, I don't have any experience with the REST capabilities of WCF, but I did wrestle a lot with understanding the implications of security choices in my WCF security question. As you've noticed, there's a real lack of documentation on WCF out their on the Web, and my REST experience is limited, so take my answers with a grain of salt:

Which security implementation should I use?

and

Should I even really be concerned about anything other than plain text if I am using SSL?

Basic authentication over SSL is fine--after all, this how a great deal of existing Web sites authenticate users. (When you log into your Amazon shopping account, they're just transmitting your username and password as you typed it over an SSL connection.) I understand what the article is saying about security and dictionary attacks, but blah blah blah, keep it simple and get something working first. UPS's Plain Old XML API asks for the username and password with each call, so does FedEx's POX API, so does PayPal's SOAP API and CyberSource's SOAP API--this seems to be fine enough for real world usage.

Is there any way to avoid sending the username/password with every WCF call? I would prefer not to send these extra bytes if a connection has been established at the beginning, which it will be before subsequent calls are allowed to be made after login.

This is one that I can answer a little bit more confidently. Usually, we try to design our public-facing WCF services to be stateless. That way, our WCF services scale easily; just throw more hardware and more servers and load balancers at the problem, and we don't have to worry about sticky sessions or maintaining session state somewhere. So that means that if we want to "keep a user logged in," then it's not something that's going to happen on the server.

What I ended up doing is treating my Web site as a trusted subsystem. It authenticated against the WCF service using a pre-shared X509 certificate, and if a customer was logged into the Web site via Forms Authentication, then it would send a customer username header to the service; a custom endpoint behavior on the WCF service would look for this header, see that it was installed by a trusted subsystem, and proceed to impersonate that user without the user's password needing to be supplied or verified against the database.

Since you're using REST, you could probably use a cookie on the client side to maintain state. If you use ASP.NET Compatibility mode, I think you can even use Forms Authentication directly, but I don't know much about this approach since my WCF service was not IIS hosted.

In short, though, you're going to have to send something with each request to identify the user, whether that is the username and password, just the username, or some hashed value stored in the cookie. If the last option, I guess you'd have to have some sort of Login() method or something on the service, something that would send an "okay, you're logged if you pass in this hash value with future requests." But not all REST clients will be expecting to receive cookies, just simple GET/PUT/POST/DELETE requests without any state.

If those were my shoes, I'd either go for the trusted subsystem approach (where a username header is supplied along with pre-shared credentials for the subsystem) or I'd require authentication on every call. The service would probably get some high-performance authentication caching mechanism if all those repeated requests became a problem.

Hope that helps a little bit.

Nicholas Piasecki
The part "and if a customer was logged into the Web site via Forms Authentication, then it would send a customer username header to the service; a custom endpoint behavior on the WCF service would look for this header, see that it was installed by a trusted subsystem, and proceed to impersonate that user without the user's password needing to be supplied or verified against the database.." is exactly what I want to do but I can't figure out how. Could you spell it out for me?
AJM
[http://piasecki.name/skiviez-crap/WCF-Trusted-Subsystem.zip](Here is a sample that I put together for someone some time ago.) Uses Windows Forms as the example client, but easily morphed into ASP.NET--check out the comments in the UserNameMessageInspector class. No warranties, could be insane code, etc., but hopefully it gets you somewhere!
Nicholas Piasecki
Well, screwed up the link: http://piasecki.name/skiviez-crap/WCF-Trusted-Subsystem.zip You get the idea, though.
Nicholas Piasecki
A: 

Thanks for the answers. Stepping back and looking clearly and unbiased at the problem as a whole (in other words ignoring the 4+ hours I invested looking into RESTful services) I am attempting to get the thing working without REST for now and the references I am attempting to follow at the moment are these: -

This seems applicable for what I want.

lextm: I hear you on this, after I wrote the post I scanned closer through the WCF security guide and made notes on all my requirements based on the options given they want you to think about on each tenet.

I have chosen:
- Transfer Security Mode: Transport Security
- Auth. Option: Basic Security
- Binding: wsHttpBinding
- Custom authentication with username validator

In light of the examples provided for each and looking at the use case of windows forms w/ WCF service it seems like the best way to go.

Nicholas: Agreed, designing the services to be stateless is probably a better approach.

So based on the article I will be following when I get time, it utilises the X509 cert. which I am very new to (understand you are using this Nicholas) will this be fine given this client app can be downloaded from the internet and installed on anybody's PC who has an account with my website?

Cheers for all your help, Graham

PS: I think this is the closest use case to my scenario (except I wish to use transport security), should I be considering implementing this as it does not bother with a cert? From the quote I read I might need the cert. as "The X509 certificate encryption is required by WCF because the client credentials (username/password) are passed as clear text in the SOAP message." - however from what I've learnt and what we said, if I am using SSL, this point is probably moot?

GONeale
It's saying the same thing, just in more complicated wording. The SSL cert is your X509 certificate (X509 is just a container spec), and WCF will use that to secure and trust the transport layer. So you can pass whatever you want in the message and not worry about message-level encryption.
Nicholas Piasecki
... so, the in basic username/pass auth you tell the server what certificate to use, and the client has a few options to set about trusting the cert on the connection. But the client *itself* doesn't authenticate with its own client-side cert, it uses a username/password.
Nicholas Piasecki
... it's confusing because certificates are used in both scenarios, but what WCF causes X509 certificate authentication is when the client has an X509 cert installed on both the client and the server and it authenticates with that. You will just have a server cert to secure the connection. HTH!
Nicholas Piasecki
Great, thanks it does immensely!
GONeale
+1  A: 

Using Basic authentication:

WebHttpBinding binding = new WebHttpBinding();
binding.SendTimeout = TimeSpan.FromSeconds(25);
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

Uri address = new Uri("http://localhost:3525/WcfRestWeb/Quotes.svc");

WebChannelFactory<IQuoteService> factory =
             new WebChannelFactory<IQuoteService>(binding, address);

factory.Credentials.UserName.UserName = "tan";
factory.Credentials.UserName.Password = "wani";

IQuoteService proxy = factory.CreateChannel();

var response = proxy.GenerateQuote(GetQuoteRequest());
Console.WriteLine("Quote Amount: " + response.QuoteAmount);
Tawani