Implementing Client-Side Security

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.

Authenticating and Registering Users

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.

images

Figure 8-3. The Login dialog from the Silverlight Business Application project template

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.

images

Figure 8-4. The Register dialog from the Silverlight Business Application project template

images 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.

Authenticating with the Server

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.

images 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.

images 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.

Registering a New User

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.

images 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.

Accessing and Updating User Information

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");

images 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);

Implementing Client-Side Restrictions

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:

  • All functions are visible, but when the user tries to access them, credentials will be checked, and users will be prevented from using the function if not allowed. This doesn't provide the best experience for the user, because using the application becomes somewhat of a hit-and-miss affair, where the user doesn't immediately know what can and can't be done within the application.
  • Only functions that the user can access are enabled, while those that they can't are disabled (grayed out). This shows the users all the things that could be done in the application with the requisite privileges, but enables them to see that they can't actually use the disabled functionality.
  • Only functions that the user can access are visible. This simplifies the application for the users and helps avoid overwhelming them with options, but they won't know when a function is actually implemented in the application and just not available to them so that they could ask for privilege elevation if they require it.

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:

  • If you name the controls that should be disabled/hidden depending on the user's privileges, you can disable/hide them in your code-behind when the view is loaded.
  • If you are using the MVVM design pattern (covered in Chapter 13), you can expose properties representing the permissions from your ViewModel that the controls in the view can bind their IsEnabled/Visibility properties to.
  • You can write some attached properties that can be applied to controls used in a view, controlling whether they are disabled or hidden depending on the user's privileges. A good example of this is in an article by Andrea Boschin on the SilverlightShow.net web site: 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.
  • You can declaratively bind to properties of the 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.

images 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/.

Storing Data Locally

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.

images Workshop: Practicing with Client-Side Security

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.

  1. Create a new view in the Views folder in your Silverlight application, named AdministrationView.
  2. Create a menu item named 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.
  3. For the purpose of this example, we'll implement the simplest method that we discussed earlier for implementing client-side restrictions: hiding this menu item for unauthenticated and nonadministrator users in the code behind. We'll handle the 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)
    {

    }
  4. When the user logs in, we want to show the Administration menu item if the user is assigned the Administrators role, so add the following code to the LoggedIn event handler:
    if (e.User.Identity.IsAuthenticated && e.User.IsInRole("Administrators"))
        AdministrationLink.Visibility = Visibility.Visible;
    else
        AdministrationLink.Visibility = Visibility.Collapsed;
  5. When the user logs out, we want to hide the Administration menu item, so add the following line of code to the LoggedOut event handler:
    AdministrationLink.Visibility = Visibility.Collapsed;
  6. Although the Administration menu item doesn't appear when you're not logged in or not an administrator, you will find that you can still access the view by tampering with the URI in the browser's address button. You, therefore, need to check that the user has permission to access the view when it is navigated to. Add an event handler for the 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.

  7. You can now go ahead and implement the administration view. This will be left as an exercise for you to undertake using the knowledge that you've gained so far from this book. Your view should display a list of users that can be maintained via the view. The user should then be able to add new users, including assigning them to roles; view/edit the details of existing users, including customizing what roles they are assigned to; and delete existing users. This exercise will require you to also modify the 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.

    images Note You can remove the automatically generated Security Question and Security Answer fields from the registration form by deleting or commenting out the Question and Answer properties from the UserRegistrationData class in the Web project.

  8. Because all user registration will be performed by the new 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.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset