Building a forum application, Part 6
This is the sixth part in a series of posts where I build a forum application. The other posts can be found here:
- Building a forum application, Part 1
- Building a forum application, Part 2
- Building a forum application, Part 3
- Building a forum application, Part 4
- Building a forum application, Part 5
I have now started to work on membership and authentication for the application. I decided, after talking to Magnus, to implement two different ways of authenticating a user. First I have a normal login with a username and a password. Then I allso decided to have the ability to authenticate using OpenID.
OpenID is a free framework that let a user sign in with the same account on every site that supports openid. This means a security boost for my application as I don´t have to store any passwords for the users that authenticates using openid. I do still create a account on the site the first time a user signs in using openid, but I don´t have to give them any password. So you will still have the same functionality available eather way you choose to authenticate.
I use RPX to verify the openid accounts. This is a free online service that takes care of the verification and returns a token to a URL that I choose that I then can use to get the user data for the signed in openid account. The action used to recive the token and sign in the user looks like this:
[ValidateInput(false)] public ActionResult AuthenticateOpenId(string token) { var profile = _rpx.GetProfileData(token); var authentication = _authenticationService.SignInWithOpenId( profile); CurrentAuthentication.SetActiveAuthentication(authentication); return RedirectToAction("List", "Area"); }
Here I first get the profile data from the openid account using a class that wrapps the RPX API. I then use that data in my AuthenticationService where I check if the user has been signed in before. If he/she hasn´t, I create a new account. I then return the authenticated user. Then I sign in the user to my site using a slightly modified version of FormsAuthentication. Then I redirect the user back to the start page.
The class that wrapps the RPX API implements a interface that looks like this:
public interface IRpxWrapper { IOpenIdWrapper GetProfileData(string token); IEnumerable<string> Mappings(string primaryKey); void Map(string identifier, string primaryKey); void Unmap(string identifier, string primaryKey); }
I will not talk about the actuall implimentation now, as It will probably change quite alot from how it looks today. But If your interested, you can download the source and check it out for yourself, and feel free to ask any questions. The basic idea is that you send a request to the RPX service and recive information back in xml or json format. Then I reformat that to a strongly typed object to have a nice programing experiance.
As I told you earlier, I´m using a slightly modified version of FormsAuthentication to authenicate a signed in user. The only thing I have realy done, is to wrapp it in a interface and added a ID property. I added the ID property because I allways want to have that present when I write my code, so that I don´t have to get it from a different source. The reason I wrapped the authenication is pretty simple: I want to test my controller actions as easy as possible. As the FormsAuthenication use the HttpContext, it´s hard to test. But if I wrapp it with a interface, it´s easy to mock. The interface I use looks like this:
public interface IAuthenticationWrapper { bool IsAuthenticated { get; } bool IsInRole(string role); void SetActiveAuthentication(IAuthentication authentication); IAuthentication ActiveAuthentication { get; } void RemoveActiveAuthentication(); }
Here is the IAuthentication interface:
public interface IAuthentication { int Id { get; } string Name { get; } IEnumerable<Role> Roles { get; } }
This is a pretty simple interface that is implemented by the Authentication model class.
I have allso worked a little bit on validation. So far I have only implemented server side validation, but I will add some client side validation as well. For this application I choose Fluent Validation to be my validation library. I have acctually never used it before, so I can´t say to much about it just yet other then I like what I have seen so far. I use the approach I describe in this post to validate my controller action input in a easy way. I have allso created a ExceptionFilter for my actions that handels all ValidationExceptions thrown in a action and adds them to the ModelState. This allows me to throw exceptions from my service when something is wrong and not have to use a "uggly" try/catch in my controller action. This is usefull when I need to go to the database to validate something. One example is when I validate a authentication. Then I need to check for the user in the database, and then validate the password if the user exists. My exception filter looks like this:
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class HandleValidationErrorAttribute : FilterAttribute, IExceptionFilter { public void OnException(ExceptionContext filterContext) { if (!(filterContext.Exception is ValidationException)) return; filterContext.Controller.ViewData.ModelState .AddValidationErrors( ((ValidationException)filterContext.Exception).Errors); filterContext.Result = new ViewResult { ViewName = (string)filterContext.RouteData .Values["action"], ViewData = filterContext.Controller.ViewData, TempData = filterContext.Controller.TempData }; filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.Clear(); filterContext.HttpContext.Response.StatusCode = 200; } }
With this approach my SignUp action becomes as simple as this:
[AcceptVerbs(HttpVerbs.Post), HandleValidationError, EntityValidation(typeof(AuthenticationValidator), "authentication"), CaptchaValidation("Invalid captcha")] public ActionResult SignUp(Authentication authentication) { if (ModelState.IsValid) _authenticationService.SignUp(authentication); return View(); }
This action will, with only two lines of code, validate the authentication for any simple model errors, validate that the username and email address doesn´t exist allready, validate a captcha field and put any error in the ModelState for the view to display. The thing missing so far is a message for a successfull signup. The CaptchaValidationAttribute I use is described here.
This is what I have done so far. You can download the complete source code from here, or use my SVN repository at: https://87.237.213.147:8443/svn/Mattias_Forum, the username is "User" and you dont need a password.