Opening up Silverlight 4 Navigation: Authentication/Authorization in an INavigationContentLoader


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.

, , , , ,

  1. #1 by Steven on January 5, 2010 - 4:35 am

    David,
    I really like your posts but as you admit an override of INavigationContentloader is in most cases overkill. But why is the PageResourceContentLoader not open. If you could subscribe on events, or override, on actions that will decide on where to get the content and how, it would be enough . I am aiming for an MVVM pattern that will allow dynamic registration with all the features presented. (side note the Brad Abrams posts or the MEF extensions will not allow this in a clean manner)

    • #2 by david.poll on January 5, 2010 - 1:51 pm

      It’s true that writing your own INavigationContentLoader is overkill for most cases, which is why my hope is that some generalized, pluggable loaders find their way out there for people to use (hopefully what I’ve written will help get the ball rolling). With some of the utility classes I’ve written in SLaB, writing another INavigationContentLoader is fairly straightforward, so writing something general as you suggest is doable.

      My hope is that developers are more often consumers of INavigationContentLoaders than writers of them :)

  2. #3 by Mike Greenway on January 9, 2010 - 10:29 am

    Dave you do GOOD work!
    Leading the way to a better futhure.
    Thanks

  3. #4 by Peter Kellner on January 13, 2010 - 7:05 am

    Thanks David! Just wondering if there are any bits that make the “tab” invisible like asp.net does so that if the user has no access to the page, they don’t see it (verses getting an error and prompt for login.

    • #5 by david.poll on January 13, 2010 - 8:24 am

      Mike, it’s not a bad idea, although I think ASP.NET uses something slightly different for this. If I’m not mistaken (and correct me if I’m wrong — I’m much less of an ASP.NET expert :) ) ASP.NET uses sitemaps for this, and then drives UI based on those sitemaps.

      I’ve been trying to think of a way to do something very similar here — whether it’s through exactly the same model (sitemaps) or driven by XAML (which I personally prefer b/c it’s easier to work with/supplement/generate in code). Any ideas/preferences?

    • #6 by Jim Evans on July 4, 2010 - 5:12 am

      Pete – I completely agree. I have always been of the opinion that it is better security practice to not expose options to the user that they are not authorized for (out of site – out of mind).

      David – Looking forward to any update you may have on this – and good work :)

  4. #8 by DjoDjo on February 9, 2010 - 8:19 am

    Hi David,
    This one looks great but I have a small problem using it.

    I downloaded the code but when I try to build says –The “ValidateXaml” task failed unexpectedly.–

    In the stackTrace I also have that Could not load file or assembly ‘file:///[SOMEWHERE_ON_MY_DISK]\AuthorizingNavigation\ScratchBusinessApplication\ScratchBusinessApplication\Libs\ActivityControl.dll’ or one of its dependencies.

    Do you have any idea how to fix that ?

    • #9 by david.poll on February 15, 2010 - 12:40 am

      DjoDjo,

      I think I may have accidentally left a reference to the Activity control in there rather than using the new BusyIndicator. You can fix this either by downloading and updating the reference to one of the older builds of the Activity control, or just by removing the reference altogether. Sorry!

      -David

  5. #10 by Matteo on March 19, 2010 - 2:22 pm

    Hi, Good Work David!

    After the login, it’s possible to redirect directly to the protected page?
    Thanks.

    • #11 by david.poll on April 9, 2010 - 10:43 am

      In the SL4 RC, we made this much easier. Instead of redirecting, allow the error loader to provide a different page altogether (i.e. don’t change the ErrorContentLoader) for your login. When the user has logged in, call NavigationService.Refresh() on the page, and it will start the loading process from scratch. Since you’re now logged in, the auth loader should allow the user access to the page, and the login page will not be loaded.

      • #12 by Matteo on April 10, 2010 - 8:16 am

        Hi David, Thanks for the explanation but I’ve just solve my problem.
        My goal is to redirect a user, after the login, to a specified Page based on his role.
        So in LoginOperation_Completed method, I’ve add something like the following:

        if (WebContext.Current.User.IsInRole(“administrators”))
        {
        System.Windows.Browser.HtmlPage.Window.Navigate(new Uri(“default.aspx#/adminPage”,UriKind.Relative);
        }
        …and other roles…

  6. #13 by Trent on April 9, 2010 - 9:12 am

    Hi David,
    I can get the authcontent loader working fine. I get the UnauthorizedAccessException when i try to go to a page that’s got a rule set on it. However, when i wrap the errorLoader around it nothing is happening. It’s not throwing the exception anymore. Your live sample doesn’t work for me – I guess because I have a newer version of SL4. I also couldn’t find a sample app in your downloads that uses the authentication.

    Please help.
    Regards,
    Trent

    • #14 by david.poll on April 9, 2010 - 10:40 am

      Sorry — my live sample required the SL4 beta, and I’ve been waiting for SL4 to release to update it. If you’d like, send me an e-mail (david at depoll dot com) with an example of your XAML for the loader, and I’ll see if I can spot anything suspicious.

  7. #15 by Trent on April 9, 2010 - 1:21 pm

    Thanks, I figured it out. I didn’t realize that I still needed the tag when using . Once I put the tag in everything worked well.

    Great job!

  8. #16 by Sam Kea on April 10, 2010 - 4:37 am

    You have rocked my silverlight world!

  9. #17 by Brad Spencer on April 22, 2010 - 7:19 am

    Are you still planning on updating this to the release bits? I also would like the navigation to hide/show links according to access… any easy way to do that?

    Thanks.

    • #18 by david.poll on April 23, 2010 - 9:50 pm

      I just went through and updated to release bits. As for navigation showing/hiding links according to access… I’ve got a solution for it that’s already in the latest SLaB bits (a few controls that make life easier), but I haven’t yet blogged about it. Expect to see it soon! :)

  10. #19 by alexander on April 23, 2010 - 2:58 pm

    silverlight is released, can you please update samples?

    great job, thanks.

    • #20 by david.poll on April 23, 2010 - 9:48 pm

      I just went through and updated most of my samples. Let me know if you run into issues.

  11. #21 by Chris Shepherd on May 4, 2010 - 12:29 pm

    How secure is it defining access to pages within the application itself? Or are you meaning this to be used purely as part of the user experience and not as the only means of authorisation of the user?

    I know that in ASP.Net it all happens on the server so I guess that makes it much more robust.

  12. #22 by sashidhar on May 19, 2010 - 10:49 pm

    Thanks Davaid .You are the man..!

  13. #23 by Chris Pietschmann on June 7, 2010 - 10:18 pm

    You should post this up as a project on http://codeplex.com

  14. #24 by Mike Povey on July 26, 2010 - 1:04 pm

    Great article, which I read avidly. I do have a question, I am struggling to incorporate the functionality of preventing users logging in more than once from a second tab or second browser! Do you know of a way to include this or a link that will show me the way?

    Regards

    Mike

  15. #25 by willson on August 4, 2010 - 2:52 pm

    Great article, here is a small question for me, how did I pass the login user account to another .xap project.
    Thanks

  16. #26 by Michael on August 30, 2010 - 3:58 pm

    Hi David,
    I have been using your SLaB content loader to load on demand pages. It works great. Thanks. But I turned my forms authentication back on and now the load on demand page’s web services don’t work. The pages still load okay but the web service calls fail. The load on demand pages exist in sub folders on the web server and each page has its own web config and web service. Any ideas?
    Thanks,
    Michael

  17. #27 by Ken on September 29, 2010 - 2:56 am

    Hi!

    How do I do “custom authentication” without using “Site Administration Tool”.

    I want to authenticate my users from a my own database. (or a simple array of user/passwords)

    Similar to the ASP.NET example here: http://www.asp.net/security/videos/using-basic-forms-authentication-in-aspnet

  18. #28 by Kevin Jones on October 11, 2010 - 10:02 pm

    Very interesting and amazing post. Very great.. You’ve been kicked. :-)

    http://www.asphostportal.com

  19. #29 by Nick Zdunic on November 2, 2010 - 5:00 pm

    This is a noobie type question so aologies in advance.

    Can this concept be applied to WPF applications?

    • #30 by david.poll on November 11, 2010 - 5:17 pm

      Unfortunately, WPF doesn’t yet support INavigationContentLoader.

  20. #31 by Shane Ritchie on December 16, 2010 - 2:59 am

    Hi David, this is an excellent piece of work and I am keen to use it in the authentication on an application I am developing. What are the licensing issues for this, am I able to do this?

    One thing I am worried about, and this may a bit of a noob question. Is the namespace on the Main.xaml usercontrol: xmlns:SLaB=”http://www.davidpoll.com/SLaB”

    What do I need to do to change this so that it references locally. (Otherwise my application is dependent on your site being up?)

    Many Thanks,

    Shane

    • #32 by david.poll on December 26, 2010 - 2:18 am

      Shane,

      Sorry for the delayed response. As far as licensing goes, all of this is released under MS-Pl, which is about as permissive as it gets. You’re welcome to use it with the caveat that you shouldn’t expect support/thorough testing from me, so it’s all for use at your own risk :)

      -David

  21. #33 by debraj on January 23, 2011 - 7:18 am

    could not find AuthContentLoader ??how to get it??

    • #34 by david.poll on January 24, 2011 - 2:47 pm

      AuthContentLoader is available as part of SLaB (see the Downloads and Samples page)

  22. #35 by Ben on January 29, 2011 - 7:46 am

    Awesome library! This is exactly what I am looking for. I am trying to test my rules though and getting this into my testing framework. I currently try to put everything in ViewModel classes to aid in testing, but your solution is so elegant that I am using it anyway. Do you have any insight into how to do unit testing on AuthContentLoader? I checked out your unit tests in your source, but it looks like you added your rules manually into the test. What I would like is to have the AuthLoader I have already defined in my MainPage.xaml as the unit under test but testing this using the Silverlight Unit Testing framework is turning into a bear. Any insight would be greatly appreciated. Love the approach (hating testing right now).

  23. #36 by joyce821 on June 10, 2011 - 8:17 pm

    This blog was interesting! I’ve got a solution for it that’s already in the latest Slab bits (a few controls that make life easier), but I haven’t yet blogged about it. Expect to see it soon. I’m excited.

(will not be published)



Please wrap all source code with [code][/code] tags.