RIA Services and Authentication - Part 2 (Using Roles)

Addendum to the RIA Services and Authentication post demonstrating use of roles in the Book Club application as part of authentication and authorization.

When I posted my RIA Services and Authentication post earlier in the week (which I highly recommend checking out first if you haven't), the first comments/tweets I saw indicated people also want to see roles working in the application. So I decided to add this bit of functionality, both in the Book Club application and the supporting functionality in the RIAEssentials framework, so you can use it easily in your own applications as well.

From a scenario perspective, what I am going to do is require an Admin role to browse and add book club members.


Adding and Using Roles

The first step is to update the AuthenticationService implementation I showed earlier to add roles into the authentication process. It only requires quite literally a couple of minor tweaks, th. The added lines are shown in bold:

public class AuthenticationService : FormsAuthenticationService<User> {

    public AuthenticationService()
        : base(/* enableRoles */ true) {
    }

    protected override User ValidateCredentials(string name, string password,
                                                string customData) {
        User user = null;
        using (BookClubEntities bookClubObjectContext = new BookClubEntities()) {
            Member member = bookClubObjectContext.Members.Where(m => m.MemberAlias == name).FirstOrDefault();

            if ((member != null) && Authentication.ValidatePassword(password, member.PasswordHash)) {
                user = new User() {
                    Name = name,
                    Roles = member.IsAdmin ? new string[] { "Admin" } : null,
                    MemberID = member.MemberID,
                    DisplayName = member.MemberName
                };

                if (member.LoginDate != DateTime.UtcNow.Date) {
                    member.LoginDate = DateTime.UtcNow.Date;
                    bookClubObjectContext.SaveChanges();
                }
            }
        }

        return user;
    }
}

As you can see, very minor updates. All the app developer has to do is enable roles, and then populate the Roles property of the user from the DAL. The next step is using the role information in authorization rules on domain services or domain service operations. For the sample app, all I need to do is declare that the MembersService requires Admin role.

[RequiresAuthentication]
[RequiresRole("Admin")]
public class MembersService : LinqToEntitiesDomainService<BookClubEntities> {
}

Finally on the client, I need to update the link to the Members page to account for the new authorization rule:

<Application.Resources>
  <fx:UserConverter x:Key="requiresAdminConverter"
    RequiresAuthentication="true" RequiredRole="Admin" />
</Application.Resources>

<HyperlinkButton Content="Members" NavigateUri="/Members"
  Visibility="{Binding Source={StaticResource webContext},
      Path=Current.User, Converter={StaticResource requiresAdminConverter}}" />

It is as simple as that. At the very least, it can be with the right framework bits in place. :-)


The "Uninteresting" Mechanics

Using roles along with forms authentication requires some involved (more so than I'd like) participation in the HTTP pipeline the moment you have a custom DAL for managing users and their roles.

Specifically it requires inspecting the user principal when it is first generated by the forms authentication module, and then loading roles for that user, and finally replacing the established principal with a new principal that contains role information. Doing so requires one to implement and register an HTTP module in configuration, which is yet another pain point. The general philosophy with RIA Services is that the framework just takes care of the ugly plumbing details that aren't specific to the application. So applying that approach here, I have updated the FormsAuthenticationService base class in the RIA Essentials framework to do all the grungy work without requiring config modifications (for those interested in the details, the source code is all there for you to step through).

There is one small wrinkle. Until the ability to extend the HTTP pipeline without adding configuration comes to ASP.NET out-of-the-box, you need to make one little change in your application to support the implementation in the FormsAuthenticationService class:

public class Global : WebContext {
}

Basically, In your Global.asax.cs, you need to derive your application type from a base class provided as part of RIAEssentials framework called WebContext instead of the deriving directly from the HttpApplication class. Think of WebContext here as the eventual equivalent of the WebContext class on the client. There might be some interesting things that WebContext on the server enables down the road.


Related Links

Posted on Wednesday, 6/30/2010 @ 1:12 PM | #Silverlight


Comments

2 comments have been posted.

KB

Posted on 8/23/2010 @ 4:11 PM
Great blog posts about getting custom authentication working... although I'm confused as to why we are doing all of this in the first place. Is the whole reason so that the RequiresRole attribute works properly? Would it be equally as sound to perform a check in the code at the beginning of every method in the domain service that has role based requirements? Something like:

if(_user.Roles.Contains("Admin"))
//allow
else
//not allowed: throw exception

KB

Posted on 8/23/2010 @ 4:17 PM
Great blog posts about getting custom authentication working... although I'm confused as to why we are doing all of this in the first place. Is the whole reason so that the RequiresRole attribute works properly? Would it be equally as sound to perform a check in the code at the beginning of every method in the domain service that has role based requirements? Something like:

if(_user.Roles.Contains("Admin"))
//allow
else
//not allowed: throw exception
Post your comment and continue the discussion.