A “refreshing” Authentication/Authorization experience with Silverlight 4

At the beginning of the year, as part of a series of posts about the INavigationContentLoader extensibility point in Silverlight 4, I described a way to use a content loader to do authorization before allowing a user to navigate to a page.  With the content loader, you can either throw an exception when an unauthorized user tries to reach a protected Page, redirect your users to another Page, or return a different page (e.g. a Login page) in its stead.  This makes for a fairly nice experience for your users, wherein they are taken directly to a login page (or at least a page with more information about why they cannot access the given page) when they lack the credentials to reach the page they are requesting.

The trouble with this, however, was that once your application reached the login page and your user attempted to log in, there was no clear/easy/universal way to get the user back to the location he/she was originally requesting.  Ideally, an application would keep its context (i.e. the Uri wouldn’t change) when it sends a user to a login page, and take the user to the restricted content once the right credentials are acquired.

When I wrote my original post, I was aware of this limitation, and didn’t have a great solution for it.  Attempting to re-navigate to the requested page was unhelpful because navigating twice to the same Uri is a no-op.  Starting with the Silverlight 4 RC (and continuing into the RTW release, of course), however, such a solution exists!  We quietly added an API to Frame and NavigationService: Refresh().

How does refreshing help?

Calling Frame.Refresh() or NavigationService.Refresh() causes the entire page to be reloaded, meaning that a custom content loader will be called, providing an opportunity to return a different page (or redirect elsewhere).  Without having to make any changes to SLaB and the AuthContentLoader or ErrorPageLoader, we can now produce the desired experience!

Now, our ContentLoader XAML looks like this:

<navigation:Frame x:Name="ContentFrame"
                    Style="{StaticResource ContentFrameStyle}"
                    Source="/Home">
    <navigation:Frame.UriMapper>
        <uriMapper:UriMapper>
            <uriMapper:UriMapping Uri=""
                                    MappedUri="/Views/Home.xaml" />
            <uriMapper:UriMapping Uri="/{pageName}"
                                    MappedUri="/Views/{pageName}.xaml" />
        </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
    <navigation:Frame.ContentLoader>
        <SLaB:ErrorPageLoader>
            <SLaB:ErrorPage ExceptionType="UnauthorizedAccessException"
                            ErrorPageUri="/Views/LoginPage.xaml" />
            <SLaB:ErrorPage ErrorPageUri="/Views/ErrorPage.xaml" />
            <SLaB:ErrorPageLoader.ContentLoader>
                <SLaB:AuthContentLoader Principal="{Binding User, Source={StaticResource WebContext}}">
                    <SLaB:NavigationAuthorizer>
                        <SLaB:NavigationAuthRule UriPattern="^/Views/About\.xaml\??.*$">
                            <SLaB:Deny Users="?" />
                            <SLaB:Allow Users="*" />
                        </SLaB:NavigationAuthRule>
                        <SLaB:NavigationAuthRule UriPattern="^/Views/RegisteredUsersPage\.xaml\??.*$">
                            <SLaB:Allow Roles="Registered Users" />
                        </SLaB:NavigationAuthRule>
                    </SLaB:NavigationAuthorizer>
                </SLaB:AuthContentLoader>
            </SLaB:ErrorPageLoader.ContentLoader>
        </SLaB:ErrorPageLoader>
    </navigation:Frame.ContentLoader>
</navigation:Frame>

The primary difference between the XAML above and the original XAML I had posted was to remove the ErrorRedirector (which caused redirection to the login page rather than loading the login page in place of the requested page).  Because this was removed, we no longer need nested ErrorPageLoaders (which existed in order to redirect only in the login case, and load the error page without changing the Uri for other errors).  You’ll note that for the About page and the RegisteredUsers page, access is restricted.  When an UnauthorizedAccessException occurs, users will see the LoginPage.

In the login page, all we need to do now is call NavigationService.Refresh() when the user logs in.  My example uses WCF RIA Service’s WebContext find out this information, but you could just as easily attempt to refresh after a ChildWindow is closed or a Login button is clicked.

My LoginPage code looks like this:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    WebContext.Current.Authentication.LoggedIn += Authentication_LoggedIn;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    WebContext.Current.Authentication.LoggedIn -= Authentication_LoggedIn;
}

void Authentication_LoggedIn(object sender, AuthenticationEventArgs e)
{
    NavigationService.Refresh();
}

