Hosting the LoginService in .NET 3.5

The January 2007 CTP version of Visual Studio “Orcas” has .NET FX 3.5 version 3.5.11209. One of the assemblies is System.Web.Extensions and hosts some great new additions to the ASP.NET stack. It has new classes in the namespace System.Web.Security, two of which are LoginService and RolesService. In this post we will take a look at the LoginService.

The LoginService class allows you to perform ASP.NET 2.0 Membership validation through a WCF service. This way other applications can also benefit from your efforts of setting up the membership service and maintaining the member store behind it. Let’s see how you do that:

The LoginService has been decorated by the ServiceContract attribute. Four of its members are opted-in as operations via the OperationContract attribute: IsLoggedIn, Login, Logout and ValidateUser. A small fragment of the class:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

[ServiceContract]

public class LoginService

{

  public static event AuthenticatingEventHandler Authenticating;

  public static event CreatingCookieEventHandler CreatingCookie;

 

  static LoginService();

  [OperationContract]

  private bool IsLoggedIn();

  [OperationContract]

  private bool Login(string username, string password, string customCredential, bool isPersistent);

  [OperationContract]

  private void Logout();

  protected virtual void OnAuthenticating(AuthenticatingEventArgs e);

  protected virtual void OnCreatingCookie(CreatingCookieEventArgs e);

  [OperationContract]

  private bool ValidateUser(string username, string password, string customCredential);

 

  // …

}

Hosting the service

Both the LoginService and RolesService need the services of the ASP.NET pipeline. They indicate so by the AspNetCompatibilityRequirements attribute. This also means that this service must be hosted in IIS, instead of another type of host. Some easy steps are needed:

  1. Create an ASP.NET Web Site or Application and add a reference to System.ServiceModel.dll and to System.Web.Extensions.dll. You can try all of this in a VPC with the Janurary 2007 build of VS Orcas, or copy the System.Web.Extensions.dll file from the VPC to your .NET 3.0 machine and use Visual Studio 2005 SP1 (I did the latter).
  2. Add a .svc file (e.g. LoginService.svc) for the WCF service and change the ServiceHost directive to the following:

    <%@ServiceHost Language=C# Debug=”true” Service=”System.Web.Security.LoginService” %>

    Note that no CodeBehindFile attribute is needed, since the service type is already compiled.

  3. Create a web.config (if necessary and add a <system.serviceModel> element:

    <system.serviceModel>

      <serviceHostingEnvironment aspNetCompatibilityEnabled=true/>

      <behaviors>

        <serviceBehaviors>

          <behavior name=aspNetBehavior>

            <serviceMetadata httpGetEnabled=true/>

            <serviceDebug includeExceptionDetailInFaults=true/>

          </behavior>

        </serviceBehaviors>

      </behaviors>

      <services>

        <service behaviorConfiguration=aspNetBehavior name=System.Web.Security.LoginService>

          <endpoint binding=basicHttpBinding bindingConfiguration=“” name=LoginServiceEndPoint contract=System.Web.Security.LoginService/>

          <endpoint address=MEX binding=mexHttpBinding bindingConfiguration=“” name=MEX bindingName=MEX contract=IMetadataExchange/>

        </service>

      </services>

    </system.serviceModel>

    The bolded part is perhaps the important bit here. This is where the ASP.NET compatibility is enabled. If you do not specify this, the service will not be available.

  4. Change the authentication mode of ASP.NET to Forms Authentication. You can do this by editing the web.config yourself or the ASP.NET Website Administration Tool, whichever you prefer best. While you’re at it, create one or more test accounts in the Membership store, if you do not have an existing one. Membership is enabled by default, and the AspNetDb.mdf SQL Server Express database should be created automatically in your App_Data folder.
  5. Save it all and visit the URL http://localhost:1337/ASPNETServicesViaWCF/LoginService.svc, subsituting your port number and site names if needed.

Creating a client

At this point you will have a working WCF service that allows clients to use the Membership services over WCF. Creating a client application is pretty straightforward, with one caveat. Just add a Service Reference and call the WCF service via the generated proxy like so:

class Program

{

  static void Main(string[] args)

  {

    string username = “alex”, password = “Str0n5P@ssmoRD”;

    LoginService proxy = new LoginServiceClient();

    Console.WriteLine(“Validate user ‘{0}’: {1}”, username,

      proxy.ValidateUser(username, password, String.Empty));

    Console.WriteLine(“Log in successful: {0}”,

      proxy.Login(username, password, String.Empty, true));

    Console.WriteLine(“Logged in: {0}”, proxy.IsLoggedIn());

  }

}

The result of this bit of client code shows the following output:

 

You need to make sure that you allow and enable the client to accept cookies if you want to make use of a persistent login. The configuration is in your binding to the service. It should look like this:

<configuration>

  <system.serviceModel>

    <bindings>

      <basicHttpBinding>

        <binding name=LoginServiceEndPoint allowCookies=true />

      </basicHttpBinding>

    </bindings>

    <client>

      <endpoint address=http://localhost:1337/ASPNETServicesViaWCF/LoginService.svc

          binding=basicHttpBinding bindingConfiguration=LoginServiceEndPoint

          contract=WCFMeetsASPNET.localhost.LoginService name=LoginServiceEndPoint />

    </client>

  </system.serviceModel>

</configuration>


Extensibility points

There are also two extensibility points in the service, provided by the two static (!) events Authenticating and CreatingCookie on the LoginService class. Each of these can have a single (although you could attach more: not recommended) handler attached in the global.asax. The constructor of the HttpApplication derived class is a good place to attach the handler to each of the events.

public class Global: HttpApplication

{

  public Global()

  {

    LoginService.Authenticating += new AuthenticatingEventHandler(LoginService_Authenticating);

    LoginService.CreatingCookie += new CreatingCookieEventHandler(LoginService_CreatingCookie);

  }

 

  void LoginService_CreatingCookie(object sender, CreatingCookieEventArgs e)

  {

    // User name might not be same as login name

    string realUserName = AuthenticationHelper.GetUserNameFromLogin(e.Username);

    FormsAuthentication.SetAuthCookie(realUserName, e.IsPersistent);

    e.CookieIsSet = true;

  }

 

  void LoginService_Authenticating(object sender, AuthenticatingEventArgs e)

  {

    bool authenticated = Membership.ValidateUser(e.Username, e.Password);

    string ticket = e.CustomCredential;

    e.Authenticated = authenticated && TicketAuthenticator.IsValidTicket(ticket);

    e.AuthenticationIsComplete = true;

  }

}

Two possible scenarios are implemented in the code fragment above.

First off, you are able to use custom credentials for your login. These could be used instead of the username and password or complement it. The event arguments for Authenticating give you the user name, password and custom credentials as a set of string properties. The boolean Authenticated property indicates whether the login has succeeded. If you managed to perform authentication the AuthenticationIsComplete should be set to true, or the regular mechanism will kick in (which may be what you want).

The other extensibility point is in the creation of the cookie. The handler for the CreatingCookie event gets arguments of type CreatingCookieEventArgs, that disclose the user name, password, custom credential and whether a persistent cookie should be created. You are given the opportunity to set the cookie yourself. Depending on the circumstances you could decide to not issue a persistent cookie, or alter the username before the cookie is created. However, to do so, you must set the cookie yourself and indicate that you have done so by changing CookieIsSet to true. Also, the cookie that you issue must be a Forms Authentication cookie, containing a encrypted FormsAuthenticationTicket.

Some critical notes

Although the addition of a WCF service that offers Membership authentication is really nice, I do raise some eyebrows to the implementation of the service. As good practice dictates the service interface should be separated from the implementation. But, it is not. This makes the use of the service less flexible. You will not be able to substitute your own alternative implementation of the same interface. Then again, remember this is a CTP implementation, and the LoginService might or might not make it into the final release as it is now (or at all). Also, what will the plans be for the Login service that the Microsoft AJAX Library 1.0 makes use of? We’ll see. Time will tell.

Happy progging.

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s