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.
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="TaskTrackerMembershipProvider">
2: <providers>
3: <add name="TaskTrackerMembershipProvider" type="System.Web.Security.SqlMembershipProvider"
4: connectionStringName="ApplicationServices"
5: enablePasswordRetrieval="false"
6: enablePasswordReset="true"
7: requiresQuestionAndAnswer="false"
8: requiresUniqueEmail="false"
9: maxInvalidPasswordAttempts="5"
10: minRequiredPasswordLength="6"
11: minRequiredNonalphanumericCharacters="0"
12: passwordAttemptWindow="10"
13: applicationName="/" />
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="C#" Service="System.Web.ApplicationServices.AuthenticationService" %>
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="true" />
4: <behaviors>
5: <serviceBehaviors>
6: <!-- New feature in .NET 4, default behaviors -->
7: <behavior>
8: <serviceMetadata httpGetEnabled="true"/>
9: <serviceDebug includeExceptionDetailInFaults="true"/>
10: </behavior>
11: </serviceBehaviors>
12: </behaviors>
13: <services>
14: <service name="System.Web.ApplicationServices.AuthenticationService">
15: <endpoint address="" binding="basicHttpBinding" contract="System.Web.ApplicationServices.AuthenticationService"/>
16: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
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="true"/>
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="true" defaultProvider="TaskTrackerRoleProvider">
2: <providers>
3: <add connectionStringName="ApplicationServices" applicationName="/"
4: name="TaskTrackerRoleProvider" type="System.Web.Security.SqlRoleProvider" />
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="C#" Service="System.Web.ApplicationServices.RoleService" %>
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="System.Web.ApplicationServices.RoleService">
4: <endpoint address="" binding="basicHttpBinding" contract="System.Web.ApplicationServices.RoleService"/>
5: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
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="true"/>
5: <roleService enabled="true"/>
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="true" defaultProvider="TaskTrackerProfileProvider">
2: <providers>
3: <add name="TaskTrackerProfileProvider"
4: type="System.Web.Profile.SqlProfileProvider"
5: connectionStringName="ApplicationServices"
6: applicationName="/" />
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="C#" Service="System.Web.ApplicationServices.ProfileService" %>
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="System.Web.ApplicationServices.ProfileService">
4: <endpoint address="" binding="basicHttpBinding" contract="System.Web.ApplicationServices.ProfileService"/>
5: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
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="true"/>
5: <roleService enabled="true"/>
6: <profileService enabled="true"/>
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.
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="application">Running application</param>
11: /// <param name="path">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: 
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: 
22: if (clientBinFolderPath.ToLower() == "clientbin")
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: 
33: return new Uri(String.Format("{0}{1}{2}", 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: 
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("Services/Authentication.svc");
15: Binding serviceBinding = CreateBindingForAddress(serviceAddress);
16:
17: EndpointAddressBuilder addressBuilder = new EndpointAddressBuilder();
18: addressBuilder.Uri = serviceAddress;
19: 
20: return new AuthenticationServiceClient(serviceBinding, addressBuilder.ToEndpointAddress());
21: }
22: 
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("Services/Profile.svc");
30: Binding serviceBinding = CreateBindingForAddress(serviceAddress);
31: 
32: EndpointAddressBuilder addressBuilder = new EndpointAddressBuilder();
33: addressBuilder.Uri = serviceAddress;
34: 
35: return new ProfileServiceClient(serviceBinding, addressBuilder.ToEndpointAddress());
36: }
37: 
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("Services/Roles.svc");
45: Binding serviceBinding = CreateBindingForAddress(serviceAddress);
46: 
47: EndpointAddressBuilder addressBuilder = new EndpointAddressBuilder();
48: addressBuilder.Uri = serviceAddress;
49: 
50: return new RoleServiceClient(serviceBinding, addressBuilder.ToEndpointAddress());
51: }
52:
53: #endregion
54: 
55: #region Private methods
56: 
57: /// <summary>
58: /// Creates a binding for the specified address
59: /// </summary>
60: /// <param name="address">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: 
66: // Enable transport security when the URI points to a SSL-enabled location
67: if (address.Scheme.ToLower() == "https")
68: {
69: securityMode = BasicHttpSecurityMode.Transport;
70: }
71: 
72: return new BasicHttpBinding(securityMode);
73: }
74: 
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.
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: 
8: private AuthenticationServiceClient _authenticationServiceProxy;
9: private ProfileServiceClient _profileServiceProxy;
10: private RoleServiceClient _roleServiceProxy;
11: 
12: #endregion
13: 
14: #region Constructors
15: 
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: 
26: #endregion
27: 
28: #region Public events
29: 
30: /// <summary>
31: /// Occurs when the authentication operation is completed
32: /// </summary>
33: public event EventHandler<AsyncOperationCompletedEventArgs<bool>> AuthenticateCompleted;
34:
35: #endregion
36: 
37: #region Public methods
38: 
39: /// <summary>
40: /// Begins to authenticate the user
41: /// </summary>
42: /// <param name="userName">User name of the user</param>
43: /// <param name="password">Password of the user</param>
44: public void BeginAuthenticate(string userName, string password)
45: {
46: //TODO: Call first service here
47: }
48: 
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: 
7: #region Private fields
8: 
9: private AuthenticationServiceClient _authenticationServiceProxy;
10: private ProfileServiceClient _profileServiceProxy;
11: private RoleServiceClient _roleServiceProxy;
12: 
13: #endregion
14: 
15: #region Constructors
16: 
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: 
27: #endregion
28: 
29: #region Public events
30: 
31: /// <summary>
32: /// Occurs when the authentication operation is completed
33: /// </summary>
34: public event EventHandler<AsyncOperationCompletedEventArgs<bool>> AuthenticateCompleted;
35: 
36: #endregion
37: 
38: #region Public methods
39: 
40: /// <summary>
41: /// Begins to authenticate the user
42: /// </summary>
43: /// <param name="userName">User name of the user</param>
44: /// <param name="password">Password of the user</param>
45: public void BeginAuthenticate(string userName, string password)
46: {
47: _authenticationServiceProxy.LoginCompleted += OnLoginCompleted;
48: _authenticationServiceProxy.LoginAsync(userName, password, "forms", false, new LoginData());
49: }
50: 
51: #endregion
52: 
53: #region Private methods
54: 
55: /// <summary>
56: /// Handles the LoginCompleted event of the authentication service client
57: /// </summary>
58: /// <param name="sender"></param>
59: /// <param name="e"></param>
60: private void OnLoginCompleted(object sender, LoginCompletedEventArgs e)
61: {
62: _authenticationServiceProxy.LoginCompleted -= OnLoginCompleted;
63: 
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: 
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: 
79: /// <summary>
80: /// Begins to retrieve the roles of the user
81: /// </summary>
82: /// <param name="data"></param>
83: private void BeginGetRoles(LoginData data)
84: {
85: _roleServiceProxy.GetRolesForCurrentUserCompleted += OnGetRolesForCurrentUserCompleted;
86: _roleServiceProxy.GetRolesForCurrentUserAsync(data);
87: }
88: 
89: /// <summary>
90: /// Handles the GetRolesForCurrentUserCompleted event of the roles service
91: /// </summary>
92: /// <param name="sender"></param>
93: /// <param name="e"></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: 
103: LoginData data = (LoginData)e.UserState;
104: data.Roles = e.Result;
105: 
106: SecurityContext.Establish(data);
107: OnAuthenticateCompleted(null, true);
108: }
109: 
110: /// <summary>
111: /// Raises the AuthenticateCompleted event
112: /// </summary>
113: /// <param name="error"></param>
114: /// <param name="success"></param>
115: private void OnAuthenticateCompleted(Exception error, bool success)
116: {
117: var handler = AuthenticateCompleted;
118: 
119: if (handler != null)
120: {
121: AsyncOperationCompletedEventArgs<bool> eventargs =
122: new AsyncOperationCompletedEventArgs<bool>(error, success);
123: 
124: Deployment.Current.Dispatcher.BeginInvoke(() => handler(this, eventargs));
125: }
126: }
127: 
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: 
4: #region Private fields
5: 
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: 
13: #endregion
14: 
15: #region Constructors
16: 
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: // Initialize the wait handles
27: _getRolesWaitHandle = new ManualResetEvent(false);
28: _getProfilePropertiesWaitHandle = new ManualResetEvent(false);
29: }
30: 
31: #endregion
32: 
33: #region Public events
34: 
35: /// <summary>
36: /// Occurs when the authentication operation is completed
37: /// </summary>
38: public event EventHandler<AsyncOperationCompletedEventArgs<bool>> AuthenticateCompleted;
39: 
40: #endregion
41: 
42: #region Public methods
43: 
44: /// <summary>
45: /// Begins to authenticate the user
46: /// </summary>
47: /// <param name="userName">User name of the user</param>
48: /// <param name="password">Password of the user</param>
49: public void BeginAuthenticate(string userName, string password)
50: {
51: _authenticationServiceProxy.LoginCompleted += OnLoginCompleted;
52: _authenticationServiceProxy.LoginAsync(userName, password, "forms", false, new LoginData());
53: }
54: 
55: #endregion
56: 
57: #region Private methods
58: 
59: /// <summary>
60: /// Begins to retrieve the user profile properties
61: /// </summary>
62: /// <param name="data"></param>
63: private void BeginGetUserDetails(LoginData data)
64: {
65: _profileServiceProxy.GetAllPropertiesForCurrentUserCompleted += OnGetPropertiesForCurrentUserCompleted;
66: _profileServiceProxy.GetAllPropertiesForCurrentUserAsync(true, data);
67: }
68: 
69: /// <summary>
70: /// Begins to retrieve the roles of the user
71: /// </summary>
72: /// <param name="data"></param>
73: private void BeginGetRoles(LoginData data)
74: {
75: _roleServiceProxy.GetRolesForCurrentUserCompleted += OnGetRolesForCurrentUserCompleted;
76: _roleServiceProxy.GetRolesForCurrentUserAsync(data);
77: }
78: 
79: /// <summary>
80: /// Handles the GetPropertiesForCurrentUserCompleted event of the profile service
81: /// </summary>
82: /// <param name="sender"></param>
83: /// <param name="e"></param>
84: private void OnGetPropertiesForCurrentUserCompleted(object sender, GetAllPropertiesForCurrentUserCompletedEventArgs e)
85: {
86: _profileServiceProxy.GetAllPropertiesForCurrentUserCompleted -= OnGetPropertiesForCurrentUserCompleted;
87: 
88: // Abort the operation when retrieving the profile properties fails
89: if (e.Error != null)
90: {
91: _error = e.Error;
92: _getProfilePropertiesWaitHandle.Set();
93: 
94: return;
95: }
96: 
97: LoginData data = (LoginData)e.UserState;
98: data.ProfileProperties = e.Result;
99: 
100: _getProfilePropertiesWaitHandle.Set();
101: }
102: 
103: /// <summary>
104: /// Handles the LoginCompleted event of the authentication service client
105: /// </summary>
106: /// <param name="sender"></param>
107: /// <param name="e"></param>
108: private void OnLoginCompleted(object sender, LoginCompletedEventArgs e)
109: {
110: _authenticationServiceProxy.LoginCompleted -= OnLoginCompleted;
111: 
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: 
119: // Retrieve the user details after the login operation is completed successfully
120: if (e.Result)
121: {
122: LoginData data = (LoginData)e.UserState;
123: 
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: 
128: // Queue the next two calls
129: ThreadPool.QueueUserWorkItem(state => BeginGetUserDetails(data));
130: ThreadPool.QueueUserWorkItem(state => BeginGetRoles(data));
131: }
132: }
133: 
134: // ... Other methods
135: 
136: #endregion
137: 
138: #region IDisposable members
139: 
140: /// <summary>
141: /// Disposes any unmanaged resources
142: /// </summary>
143: public void Dispose()
144: {
145: Dispose(true);
146: GC.SuppressFinalize(this);
147: }
148: 
149: /// <summary>
150: /// Disposes any the unmanaged resources used
151: /// </summary>
152: /// <param name="disposing"></param>
153: protected virtual void Dispose(bool disposing)
154: {
155: if (disposing)
156: {
157: ((IDisposable)_getRolesWaitHandle).Dispose();
158: ((IDisposable)_getProfilePropertiesWaitHandle).Dispose();
159: 
160: _getProfilePropertiesWaitHandle = null;
161: _getRolesWaitHandle = null;
162: }
163: }
164: 
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: 
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: 
17: /// <summary>
18: /// Raises the AuthenticateCompleted event
19: /// </summary>
20: /// <param name="error"></param>
21: /// <param name="success"></param>
22: private void OnAuthenticateCompleted(Exception error, bool success)
23: {
24: var handler = AuthenticateCompleted;
25: 
26: if (handler != null)
27: {
28: AsyncOperationCompletedEventArgs<bool> eventargs =
29: new AsyncOperationCompletedEventArgs<bool>(error, success);
30: 
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.  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: 
8: private IPrincipal _currentPrincipal;
9:
10: #endregion
11: 
12: #region Constructors
13: 
14: /// <summary>
15: /// Initializes a new instance of <see cref="SecurityContext"/>
16: /// </summary>
17: /// <param name="data"></param>
18: public SecurityContext(LoginData data)
19: {
20: _currentPrincipal = new TaskTrackerPrincipal(data);
21: }
22: 
23: #endregion
24: 
25: #region Singleton mechanics
26: 
27: private static object _lockHandle = new object();
28: private static SecurityContext _currentContext;
29: 
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: 
44: /// <summary>
45: /// Establishes a new login context
46: /// </summary>
47: /// <param name="data"></param>
48: public static void Establish(LoginData data)
49: {
50: lock (_lockHandle)
51: {
52: _currentContext = new SecurityContext(data);
53: }
54: }
55: 
56: #endregion
57: 
58: #region Public properties
59: 
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: 
8: private Dictionary<string, object> _profileProperties;
9: private string _name;
10: 
11: #endregion
12: 
13: #region Constructor
14: 
15: /// <summary>
16: /// Initializes a new instance of the TaskTrackerIdentity class.
17: /// </summary>
18: /// <param name="name"></param>
19: /// <param name="profileProperties"></param>
20: public TaskTrackerIdentity(string name, Dictionary<string, object> profileProperties)
21: {
22: _profileProperties = profileProperties;
23: _name = name;
24: }
25: 
26: #endregion
27: 
28: #region Public properties
29: 
30: /// <summary>
31: /// Gets the type of authentication used
32: /// </summary>
33: public string AuthenticationType
34: {
35: get { return "Forms"; }
36: }
37: 
38: /// <summary>
39: /// Gets whether this user is authenticated
40: /// </summary>
41: public bool IsAuthenticated
42: {
43: get { return true; }
44: }
45: 
46: /// <summary>
47: /// Gets the name of the identity
48: /// </summary>
49: public string Name
50: {
51: get { return _name; }
52: }
53: 
54: #endregion
55: }
56: 
57: /// <summary>
58: /// Custom task tracker principal
59: /// </summary>
60: public class TaskTrackerPrincipal: IPrincipal
61: {
62: #region Private fields
63: 
64: private TaskTrackerIdentity _identity;
65: private IEnumerable<string> _roles;
66: 
67: #endregion
68: 
69: #region Constructors
70: 
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: 
80: #endregion
81: 
82: #region Public methods
83: 
84: /// <summary>
85: /// Validates if this principal is in a particular role
86: /// </summary>
87: /// <param name="role"></param>
88: /// <returns></returns>
89: public bool IsInRole(string role)
90: {
91: return _roles.Contains(role);
92: }
93:
94: #endregion
95: 
96: #region Public properties
97: 
98: /// <summary>
99: /// Gets the identity of the principal
100: /// </summary>
101: public IIdentity Identity
102: {
103: get { return _identity; }
104: }
105: 
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.