Introduction
As we know, Silverlight is very capable platform for building Line of Business applications today, both in and out of the browser. It’s come to the point that – at least in my experience – a lot of desktop applications are now built with Silverlight instead of WinForms, skipping over WPF completely.
With that in mind, it’s pretty weird that there’s an essential part that seems to be missing: authentication & authorization. When you look at the Silverlight Core CLR, there’s not much there concerning this – although it’s a no-brainer for business and enterprise applications: you need to make sure certain parts of your application are only available to users that are authenticated or have a specific role. Sure, you can use the hosting web page & ASP .NET authentication to ensure only authenticated persons can reach your Silverlight application, but there’s no out of the box way to enable or block a user from navigating to a specific view in your application.
Luckily, it only requires a little bit of coding and all in all: it’s quite easy to enable authenticated & authorized navigation in your Silverlight applications, mainly thanks to the introduction of a new class in Silverlight 4: the custom content loader.
In this article, we’ll look into enabling scenarios to enable/disable certain parts of your application for authenticated users, and to automatically ask the user for his credentials if he’s trying to access a part of the application that requires him to be authenticated or to have a specific role. But let’s start with the beginning: the custom content loader.
The accompanying source code for this article can be downloaded here.
This is the first of a two-part article series on authorization in Silverlight. In the second part, we’ll look into automatically manipulating (disabling, hiding) UI elements depending on the credentials of the authorized user.
Introducing: the Custom Content Loader.
In Silverlight 4, a new interface was introduced: the INavigationContentLoader interface. Together with that, a Navigation Frame was given a ContentLoader property, which can be set to any class implementing said INavigationContentLoader. As the name implies, the content loader is responsible for (asynchronously) loading the content that’s associated with the target Uri. This opens up a whole load of possibilities (I’ve seen the content loader being used for, for example, loading content from a different XAP), one of which is authorized navigation.
This is how this interface looks:
public interface INavigationContentLoader
{
IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState);
void CancelLoad(IAsyncResult asyncResult);
bool CanLoad(Uri targetUri, Uri currentUri);
LoadResult EndLoad(IAsyncResult asyncResult);
}
As you can see, one of the methods is named CanLoad: this is the perfect method for what we’re trying to do: we can test if a user has the rights to navigate to a specific view, returning true or false to the CanLoad method of a custom content loader.
The required parts.
With this in mind, we can start building our Authenticated Navigation parts. We’ll require a few things:
- A way to define which Views need which rights: does a user need to be authenticated to access a view, and/or does he need a certain role? We’ll wrap this information in a UriRightMapping class.
- An Authentication Context: this is the main class. It will contain an IPrincipal, a list of UriRightMapping objects, and the main logic to check if a user has the rights to access a certain view, and optionally show a login dialog if he hasn’t. It’s an instance of this class that will be called in the CanLoad method of our custom content loader.
- A custom content loader, named AuthorizedContentLoader.
We’ll start with the UriRightMapping class.
Uri right mappings: who can access which resource?
The UriRightMapping class should contain the View & the access requirements for this View. Therefore, it requires a Uri property (to the View), an AuthenticationRequired property, and an AllowedRoles property.
public interface IUriRightMapping
{
string AllowedRoles { get; set; }
bool AuthenticationRequired { get; set; }
bool CanNavigate(System.Security.Principal.IPrincipal principal);
Uri Uri { get; set; }
bool Matches(System.Uri targetUri);
}
The CanNavigate-method, accepting an IPrincipal, is the most important one of this class: it will check the Uri & required access against the user who’s logged in (= the IPrincipal). This method is called from the Authentication Context implementation, which in turn is called when we’re navigating to a certain Uri.
When an AllowedRole has been defined, the requirement for authentication is logically assumed. When AuthenticationRequired is true, but no roles have been defined (or the role “Any” has been defined), any authenticated user can access the Uri we’re trying to navigate to.
The code for this check looks as follows:
public bool CanNavigate(IPrincipal principal)
{
// No uri?
if (this.Uri == null)
return false;
// check authentication
if (principal == null && AuthenticationRequired)
return false;
else if (principal == null && !AuthenticationRequired)
return true;
if (!principal.Identity.IsAuthenticated && AuthenticationRequired)
return false;
// authentication checked. Now check authorization.
// check role
if (_rolesAsList.Count == 0 || _rolesAsList.Contains<string>("Any"))
return true;
foreach (var role in _rolesAsList)
{
if (principal.IsInRole(role))
return true;
}
return false;
}
So, how do we define these mappings? We can simply do this in XAML, as such:
<local:UriRightMapping AuthenticationRequired="True"
Uri="/Views/FirstView.xaml"></local:UriRightMapping>
<local:UriRightMapping AllowedRoles="Administrator"
Uri="/Views/SecondView.xaml"></local:UriRightMapping>
In the last part of this article (“bringing it all together”), we’ll see just where we need to position this XAML.
The Authentication context
The Authentication context is where most of the logic resides. This is what it should implement:
public interface IAuthenticationContext
{
bool CheckAuthenticationAndAuthorization(Uri targetUri, Uri currentUri);
bool ShowLoginBoxWhenUnauthorized { get; set; }
DependencyObjectCollection<UriRightMapping> UriRightMappings { get; set; }
IPrincipal User { get; set; }
}
We’ve got pretty self-explanatory properties like ShowLoginBoxWhenUnauthorized, a list of UriRightMappings, and the User (IPrincipal). The most important part of the interface is the CheckAuthenticationAndAuthorization method: this method will be called from our custom content loader, and will do all the credentials checking:
public bool CheckAuthenticationAndAuthorization(Uri targetUri, Uri currentUri)
{
// get mapping
var mapping = this.UriRightMappings.FirstOrDefault(m => m.Matches(targetUri));
// no mapping exists, navigation is allowed
if (mapping == null)
return true;
var navigationAllowed = mapping.CanNavigate(User);
if (!navigationAllowed && ShowLoginBoxWhenUnauthorized)
{
// show login, and afterwards, renavigate so the logic is executed again
LoginDialogService loginService = new LoginDialogService();
loginService.ShowDialog<LoginViewModel>((vm) =>
{
// log in
var loginOp = WebContext.Current.Authentication.Login(vm.UserName, vm.Password);
loginOp.Completed += (send, args)
=>
{
// navigate again: this will re-check the credentials and continue to the
// correct page if you're logged in / got the required role
if (Application.Current.RootVisual is MainPage)
{
((MainPage)(Application.Current.RootVisual)).ContentFrame.Navigate(targetUri);
}
};
vm.Cleanup();
},
(vm) =>
{
// cancel login in - stay on the current page.
vm.Cleanup();
});
}
return navigationAllowed;
}
First, we’ll find the correct UriRightMapping, and check if we can navigate to it with our current credentials by calling the CanNavigate(IPrincipal) method of the UriRightMapping. If this is allowed, navigation simply continues.
If it isn’t allowed, we can either simply block it (return false), or show a dialog box. This dialog box is used for logging in, and in the callback of the login method, we again navigate to the target Uri. This might look a bit weird: why not just wait until the user tried to log in, and then decide wether or not to continue the navigation? Well: as each service call in Silverlight is async, we can’t just “wait”: our CheckAuthenticationAndAuthorization method must return a value, in this case: false. But by renavigating to the target Uri after we’ve (successfully or not) tried to log in, the same flow will be executed again (through CanLoad on the custom content loader), resulting in the behavior we’re looking for.
As far as getting an IPrincipal to the client, for this demo, I’ve used a WCF RIA Service Authentication Domain Service, the out of the box implementation. As a user, you can log in with 2 different sets of credentials: log in with username “RegularUser” (no password required) to get authenticated, without a specific role, or log in with username “Administrator” to get authenticated and get the Administrator role.
As the Authentication Context expects an IPrincipal, you are of course not obligated to use WCF RIA Services: any IPrincipal will do.
Our own custom content loader
The last part is our custom content loader, AuthorizedContentLoader, which implements INavigationContentLoader so it can be used with a Frame. Next to an AuthenticationContext property, there’s not much code here: actually, the only thing that’s of importance here is the CanLoad method, in which we check authentication and authorization through our AuthenticationContext:
public bool CanLoad(Uri targetUri, Uri currentUri)
{
return AuthenticationContext.CheckAuthenticationAndAuthorization(targetUri, currentUri);
}
Bringing it all together…
When we bring this all together in XAML, this is how the code looks:
<navigation:Frame x:Name="ContentFrame"
Source="/Views/Home.xaml"
Style="{StaticResource NavContentFrameStyle}"
Navigated="ContentFrame_Navigated"
NavigationFailed="ContentFrame_NavigationFailed">
<navigation:Frame.ContentLoader>
<local:AuthorizedContentLoader>
<local:AuthorizedContentLoader.AuthenticationContext>
<local:AuthenticationContext User="{Binding User, Source={StaticResource WebContext}}"
ShowLoginBoxWhenUnauthorized="True">
<local:AuthenticationContext.UriRightMappings>
<local:UriRightMapping AuthenticationRequired="True"
Uri="/Views/FirstView.xaml"></local:UriRightMapping>
<local:UriRightMapping AllowedRoles="Administrator"
Uri="/Views/SecondView.xaml"></local:UriRightMapping>
</local:AuthenticationContext.UriRightMappings>
</local:AuthenticationContext>
</local:AuthorizedContentLoader.AuthenticationContext>
</local:AuthorizedContentLoader>
</navigation:Frame.ContentLoader>
</navigation:Frame>
Our Frame has a ContentLoader property, which we set to our AuthorizedContentLoader. This custom content loader has an AuthenticationContext property, which we set to our AuthenticationContext, passing in a User (the user from the RIA Services’ WebContext), and we set ShowLoginBoxWhenUnauthorized to true, so the users’ credentials will be requested when he tries to access a Uri he hasn’t got sufficient rights for (so: without being logged in, or with the wrong role).
In this Authentication Context, we can input a list of UriRightMapping objects through the UriRightMappings DependencyObjectCollection. And finally, in these mappings, the required credentials for each view are defined.
So, what happens when we navigate? When our frame is navigated to a certain resource, the CanLoad method on our AuthorizedContentLoader will be executed. This calls the CheckAuthenticationAndAuthorization method on the Authentication Context, which gets the correct UriRightMapping from its mapping collection, and checks if the logged in user can navigate to that mapping, through the mapping.CanNavigate method. If navigation is allowed, the load continues. If not, the login dialog window is shown, using WCF RIA Services to log in, and after the user has entered its credentials, we renavigate to the target Uri (which ensures the rights are checked).
Conclusion
As you can see, it’s not that hard to get authorized navigation in Silverlight with the help of a custom content loader: a few classes with a little bit of code is all it takes. Of course, this is a pretty basic authentication system: you’ll have to define mappings for all your views, for example, instead of applying some rules that work on all views conforming to one of those rules. But that’s not too hard to implement either, it’s mainly a matter of extending the mapping classes. In this article, we’ve learned how to get authorized navigation in Silverlight, and how easy it is to get this up & running quickly.
About the author
Kevin Dockx lives in Belgium and works at RealDolmen, one of Belgium's biggest ICT companies, where he is a technical specialist/project leader on .NET web applications, mainly Silverlight, and a solution manager for Rich Applications (Silverlight, Windows Phone 7 Series, WPF, Surface, HTML5). His main focus lies on all things Silverlight, but he still keeps an eye on the new developments concerning other products from the Microsoft .NET (Web) Stack. As a Silverlight enthusiast, he's a regular speaker on various national and international events, like Microsoft DevDays in The Netherlands, Microsoft Techdays in Belgium & Portugal, NDC2011, ... Next to that, he also authored a best-selling Silverlight book, Packt Publishing's Silverlight 4 Data and Services Cookbook, together with Gill Cleeren. His blog, which contains various tidbits on Silverlight, .NET, and the occasional rambling, can be found at http://blog.kevindockx.com/, and you can contact him on Twitter via @KevinDockx.