Category Archives: Silverlight

Role-based access Silverlight Applications

Security is a key component of applications and something that developers often struggle with to get right. How do you authenticate a user? How do you integrate roles and use them to show or hide different parts of a screen? These and other questions commonly come up as I talk with developers working on ASP.NET and Silverlight applications.

I was recently presenting a workshop on Silverlight at the DevConnections conference in Orlando and had a question from the audience on how I handle security roles in Silverlight applications. Since I had just implemented a security mechanism for a customer I gave a brief response but didn’t have a sample application available to share to point people in the right direction. After the workshop was over I put together a sample application to demonstrate one potential approach for accessing user names and roles. I’ll walk through the sample application in this post and highlight the key components.

The goal of the post isn’t to dictate how to authenticate users since every application has unique requirements. However, I will discuss general techniques for accessing user names and working with roles to block access to views and show or hide controls.

Security Techniques

Silverlight applications can take advantage of Windows and Forms authentication techniques and can integrate user roles into the mix as well. However, unless you use WCF RIA Services on the backend you’ll need to write the plumbing code to authenticate a user if you need to do it directly within the application. WCF RIA Services projects provide login and registration screens out of the box that leverage Forms authentication by default. You can view a walk-through of the WCF RIA Services authentication process here http://msdn.microsoft.com/en-us/library/ee942449(VS.91).aspx.

WCF RIA Services also provides a means for accessing an authenticated user’s user name and roles by using a WebContext object (seehttp://msdn.microsoft.com/en-us/library/ee707361(VS.91).aspx). This isn’t possible out-of-the-box in a standard Silverlight application unless you write custom code to handle it. If WCF RIA Services is appropriate for your project then it’s a great way to go for data exchange and security tasks. If you won’t be using WCF RIA Services then this post will provide insight into other techniques that can be used.

Most of the Silverlight Line of Business (LOB) applications I’ve worked on authenticate the user at the page level using Windows authentication. If the user can’t authenticate into the page then the Silverlight application is never displayed. With out-of-browser applications the Windows user account can be passed through and accessed as calls to a service are made. The sample application available with this post assumes that authentication occurs at the page level as opposed to within the Silverlight application itself.

Accessing a User’s Identity

To access an authenticated user’s user name within a Silverlight application you can either pass the user name into the object tag’s initialization parameter (called “initParams”) or call a service that returns the user name. An example of passing in the user name using the initParams option within an ASP.NET page that is hosting the object tag is shown next:

<param name="initParams" value="UserName=<%=User.Identity.Name%>" />

Within App.xaml.cs you can access the initParams parameters and store them. The code below shows how to do this and add initParams values into the application resources so that they can be accessed throughout the application.

private void Application_Startup(object sender, StartupEventArgs e)
{
    ProcessInitParams(e.InitParams);
    this.RootVisual = new MainPage();
}

private void ProcessInitParams(IDictionary<string, string> initParams)
{
    if (initParams != null)
    {
        foreach (var item in initParams)
        {
            this.Resources.Add(item.Key, item.Value);
        }
    }
}

I don’t personally like to embed the user name into the object tag unless it’s simply going to be displayed in the application. If you’ll be doing look-ups against different databases or other resources based upon the user name then it’s better to let a service resolve the user name dynamically so that it can’t be spoofed. Otherwise, a user that authenticated into the application could potentially change the user name defined in initParams and bypass security.

To access the user name using a service you can create a WCF security service as shown next and add an operation that is responsible for returning the user name. The easiest way to create the service is to add a Silverlight-enabled WCF Service into the Web project.

[ServiceContract(Namespace = "YourNamespace")]
[SilverlightFaultBehavior]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class SecurityService
{
    [OperationContract]
    public string GetLoggedInUserName()
    {
        return new SecurityRepository().GetUserName(OperationContext.Current);
    }

    [OperationContract]
    public List<Role> GetRoles()
    {
        return new SecurityRepository().GetRoles();
    }

    [OperationContract]
    public UserSecurity GetUserSecurity()
    {
        return new SecurityRepository().GetUserSecurity(OperationContext.Current);
    }
}

The previous code contains a GetLoggedInUserName() operation that makes a call into a SecurityRepository class’s GetUserName() method to access the user name. GetUserName() accesses the user name through the OperationContext object’s ServiceSecurityContext property which provides access to the user’s identity object (note that specific configuration changes must be made for this object to be useful – see the sample project’s web.config file for more details).

The following code shows the SecurityRepository class. The class simulates roles by adding them directly into the code but could easily be enhanced to retrieve roles from a database or other store.

public class SecurityRepository
{
    List<Role> _Roles = new List<Role>();

    public SecurityRepository()
    {
         //Simulate roles 

        _Roles.Add(new Role { Name = "Admin" });
        _Roles.Add(new Role { Name = "Editor" });
        _Roles.Add(new Role { Name = "User" });
    }

    public string GetUserName(OperationContext opContext)
    {
        return GetOpContextUserName(opContext);
    }

    public List<Role> GetRoles()
    {
        return _Roles;
    }

    public UserSecurity GetUserSecurity(OperationContext opContext)
    {
        var userName = GetOpContextUserName(opContext);

        if (userName != null)
        {
            return new UserSecurity { UserName = userName, Roles = _Roles };
        }
        return null;
    }

    private string GetOpContextUserName(OperationContext opContext)
    {
        return (opContext.ServiceSecurityContext != null &&
                opContext.ServiceSecurityContext.WindowsIdentity != null) ? opContext.ServiceSecurityContext.WindowsIdentity.Name : null;
    }
}

The GetUserSecurity() method provides a way for the Silverlight application to make a single call and get the user name and roles for the authenticated user. This method is called by the Silverlight client and used to show and hide controls within the application. Let’s take a look at how that process works.

Building a SecurityManager for Silverlight

The WCF service shown earlier provides a way for a Silverlight application to retrieve a user name and roles. How do you go about calling the service and storing the resulting information? For a recent customer application I created a SecurityManager class that was responsible for storing user name and role information and exposing properties such as IsAdmin and IsEditor to handle determining what role a user was in. It went through a service agent class that was responsible for calling the WCF service and returning the data to the SecurityManager. By going this route a single class is responsible for security which avoids scattering security logic throughout an application. The following code shows the SecurityManager class available with the sample application.

public class SecurityManager : ISecurityManager
{
    public ISecurityServiceAgent SecurityServiceAgent { get; set; }
    public event EventHandler UserSecurityLoaded;

    public SecurityManager()
    {
        SecurityServiceAgent = new SecurityServiceAgent();
        GetUserSecurityDetails();
    }

    public void OnUserRolesLoaded(object sender, EventArgs e)
    {
        if (UserSecurityLoaded != null)
        {
            UserSecurityLoaded(sender, e);
        }
    }

    #region Properties

    public ObservableCollection<Role> UserRoles { get; set; }
    public string UserName { get; set; }

    public bool IsAdmin
    {
        get
        {
            if (UserRoles == null) return false;
            return UserIsInAnyRole("Admin");
        }
    }

    public bool IsInUserRole
    {
        get
        {
            if (UserRoles == null) return false;
            return (UserRoles.Count == 1 && UserRoles.Any(r => r.Name == "User"));
        }
    }

    public bool IsValidUser
    {
        get { return UserName != null && (UserRoles != null && UserRoles.Count > 0); }
    }

    public bool IsEditor
    {
        get
        {
            if (UserRoles == null) return false;
            return IsAdmin || UserIsInAnyRole("Editor", "HRAdmin");
        }
    }

    public bool IsUserSecurityLoadComplete { get; set; }

    #endregion

    private void GetUserSecurityDetails()
    {
        SecurityServiceAgent.CallService<GetUserSecurityCompletedEventArgs>(
        (s, args) =>
        {
            IsUserSecurityLoadComplete = true;
            UserRoles = args.Result.Roles;
            UserName = args.Result.UserName;
            OnUserRolesLoaded(this, EventArgs.Empty);
        });
    }

    //Determine if a user has rights to see an application view or not
    public bool CheckUserAccessToUri(Uri uri)
    {
        if (UserRoles != null)
        {
            string screenUri = uri.ToString().ToLower();
            if (screenUri.StartsWith("/home"))
                return IsValidUser;
            if (screenUri.StartsWith("/customers"))
                return IsAdmin;
            if (screenUri.StartsWith("/about"))
                return IsValidUser;
        }
        return false;
    }

    public bool UserIsInRole(string role)
    {
        if (UserRoles == null) return false;
        return UserRoles.Any(r => r.Name == role);
    }

    public bool UserIsInAnyRole(params string[] roles)
    {
        if (UserRoles == null) return false;
        return (from ur in UserRoles
                from r in roles
                where ur.Name.Contains(r)
                select r).Any();
    }
}

The key part of the SecurityManager class is found in the constructor where a call is made to another class named SecurityServiceAgent (a “service agent” that specializes in data retrieval) to retrieve the user name and roles from the WCF security service shown earlier. Since the service call is asynchronous an event is defined in SecurityManager named UserSecurityLoaded that is raised once the data is loaded in the Silverlight client.[

In addition to getting and storing user data, the SecurityManager class also has methods such as UserIsInAnyRole() to check if the user is a member of an array of roles, UserIsInRole() to check if they’re in a specific role and CheckUserAccessToUri() to verify whether or not they have access to a specific view. Properties such as IsAdmin and IsEditor provide a simple way for consumers of the SecurityManager class to check if a user is in a role specific to the application.

Using the SecurityManager to Handle Roles and User Names

The SecurityManager class can be used directly in views or within ViewModel classes. When using the MVVM pattern a property can be added into a ViewModel base class (a class that all ViewModel classes derive from) as shown next:

public ISecurityManager SecurityManager { get; set; }

This property allows security functionality to be available across all ViewModel classes. The sample application contains two ViewModel classes that use SecurityManager named MainPageViewModel and HomeViewModel. MainPageViewModel uses the SecurityManager class to render the user name in the MainPage.xaml view and hide any HyperlinkButton controls that a user shouldn’t be able to see. The following code shows the MainPageViewModel class.

public class MainPageViewModel : ViewModelBase
{
    private bool _IsAdmin;
    private string _UserName;

    public MainPageViewModel()
    {
        if (!IsDesignTime) SecurityManager.UserSecurityLoaded += SecurityManagerUserSecurityLoaded;
    }

    public bool IsAdmin
    {
        get
        {
            return _IsAdmin;
        }
        private set
        {
            if (_IsAdmin != value)
            {
                _IsAdmin = value;
                OnNotifyPropertyChanged("IsAdmin");
            }
        }
    }

    public string UserName
    {
        get
        {
            return _UserName;
        }
        private set
        {
            if (_UserName != value)
            {
                _UserName = value;
                OnNotifyPropertyChanged("UserName");
            }
        }
    }

    void SecurityManagerUserSecurityLoaded(object sender, EventArgs e)
    {
        IsAdmin = SecurityManager.IsAdmin;
        UserName = SecurityManager.UserName;
    }
}

MainPageViewModel starts by attaching to the SecurityManager’s UserSecurityLoaded event. Once the event fires, SecurityManagerUserSecurityLoaded is called and the IsAdmin and UserName properties are assigned to the ViewModel’s properties. These properties are then bound to controls in the view using standard Silverlight data binding techniques. The IsAdmin property is bound to HyperlinkButton controls and used to show or hide the controls based on if the user is in the “Admin” role or not. A value converter is used in the view to handle converting the Boolean value to a Visibility enumeration value. The UserName property is bound to a TextBlock control that displays the user name in the interface.

HomeViewModel uses the SecurityManager class to determine if edit controls that allow customer information to be saved and edited should be present or not. If the user is in the Admin or Editor role then the controls are shown. If not, the controls are hidden.

Securing Views

Although accessing user name and role functionality is important in order to customize the user interface based upon the user’s security rights, you’ll also need to secure individual views in many cases. For example, the MainPageViewModel defines an IsAdmin property (shown previously) that is used to show or hide a HyperlinkButton to prevent a user from going to a specific view. However, if the user knows the path to the view they can type it directly into the browser’s URL and load the view directly which bypasses the intended security. To prevent this, the CheckUserAccessToUri() method in the SecurityManager class can be used in conjunction with the Navigating event of the Frame within MainPage.xaml (the Frame control is included since the sample project uses the Silverlight navigation application project template).

The following snippet shows the code that handles checking if a user has access to a specific view as the Frame in MainPage.xaml loads content. The code shown in MainPage_Loaded handles attaching to the Frame’s Navigating event. When the event is raised the code in the ContentFrame_Navigating event handler cancels the Navigating event if the user isn’t determined to be a valid user. It also makes the call to the CheckUserAccessToUri() method to determine if the user is allowed to get to the view that the content Frame is attempting to load. If the user doesn’t access, a view named AccessDenied.xaml is loaded which displays the appropriate “Access Denied” error message.

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    PeopleEventBus.OperationCompleted += PeopleEventBus_OperationCompleted;
    ViewModel = (MainPageViewModel)this.Resources["ViewModel"];
    ViewModel.SecurityManager.UserSecurityLoaded += SecurityManagerUserSecurityLoaded;

    ContentFrame.Navigating += ContentFrame_Navigating;
    ContentFrame.Navigated += ContentFrame_Navigated;
}

void SecurityManagerUserSecurityLoaded(object sender, EventArgs e)
{
    //Cause frame to navigate to view user originally wanted to see
    ViewModel.SecurityManager.UserSecurityLoaded -= SecurityManagerUserSecurityLoaded;
    ContentFrame.Navigate(ContentFrame.Source);
}

private void ContentFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
    //No user name or roles found
    if (!ViewModel.SecurityManager.IsValidUser)
    {
        e.Cancel = true;
        return;
    }

    //Check if user has access to page that they're trying to nagivate to
    var hasAccess = ViewModel.SecurityManager.CheckUserAccessToUri(e.Uri);
    if (!hasAccess)
    {
        ContentFrame.Content = new AccessDenied();
        e.Cancel = true;
    }
}

Security is an important part of Line of Business (LOB) applications and something that definitely must be thought through and planned carefully. In this post you’ve seen different ways to access a user name and associated roles in a Silverlight application. You’ve also seen how a SecurityManager class can be created to perform security checks that are used by ViewModel classes to show or hide controls. To the person in the DevConnections workshop mentioned at the beginning, thanks for asking the question and I hope the sample application (available below) helps get you started in the right direction integrating security features into your Silverlight applications.

Download the sample application code here.

Silverlight 5 new features

As all of us know silverlight5 has been released at MIX2011 by Scott Gu. If you want to start exploring SL5 features its time to start and do it now. Sl5 moves development more towards WPF, so some of the benefits that WPF developers are enjoyed that have been now added to SL5.

How to get Started with this Developer’s product ?

  • Install VS2010 SP1 from www.silverlight.net
  • Install Silverlight5 Beta Tools for VS2010 from here
  • Optionally install the Expression Blend Preview does SL5 Beta

Binding

  • We can place Breakpoints in XAML for debugging. In the editor directly can put breakpoint and debug in editor check or error messages.
  • Ancestor Relative Source for Binding to a property on parent control.
  • Binding now possible in style setters
    Implicit Data Templates

Media

  • New support for Low latency sound effects using the Sound Effect API. No more round robin queue. It’s fast and very responsive. Since the sound effects, sound effect instances will be reuse sound effects inside the applications.
  • We have Variable Speed Playback ( “Trick Play”). There is no audio support for beta only. It will be there in RC/ RTM
  • H 264 media is now decoded as hardware for a huge performance boost.

