in·dom·i·ta·ble
adj.
Incapable of being overcome, subdued, or vanquished; unconquerable.
9th
FEB
Forms Authentication in Asp.Net MVC, Part III
Posted by indomitablehef | Filed under Asp.Net MVC
This is part 3 of 4
Forms Authentication in Asp.Net MVC
Moving on from where we left off in part II, we now need to implement our MembershipProvider.
We have two final tasks left to accomplish
- Retrieve the User from the datastore
- Check to see if the username and password are valid
Following the Single Responsibility Principle, we’ll separate these, using the MembershipProvider for validating the username/password, and a UserRepository for retrieving the user from the datastore.
From last time, recall that our IMembershipProvider looks like this:
1 2 3 4 | public interface IMembershipProvider { bool ValidateUser(string userName, string password, out string validationMessage); } |
Now we’re going to need a user repository, or some other form of User data access object. I’m using NHibernate, but you could use Linq to Sql or your ORM of choice. Either way, the interface for our IUserRepository is quite simple. I leave the implementation entirely up to you.
1 2 3 4 | public interface IUserRepository { User GetByUserName(string userName); } |
So, we can implement our MembershipProvider like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public class MembershipProvider : IMembershipProvider { private IUserRepository userRepository; public MembershipProvider(IUserRepository userRepository) { this.userRepository = userRepository; } public MembershipProvider() : this(ServiceLocator.Current.GetInstance<IUserRepository>()) { } public bool ValidateUser(string userName, string password, out string validationMessage) { User user; try { user = userRepository.GetByUserName(userName); if (user == null) { validationMessage = "invalid username"; return false; } if (user.Password != password) { validationMessage = "invalid password"; return false; } validationMessage = ""; return true; } catch (Exception ex) { throw new Exception("Encountered an error while attempting to validate username and password",ex); } } } |
And we can test it like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | [TestFixture] public class MembershipProviderTests { #region Setup/Teardown [SetUp] public void SetUp() { mockedUserRepository = MockRepository.GenerateMock<IUserRepository>(); provider = new MembershipProvider(mockedUserRepository); } #endregion private IMembershipProvider provider; private IUserRepository mockedUserRepository; [Test] public void IsValidWhenUserNameAndPasswordMatchExistingUser() { //arrange var user = MockRepository.GenerateStub<User>(); user.UserName = "billy.pilgrim"; user.Password = "tralfamador"; mockedUserRepository.Expect(x => x.GetByUserName("billy.pilgrim")).Return(user); string validationMessage = ""; //act var isValid = provider.ValidateUser("billy.pilgrim", "tralfamador", out validationMessage); //assert Assert.IsTrue(isValid); } [Test] public void IsInValidWhenUserDoesNotExist() { //arrange mockedUserRepository.Expect(x => x.GetByUserName("billy.pilgrim")).Return(null); string validationMessage = ""; //act var isValid = provider.ValidateUser("billy.pilgrim", "tralfamador", out validationMessage); //assert Assert.IsFalse(isValid); Assert.AreEqual("invalid username",validationMessage); } [Test] public void IsInValidWhenPasswordDoesNotMatch() { //arrange var user = MockRepository.GenerateStub<User>(); user.UserName = "billy.pilgrim"; user.Password = "tralfamador"; mockedUserRepository.Expect(x => x.GetByUserName("billy.pilgrim")).Return(user); string validationMessage = ""; //act var isValid = provider.ValidateUser("billy.pilgrim", "wrongpassword", out validationMessage); //assert Assert.IsFalse(isValid); Assert.AreEqual("invalid password",validationMessage); } } |
And we’re basically done. There’s one more installment coming, though. We’ll take a look at setting up the IoC container used in these examples (Castle Windsor) and do a little refactoring.
Reader's Comments
Leave a Reply
Post Meta
-
February 9, 2009 -
Asp.Net MVC -
6 Comments
-
Comments Feed -
Del.ico.us
-
Digg This
Hi, thanks for this series, I have been following with interest.
If I could make a request for a follow up… How about an implementation for authorization? I have seen samples with authorization filters in the attributes, but that seems very limiting, having to know all your roles up front. I am interesed to see if there is a more flexible approach that allows “administrators” to create their own roles and then assign to users.
Thanks for the time and effort you have put into these articles.
Thanks, Col. I’ll definitely try to get into that, though it might be a couple steps down the road in this series. From a high level, however, here’s what I would do. Create an AuthorizeActionAttribute, just like our RequiresAuthenticationAttribute, and give it public properties of string[] Role and string Action.
Then you could authorize individual controller actions like this:
[AuthorizeAction(Roles = new string[]{”Administrator”,”AccountAdministrator”})]
public ActionResult ResetPassword(User user)
{
< ...>
}
or
[AuthorizeAction(Action = “Password Reset”)]
public ActionResult ResetPassword(User user)
{
< ...>
}
…or even combine the two. You’d need some sort of IAuthorizationProvider implementation that would have methods like:
bool AuthorizeRole(string role, ControllerContext context);
bool AuthorizeAction(string action, ControllerContext context);
And you’d need to have your roles associated with actions, and be able to retrieve those relationships from some repository so that you can check to see if your user is in a role that is authorized to perform a given action.
Thanks for the suggestion and it is actually where I have gone. The one stumbling area that I have is how do I check it from the view?
I want to be able to haave something like
if (HttpContext.Current.User.HasRight(”Password Reset”)) {
}
I have a feeling I need to do something with IPrincipal, but this is new to me. Any suggestions?
Sounds like a perfect case for an extension method to the HtmlHelper class, as described here (http://www.iridescence.no/post/Conditional-Rendering-in-AspNet-MVC-Views.aspx)
With a little work, your code could then look like
<% Html.IfUserHasPermissionTo("Reset Password",ViewContext, html => html.RenderPartial("PasswordReset")) %>As for working with IPrincipal, I would avoid having the User class know about what rights it has, instead delegating that to an AuthorizationProvider, (like the one described above) and your extension method could use that.
Thanks again for the feedback. I went ahead and implemented an extension - another first!
I have just implemented a bool return for now but it is making the page messy, just as the link you gave is trying to remove, so I’ll continue work on it to make it a neater solution.
I would just like to say that this series of blogs and your comments have really helped me. They have been written very clearly with great explanation. I will be coming back to see what else you produce… keep it up.
Hi,
Thank you very much for this series. I would like to add Roles to the Contexts, I have the roles in the database, stored and assigned to the user. I just want to assign it to the ControllerContext object. I was not able to find the correct place to assign these values.
What I want to see is, when I break in any page, I can see the user information as well as Roles information for the user.
Thank you very much for your help.
JS