Using ASP.NET application services from Silverlight

Silverlight is a platform the behaves pretty much like any windows application. This makes sense when you think about where the application is actually running. It is deployed from a website, but doesn’t actually run on the web server.

Because Silverlight doesn’t run on the server you should prepare for a little more complex of way of enforcing permissions and setting up a solution to authenticate users. The basics are not that different from what you might be doing in Windows Forms or WPF. There are however new tools available that make setting up authentication and authorization of users a whole lot simpler.

In this article I’m going to show how you can use ASP.NET application services to enable authentication and authorization of users inside a Silverlight application.

Application services for ASP.NET

In ASP.NET you can choose between different providers to authenticate and authorize users on the website. There’s the membership provider, the role provider and the profile provider which can use for authentication, authorization and to create a user profile. These providers can be accessed from the code behind by calling the correct methods on these providers.

There is also another way of accessing the various providers offered by ASP.NET, by using a WCF service that you can expose on your website. You don’t need to implement them yourself, all that is needed is to create the necessary endpoints and setup the correct configuration.

Setting up the database for the application services

Before you can start using the various providers for membership, roles and profiles you will need a database that contains the data for all of these areas. To do this you will need to execute the following application from within the .NET Framework directory:

  • C:windowsmicrosoft.netFrameworkv2.0.50727aspnet_regsql.exe
  • C:windowsmicrosoft.netFrameworkv4.0.30128aspnet_regsql.exe

When you start the utility you will be presented with the following wizard.

clip_image002

You can choose to create a new asp.net application services database. You can also use this wizard to remove an existing application services database.

Once you have the database you can update the connection string used by the various providers in the web application to connect to the new application services database.

Enabling the authentication service

To use the authentication service in Silverlight you will need to enable forms authentication and setup the membership provider with a custom database.

To configure the web application for forms authentication you can set the <authentication> element to <authentication mode=”Forms”/>

After you have enabled forms authentication you will need to setup the membership provider. The following XML snippet demonstrates how the membership provider can be configured.

   1: <membership defaultProvider=&quot;TaskTrackerMembershipProvider&quot;>

   2:   <providers>

   3:     <add name=&quot;TaskTrackerMembershipProvider&quot; type=&quot;System.Web.Security.SqlMembershipProvider&quot;

   4:          connectionStringName=&quot;ApplicationServices&quot;

   5:          enablePasswordRetrieval=&quot;false&quot;

   6:          enablePasswordReset=&quot;true&quot;

   7:          requiresQuestionAndAnswer=&quot;false&quot;

   8:          requiresUniqueEmail=&quot;false&quot;

   9:          maxInvalidPasswordAttempts=&quot;5&quot;

  10:          minRequiredPasswordLength=&quot;6&quot;

  11:          minRequiredNonalphanumericCharacters=&quot;0&quot;

  12:          passwordAttemptWindow=&quot;10&quot;

  13:          applicationName=&quot;/&quot; />

  14:   </providers>

  15: </membership>

The next step in making the authentication service available to Silverlight is to setup the WCF services. For this you will need to create a new file with the name Authentication.svc inside your web application. The service file should contain the following content:

   1: <%@ ServiceHost Language=&quot;C#&quot; Service=&quot;System.Web.ApplicationServices.AuthenticationService&quot; %>

Next you will need to configure the service in the web.config file of the application. The configuration should look similar to the following snippet:

   1: <system.serviceModel>

   2:   <!-- Required for the application services to work correctly -->

   3:   <serviceHostingEnvironment aspNetCompatibilityEnabled=&quot;true&quot; />

   4:   <behaviors>

   5:     <serviceBehaviors>

   6:       <!-- New feature in .NET 4, default behaviors -->

   7:       <behavior>

   8:         <serviceMetadata httpGetEnabled=&quot;true&quot;/>

   9:         <serviceDebug includeExceptionDetailInFaults=&quot;true&quot;/>

  10:       </behavior>

  11:     </serviceBehaviors>

  12:   </behaviors>

  13:   <services>

  14:     <service name=&quot;System.Web.ApplicationServices.AuthenticationService&quot;>

  15:       <endpoint address=&quot;&quot; binding=&quot;basicHttpBinding&quot; contract=&quot;System.Web.ApplicationServices.AuthenticationService&quot;/>

  16:       <endpoint address=&quot;mex&quot; binding=&quot;mexHttpBinding&quot; contract=&quot;IMetadataExchange&quot;/>

  17:       <!-- ... Other services ... -->

  18:     </service>

  19:   </services>

  20: </system.serviceModel>

The web service is now available when you request the service file from the web address the website is hosted on. It is however not possible to call it from any external application. To enable this you will need to add an extra piece of configuration that enables the authentication service.

   1: <system.web.extensions>

   2:   <scripting>

   3:     <webServices>

   4:       <authenticationService enabled=&quot;true&quot;/>

   5:       <!-- ... Other application services ... -->

   6:     </webServices>

   7:   </scripting>

   8: </system.web.extensions>

Enabling the role service

The membership provider that is configured in the previous section enables users to authenticate themselves. The provider however doesn’t allow for the application to enforce role-based authorization.

To enable role-based authorization in Silverlight you will need to enable the roles provider and the roles webservice for ASP.NET.

Enabling the roles provider is done by adding a roles provider to the web.config file of the web application. The following snippet demonstrates this:

   1: <roleManager enabled=&quot;true&quot; defaultProvider=&quot;TaskTrackerRoleProvider&quot;>

   2:   <providers>

   3:     <add connectionStringName=&quot;ApplicationServices&quot; applicationName=&quot;/&quot;

   4:          name=&quot;TaskTrackerRoleProvider&quot; type=&quot;System.Web.Security.SqlRoleProvider&quot; />

   5:   </providers>

   6: </roleManager>

The provider uses the same database as the membership provider, so there’s no need to create a new database. Although you could of course do that if you have the need for it.

After configuring the roles provider you can enable webservice access by creating a new svc file with the following content:

   1: <%@ ServiceHost Language=&quot;C#&quot; Service=&quot;System.Web.ApplicationServices.RoleService&quot; %>

To finalize the configuration of the webservice you will also need to update the WCF configuration in the web.config file of the web application by adding the following snippet of XML to the system.serviceModel section:

   1: <services>

   2:   <!-- ... Other services ... -->

   3:   <service name=&quot;System.Web.ApplicationServices.RoleService&quot;>

   4:     <endpoint address=&quot;&quot; binding=&quot;basicHttpBinding&quot; contract=&quot;System.Web.ApplicationServices.RoleService&quot;/>

   5:     <endpoint address=&quot;mex&quot; binding=&quot;mexHttpBinding&quot; contract=&quot;IMetadataExchange&quot;/>

   6:   </service>

   7: </services>

Finally you need to enable the roles service in the system.web.extensions section of the web.config file. The following snippet demonstrates this:

   1: <system.web.extensions>

   2:   <scripting>

   3:     <webServices>

   4:       <authenticationService enabled=&quot;true&quot;/>

   5:       <roleService enabled=&quot;true&quot;/>

   6:       <!-- ... Other services ... -->

   7:     </webServices>

   8:   </scripting>

   9: </system.web.extensions>

Enabling the profile service

Configuring the profile service is completely optional. With the authentication service and roles service you already have enough to setup a complete authorization model for Silverlight. The profile service however adds more options by enabling users to have profile properties that can be read and written from the Silverlight application.

Setting up the profile provider works the same as for the other providers. Add the following snippet to the web.config file to setup the profile provider itself:

   1: <profile enabled=&quot;true&quot; defaultProvider=&quot;TaskTrackerProfileProvider&quot;>

   2:   <providers>

   3:     <add name=&quot;TaskTrackerProfileProvider&quot;

   4:          type=&quot;System.Web.Profile.SqlProfileProvider&quot;

   5:          connectionStringName=&quot;ApplicationServices&quot;

   6:          applicationName=&quot;/&quot; />

   7:   </providers>

   8: </profile>

After that you can create a new service file and add configuration for the service file. Here’s the contents of the service file:

   1: <%@ ServiceHost Language=&quot;C#&quot; Service=&quot;System.Web.ApplicationServices.ProfileService&quot; %>

The following snippet demonstrates the configuration that needs to be added to the system.serviceModel section of the web.config file in the web application:

   1: <services>

   2:   <!-- ... Other services ... -->

   3:   <service name=&quot;System.Web.ApplicationServices.ProfileService&quot;>

   4:     <endpoint address=&quot;&quot; binding=&quot;basicHttpBinding&quot; contract=&quot;System.Web.ApplicationServices.ProfileService&quot;/>

   5:     <endpoint address=&quot;mex&quot; binding=&quot;mexHttpBinding&quot; contract=&quot;IMetadataExchange&quot;/>

   6:   </service>

   7: </services>

Finally you need to enable the profile provider by adding the following snippet to the system.web.extensions section.

   1: <system.web.extensions>

   2:   <scripting>

   3:     <webServices>

   4:       <authenticationService enabled=&quot;true&quot;/>

   5:       <roleService enabled=&quot;true&quot;/>

   6:       <profileService enabled=&quot;true&quot;/>

   7:     </webServices>

   8:   </scripting>

   9: </system.web.extensions>

Using application services from Silverlight

Once the services have been configured you can access them from Silverlight by adding a service reference to them in Visual Studio. When you’ve enabled the mex endpoints on the services and specified the behavior for service metadata you should be able to click discover in the Add service reference dialog and have a list of application services show right up in the dialog.

image

Getting a proxy instance for an application service

By default Visual Studio will generate a proxy for the application services with an URI specified that points towards the location the service was originally located. This is fine on a development environment, but when you change the URI of the webservice you will also need to update the URI inside the XAP package. This is not really what you want when running on a production server.

To make things a little simpler I’ve created a combination of an extension method and a factory class that makes getting a new instance of a service proxy a breeze.

   1: /// <summary>

   2: /// Provides some useful extensions on the application class

   3: /// </summary>

   4: public static class ApplicationExtensionMethods

   5: {

   6:     /// <summary>

   7:     /// Combines the path of the Silverlight application and a relative path

   8:     /// into one absolute URI.

   9:     /// </summary>

  10:     /// <param name=&quot;application&quot;>Running application</param>

  11:     /// <param name=&quot;path&quot;>Relative path to convert</param>

  12:     /// <returns>Returns the full URI to the specified relative path</returns>

  13:     public static Uri GetRelativeUri(this Application application, string path)

  14:     {

  15:         const char UriSeparator = '/';

  16:&#160; 

  17:         string sourceFileUri = application.Host.Source.AbsoluteUri;

  18:         string[] sourceFileUriParts = sourceFileUri.Split(UriSeparator);

  19:         string clientBinFolderPath = sourceFileUriParts[sourceFileUriParts.Length - 2];

  20:         string outputFolderPath = null;

  21:&#160; 

  22:         if (clientBinFolderPath.ToLower() == &quot;clientbin&quot;)

  23:         {

  24:             outputFolderPath = String.Join(UriSeparator.ToString(), 

  25:                 sourceFileUriParts.Take(sourceFileUriParts.Length - 2).ToArray());

  26:         }

  27:         else

  28:         {

  29:             outputFolderPath = String.Join(UriSeparator.ToString(), 

  30:                 sourceFileUriParts.Take(sourceFileUriParts.Length - 1).ToArray());

  31:         }

  32:&#160; 

  33:         return new Uri(String.Format(&quot;{0}{1}{2}&quot;, outputFolderPath, UriSeparator, path), 

  34:             UriKind.RelativeOrAbsolute);

  35:     }

  36: }

The extension method makes it possible to combine a relative URI with the absolute URI of the location where the Silverlight application is hosted. This method can be reused to locate various other resources from the website.

The factory class combines a two-step operation to configure the address and binding for each service requested. Each service that can be created has its own method on the factory class, you can however make this more generic if you want to. This implementation however helps to keep things straightforward and easier to maintain.

   1: /// <summary>

   2: /// Automates the creation of the various agents in the frontend

   3: /// </summary>

   4: public static class ServiceFactory

   5: {

   6:     #region Public methods

   7:&#160; 

   8:     /// <summary>

   9:     /// Creates a new service proxy for the authentication service

  10:     /// </summary>

  11:     /// <returns></returns>

  12:     public static AuthenticationServiceClient CreateAuthenticationClient()

  13:     {

  14:         Uri serviceAddress = Application.Current.GetRelativeUri(&quot;Services/Authentication.svc&quot;);

  15:         Binding serviceBinding = CreateBindingForAddress(serviceAddress);

  16:         

  17:         EndpointAddressBuilder addressBuilder = new EndpointAddressBuilder();

  18:         addressBuilder.Uri = serviceAddress;

  19:&#160; 

  20:         return new AuthenticationServiceClient(serviceBinding, addressBuilder.ToEndpointAddress());

  21:     }

  22:&#160; 

  23:     /// <summary>

  24:     /// Creates a new service proxy for the profile service

  25:     /// </summary>

  26:     /// <returns></returns>

  27:     public static ProfileServiceClient CreateProfileClient()

  28:     {

  29:         Uri serviceAddress = Application.Current.GetRelativeUri(&quot;Services/Profile.svc&quot;);

  30:         Binding serviceBinding = CreateBindingForAddress(serviceAddress);

  31:&#160; 

  32:         EndpointAddressBuilder addressBuilder = new EndpointAddressBuilder();

  33:         addressBuilder.Uri = serviceAddress;

  34:&#160; 

  35:         return new ProfileServiceClient(serviceBinding, addressBuilder.ToEndpointAddress());

  36:     }

  37:&#160; 

  38:     /// <summary>

  39:     /// Creates a new service proxy for the role service

  40:     /// </summary>

  41:     /// <returns></returns>

  42:     public static RoleServiceClient CreateRoleClient()

  43:     {

  44:         Uri serviceAddress = Application.Current.GetRelativeUri(&quot;Services/Roles.svc&quot;);

  45:         Binding serviceBinding = CreateBindingForAddress(serviceAddress);

  46:&#160; 

  47:         EndpointAddressBuilder addressBuilder = new EndpointAddressBuilder();

  48:         addressBuilder.Uri = serviceAddress;

  49:&#160; 

  50:         return new RoleServiceClient(serviceBinding, addressBuilder.ToEndpointAddress());

  51:     }

  52:     

  53:     #endregion

  54:&#160; 

  55:     #region Private methods

  56:&#160; 

  57:     /// <summary>

  58:     /// Creates a binding for the specified address

  59:     /// </summary>

  60:     /// <param name=&quot;address&quot;>Address of the service</param>

  61:     /// <returns>Returns the channel binding to use</returns>

  62:     private static Binding CreateBindingForAddress(Uri address)

  63:     {

  64:         BasicHttpSecurityMode securityMode = BasicHttpSecurityMode.None;

  65:&#160; 

  66:         // Enable transport security when the URI points to a SSL-enabled location

  67:         if (address.Scheme.ToLower() == &quot;https&quot;)

  68:         {

  69:             securityMode = BasicHttpSecurityMode.Transport;

  70:         }

  71:&#160; 

  72:         return new BasicHttpBinding(securityMode);

  73:     }

  74:&#160; 

  75:     #endregion

  76: }

