views:

9829

answers:

8

I have written a Silverlight 2 application communicating with a WCF service (BasicHttpBinding). The site hosting the Silverlight content is protected using a ASP.NET Membership Provider. I can access the current user using HttpContext.Current.User.Identity.Name from my WCF service, and I have turned on AspNetCompatibilityRequirementsMode.

I now want to write a Windows application using the exact same web service. To handle authentication I have enabled the Authentication service, and can call "login" to authenticate my user... Okey, all good... But how the heck do I get that authentication cookie set on my other service client?!

Both services are hosted on the same domain

  • MyDataService.svc <- the one dealing with my data
  • AuthenticationService.svc <- the one the windows app has to call to authenticate.

I don't want to create a new service for the windows client, or use another binding...

The Client Application Services is another alternative, but all the examples is limited to show how to get the user, roles and his profile... But once we're authenticated using the Client Application Services there should be a way to get that authentication cookie attached to my service clients when calling back to the same server.

According to input from colleagues the solution is adding a wsHttpBinding end-point, but I'm hoping I can get around that...

+1  A: 

Web services, such as those created by WCF, are often best used in a "stateless" way, so each call to a Web service starts afresh. This simplifies the server code, as there's no need to have a "session" that recalls the state of the client. It also simplifies the client code as there's no need to hold tickets, cookies, or other geegaws that assume something about the state of the server.

Creating two services in the way that is described introduces statefulness. The client is either "authenticated" or "not authenticated", and the MyDataService.svc has to figure out which.

As it happens, I've found WCF to work well when the membership provider is used to authenticate every call to a service. So, in the example given, you'd want to add the membership provider authentication gubbins to the service configuration for MyDataService, and not have a separate authentication service at all.

For details, see the MSDN article here.

[What's very attractive about this to me, as I'm lazy, is that this is entirely declarative. I simply scatter the right configuration entries for my MembershipProvider in the app.config for the application and! bingo! all calls to every contract in the service are authenticated.]

It's fair to note that this is not going to be particularly quick. If you're using SQL Server for your authentication database you'll have at least one, perhaps two stored procedure calls per service call. In many cases (especially for HTTP bindings) the overhead of the service call itself will be greater; if not, consider rolling your own implementation of a membership provider that caches authentication requests.

One thing that this doesn't give is the ability to provide a "login" capability. For that, you can either provide an (authenticated!) service contract that does nothing (other than raise a fault if the authentication fails), or you can use the membership provider service as described in the original referenced article.

Jeremy McGee
+2  A: 

I finally found a way to make this work. For authentication I'm using the "WCF Authentication Service". When authenticating the service will try to set an authentication cookie. I need to get this cookie out of the response, and add it to any other request made to other web services on the same machine. The code to do that looks like this:

var authService = new AuthService.AuthenticationServiceClient();
var diveService = new DiveLogService.DiveLogServiceClient();

string cookieHeader = "";
using (OperationContextScope scope = new OperationContextScope(authService.InnerChannel))
{
    HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty();
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestProperty;
    bool isGood = authService.Login("jonas", "jonas", string.Empty, true);
    MessageProperties properties = OperationContext.Current.IncomingMessageProperties;
    HttpResponseMessageProperty responseProperty = (HttpResponseMessageProperty)properties[HttpResponseMessageProperty.Name];
    cookieHeader = responseProperty.Headers[HttpResponseHeader.SetCookie];                
}

using (OperationContextScope scope = new OperationContextScope(diveService.InnerChannel))
{
    HttpRequestMessageProperty httpRequest = new HttpRequestMessageProperty();
    OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest);
    httpRequest.Headers.Add(HttpRequestHeader.Cookie, cookieHeader);
    var res = diveService.GetDives();
}

As you can see I have two service clients, one fo the authentication service, and one for the service I'm actually going to use. The first block will call the Login method, and grab the authentication cookie out of the response. The second block will add the header to the request before calling the "GetDives" service method.

I'm not happy with this code at all, and I think a better alternative might be to use "Web Reference" in stead of "Service Reference" and use the .NET 2.0 stack instead.

Jonas Follesø
A: 

Hi Jonas,

It is possible to hide much of the extra code behind a custom message inspector & behavior so you don't need to take care of tinkering with the OperationContextScope yourself.

I'll try to mock something later and send it to you.

--larsw

larsw
Sounds great Lars. Give me a good example so that we can "close this sucker" down ;) Cheers, jonas
Jonas Follesø
A: 

Hi larsw, could you show us how to do that?

A: 

You should take a look at the CookieContainer object in System.Net. This object allows a non-browser client to hang on to cookies. This is what my team used the last time we ran into that problem.

Here is a brief article on how to go about using it. There may be better ones out there, but this should get you started.

We went the stateless route for our current set of WCF services and Silverlight 2 application. It is possible to get Silverlight 2 to work with services bound with TransportWithMessageCredential security, though it takes some custom security code on the Silverlight side. The upshot is that any application can access the services simply by setting the Username and Password in the message headers. This can be done once in a custom IRequestChannel implementation so that developers never need to worry about setting the values themselves. Though WCF does have an easy way for developers to do this which I believe is serviceProxy.Security.Username and serviceProxy.Security.Password or something equally simple.

Chuck
A: 

I wrote this a while back when I was using Client Application Services to authenticate against web services. It uses a message inspector to insert the cookie header. There is a word file with documentation and a demo project. Although its not exactly what you are doing, its pretty close. You can download it from here.

A: 

On the client modify your <binding> tag for the service (inside <system.serviceModel>) to include: allowCookies="true"

The app should now persist the cookie and use it. You'll note that IsLoggedIn now returns true after you log in -- it returns false if you're not allowing cookies.

A: 

Hi Jonas,

could you please share with me a working solution where you use (!) basicHttpBinding, authenticate through one service and then use this authentication in second one ???

I'm looking for such solution over 4 days but still not able to config wcf services properly....

I'll very appreciate your help.

thanks in advance, my best regards, Volodymyr Fedyk

Volodymyr Fedyk