Those of you who have been building ASP.NET applications for a while now are no doubt familiar with the provider model, which includes provider abstractions for membership (authentication), roles (authorization), profiles (user data), and session state. These providers make it incredibly easy to provide a secure framework for your application. In fact, with an ASP.NET application template right out of the box, you can have a fully functioning authenticated, secure website in minutes.
What a lot of people have less familiarity with is the notion of provider
services. You can actually create a WCF service head that sits on top of the ASP.NET membership system. This allows client applications to authenticate against your ASP.NET website
using exactly the same authentication scheme that your users use. This means that a user who has access to your website should also be able to have access to the client application seamlessly.
To see how this works, create yourself a new ASP.NET MVC app (you can do this with a regular ASP.NET app, but I just happened to use MVC). Before doing anything, run the app, go to the “Log On” area, register yourself as a user and then quit the app. If you’ve got SQL Express installed properly and everything else is in order, your app now knows who you are.
Next, add a new WCF service to this application (call it
Authentication.svc). Delete the files
Authentication.svc.cs and the
IAuthentication.cs. Open up the
Authentication.svc file and replace it’s contents entirely with the following:
| <%@ ServiceHost Language= "C#"
Service= "System.Web.ApplicationServices.AuthenticationService" %>
|
Note here that there’s no code-behind. All we’re doing is exposing a service that is
already part of the ASP.NET framework at a specific endpoint. You’re not quite ready yet – we have to make this service ASP.NET compatible, so open up your
Web.config file and make sure that your
system.servicemodel section looks like this:
| < system.serviceModel >
< services >
< service name = "System.Web.ApplicationServices.AuthenticationService"
behaviorConfiguration = "AuthenticationServiceBehaviors" >
< endpoint contract = "System.Web.ApplicationServices.AuthenticationService"
binding = "basicHttpBinding" />
</ service >
< service name = "Wp7AspNetMembership.HelloService"
behaviorConfiguration = "AuthenticationServiceBehaviors" >
< endpoint
contract = "Wp7AspNetMembership.IHelloService"
binding = "basicHttpBinding" />
</ service >
</ services >
< serviceHostingEnvironment aspNetCompatibilityEnabled = "true"
multipleSiteBindingsEnabled = "true" />
< behaviors >
< serviceBehaviors >
< behavior name = "AuthenticationServiceBehaviors" >
< serviceMetadata httpGetEnabled = "true" />
</ behavior >
< behavior name = "" >
< serviceMetadata httpGetEnabled = "true" />
< serviceDebug includeExceptionDetailInFaults = "false" />
</ behavior >
</ serviceBehaviors >
</ behaviors >
</ system.serviceModel >
< system.web.extensions >
< scripting >
< webServices >
< authenticationService enabled = "true" requireSSL = "false" />
</ webServices >
</ scripting >
</ system.web.extensions >
|
What’s going on in this
Web.config file is pretty interesting. First, we’re exposing
Authentication.svc and
HelloService.svc (not yet created) over HTTP, with metadata allowed, with ASP.NET compatibility enabled. We’ve also used the
system.web.extensions element to indicate that the authentication service is being enabled. In a production environment, you would set
requireSSL to true.
At this point, you should be able to hit the URL
/Authentication.svc and see the standard metadata page. Now let’s create
HelloService.svc by creating a standard WCF service. Change the interface so it only has a single method called
HelloWorld() that returns a string. The following is the implementation of
HelloService.svc.cs:
| using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Web;
using System.ServiceModel.Activation;
namespace MvcApplication1
{
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class HelloService : IHelloService
{
public string HelloWorld()
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
return HttpContext.Current.User.Identity.Name;
else
return "Unauthenticated Person" ;
}
}
}
|
This is a pretty simple service. Just pretend that instead of returning the name of the authenticated user, we’re actually performing some vital business function that requires a valid user context.
Now, we can get to the good stuff : the WP7 application
In this WP7 application we’re going to create a login form, submit user credentials over the wire, get validation results and, if the user was authenticated, we’re going to call the
HelloWorld() method with a validated, secure context – all of this with an incredibly small amount of work on the part of the WP7 client.
Add a new WP7 application (just the stock one, not the list view) to the solution, I called mine
TestMembershipClient but you can pick whatever you like. Add service references to
Authentication.svc and to
HelloWorld.svc (I named the reference namespaces
MvcWebAppReference for authentication and
AspNetMvcRealReference for the service that simulates doing real work).
On your main page, drop a couple text block labels, two text boxes, and a submit button. When you run the app at this point, it should look like this screenshot:
WP7 Login Form
Now rig up a click handler for the submit button with code that looks like this:
| private void LoginButton_Click( object sender, RoutedEventArgs e)
{
MvcWebAppReference.AuthenticationServiceClient authService =
new MvcWebAppReference.AuthenticationServiceClient();
cc = new CookieContainer();
authService.CookieContainer = cc;
authService.LoginCompleted +=
new EventHandler<MvcWebAppReference.LoginCompletedEventArgs>(
authService_LoginCompleted);
authService.LoginAsync(UsernameBox.Text, PasswordBox.Text, "" , true );
}
void authService_LoginCompleted( object sender,
MvcWebAppReference.LoginCompletedEventArgs e)
{
if (e.Error != null )
{
MessageBox.Show( "Login failed, you Jackwagon." );
}
else
{
AspNetMvcRealReference.HelloServiceClient helloService =
new AspNetMvcRealReference.HelloServiceClient();
helloService.CookieContainer = cc;
helloService.HelloWorldCompleted +=
new EventHandler<AspNetMvcRealReference.HelloWorldCompletedEventArgs>(
helloService_HelloWorldCompleted);
helloService.HelloWorldAsync();
}
}
void helloService_HelloWorldCompleted( object sender,
AspNetMvcRealReference.HelloWorldCompletedEventArgs e)
{
MessageBox.Show( "You're logged in, results from svc: " + e.Result);
}
|
Most of this is pretty straightforward. When the user clicks the submit button, it calls the
LoginAsync method on the membership service (remember that all service calls in Silverlight are
asynchronous). When we get the results of that call, we either tell the user that their login has failed, or we invoke the
HelloWorld method (also asynchronously). When the hello world method comes back from the server, we display the results of the execution in a message box that looks like the one in the screenshot below:
Results of Calling a Secured Service from WP7
One thing to take very careful note of is the
CookieContainer. Because we’re using two different proxies: 1 to talk to the authentication service and 1 to talk to the hello world service, we have to ensure that these two proxies are using the same cookie container so that the auth cookie can be used by subsequent method calls on other services. You can do this for an unlimited number of services
that are on the same DNS domain, so long as they all share the same cookie container. To enable the use of cookie containers in WCF services in Silverlight (it’s disabled by default), you have to set the
enableHttpCookieContainer property to
true. in the
binding element in the
ServiceReferences.ClientConfig file.
On the surface you might not think this is all that big of a deal. You might also think this is difficult but, keep in mind that I provided a pretty detailed walkthrough. This whole thing only took me about 15 minutes to set up from start to finish once I’d figured out how the
CookieContainer thing worked. So why bother with this?
Consider this: If you already have an ASP.NET application that is using the membership provider, role provider, and profile provider you can
quickly, easily, and securely expose services to a mobile (WP7) client that allow that client to have secured, remote access to services exposed by that site. In short, any user of your existing web application can use their existing credentials to log in from their WP7 device and access any services you decide to make available.
ASP.NET provider services, coupled with WP7 and the fact that Silverlight has access to WCF client proxy generation, means you can
very easily prep your site for a rich WP7 experience.