Text

  • Text tracking and leading control
  • Linked Rick Text Boxes for seamless workflow and multi –column text

Input

  • Click Count property for multi-click support, Now we can get number of clicks user have done
    • For example Double click
  • Listbox / ComboBox controls have type-ahead text searching

Full-Trust Applications

  • Through Group Policy settings, elevated trust available in browser foe signed applications
    • Including keyboard support in full-screen mode support kiosk-type applications
    • Windows-only for Beta
  • Un restricted File System Access
    • No longer restricted to “My Documents”
  • Native OS Windows Support

Graphics

  • Graphics stack improved using lessons learned from mobile, such as independent animations.
  • XNA 3D API for low-level access.
  • SL5 has XNA .3D API for low-level access to GPU, vertex shades and 3D primitives
  • Also works as an immediate mode 2d API

Performance Improvements

  • Improved XAML parse times for User Controls and Resource Dictionaries
  • 90% performance improvement in ClientHttpWebRequest scenarios
  • Hardware accelerated rendering in IE9, windowless (SL5 applications ) mode using the new SurfacePresenter APIs

Others

  • Create custom markup extensions
  • In-Browser HTML support
  • Default filename in the Save File Dialog
  • Many more fixes and improvements throughout the product

Seems that Ms has taken the feedback and implemented the same things that developers or customers asked for. I am exciting about this release and start playing with SL5 and if you can find any bugs in Beta, you can post at connect.microsoft.com for the fix and support.