The idea behind this class is that you can ask it for a service and it will create a binding and configure the address based on whether the Silverlight application is accessed over SSL or not. This makes it possible to deploy the website with the Silverlight application anywhere, without having to do a whole lot of configuration in each environment you drop the application in.

Building an authentication frontend service

You will need to follow a three-step process authenticate a user inside the Silverlight application. The first step is to actually authenticate the user. This is done by calling the authentication service on the website. If this operation succeeds and the user is authenticated you can call the role service to retrieve the roles of the user and the profile service to retrieve the profile properties of the user. The last two calls can be made in parallel to save some time on the login process.

image

It’s a good idea to encapsulate this process in a single component in the frontend. Because of the fact that all these service calls have to be made asynchronously and the code to do this can be quite complex.

   1: /// <summary>

   2: /// Implementation of the authentication frontend service

   3: /// </summary>

   4: public class Authentication: IAuthentication

   5: {

   6:     #region Private fields

   7:&#160; 

   8:     private AuthenticationServiceClient _authenticationServiceProxy;

   9:     private ProfileServiceClient _profileServiceProxy;

  10:     private RoleServiceClient _roleServiceProxy;

  11:&#160; 

  12:     #endregion

  13:&#160; 

  14:     #region Constructors

  15:&#160; 

  16:     /// <summary>

  17:     /// Initializes a new instance of the Authentication class.

  18:     /// </summary>

  19:     public Authentication()

  20:     {

  21:         _authenticationServiceProxy = ServiceFactory.CreateAuthenticationClient();

  22:         _profileServiceProxy = ServiceFactory.CreateProfileClient();

  23:         _roleServiceProxy = ServiceFactory.CreateRoleServiceClient();

  24:     }

  25:&#160; 

  26:     #endregion

  27:&#160; 

  28:     #region Public events

  29:&#160; 

  30:     /// <summary>

  31:     /// Occurs when the authentication operation is completed

  32:     /// </summary>

  33:     public event EventHandler<AsyncOperationCompletedEventArgs<bool>> AuthenticateCompleted;

  34:     

  35:     #endregion

  36:&#160; 

  37:     #region Public methods

  38:&#160; 

  39:     /// <summary>

  40:     /// Begins to authenticate the user

  41:     /// </summary>

  42:     /// <param name=&quot;userName&quot;>User name of the user</param>

  43:     /// <param name=&quot;password&quot;>Password of the user</param>

  44:     public void BeginAuthenticate(string userName, string password)

  45:     {

  46:         //TODO: Call first service here

  47:     }

  48:&#160; 

  49:     #endregion

  50: }

The authentication class shown in the codesnippet is a very large class and as such it’s not completedly printed here. There are however a few specifics that I would like to share, because these can help you prevent nasty threading issues in Silverlight when doing these kinds of service calls.

Starting the authentication process

The first step in the authentication process is the actual authentication and establishes a server-side identity. Once this is identity is established the process should call two other services to gather all the data required by the Silverlight application.

The easiest way to build this would be to Invoke the next service from the callback that is performed when the first service call completes. This however causes a really nasty bug in the threading mechanics of Silverlight.

The threading stuff that was used by the first service call doesn’t get cleaned up immediately, but rather after the final call in the chain has been completed. The effect of this is that your frontend will tend to hang up on itself. Animations start to stutter and the application may become unresponsive due to the large amounts of locks and threads that are active in the Silverlight application.

To resolve this issue you should chain service calls by queuing the next service call on the threadpool, rather than invoking it directly. This gives Silverlight the chance to clean up after itself, before performing the next service call(s).

   1: /// <summary>

   2: /// Implementation of the authentication service

   3: /// </summary>

   4: public class Authentication : IAuthentication

   5: {

   6:&#160; 

   7:     #region Private fields

   8:&#160; 

   9:     private AuthenticationServiceClient _authenticationServiceProxy;

  10:     private ProfileServiceClient _profileServiceProxy;

  11:     private RoleServiceClient _roleServiceProxy;

  12:&#160; 

  13:     #endregion

  14:&#160; 

  15:     #region Constructors

  16:&#160; 

  17:     /// <summary>

  18:     /// Initializes a new instance of the Authentication class.

  19:     /// </summary>

  20:     public Authentication()

  21:     {

  22:         _authenticationServiceProxy = ServiceFactory.CreateAuthenticationClient();

  23:         _profileServiceProxy = ServiceFactory.CreateProfileClient();

  24:         _roleServiceProxy = ServiceFactory.CreateRoleServiceClient();

  25:     }

  26:&#160; 

  27:     #endregion

  28:&#160; 

  29:     #region Public events

  30:&#160; 

  31:     /// <summary>

  32:     /// Occurs when the authentication operation is completed

  33:     /// </summary>

  34:     public event EventHandler<AsyncOperationCompletedEventArgs<bool>> AuthenticateCompleted;

  35:&#160; 

  36:     #endregion

  37:&#160; 

  38:     #region Public methods

  39:&#160; 

  40:     /// <summary>

  41:     /// Begins to authenticate the user

  42:     /// </summary>

  43:     /// <param name=&quot;userName&quot;>User name of the user</param>

  44:     /// <param name=&quot;password&quot;>Password of the user</param>

  45:     public void BeginAuthenticate(string userName, string password)

  46:     {

  47:         _authenticationServiceProxy.LoginCompleted += OnLoginCompleted;

  48:         _authenticationServiceProxy.LoginAsync(userName, password, &quot;forms&quot;, false, new LoginData());

  49:     }

  50:&#160; 

  51:     #endregion

  52:&#160; 

  53:     #region Private methods

  54:&#160; 

  55:     /// <summary>

  56:     /// Handles the LoginCompleted event of the authentication service client

  57:     /// </summary>

  58:     /// <param name=&quot;sender&quot;></param>

  59:     /// <param name=&quot;e&quot;></param>

  60:     private void OnLoginCompleted(object sender, LoginCompletedEventArgs e)

  61:     {

  62:         _authenticationServiceProxy.LoginCompleted -= OnLoginCompleted;

  63:&#160; 

  64:         // Abort the authenticate operation if there's a problem

  65:         if (e.Error != null)

  66:         {

  67:             OnAuthenticateCompleted(e.Error, false);

  68:             return;

  69:         }

  70:&#160; 

  71:         // Retrieve the user details after the login operation is completed successfully

  72:         if (e.Result)

  73:         {

  74:             LoginData data = (LoginData)e.UserState;

  75:             ThreadPool.QueueUserWorkItem(state => BeginGetRoles(data));

  76:         }

  77:     }

  78:&#160; 

  79:     /// <summary>

  80:     /// Begins to retrieve the roles of the user

  81:     /// </summary>

  82:     /// <param name=&quot;data&quot;></param>

  83:     private void BeginGetRoles(LoginData data)

  84:     {

  85:         _roleServiceProxy.GetRolesForCurrentUserCompleted += OnGetRolesForCurrentUserCompleted;

  86:         _roleServiceProxy.GetRolesForCurrentUserAsync(data);

  87:     }

  88:&#160; 

  89:     /// <summary>

  90:     /// Handles the GetRolesForCurrentUserCompleted event of the roles service

  91:     /// </summary>

  92:     /// <param name=&quot;sender&quot;></param>

  93:     /// <param name=&quot;e&quot;></param>

  94:     private void OnGetRolesForCurrentUserCompleted(object sender, GetRolesForCurrentUserCompletedEventArgs e)

  95:     {

  96:         // Abort the operation when retrieving the roles fails

  97:         if (e.Error != null)

  98:         {

  99:             OnAuthenticateCompleted(e.Error, false);

 100:             return;

 101:         }

 102:&#160; 

 103:         LoginData data = (LoginData)e.UserState;

 104:         data.Roles = e.Result;

 105:&#160; 

 106:         SecurityContext.Establish(data);

 107:         OnAuthenticateCompleted(null, true);

 108:     }

 109:&#160; 

 110:     /// <summary>

 111:     /// Raises the AuthenticateCompleted event

 112:     /// </summary>

 113:     /// <param name=&quot;error&quot;></param>

 114:     /// <param name=&quot;success&quot;></param>

 115:     private void OnAuthenticateCompleted(Exception error, bool success)

 116:     {

 117:         var handler = AuthenticateCompleted;

 118:&#160; 

 119:         if (handler != null)

 120:         {

 121:             AsyncOperationCompletedEventArgs<bool> eventargs =

 122:                 new AsyncOperationCompletedEventArgs<bool>(error, success);

 123:&#160; 

 124:             Deployment.Current.Dispatcher.BeginInvoke(() => handler(this, eventargs));

 125:         }

 126:     }

 127:&#160; 

 128:     #endregion

 129: }

Completing the authentication process

There are two parallel calls in the authentication components at the end of the authentication process. One is to retrieve the roles of the current user and the other is for retrieving the profile properties of the current user.

These two calls need to be completed before the authentication process is completed. The best way to do this is to have two wait handles that get signaled when the two webservice calls are completed. The following snippet demonstrates how you can build such a webservice call into your application:

   1: public class Authentication : IAuthentication, IDisposable

   2: {

   3:&#160; 

   4:     #region Private fields

   5:&#160; 

   6:     private AuthenticationServiceClient _authenticationServiceProxy;

   7:     private ProfileServiceClient _profileServiceProxy;

   8:     private RoleServiceClient _roleServiceProxy;

   9:     private ManualResetEvent _getRolesWaitHandle;

  10:     private ManualResetEvent _getProfilePropertiesWaitHandle;

  11:     private Exception _error;

  12:&#160; 

  13:     #endregion

  14:&#160; 

  15:     #region Constructors

  16:&#160; 

  17:     /// <summary>

  18:     /// Initializes a new instance of the Authentication class.

  19:     /// </summary>

  20:     public Authentication()

  21:     {

  22:         _authenticationServiceProxy = ServiceFactory.CreateAuthenticationClient();

  23:         _profileServiceProxy = ServiceFactory.CreateProfileClient();

  24:         _roleServiceProxy = ServiceFactory.CreateRoleServiceClient();

  25:&#160; 

  26:         // Initialize the wait handles

  27:         _getRolesWaitHandle = new ManualResetEvent(false);

  28:         _getProfilePropertiesWaitHandle = new ManualResetEvent(false);

  29:     }

  30:&#160; 

  31:     #endregion

  32:&#160; 

  33:     #region Public events

  34:&#160; 

  35:     /// <summary>

  36:     /// Occurs when the authentication operation is completed

  37:     /// </summary>

  38:     public event EventHandler<AsyncOperationCompletedEventArgs<bool>> AuthenticateCompleted;

  39:&#160; 

  40:     #endregion

  41:&#160; 

  42:     #region Public methods

  43:&#160; 

  44:     /// <summary>

  45:     /// Begins to authenticate the user

  46:     /// </summary>

  47:     /// <param name=&quot;userName&quot;>User name of the user</param>

  48:     /// <param name=&quot;password&quot;>Password of the user</param>

  49:     public void BeginAuthenticate(string userName, string password)

  50:     {

  51:         _authenticationServiceProxy.LoginCompleted += OnLoginCompleted;

  52:         _authenticationServiceProxy.LoginAsync(userName, password, &quot;forms&quot;, false, new LoginData());

  53:     }

  54:&#160; 

  55:     #endregion

  56:&#160; 

  57:     #region Private methods

  58:&#160; 

  59:     /// <summary>

  60:     /// Begins to retrieve the user profile properties

  61:     /// </summary>

  62:     /// <param name=&quot;data&quot;></param>

  63:     private void BeginGetUserDetails(LoginData data)

  64:     {

  65:         _profileServiceProxy.GetAllPropertiesForCurrentUserCompleted += OnGetPropertiesForCurrentUserCompleted;

  66:         _profileServiceProxy.GetAllPropertiesForCurrentUserAsync(true, data);

  67:     }

  68:&#160; 

  69:     /// <summary>

  70:     /// Begins to retrieve the roles of the user

  71:     /// </summary>

  72:     /// <param name=&quot;data&quot;></param>

  73:     private void BeginGetRoles(LoginData data)

  74:     {

  75:         _roleServiceProxy.GetRolesForCurrentUserCompleted += OnGetRolesForCurrentUserCompleted;

  76:         _roleServiceProxy.GetRolesForCurrentUserAsync(data);

  77:     }

  78:&#160; 

  79:     /// <summary>

  80:     /// Handles the GetPropertiesForCurrentUserCompleted event of the profile service

  81:     /// </summary>

  82:     /// <param name=&quot;sender&quot;></param>

  83:     /// <param name=&quot;e&quot;></param>

  84:     private void OnGetPropertiesForCurrentUserCompleted(object sender, GetAllPropertiesForCurrentUserCompletedEventArgs e)

  85:     {

  86:         _profileServiceProxy.GetAllPropertiesForCurrentUserCompleted -= OnGetPropertiesForCurrentUserCompleted;

  87:&#160; 

  88:         // Abort the operation when retrieving the profile properties fails

  89:         if (e.Error != null)

  90:         {

  91:             _error = e.Error;

  92:             _getProfilePropertiesWaitHandle.Set();

  93:&#160; 

  94:             return;

  95:         }

  96:&#160; 

  97:         LoginData data = (LoginData)e.UserState;

  98:         data.ProfileProperties = e.Result;

  99:&#160; 

 100:         _getProfilePropertiesWaitHandle.Set();

 101:     }

 102:&#160; 

 103:     /// <summary>

 104:     /// Handles the LoginCompleted event of the authentication service client

 105:     /// </summary>

 106:     /// <param name=&quot;sender&quot;></param>

 107:     /// <param name=&quot;e&quot;></param>

 108:     private void OnLoginCompleted(object sender, LoginCompletedEventArgs e)

 109:     {

 110:         _authenticationServiceProxy.LoginCompleted -= OnLoginCompleted;

 111:&#160; 

 112:         // Abort the authenticate operation if there's a problem

 113:         if (e.Error != null)

 114:         {

 115:             OnAuthenticateCompleted(e.Error, false);

 116:             return;

 117:         }

 118:&#160; 

 119:         // Retrieve the user details after the login operation is completed successfully

 120:         if (e.Result)

 121:         {

 122:             LoginData data = (LoginData)e.UserState;

 123:&#160; 

 124:             // Create the completion thread that is going to wait for the next two calls to complete

 125:             Thread completionThread = new Thread(CompleteAuthenticationProcess);

 126:             completionThread.Start();

 127:&#160; 

 128:             // Queue the next two calls

 129:             ThreadPool.QueueUserWorkItem(state => BeginGetUserDetails(data));

 130:             ThreadPool.QueueUserWorkItem(state => BeginGetRoles(data));

 131:         }

 132:     }

 133:&#160; 

 134:     // ... Other methods

 135:&#160; 

 136:     #endregion

 137:&#160; 

 138:     #region IDisposable members

 139:&#160; 

 140:     /// <summary>

 141:     /// Disposes any unmanaged resources

 142:     /// </summary>

 143:     public void Dispose()

 144:     {

 145:         Dispose(true);

 146:         GC.SuppressFinalize(this);

 147:     }

 148:&#160; 

 149:     /// <summary>

 150:     /// Disposes any the unmanaged resources used

 151:     /// </summary>

 152:     /// <param name=&quot;disposing&quot;></param>

 153:     protected virtual void Dispose(bool disposing)

 154:     {

 155:         if (disposing)

 156:         {

 157:             ((IDisposable)_getRolesWaitHandle).Dispose();

 158:             ((IDisposable)_getProfilePropertiesWaitHandle).Dispose();

 159:&#160; 

 160:             _getProfilePropertiesWaitHandle = null;

 161:             _getRolesWaitHandle = null;

 162:         }

 163:     }

 164:&#160; 

 165:     #endregion        

 166: }