Yep, that’s all it takes!  Now, when a user logs in (either by clicking the login button on the page or logging in through some other dialog in the application), the Frame’s content is refreshed, and the AuthContentLoader attempts to verify the user’s credentials once again.

Cool!  Can I see it in action?

You know I would never leave you without a sample!  Click the image below to see the sample application (based on my original example, just updated for SL4).  First try navigating to the protected pages without logging in, then try logging in and note how the page automatically is refreshed based upon your new credentials.

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

A WCF RIA Services application with the AuthContentLoader

 

You can find the source for this application here.

Anything else I should know about Refresh()?

Without a doubt, Refresh()’s usefulness is not restricted to this scenario.  With custom content loaders, it’s particularly useful to be able to refresh the page, since the page returned as a result of that navigation may change from one attempt to the next.  Even without a custom content loader, Refresh() allows you to create a new instance of a page, making re-initializing the page you’ve navigated to clean and simple.  The behavior is identical to navigating to a new page – the only difference is that the old and new Uris are identical, and the NavigationMode of the operation is “Refresh”.

Please note: Refresh() will still respect the NavigationCacheMode of the Page and the CacheSize of the Frame.  If a Page is being cached, calling Refresh() will not create a new instance (but will still cause the Navigating/Navigated events and the corresponding overrides on Page to be raised/called).  To prevent this from happening, set the NavigationCacheMode of the page being refreshed to Disabled before the new page would be loaded (i.e. before Refresh() is called or while handling the Navigating event).

Is that it?

Yep, that’s it! :)  Let me know what you think!  What else would you like to see?

32 thoughts on “A “refreshing” Authentication/Authorization experience with Silverlight 4”

  1. Thanks David. with SLaB and the new Refresh(), it has vastly improved the feel of the app. Really appreciate you posting this.

    thanks

  2. great stuff you have been doing…. some of it should IMHO bepart of the standard framework.

    PLEASE work with John Papa on a few segments to help us all get this stuff working in our apps.

    I am working on an app and will soon be trying to figure out what bits of SLaB i need in my app or what code to “adopt” or whatever….
    I really want to hide menu items the user does not have rights to go to in my app. I also want the URL to send them to a “not authorized” page if they bookmark a page but do not have the role for the page.

    1. Thanks! :) What parts do you think need to be part of the standard framework? Which pieces are fundamental in that way?

      As for your hiding links/menu items… it’s coming in a future blog post :). The code is actually already in SLaB, I just haven’t been able to blog about it yet. The AuthContentLoader/ErrorPageLoader should be sufficient to get you the “bookmarked/unauthorized” experience you’re describing.

  3. well really the loaders with some way to allow a simple hookup.
    there was some code done by another dev that used the concept of attributes that i really like, i have the link at home to his code and i will post it here tonight if i do not forget.

    to me this would be the uber way to do it:
    on the code behind on in the xaml of each page if i could use an attribute to declare what roles / users can view that page.
    like they did with the RIA services to require a role to access a service method.
    so then the navigate would see if the page load should be blocked or allowed based on the user.
    the menu i am less sure – possibly a map like the asp.net sitemap or possibly by a method like the MEF composing at runtime ?
    In asp.net i dislike having to maintain 2 sets of data
    i have the sitemap file and then also also have the web.config folder / page security rules to maintain…
    in my apps they need to really reflect the same set of rules so i would favor allowing a combined map for cases where that’s all that i need.
    if an admin role gets a page i would declare that one time and have the rest of the system follow that.

  4. great update david, just like we talk on twitter.. i started yesterday updating my dev machine to the latest build, and will update my app that usas slab authentication 0.4 to this one.

    I also think this content load with authorization should be on the framework, the hide menu things should’t, rigth now, i accomplashi that with staets, and since is something that is really diffrenc in each app it’ts to important to be on the bits. But i m excited to see what David came out to do this trick, may is clenner then mine, i just subscrive the authentication events on the mainpage, and chage the state.

    Thanks david, imo this and the print api that i would be testing today is already is a must have code for every silverlight developer.

  5. Hi,

    I downloaded your zip and tried to run it. I get ab error that I am missing the namespace BusyIndicator. Is there a missing dll?

    Larry

  6. David, take a look at this link:
    http://www.olsonsoft.com/blogs/stefanolson/post/Updates-to-the-Custom-and-Authenticating-URI-Mappers-for-Silverlight-3-RTW.aspx

    Stephan did some stuff to add attributes to the codebehind for a page to allow Requires Attributes.
    i think that option would be a good thing to use in paralell with the RIA service
    [RequiresRole("")] and [RequiresAuthentication()]
    attributes…. allow the dev to put them on the code file and evaluate them at runtime like the domain service does….

    1. I considered this, and it’s certainly an option. The question is just how flexible you’d like to be. With the classes I provided with the AuthContentLoader, you can provide an INavigationAuthorizer that checks this attribute (rather than using the declarative allow/deny tags). Part of the reason I don’t do this by default is that this forces you to have the types for the pages on hand. If you’re using the XapContentLoader, that’s undesirable — instead, you want to authorize before even attempting to download the (potentiallly large) XAP containing that page that would otherwise have these attributes on it.

  7. I’m excited about SLaB, but having problems compiling:

    This doesn’t resolve for me…
    xmlns:SLaB=”http://www.davidpoll.com/SLaB”
    plus
    The tag ‘AuthContentLoader’ does not exist in XML namespace ‘clr-namespace:SLaB.Navigation.ContentLoaders.Auth;assembly=SLaB.Navigation.ContentLoaders.Auth’.
    and
    System.IO.FileNotFoundException was thrown on ~ Could not load file or assembly ‘SLaB.Navigation.ContentLoaders.Utilities, ~ or one of its dependencies.

    1. This looks like you’re not referencing the right libraries. SLaB.Navigation.ContentLoaders.Auth depends on SLaB.Navigation.ContentLoaders.Utilities, and you’ll need to reference both of those in your app in order to build.

      1. Yes, I agree. I do have four references for SLaB.Navigation.ContentLoaders* in my application’s References. I’ve been comparing my solution to the ScratchBusinessApplication I downloaded that DOES work fine. The only difference is that where my application doesn’t complain about
        Principal=”{Binding User, Source={StaticResource WebContext}}”>
        whereas the ScratchBusinessApplication does (e.g. “The resource “WebContext” could not be resolved”).
        I’m stumped. ScratchBusinessApplication runs like a champ.

      2. Fixed my problem! :)

        I was using dlls from your earlier project, such that when I removed them and copied the ones from the ScratchBusinessApplication (from ‘AuthorizingNavigation.zip’) it worked!

        Thanks for the encouragement and moreso for the excellent code. Gracious David.

  8. Unable to login to ur example page when I click the image and try to login.
    username: Test
    pwd: _Testing

  9. The asynchronous nature of SL is driving me nuts. I hope the following might help someone.

    I wanted to present the LoginWindow if the user is not authenticated.

    In the OnNavigatedTo event handler, I had
    If WebContext.Current.User.IsAuthenticated Then
    New LoginUI.LoginRegistrationWindow.Show()
    End If

    However, this turns out to be wrong because the WebContext.Current.Authentication.IsBusy, because “Keep me logged in” was checked, then the login form shouldn’t be shown, until the operation is complete and we know whether the user is logged in or not.

    Therefore we need to be notified not only the user’s authentication status changes, but also when authentication.IsBusy changes.

    I’ve provided a code sample at http://www.redmountainsw.com/wordpress/archives/sl4-navigation-authorization

  10. Great work David, I am using your Content loaders and SiteMap stuff to drive a SL based dashboard. I am not using Frames, but I am getting the dynamically loaded content (.Page) from the IAsyncResult and then making that the content of another container (dashboard item). In any event, I had to make a fix/change to the XapLoader.cs in SLaB.Utilities.Xap to Slabify the SecuritySettings element in the OutOfBrowserSettings:

            private static Deployment.Deployment GetManifest(Stream manifestStream)
            {
                StreamReader manifestReader = new StreamReader(manifestStream);
                string manifestXaml = manifestReader.ReadToEnd();
                manifestXaml = SLaBify(manifestXaml, "Deployment");
                manifestXaml = SLaBify(manifestXaml, "Icon");
                manifestXaml = SLaBify(manifestXaml, "IconCollection");
                manifestXaml = SLaBify(manifestXaml, "OutOfBrowserSettings");
                manifestXaml = SLaBify(manifestXaml, "WindowSettings");
                manifestXaml = SLaBify(manifestXaml, "SecuritySettings");
                manifestXaml = manifestXaml.Replace("xmlns=\"http://schemas.microsoft.com/client/2007/deployment\"",
                                                    "xmlns=\"http://schemas.microsoft.com/client/2007/deployment\" xmlns:SLaB=\"clr-namespace:SLaB.Utilities.Xap.Deployment;assembly=SLaB.Utilities.Xap\"");
                return UiUtilities.ExecuteOnUiThread(() => (Deployment.Deployment)XamlReader.Load(manifestXaml));
            }
    
    1. Oh, and by the way, that sounds like a really cool application of an INavigationContentLoader :). I’d love to see the end result if it’s public!

      -David

  11. david.poll :This looks like you’re not referencing the right libraries. SLaB.Navigation.ContentLoaders.Auth depends on SLaB.Navigation.ContentLoaders.Utilities, and you’ll need to reference both of those in your app in order to build.

    I met the same problem. I include dll
    SLaB.Navigation.ContentLoaders.Auth
    SLaB.Navigation.ContentLoaders.Error
    SLaB.Navigation.ContentLoaders.Utilities
    SLaB.Navigation.ContentLoaders.Xap
    in project and the version is SLaBv0.9.
    Can you help me?
    Thanks

  12. willson :

    david.poll :This looks like you’re not referencing the right libraries. SLaB.Navigation.ContentLoaders.Auth depends on SLaB.Navigation.ContentLoaders.Utilities, and you’ll need to reference both of those in your app in order to build.

    I met the same problem. I include dllSLaB.Navigation.ContentLoaders.AuthSLaB.Navigation.ContentLoaders.ErrorSLaB.Navigation.ContentLoaders.UtilitiesSLaB.Navigation.ContentLoaders.Xapin project and the version is SLaBv0.9.Can you help me?Thanks

    I mean “The resource “WebContext” could not be resolved Problem
    Thanks

    1. WebContext is from a RIA Services project. Is this what you used to start the project? Specifically, you must have used the Business Application project template when you started building the application.

  13. I stumble on your blog just now… Very good posts… That authentication stuff is a life saver… Hoppefully you’ll post new bits and things soon ;)
    Thanks…

  14. Hi,
    As i read in forums, I understand that using XMAL property of RichTextBox control, I will be able to add a text value to Db along with its format. But can you provide me some snippets (C#) to retrive the stored data to RichtextBox again.

  15. Hi David,

    Please accept my apologies for not checking your downloads page for a more recent build of SLaB prior to sending my previous email. I subscribed to your blog, and assumed there would be an article to follow a new release. I check your home page periodically, but usually not the downloads. 

    At-any-rate, here is a link (below paragraph) to your Sitemaps.xaml file with a very small change (with comments). If you replace your Sitemaps.xaml file in the ScratchContent project with the one provided in the link below, hit F5, you’ll notice that the browser hangs and is non-responsive for about 7.976 seconds. The progress bar is visible, but motionless.

    Sitemaps.xaml file link.
    http://cid-e088706e10573b74.office.live.com/self.aspx/.Public/Work/Sitemaps.xaml

    The small change in the Sitemaps.xaml file is simply an addition of a few more Sitemapnodes. To continue, adding only a few more nodes magnifies the hang-time by ^2. (Please see comments in Sitemaps.xaml file to see a longer hang-time)

    In my previous email I mentioned where I believe this problem exists. Do you get my last email regarding the BreadCrumNavigator? I can resend it if need be.

    Is there any chance you might be able to take a look at the BreadCrumNavigator & the SLaB.Utilities.ChangeLinq project to see where the performance can be improved? I will be forever grateful. Just to recap, the TreeViewNavigator is perfectly fine, that is, it doesn’t override the OnTrimmedSitemapChanged method. I really want to use the BreadCrumNavigator, however, the size of the Sitemap I am producing can range anywhere from 10 nodes to 200 nodes. The original Sitemap.xaml file in your sample project only has 27 Sitemap nodes.

    Again, I Just wanted to say a huge thank you for providing some awesome libraries to aide us all with SL development. They have saved me a lot of dev time.

    I look forward to hearing from you regarding the Sitemap size / performance problem with the BreadCrumNavigator.

    Sincerely,

  16. I have a slight problem and I was wondering if anyone on this blog can help me.

    But my problem involves a Silverlight 4 RIA Services application which utilizes Windows Authentication and needs to run or operate as an out of browser application. The application must obtain detailed or descriptive user info in order to correctly construct the initial application user interface. However, the dilemma is that the Authentication Service call was timing out every time I executed the application, and my RIA Service was never even executed.

    So if I can receive any help, then that will be great from the blog readers, then that will be great.

Leave a Reply