Now that the server has been configured to authenticate and register users and restrict access to data and operations accordingly, we should look at implementing the client-side security aspects of this functionality. In this section, we'll take a look at how to authenticate and register users on the client; get information about the user, such as their roles and profile; and restrict users from parts of the application based on their authentication status or assigned roles.
Let's take a look at what is already in the Silverlight project to support user authentication. When running the Silverlight project, you will find a login hyperlink at the top right of the window, below the About button. This will pop up a Login dialog, shown in Figure 8-3, that you can use to log into the system.
The first step you need to take is to actually register as a user, and you can do so by clicking the Register Now hyperlink in the bottom left of the Login dialog. This pops up a registration screen like the one shown in Figure 8-4.
Note You can modify the required password strength in the Web project's web.config
file via the properties of the ASP.NET Membership provider, particularly its minRequiredPasswordLength
and minRequiredNonalphanumericCharacters
properties.
The Login and Registration screens communicate with the AuthenticationService
and UserRegistrationService
, respectively, on the server, as discussed earlier. Because these services are standard domain services, a corresponding context will be created for each service in the Silverlight client application (AuthenticationContext
for the AuthenticationService
service and UserRegistrationContext
for the UserRegistrationService
service).
However, although the UserRegistrationContext
class is instantiated like all other domain contexts, the AuthenticationContext
is rarely used. Instead, you use the FormsAuthentication
or WindowsAuthentication
classes, depending on what type of authentication you are using; both are provided by RIA Services.
Inside the App.xaml.cs
file, you will find the following code in the App
constructor:
WebContext webContext = new WebContext();
webContext.Authentication = new FormsAuthentication();
//webContext.Authentication = new WindowsAuthentication();
this.ApplicationLifetimeObjects.Add(webContext);
As you can see, the WebContext
object is instantiated (its class generated by RIA Services); an instance of the authentication provider it should use is assigned to its Authentication
property, and the WebContext
object is then added to the application's ApplicationLifetimeObjects
collection.
Note The WebContext
object needs to be kept alive for the lifetime of the application, because it is used to maintain the current authentication status of the user and how the application authenticates with the server. It should be created when the application is started and not destroyed until the application is closed. Therefore, it is added to the Application
object's ApplicationLifetimeObjects
collection, ensuring it stays alive throughout the application's lifetime.
By default, the application uses Forms Authentication, but you can comment out the FormsAuthentication
line and uncomment the WindowsAuthentication
line to use Windows Authentication instead.
Note The authentication provider you select must match what you have configured in the web.config
file in your Web project.
You will find a full example in the code behind the LoginForm
view, but the following line demonstrates calling the Login
method of the authentication object, configured in the application's constructor, passing it a username and password:
LoginOperation op = WebContext.Current.Authentication.Login(userName, password);
When the user has successfully been logged into the system, the Completed
event will be raised on the LoginOperation
object, which is returned when calling the authentication object's Login
method. Alternatively, you can handle the LoggedIn
event on the authentication object itself, which can be handled application-wide. There is also a LoggedOut
event on the same object, which will be raised when the user logs out of the application.
To register a new user, you need to populate a new RegistrationData
entity with the user details and then pass it and the user's password to the CreateUser
method of the RegistrationContext
object, for example:
RegistrationData newUser = new RegistrationData();
newUser.UserName = userName;
newUser.FriendlyName = friendlyName;
// Populate any other properties on the RegistrationData object as required
UserRegistrationContext context = new UserRegistrationContext();
var op = context.CreateUser(newUser, password);
As with the login behavior, you will find the full example of the code required to register a user already implemented in the RegistrationForm
view in the project.
Note The registration form registers the user and then immediately logs into the system as that user. If you are implementing the scenario described earlier in this chapter where an administrator creates users, rather than users registering themselves, you will want to disable this behavior. To do so, instead of calling the Login
method once the user registration is complete, simply close the window.
Once the users are logged in, you may wish to customize the user interface to their needs (that is, target the user experience), read and save user preferences, display some information about a particular user, such as a name, or restrict access to certain functionality based on assigned roles. All of this is available to you from the User
object that RIA Services exposes on the client through the static Current
property on the WebContext
class.
To start with, determining whether the current user has logged in and is authenticated simply requires you to check the IsAuthenticated
property of the User
object:
bool isUserAuthenticated = WebContext.Current.User.IsAuthenticated;
You can get an array of all the roles that the user is assigned to via the Roles
property of the User
object:
string[] userRoles = (string[])WebContext.Current.User.Roles;
Alternatively, you can simply check whether a user is in a given role using the IsInRole
method of the User
object:
bool isInAdminRole = WebContext.Current.User.IsInRole("Administrators");
Note Best practice is to always specify each of your roles as constants and to use those instead of hard-coding the role names when checking whether a user is in a given role.
Any properties that you defined on the User
object in the Web project will automatically have corresponding properties created on the User
object in the Silverlight project. These are automatically populated with the data from the database when the user is logged in, and this object is returned to the client. Therefore, as soon as the user is successfully logged in, you can start accessing profile settings via these properties. For example, the custom property FriendlyName
that was created as a part of the Silverlight Business Application project template can be accessed from the client in the following manner:
string friendlyName = WebContext.Current.User.FriendlyName;
If you are storing user profile data, such as user preferences, that you have changed on the User
object and you now want to update on the server, simply call the SaveUser
method on the AuthenticationContext
domain context:
WebContext.Current.User.FriendlyName = "Chris Anderson";
WebContext.Current.Authentication.SaveUser(true);
Although securing access to data and operations should be handled on the server, it can also be useful to implement additional restrictions on the client side. For example, you might want to hide parts of the application while the user is not logged in, or hide parts that the user does not have permission to access. To do so, simply use the methods described in the previous section to determine whether the user is logged in and what roles that user is assigned. The strategies that you might choose for implementing client-side restrictions will generally be one of the following:
Which strategy you choose really depends on the nature and design of the application. The first option can be simply implemented in code using the methods described in the previous section before performing the requested function. However, there are a few different means to implement the other two options:
IsEnabled
/Visibility
properties to.www.silverlightshow.net/items/A-RoleManager-to-apply-roles-declaratively-to-user-interface.aspx
. The RIA Services team has also released a sample project implementing a similar method, which you can download from the WCF RIA Services Code Gallery, in the sample named “Authorization Based Dynamic UI and Navigation,” found here: http://code.msdn.microsoft.com/RiaServices
. Kyle McClellan has written instructions for using the library from this sample project here: http://blogs.msdn.com/b/kylemc/archive/2010/05/03/authorization-sample-101.aspx
.User
and AuthenticationContext
objects in XAML. If you open the code-behind for the App.xaml
file, you will find that the current WebContext
object is added to the application's resources when the application starts up. This means you can bind to it just as you would any other resource in XAML. If you want to bind to the Visibility
property of a control, you will need to write a value converter to convert a Boolean value to a Visibility
enumeration (value converters will be covered in Chapter 11). If you want to control whether the control is disabled or hidden based on the roles that the user is assigned, you can write a value converter to do that too. Here is a simple example of declaratively enabling a control only when the user is logged into the system:<Button Content="Click Me!"
IsEnabled="{Binding Path=User.IsAuthenticated,
Source={StaticResource WebContext}}" />
Ultimately, the best choice from the preceding options really depends on the structure of your project. If you are using the MVVM design pattern, the second option tends to give the cleanest and most easily maintained solution.
Note Simply preventing users from getting to a view by hiding or disabling the button that opens it won't stop them from opening that view. As described in Chapter 3, you can navigate directly to a view simply by entering the URI that opens it in the address bar of the web browser. This means that users can bypass your security measures based on what's available in the user interface and go directly to a view, if they know or can guess the URI. Therefore, you should always check that users have permission to access a view when they navigate to it and redirect them to a different view if they don't. Alternatively, you could implement a custom content loader for the navigation framework that provides this functionality, as detailed by David Poll his blog post at http://www.davidpoll.com/2010/01/01/opening-up-silverlight-4-navigation-authenticationauthorization-in-an-inavigationcontentloader/
and continued at http://www.davidpoll.com/2010/04/25/a-refreshing-authenticationauthorization-experience-with-silverlight-4/
.
It's worth noting that any data stored in isolated storage, which will be discussed in Chapter 16, should not be considered safe. Other Silverlight applications cannot access isolated storage for this application, but data is stored as files on disk and can be read like any other file. The data is usually stored somewhere in the %AppData%LocalLowMicrosoftSilverlightis
folder. The paths are not self-explanatory, but you could keep looking until you find what you are after. Therefore, it's recommended that you do not store sensitive data, passwords, and so on in isolated storage without encrypting them first.
In this workshop, we'll create an Administration view that allows you to manage users and their roles, and prevent users from accessing this page unless they are administrators. The majority of the steps in this example have already been covered in previous examples in this book, so I won't explain those steps in detail again.
Views
folder in your Silverlight application, named AdministrationView
.AdministrationLink
that, when clicked, will navigate to the new AdministrationView
view. (See Part 1 of the example in Chapter 3 for more on how to do this.) Set its Visibility
property to Collapsed
.LoggedIn
and LoggedOut
events of the Authentication
object, and hide/show this menu item based on the user's authentication status and assigned roles. In the MainPage.xaml.cs
file, add event handlers for the Authentication
object's LoggedIn
and LoggedOut
events, and register these event handlers in the MainPage
constructor:
public MainPage()
{
InitializeComponent();
WebContext.Current.Authentication.LoggedIn += Authentication_LoggedIn;
WebContext.Current.Authentication.LoggedOut += Authentication_LoggedOut;
}
private void Authentication_LoggedIn(object sender, AuthenticationEventArgs e)
{
}
private void Authentication_LoggedOut(object sender, AuthenticationEventArgs e)
{
}
LoggedIn
event handler:
if (e.User.Identity.IsAuthenticated && e.User.IsInRole("Administrators"))
AdministrationLink.Visibility = Visibility.Visible;
else
AdministrationLink.Visibility = Visibility.Collapsed;
LoggedOut
event handler:
AdministrationLink.Visibility = Visibility.Collapsed;
AdministrationView
view's Loaded
event, and add the following code to it:
var user = WebContext.Current.User;
if (!user.IsAuthenticated || !user.IsInRole("Administrators"))
NavigationService.Navigate(new Uri("Home", UriKind.Relative));
This will simply navigate back to the Home
view if the user tries to access the AdministrationView view without being logged in or not being assigned the Administrators role.
UserRegistrationService
domain service in the Web project to add domain operations that return a list of all the registered users, return the details of a particular user, and delete a user. All this functionality is available on the ASP.NET membership provider, using the following methods:
Membership.GetAllUsers()
Membership.GetUser(string username)
Membership.DeleteUser(string username)
You might like to use the existing user registration view, found at ViewsLoginRegistrationForm.xaml
, in your AdministrationView
view to provide the add user functionality. You will want to take out the logic where it logs into the application as the new user once it's created. You'll also want to remove the “Already registered? Back to Login” section from its user interface.
AdministrationView
view, you should now remove the register user functionality from the LoginRegistrationWindow
view. You will also need to remove the “Not registered yet? Register now” section from the LoginForm
control's user interface.