views:

2270

answers:

3

How do I return a string message from the ValidateUser method in my custom membership provider? I need this because I want to perform several checks (user is approved, user is blocked etc.) and give the user a good description, if the login process fails.

One option would be to throw an exception, but someone said that this is not a proper way for handling such situations.

For now I can only say "Login failed" or "Login succeeded" because of the bool return type.

Is it possible to create my own ValidateUser method or does the ASP.NET Membership mechanism uses the default one in it's internal operations?

+2  A: 

Those are two different operations.

To see if a user is approved, locked out, etc., look up the user (with GetUser()) and look at the IsApproved, IsLockedOut, etc. properties of the returned user. ValidateUser() is only for login, but you can do both.

Craig Stuntz
Thanks Craig. So you mean I should first validate the user (username, password), then if the record exists check if the user is locked out, approved etc. and then set the authentication cookie? Am I right?Second question. I'm using a custom RegUser object (not MembershipUser). Should I create separate methods in my Membership provider like GetRegUser in order to check my custom attributes?
Cosmo
I think I would subtype `MembershipUser` rather than replace it, but before you do, note (1) there is already a property intended for app-specific data http://msdn.microsoft.com/en-us/library/system.web.security.membershipuser.comment.aspx and (2) app-specific user info is often better suited for IIdentity than on a membership user. Membership users really just say whether you can get in. IIdentity says *who you are.*
Craig Stuntz
Oh, and this is probably obvious, but you only need to look up the user if login fails. If it succeeds, they must be approved, and cannot be locked out.
Craig Stuntz
"If it succeeds, they must be approved, and cannot be locked out" - following that, am I right that the authentication cookie should only be set after checking ValidateUser and the other properties (locked out, approved...)? I mean: (Username, Password) -> ValidateUser() -> GetUser().IsApproved -> GetUser().IsLockedOut -> SetAuthCookie(). Is this order correct?
Cosmo
At least with the Forms Authentication provider, ValidateUser returns false if the user is logged out, etc. Your provider may or may not behave the same, but I'd argue that it should. Providers are intended to be swappable.
Craig Stuntz
@Cosmo - to reiterate what craig said - If a user is locked out or not approved, validation will return false. Only if you have implemented a custom provider or inherited a provider and wish to react to different reasons, say to pass a message to the page, would you need to check the IsXXX properties.
Sky Sanders
A: 

Hey,

You would have to create your own mechanism; this doesn't happen by default and can't be done with the built-in membership provider. You could wrap that provider and add this method and do it yourself... that is an option. But then this doesn't work in line with the login control if you are using it.

Brian
Thanks Brian, I'm not using the login control. Probably that would be an option.
Cosmo
+1  A: 

You can implement any methods you want to on your custom provider, and in your case it might make sense to do so and just cast Membership to your type before using.

But breaking the interface to get some simple out of band info may come back to bite you in the future. There are other ways to do this and preserve the provider api and keep your future options open.

In the past, I have used a cookie to pass out-of-band info like this from a provider to the consumer.

The HttpContext.Current is the same for the provider as it is for the page so a cookie set in the provider can be read in the consumer.

Just make sure that you remove the cookie after calling your provider. Creating a transient cookie helps minimize mistakes, but just remove it from the collection anyway.

Here is a working example.

CookieChannelMembershipProvider

using System;
using System.Web;
using System.Web.Security;

namespace CookieChannel
{
    public class CookieChannelMembershipProvider : MembershipProvider
    {
        public override bool ValidateUser(string username, string password)
        {
            if(username=="asshat")
            {
                HttpContext.Current.Request.Cookies.Add(new HttpCookie("__cookiechannel", "user is an asshat. do not let him in."));
                return false;
            }
            return true;
        }

        #region Not implemented

        public override bool EnablePasswordRetrieval
        {
            get { throw new NotImplementedException(); }
        }

        public override bool EnablePasswordReset
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotImplementedException(); }
        }

        public override string ApplicationName
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotImplementedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotImplementedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }


        public override MembershipUser CreateUser(string username, string password, string email,
                                                  string passwordQuestion, string passwordAnswer, bool isApproved,
                                                  object providerUserKey, out MembershipCreateStatus status)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password,
                                                             string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }


        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize,
                                                                 out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize,
                                                                  out int totalRecords)
        {
            throw new NotImplementedException();
        } 
        #endregion
    }
}

Web.config

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true"/>
    <authentication mode="Windows" />
    <membership defaultProvider="cookieChannelProvider" userIsOnlineTimeWindow="15">
      <providers>
        <add
          name="cookieChannelProvider"
          type="CookieChannel.CookieChannelMembershipProvider, CookieChannel"
          connectionStringName="none"
          enablePasswordRetrieval="true"
          enablePasswordReset="true"
          requiresQuestionAndAnswer="true"
          />
      </providers>
    </membership>
  </system.web>
</configuration>

Default.aspx

<%@ Page Language="C#" %>
<script runat="server">
    protected void Button1_Click(object sender, EventArgs e)
    {
        ValidateUser("user", "user");
    }
    protected void Button2_Click(object sender, EventArgs e)
    {
        ValidateUser("asshat", "asshat");
    }

    private void ValidateUser(string username, string password)
    {
        bool validated = Membership.ValidateUser(username, password);
        string message = validated.ToString();

        if (Request.Cookies["__cookiechannel"] != null)
        {
            message += ":" + Request.Cookies["__cookiechannel"].Value;
            Request.Cookies.Remove("__cookiechannel");
        }
        Label1.Text = message;
    }
</script>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

        <asp:Button ID="Button1" runat="server" onclick="Button1_Click" 
            Text="Validate Valued User" />
        <asp:Button ID="Button2" runat="server" onclick="Button2_Click" 
            Text="Validate Asshat User" />

    </div>
    <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
    </form>
</body>
</html>
Sky Sanders