Continuing my series of posts on ways to use INavigationContentLoader (a feature in the Silverlight 4 Beta SDK), in this post, I’ll explore another idea for a composable INavigationContentLoader that protects access to your pages based upon the credentials of the user of your Silverlight application.  To demonstrate its use, I’m using a WCF RIA Services application (purely for their Authentication/Authorization provisions).

The basic problem is this: as you would with a website, you build an application with multiple pages – some of which are intended for anonymous users or users in a particular role and some are intended for users with greater privileges.  How do you prevent such users from navigating to those pages and give them an appropriate user experience if they do try to reach pages for which they are not authorized?

To that end, I’ve added another INavigationContentLoader to my SLaB examples – the “AuthContentLoader” – that checks to see whether your user is authorized to view a page before navigating to it.  If the user is not authenticated, the AuthContentLoader throws, resulting in a NavigationFailed event on the Frame/NavigationService that you can handle in order to provide better feedback to your users if there is an UnauthorizedAccessException.

Cool – How does it work?

If you’re familiar with authorization with ASP.NET (settings you might add to your web.config file), this ContentLoader’s use should come pretty easily to you.  For example, in ASP.NET, you might have the following in your web.config:

<system.web>
  <authorization>
    <allow roles="Role1, Role2" />
    <allow users="SuperUser"/>
    <deny roles="Role3, Role4" users="LessImpressiveUser" />
    <deny users="?" />
    <allow users="*"/>
  </authorization>
</system.web>

I tried to keep the API for the AuthContentLoader quite similar.  With the AuthContentLoader, you can:

  • Allow or deny users access to pages that match a Regular Expression (allowing you to scope the authorization to particular pages)
  • Allow or deny users access to pages based upon their roles, authentication status, and user name
  • Wrap any other INavigationContentLoader in order to protect access

To accomplish this, there are just a few steps:

  1. Set your Frame.ContentLoader to an AuthContentLoader.
  2. Bind AuthContentLoader.Principal to any IPrincipal (in my example, I’ll use the built-in authentication context in WCF RIA Services).  This is what the ContentLoader uses to check the user’s credentials.
  3. Add rules for your pages.  If no rule matches a page, the page is assumed to be broadly accessible.

Put all of this together, and you end up with something like this:

<navigation:Frame ...>
    <navigation:Frame.ContentLoader>
        <authLoader:AuthContentLoader Principal="{Binding User, Source={StaticResource WebContext}}">
            <authLoader:NavigationAuthorizer>
                <authLoader:NavigationAuthRule UriPattern="^/Views/About\.xaml\??.*$">
                    <authLoader:Deny Users="?" />
                    <authLoader:Allow Users="*" />
                </authLoader:NavigationAuthRule>
                <authLoader:NavigationAuthRule UriPattern="^/Views/RegisteredUsersPage.xaml\??.*$">
                    <authLoader:Allow Roles="Registered Users" />
                </authLoader:NavigationAuthRule>
            </authLoader:NavigationAuthorizer>
        </authLoader:AuthContentLoader>
    </navigation:Frame.ContentLoader>
</navigation:Frame>

Here, we have rules for About.xaml (with any querystring) and RegisteredUsersPage.xaml (again, with any querystring).  Note that the UriPattern is a Regex, and must also take into account the possible query strings that could be attached to the request.  I know it looks a little arcane, but it does the trick, and allows you to specify whole sets of Uri’s that share the same authorization characteristics (e.g. any page in the “PrivateViews” folder could be restricted to Administrators).

The XAML snippet above places restrictions on two pages:

  • About.xaml – anonymous (Principal == null || Principal.Identity == null || Principal.Identity.IsAuthenticated == false) users are denied, and all other users are allowed
  • RegisteredUsersPage.xaml – only users that belong to the “Registered Users” role are allowed
  • Users are granted access to all other pages

Like the ErrorPageLoader, the AuthContentLoader can take another ContentLoader (but defaults to the PageResourceContentLoader if none is specified), and will delegate the actual loading (after the user has been authorized) to that loader.

And there you go!  Easy as pie!  Feel free to give it a try and play around with it!  Happy New Year!

Wait!  Don’t stop yet!  Please tie this back to your other posts!

Relax!  I won’t leave you hanging!  After all, what’s the point of having two composable INavigationContentLoaders (AuthContentLoader and ErrorPageLoader) if you’re not going to use them together? :)

I made a very specific choice with the AuthContentLoader: when the user doesn’t have permission to access a page, the AuthContentLoader throws an exception.  That’s very convenient when you want to use the ErrorPageLoader to handle authentication failures.  The AuthContentLoader, by default, throws an UnauthorizedAccessException, so we can handle that exception explicitly using the ErrorPageLoader.  In this case, we’ll redirect users who visit an unauthorized page to another page that directs them to log in.  The XAML for this follows:

<errorLoader:ErrorPageLoader>
    <errorLoader:ErrorPageLoader.ErrorPages>
        <errorLoader:ErrorPage ExceptionType="UnauthorizedAccessException" ErrorPageUri="/LoginPage" />
    </errorLoader:ErrorPageLoader.ErrorPages>
    <errorLoader:ErrorPageLoader.ErrorContentLoader>
        <errorLoader:ErrorRedirector />
    </errorLoader:ErrorPageLoader.ErrorContentLoader>
    <errorLoader:ErrorPageLoader.ContentLoader>
        <authLoader:AuthContentLoader Principal="{Binding User, Source={StaticResource WebContext}}">
            <authLoader:NavigationAuthorizer>
                <authLoader:NavigationAuthRule UriPattern="^/Views/About\.xaml\??.*$">
                    <authLoader:Deny Users="?" />
                    <authLoader:Allow Users="*" />
                </authLoader:NavigationAuthRule>
                <authLoader:NavigationAuthRule UriPattern="^/Views/RegisteredUsersPage.xaml\??.*$">
                    <authLoader:Allow Roles="Registered Users" />
                </authLoader:NavigationAuthRule>
            </authLoader:NavigationAuthorizer>
        </authLoader:AuthContentLoader>
    </errorLoader:ErrorPageLoader.ContentLoader>
</errorLoader:ErrorPageLoader>

Cool!  Now, if users navigate to a page they’re not authorized to see, they’ll be redirected to a login page!  Note the exception type being handled (UnauthorizedAccessException), the ErrorPageUri (unmapped, because we’re using the ErrorRedirector, which will cause a brand new navigation to take place – including mapping), and the use of the ErrorRedirector to redirect to a new Uri rather than just loading alternate content (allowing the user to come back to the page rather than assuming that the login page is the real content).

Ok, we’re now in pretty good shape, but users can still hit problems besides the UnauthorizedAccessException, such as attempting to load a page that does not exist.  In my last post, we solved this using an ErrorPageLoader, and we’ll do the same this time.  In these cases, I actually do want to load alternate content rather than redirect to an error page, since this is the typical experience with web error pages (e.g. a 404 page).  I can accomplish this by adding a second ErrorPageLoader to the mix, like so:

<errorLoader:ErrorPageLoader>
    <errorLoader:ErrorPageLoader.ErrorPages>
        <errorLoader:ErrorPage ErrorPageUri="/Views/ErrorPage.xaml" />
    </errorLoader:ErrorPageLoader.ErrorPages>
    <errorLoader:ErrorPageLoader.ContentLoader>
        <errorLoader:ErrorPageLoader>
            <errorLoader:ErrorPageLoader.ErrorPages>
                <errorLoader:ErrorPage ExceptionType="UnauthorizedAccessException" ErrorPageUri="/LoginPage" />
            </errorLoader:ErrorPageLoader.ErrorPages>
            <errorLoader:ErrorPageLoader.ErrorContentLoader>
                <errorLoader:ErrorRedirector />
            </errorLoader:ErrorPageLoader.ErrorContentLoader>
            <errorLoader:ErrorPageLoader.ContentLoader>
                <authLoader:AuthContentLoader Principal="{Binding User, Source={StaticResource WebContext}}">
                    <authLoader:NavigationAuthorizer>
                        <authLoader:NavigationAuthRule UriPattern="^/Views/About\.xaml\??.*$">
                            <authLoader:Deny Users="?" />
                            <authLoader:Allow Users="*" />
                        </authLoader:NavigationAuthRule>
                        <authLoader:NavigationAuthRule UriPattern="^/Views/RegisteredUsersPage\.xaml\??.*$">
                            <authLoader:Allow Roles="Registered Users" />
                        </authLoader:NavigationAuthRule>
                    </authLoader:NavigationAuthorizer>
                </authLoader:AuthContentLoader>
            </errorLoader:ErrorPageLoader.ContentLoader>
        </errorLoader:ErrorPageLoader>
    </errorLoader:ErrorPageLoader.ContentLoader>
</errorLoader:ErrorPageLoader>

And that’s it!  Now, when users access your site, they’re protected from any type of error and have access restricted appropriately!  Give it a shot with my live sample application:

Note: Log in with User = “Test”, Password = “_Testing”

Also Note: I’ve been having a little trouble with my server, so if this doesn’t work for you, feel free to try downloading and running the code locally (see below for a link).

Live Sample Application

First, try clicking on the restricted links at the top of the application and observe the login page that appears.  Next, click the broken link (or type something random into your browser after the “#” in the URL), and observe the error page that appears.  Now, log in to the application (feel free to create your own account or use the test account above – any account should give you access to the pages that are currently restricted), and try visiting those pages again!

Ok, I think I’m beginning to get it.  Give me the goods so I can go play with it!

As always, I can’t leave you empty-handed.  I’ve added the AuthContentLoader to SLaB, which you can download to get both binaries and code.  I’ve also included the source (which requires WCF RIA Services and the Silverlight 4 Beta) for the demo application I linked to above:

  • Live Sample (source)
  • SLaB v0.0.2 (includes source, a sample app, some tests, and binaries)
    • For the latest version, please check out SLaB on my Downloads and Samples page.
    • The v0.0.2 download of SLaB includes:
      • AuthContentLoader and related classes
      • ErrorPageLoader moved into its own assembly (to keep its size down)

In Conclusion...

In my humble opinion, there is a lot of power in composing these types of INavigationContentLoaders.  With the AuthContentLoader, you can prevent Uri’s from being loaded in the context of your application.  Whether you’re just trying to provide a good user experience or actually prevent users from reaching certain Uri’s (e.g. dynamically downloaded XAPs/assemblies that should only be accessible if you’re logged in) a ContentLoader like this could be useful.  The AuthContentLoader works well with WCF RIA Services, which provides easy access through its “WebContext” concept to a User that represents both an IPrinciple and an IIdentity.  Stay tuned for more ideas – I’m still working on some fun little experiments.  Hopefully these posts inspire some cool ideas!  If you’ve got ‘em, I’d love to hear ‘em!

Remember, SLaB is just a collection of the samples and experimental components I’ve been putting together so that they’re all in one place.  I can’t make any guarantees about maintaining them, fixing bugs, not making breaking changes, etc., but you’re more than welcome to try them out, use them, and let them inspire your development (or show you what not to do if you really dislike something I’m doing!) :).

Disclaimer

The AuthContentLoader does not protect your data in any way – it simply provides a user experience around navigating to pages that may have restricted access.  Users can still open up your XAP and see its contents (so the XAML/code for those restricted pages isn’t protected), but it does do an effective job of limiting what users are able to access within your application.  You should still be aggressively securing your application if need be.  :)

Don’t you have something else to say?

Oh, yeah!  Happy New Year!  Here’s to (and good riddance to) the noughties, bring on the teens!  I hope you all have had a wonderful holiday season, and enjoy prosperity in the year ahead.