To make the solution complete, there should be a third thread in the application that should wait for these two wait handles and only continue when these two handles have been set. When the two handles have been set it should complete the authentication process by building a principal and raise the completed event. The following sample shows the last step for synchronizing two webservice calls:

   1: /// <summary>

   2: /// Completes the authentication process

   3: /// </summary>

   4: private void CompleteAuthenticationProcess()

   5: {

   6:     WaitHandle.WaitAll(new WaitHandle[] 

   7:     {

   8:         _getProfilePropertiesWaitHandle,

   9:         _getRolesWaitHandle

  10:     });

  11:&#160; 

  12:     // If this thread can complete the authentication step was succesfull.

  13:     // There could however be other problems, hence the error variable.

  14:     OnAuthenticateCompleted(_error, true);

  15: }

  16:&#160; 

  17: /// <summary>

  18: /// Raises the AuthenticateCompleted event

  19: /// </summary>

  20: /// <param name=&quot;error&quot;></param>

  21: /// <param name=&quot;success&quot;></param>

  22: private void OnAuthenticateCompleted(Exception error, bool success)

  23: {

  24:     var handler = AuthenticateCompleted;

  25:&#160; 

  26:     if (handler != null)

  27:     {

  28:         AsyncOperationCompletedEventArgs<bool> eventargs =

  29:             new AsyncOperationCompletedEventArgs<bool>(error, success);

  30:&#160; 

  31:         Deployment.Current.Dispatcher.BeginInvoke(() => handler(this, eventargs));

  32:     }

  33: }

The SecurityContext class

When the AuthenticateCompleted event is raised you can build a custom principal based on the authentication data gathered by the authentication frontend service.

I’ve created a specialized class for converting authentication data into a principle Inside the TaskTracker application named SecurityContext.&#160; This component is responsible for keeping track of the principal and for setting up the necessary calls to initialize the security context of the application. The following snippet shows this class.

   1: /// <summary>

   2: /// Contains security information about the application

   3: /// </summary>

   4: public class SecurityContext

   5: {

   6:     #region Private fields

   7:&#160; 

   8:     private IPrincipal _currentPrincipal;

   9:     

  10:     #endregion

  11:&#160; 

  12:     #region Constructors

  13:&#160; 

  14:     /// <summary>

  15:     /// Initializes a new instance of <see cref=&quot;SecurityContext&quot;/>

  16:     /// </summary>

  17:     /// <param name=&quot;data&quot;></param>

  18:     public SecurityContext(LoginData data)

  19:     {

  20:         _currentPrincipal = new TaskTrackerPrincipal(data);

  21:     }

  22:&#160; 

  23:     #endregion

  24:&#160; 

  25:     #region Singleton mechanics

  26:&#160; 

  27:     private static object _lockHandle = new object();

  28:     private static SecurityContext _currentContext;

  29:&#160; 

  30:     /// <summary>

  31:     /// Gets the current security context

  32:     /// </summary>

  33:     public static SecurityContext Current

  34:     {

  35:         get

  36:         {

  37:             lock (_lockHandle)

  38:             {

  39:                 return _currentContext;

  40:             }

  41:         }

  42:     }

  43:&#160; 

  44:     /// <summary>

  45:     /// Establishes a new login context

  46:     /// </summary>

  47:     /// <param name=&quot;data&quot;></param>

  48:     public static void Establish(LoginData data)

  49:     {

  50:         lock (_lockHandle)

  51:         {

  52:             _currentContext = new SecurityContext(data);

  53:         }

  54:     }

  55:&#160; 

  56:     #endregion

  57:&#160; 

  58:     #region Public properties

  59:&#160; 

  60:     /// <summary>

  61:     /// Gets the current principal

  62:     /// </summary>

  63:     public IPrincipal Principal

  64:     {

  65:         get

  66:         {

  67:             return _currentPrincipal;

  68:         }

  69:     }

  70:     

  71:     #endregion

  72: }

