Silverlight pages that require login, part 3

This page is part 3 in a series of pages about “Silverlight pages that require login”. The parts are part 1, part 2 and part 3.

I’m evaluating using Silverlight and RIA services to create a web based application. Right now I’d like to set something like this up;

  1. Some links – secured links – cannot be clicked unless you’re logged in – part 1
  2. If the user logs in, the secured links should automatically be enabled – part 1
  3. If the user logs out, the secured links should automatically be disabled – part 1
  4. If the user logs out while accessing a page that requires authentication, the user should be booted off the page – part 2
  5. If anyone directly accesses a page that requires authentication, the user shouldn’t see any information on the page – all information on the page is to be considered sensitive. part 2
  6. If the user is logged off while on the on the page, then the user should be booted off the page – part 2
  7. If the user isn’t logged in, the user should be asked to log in and if the user fails to log in, he should be booted to the main page. This is similar to 5 but 5 is about prohibiting access, this is about allowing login then access and the prohibition is a fallback – part 3

I could plug the check into the page itself, in the OnNavigatedTo event, but I want to use the navigation framework. The Frame event Navigated is too late, that’s after the navigation has taken place. If only there was a way to inject handling before the actual navigation has taken place… Well, there seems to be just such an event, Navigated. Lets give that a try, in our main page, add the following event handler and navigate to it;

            <navigation:Frame
                x:Name="ContentFrame"
                Style="{StaticResource ContentFrameStyle}"
                Source="/Home"
                Navigated="ContentFrame_Navigated"
                Navigating="ContentFrame_Navigating"
                NavigationFailed="ContentFrame_NavigationFailed">
                <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>

Initially, we’ll simply try to trap the navigation attempt and kill it dead – if the user isn’t logged in. Note how we use the name of the page as a string and not a constant or some other nicer method. This is clearly bad, and will turn ugly if more than one secured page is used. Therefore it’s I might look for a better solution further on.

Anyway, here’s the code that cancels forbidden navigation attempts, or at least that looks like its doing the job;

        private void ContentFrame_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            if (e.Uri.Equals("/DemoPage"))
            {
                if (WebContext.Current.User.IsAuthenticated == false)
                {
                    e.Cancel = true;
                }
            }
        }

About the only Uri property that I can access is the OriginalString, because the Uri is so seriously broken. This leaves us with a huge problem, because program will stop the user accessing the secured page using this url;

http://localhost:58921/Myapp.ApplicationTestPage.aspx#/DemoPage

But this one will be let right through;

http://localhost:58921/Myapp.ApplicationTestPage.aspx#/DemoPage#bob

Where “bob” will be passed as a parameter string to the backing service. This is bad, because that makes circumventing our security a breeze! Luckily, in part 2 I added a fallback that raises an exception, but still, bad show!

And this one also fails, because equals is case sensitive, but the page-mapping code isn’t case sensitive;

http://localhost:58921/Myapp.ApplicationTestPage.aspx#/DemoPAge

So what to do? What I’d like to do is ask the navigation framework what page-class it intends to use to fullfill the navigation, and then prevent users from accessing secured pages. I would have liked the NavigatingCancelEventArgs event to have that information – but alas. Anyway, it turns out that our Frame.UriMapper contains a method called MapUri. This method takes the incoming Uri and performs the internal mapping with the different translations that have been set up.

Using strings have these disadvantages;
I decided to use create an application that

1) Disables certain links when you’re logged off
2) Asks you to login when trying to access those pages through deep linking

I succeeded – it wasn’t even very difficult, nice works guys! You can read about my attempts here; https://lotsacode.wordpress.com/2010/02/21/silverlight-pages-that-require-login-part-1/

Unfortunately, that only takes us half way, we’re still stuck with case-differences and the fact that arguments are padded to the actual path.