The principal and identity are created by implementing the IPrincipal and IIdentity interface that you may already know from the .NET Framework.

   1: /// <summary>

   2: /// Represents a tasktracker identity

   3: /// </summary>

   4: public class TaskTrackerIdentity: IIdentity

   5: {

   6:     #region Private fields

   7:&#160; 

   8:     private Dictionary<string, object> _profileProperties;

   9:     private string _name;

  10:&#160; 

  11:     #endregion

  12:&#160; 

  13:     #region Constructor

  14:&#160; 

  15:     /// <summary>

  16:     /// Initializes a new instance of the TaskTrackerIdentity class.

  17:     /// </summary>

  18:     /// <param name=&quot;name&quot;></param>

  19:     /// <param name=&quot;profileProperties&quot;></param>

  20:     public TaskTrackerIdentity(string name, Dictionary<string, object> profileProperties)

  21:     {

  22:         _profileProperties = profileProperties;

  23:         _name = name;

  24:     }

  25:&#160; 

  26:     #endregion

  27:&#160; 

  28:     #region Public properties

  29:&#160; 

  30:     /// <summary>

  31:     /// Gets the type of authentication used

  32:     /// </summary>

  33:     public string AuthenticationType

  34:     {

  35:         get { return &quot;Forms&quot;; }

  36:     }

  37:&#160; 

  38:     /// <summary>

  39:     /// Gets whether this user is authenticated

  40:     /// </summary>

  41:     public bool IsAuthenticated

  42:     {

  43:         get { return true; }

  44:     }

  45:&#160; 

  46:     /// <summary>

  47:     /// Gets the name of the identity

  48:     /// </summary>

  49:     public string Name

  50:     {

  51:         get { return _name; }

  52:     }

  53:&#160; 

  54:     #endregion

  55: }

  56:&#160; 

  57: /// <summary>

  58: /// Custom task tracker principal

  59: /// </summary>

  60: public class TaskTrackerPrincipal: IPrincipal

  61: {

  62:     #region Private fields

  63:&#160; 

  64:     private TaskTrackerIdentity _identity;

  65:     private IEnumerable<string> _roles;

  66:&#160; 

  67:     #endregion

  68:&#160; 

  69:     #region Constructors

  70:&#160; 

  71:     /// <summary>

  72:     /// Initializes a new instance of the TaskTrackerPrincipal class.

  73:     /// </summary>

  74:     public TaskTrackerPrincipal(LoginData data)

  75:     {

  76:         _roles = data.Roles;

  77:         _identity = new TaskTrackerIdentity(data.UserName, data.ProfileProperties);

  78:     }

  79:&#160; 

  80:     #endregion

  81:&#160; 

  82:     #region Public methods

  83:&#160; 

  84:     /// <summary>

  85:     /// Validates if this principal is in a particular role

  86:     /// </summary>

  87:     /// <param name=&quot;role&quot;></param>

  88:     /// <returns></returns>

  89:     public bool IsInRole(string role)

  90:     {

  91:         return _roles.Contains(role);

  92:     }

  93:     

  94:     #endregion

  95:&#160; 

  96:     #region Public properties

  97:&#160; 

  98:     /// <summary>

  99:     /// Gets the identity of the principal

 100:     /// </summary>

 101:     public IIdentity Identity

 102:     {

 103:         get { return _identity; }

 104:     }

 105:&#160; 

 106:     #endregion

 107: }

Security considerations

It’s important to know that while the application in principal is now secured through the use of the ASP.NET authentication service and role service, it’s important to pay extra attention some of the security aspects of the application.

SSL connections

Because this solution uses forms authentication all passwords are transferred in plain text towards the server. The use of a SSL connection is mandatory to eliminate the possibility for a hacker to capture passwords that are transmitted to the server.

Larger attack surface of application platform services

With the ASP.NET application services exposed as WCF services there’s a larger attack surface that hackers can use to attack your application. Setting up a working remote-access policy file for Silverlight is important to prevent abuse by unauthorized Silverlight and Flash applications that try to access the services.

It’s also a good idea to enable the least amount of services possible. For example, leave the profile service disabled if you don’t need it. This saves you a whole lot of trouble in the long run.

Conclusion

The application services offered by ASP.NET greatly help the development of secure Silverlight applications. This article explains how you can use the application services in a plain vanilla Silverlight application. They are however also available to developers working with WCF RIA services.