However; the method I use to verify which pages are ok to visit leaves me dissatisfied. I’d like to be able to ask the navigation framework which frame-class it intends to use, not what the name of the .xaml file is! Using the name of the xaml file these caveats;

  1. It will break as soon as I refactor the name of the class/xaml
  2. It forces me to compare strings, and string comparison is case sensitive in c#, but the navigation framework isn’t. So that leaves a big risk, locked down pages may be visited by changing the casing in the query. I’ve seen this bug in actual forum answers.
  3. It’s just plain ugly.

Comparing types is sooo much nicer. If I could get the type, I could inherit from something like <code>SecuredPage</code> – and automatically resctrict access to all pages that inherit from that class.

The closest I can come at this point is to check the path for the presence of my xaml file name, in a non-case sensitive manner. Note that I’m including the initial slash and the .xaml token – this is to prevent false positives;

        public bool CaseInsensitiveContains(Uri uri, string value)
        {
            string fullString = uri.ToString().ToLower(CultureInfo.InvariantCulture);
            value = value.ToLower(CultureInfo.InvariantCulture);
            return fullString.Contains(value);
        }

        private void ContentFrame_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            Uri mappedUri = ContentFrame.UriMapper.MapUri(e.Uri);
            if (CaseInsensitiveContains(mappedUri, "/DemoPage.xaml"))
            {
                if (WebContext.Current.User.IsAuthenticated == false)
                {
                    e.Cancel = true;
                }
            }
        }

Now with that horror out of the way, let’s look at how we can ask the user to login.

If a login is required, the login window should be opened. And once that’s closed and the the login was a success, then the user should be navigated to the desired page. Otherwise, the page should navigate to the home page. From what I can tell, there are no events in the login form that we can hook into, so I’m adding my own event.

I’m adding this code to to the class LoginRegistrationWindow

        public event EventHandler<EventArgs> Closed;

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            if (Closed!=null)
            {
                Closed(this, e);
            }
        }

And I’m adding code to show the login if the page is restricted. The navigation code in the MainPage looks like this;

        private void LoginOrGoToMain(Uri uri)
        {
            if (WebContext.Current.User.IsAuthenticated == false)
            {
                LoginRegistrationWindow loginRegistrationWindow = new LoginRegistrationWindow();
                loginRegistrationWindow.Closed +=
                    (s, e) =>
                        {
                            if(WebContext.Current.User.IsAuthenticated)
                            {
                                // Try again!
                                ContentFrame.Navigate(new Uri(uri.ToString(), UriKind.Relative));
                            }
                        };
                loginRegistrationWindow.Show();
            }
        }

        private void ContentFrame_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            Uri mappedUri = ContentFrame.UriMapper.MapUri(e.Uri);
            if (CaseInsensitiveContains(mappedUri, "/DemoPage.xaml"))
            {
                if (WebContext.Current.User.IsAuthenticated == false)
                {
                    e.Cancel = true;
                    LoginOrGoToMain(e.Uri);
                }
            }
        }

Which works so good that I’m tempted to remove the disabling of the hyperlinks that started us off in part 1…

About mfagerlund
Writes code in my sleep - and sometimes it even compiles!

3 Responses to Silverlight pages that require login, part 3

  1. Pingback: Silverlight pages that require login, part 2 « Mattias Fagerlund's Coding Blog

  2. Pingback: Silverlight pages that Require login, part 1 « Mattias Fagerlund's Coding Blog

  3. Julien says:

    Hi Mattias,

    Thanks for the great article it helped me get started.

    Regarding what you said “Comparing types is sooo much nicer. If I could get the type, I could inherit from something like SecuredPage – and automatically resctrict access to all pages that inherit from that class.”
    I did something like that.

    For starters my main page “Home.xaml” is the one that does all the authentication, whenever it’s loaded it requires the user to be authenticated and keeps prompting until the user is.
    Then I created a base class SecurePage which derives from Page and overrides and seals the NavigatingTo and NavigatingFrom methods.
    If the user tries to navigate to the SecurePage without being authenticated, he or she is re-directed to the Main page which will then authenticate the user.

    That way whenever there is a page I want to be secured I just derived from SecurePage, and change the class in the XAML; pretty neat🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: