Changing the Login Control to Display a List of Users

A while ago I saw this post on the P2P Wrox forum (near the middle of the thread) where the poster asked for a way to replace the standard TextBox in the asp:Login control with a list of user names from the system. He didn't mind the lowered security implications of this solution and instead wanted to make it a little easier for users to login. With his proposed solution, a user can pick a name from the list, enter a password and log in. I guess fewer things to remember means fewer calls to the helpdesk.

It turns out this is actually quite easy to do.

Changing the Login Control

To make this work, the first thing you need to do is convert a standard <asp:Login> control to a templated version. This is quite easy; right-click the Login control in Visual Studio and choose Convert to Template. You'll end up with lots of HTML mixed with ASP.NET server controls. The User Name text box now ends up like this:

<tr>
  <td align="right">
    <asp:Label ID="UserNameLabel" runat="server" 
          AssociatedControlID="UserName">User Name:</asp:Label>
  </td>
  <td>
    <asp:TextBox ID="UserName" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="UserNameRequired" runat="server" 
            ControlToValidate="UserName" ErrorMessage="User Name is required." 
            ToolTip="User Name is required." ValidationGroup="Login1">*
    </asp:RequiredFieldValidator>
  </td>
</tr>  

You may be tempted to delete the entire TextBox called UserName and replace it with a DropDownList control. However, as soon as you do that and run the page in the browser, you get this exception:

Login1: LayoutTemplate does not contain an IEditableTextControl 
        with ID UserName for the username.

Apparently, the Login control thinks it needs a TextBox called UserName to operate properly. Fortunately, it's easy to circumvent this problem by hiding the TextBox instead of removing it. Simply adding Visible="False" is enough to prevent the error from occurring.

The next step is to add an <asp:DropDownList> control to the table cell that holds the user name text box. While you're there, you might as well remove the RequiredFieldValidator as it's no longer used. You should end up with code like this:

  </td>   
  <td align="right">
    <asp:Label ID="UserNameLabel" runat="server" 
          AssociatedControlID="UserName">User Name:</asp:Label>
  </td>
  <td>
    <asp:TextBox ID="UserName" runat="server" Visible="false"></asp:TextBox>
    <asp:DropDownList ID="DropDownList1" runat="server" />
  </td>
</tr>

With the drop down list in place, it's time to add an ObjectDataSource control that gets a list with users from the system. Depending on your requirements, you have two options to get this list. First, you could call Membership.GetAllUsers() to get a MembershipCollection object that holds all your MembershipUser objects. The advantage of this method is that you get strongly typed user objects so you can access all the user properties like CreateDate, Email etc. The downside is that there isn't an easy way to filter users. You always get all the users in the system. You could use the FindUsersByEmail and FindUsersByName methods to limit the list, but they can't be used to find, say, users in a specific role.

The alternative is to use the shared GetUsersInRole method of the Roles class. This method allows you to query the users in a specific role. This way, you can limit the users in the drop down to those in a specific role, allowing you to filter out special accounts like Administrator. The downside of this method is that all you get is the user name, and nothing else. For the purpose of filling the drop down, this is enough though.

I'll show you how to use Membership.GetAllUsers first, followed by Roles.GetUsersInRole.

Both alternatives use an ObjectDataSource (ODS), so you should add one to the page. Normally, you'd use the Configure Data Source wizard to fill in the SelectMethod and other properties of the ODS. However, this wizard doesn't allow you to type in values manually, and only shows objects that are present in your current solution. Instead, you should switch to Code View and type the code for the ODS manually.

Using Membership.GetAllUsers()

To use the Membership class, you need to set the following properties on the ODS:

Property Value
TypeName System.Web.Security.Membership
SelectMethod GetAllUsers
DataObjectTypeName System.Web.Security.MembershipUser


You should end up with this code:

<asp:ObjectDataSource ID="ObjectDataSource1"
        DataObjectTypeName="System.Web.Security.MembershipUser" 
        runat="server" SelectMethod="GetAllUsers"
        TypeName="System.Web.Security.Membership">
</asp:ObjectDataSource>  

When the ODS is about to get its data, it fires the GetAllUsers method to get the list of users from the database. You can use this list with users to fill the drop down as follows:

  1. Switch to Design View

  2. Click the little arrow on the DropDownList control you added earlier to open its Task Pane.

  3. Click Choose Data Source.

  4. Select ObjectDataSource1 from the first drop down.

  5. To have the properties of the user show up in the other two drop downs, click the Refresh Schema link at the bottom of the dialog.

  6. Choose UserName for the second drop down (to select a data display field).

  7. Repeat the previous step for the third drop down to select a data value field.

  8. Click OK to close dialog, save the page and open it in your browser. Instead of the default text box for the user name, you're now presented with a nice drop down:

The Login Control Showing a DropDownList with User Names
Figure 1 - The Login Control Showing a DropDownList with User Names

The final step you need to perform is to get the user name from the drop down and assign it to the Login control's UserName property so the user name from the drop down is used for authentication. You can do that in the LoggingIn event of the Login control like this:

VB.NET
Protected Sub Login1_LoggingIn(ByVal sender As Object, _
          ByVal e As System.Web.UI.WebControls.LoginCancelEventArgs) _
          Handles Login1.LoggingIn
Login1.UserName = CType(Login1.FindControl("DropDownList1"), _ DropDownList).SelectedValue End Sub C# protected void Login1_LoggingIn(object sender, LoginCancelEventArgs e)
{
Login1.UserName = ((DropDownList)
Login1.FindControl("DropDownList1")).SelectedValue;
}

The LoggingIn event fires as soon as the user hits the Login button, but before authentication takes place. This is the perfect location to get the selected user name from the drop down and assign it to the UserName property of the Login control. Once the Login control starts with its authentication process, it contains a valid user name so authentication succeeds (provided you entered a valid password). The Login control doesn't even know you changed its behavior.

Using Roles.GetUsersInRole(roleName)

To use the Roles class, you need to set the following properties on the ODS:

Property Value
TypeName System.Web.Security.Roles
SelectMethod GetUsersInRole


Additionally, you need to add a parameters to the <SelectParameters> element, call it roleName and set its DefaultValue to the role you want your users to be in.

You should end up with this code:

<asp:ObjectDataSource ID="ObjectDataSource1"
      runat="server" SelectMethod="GetUsersInRole"
      TypeName="System.Web.Security.Roles">
  <SelectParameters>
    <asp:Parameter DefaultValue="Members" Name="roleName" />
  </SelectParameters>
</asp:ObjectDataSource>  

When the ODS is about to get its data, it fires the GetUsersInRole method and passes it the roleName, Members in this example. This gets a list of users from the database that are in the specified role. You can use this list with users again to fill the drop down. However, because GetUsersInRole returns an array of strings, you can no longer set the DataTextField and DataValueField to UserName. So, all you need to do is assign the drop down list a valid ObjectDataSource with its DataSourceID property:

<asp:DropDownList ID="DropDownList1" 
     runat="server" DataSourceID="ObjectDataSource1" />  

The code in the LoggingIn event remains is the same, whether you use the Membership or the Roles class to get the list of users.

Note that the role you're specifying in the <SelectParameters> element needs to exist in the database, or you'll get an exception.

Conclusion

The collection of Login features in ASP.NET 2.0 (the Login control, CreateUserWizard but also the Membership and Roles API) are very powerful, allowing you to implement authentication in your web site in no time. With the ability to change the behavior of some of the features as presented in this article, they become even more powerful.

You should use the idea presented in this article with caution; giving away the user names in your system makes it easier for hackers to perform a brute force attack against your system. By giving them the user name, you give up half the security of the site; all hackers need to do is "guess" the password.

Download Files

Full Source Code for this Article in VB.NET and C#

Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Thursday, January 25, 2007 8:35:44 AM PohEe.com said:
Nice article. If this control support AJAX implementation then it will be perfect. Good work. Keep it up :)
On Thursday, January 25, 2007 9:00:50 AM Jody said:
While this is a great example to adapt to anything other than a Login - I woud not recommend this from a security standpoint.  Considering that yes one may argue its easy enough to build a list of users from a forum or something, having it suc that a bot or a hacker can just be presented with login namesis a big security risk as 50% of the work is done.  

This is the reason why usually a login email is used but in all the forums is only displayed a alias name.  Such that it can not be readily tied to it.  In fact by using tyour approach - you are inviting the start of a DOS attack as one can just loop from screen scraping programs that just pull the list of names  and start with looping through the most common passwords and then analytical alogrithms..

Nice article though but I would never recommend a site to use this approach as it just invites a securty issue that one probably doesn't want to deal with....

jody

http://tech-review.org
On Thursday, January 25, 2007 9:54:55 AM Norwegian dude said:
I agree with Jody. You code is alright, but the feature you are suggesting is not at all recommended!!
On Thursday, January 25, 2007 10:25:12 AM Imar Spaanjaars said:
Couldn't agree more with you all. That's why the article ends with this:

"You should use the idea presented in this article with caution; giving away the user names in your system makes it easier for hackers to perform a brute force attack against your system. By giving them the user name, you give up half the security of the site; all hackers need to do is "guess" the password. "

However, there are situations where security is less of a problem, for example in an Intranet or other trused environment.

I wrote the article after a user's request for this; he was fully aware of the security implications.

But I agree: use with caution and use at your own risk.

Imar
On Friday, January 26, 2007 6:23:50 AM Muhammad Minhaj Mehmood said:
nice work.

Regards,
Minhajkk
On Saturday, February 24, 2007 11:10:48 AM Andry said:
Hi,

You said we could use the FindUsersByEmail and FindUsersByName methods to limit the list.

I'm having a problem with binding it with my gridview.

The error message I got is:
ObjectDataSource 'odsMember3' could not find a non-generic method 'FindUsersByEmail' that has parameters: Email.

The following is the ObjectDataSource I have:
[asp:ObjectDataSource ID="odsMember4" DataObjectTypeName="System.Web.Security.MembershipUser" SelectMethod="FindUsersByName" TypeName="System.Web.Security.Membership" runat="server"]
  [SelectParameters]
    [asp:ControlParameter ControlID="txtSrcUserName" Name="UserNameSearch" PropertyName="Text" Type="String" DefaultValue=" " /]
  [/SelectParameters]
[/asp:ObjectDataSource]

Please helpppp!! ;)
On Saturday, February 24, 2007 11:22:22 AM Imar Spaanjaars said:
Hi Andry,

If you use a different method, you have to make sure you add the right parameters to the SelectParameters of the ODS. In the case of FindUsersByName you need to set up a parameter for the userNameToMatch argument of the method. It's important that the Name of the Parameter matches the name of the argument of FindUsersByName.

This should probably work:

[asp:ControlParameter ControlID="txtSrcUserName" Name="userNameToMatch" PropertyName="Text" Type="String" DefaultValue=" " /]

If that doesn't help, I think you're better off posting this on a forum where it's easier to post code. Something that's not so easy here, as you found out... ;-)

BTW, the error you posted talks about ODS3 while the code is for ODS4. Maybe you're overlooking something obvious?

Imar
On Friday, August 24, 2007 2:24:47 AM Nimesh said:
In default login control, we use username of membership, but actually I need the emailaddress and the password to authenticate the user instead of username...any suggestions please for this

Thanking in advance,
Nimesh
On Friday, August 24, 2007 5:47:39 AM Imar Spaanjaars said:
Hi Nimesh,

You need to set the UserName of the CreateUserWizard to the EmailAddress in the CreatingUser event. That way, the user name *is* the e-mail address.

Imar
On Thursday, November 20, 2008 10:11:41 PM Roberto said:
Hello Imar, I am using Membership.GetAllUsers() and I do not understand the last step. I can get my list of user listed in the User Name Drop list  but in the final step  I don't  know how I put that Sub Login1.LoggingIn procedure in my program. Note that I am using Master Pages. And every control are inside my content1. Thank you
On Thursday, November 20, 2008 11:01:51 PM Imar Spaanjaars said:
Hi Roberto,

That last step is necessary to feed the Login control the name of the user you selected in the drop down list.

Whether you're using Master Pages or not is largely irrelevant; you should write this code in the code behind of the content page or master page that contains the Login control.

Hope this helps,

Imar
On Friday, November 21, 2008 12:34:14 AM Roberto said:
OK, when a write this code in the HomePage.aspx.vb , I got this errors:

Error 1 Handles clause requires a WithEvents variable defined in the containing type or one of its base types. C:\Public\HomePage.aspx.vb 4 127 C:\VIPINSTALLATIONS\
Error 2 Name 'Login1' is not declared. C:\Public\HomePage.aspx.vb 5 9
Error 3 Name 'Login1' is not declared. C:\Public\HomePage.aspx.vb 5 33 C:\VIPINSTALLATIONS\

Just in case this is my file:

Partial Class Membership_VB
    Inherits System.Web.UI.Page

    Protected Sub Login1_LoggingIn(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.LoginCancelEventArgs) Handles Login1.LoggingIn
        Login1.UserName = CType(Login1.FindControl("DropDownList1"), DropDownList).SelectedValue
    End Sub

End Class

Partial Class HomePage
    Inherits System.Web.UI.Page

    Protected Sub LoginButton_Click(ByVal sender As Object, ByVal e As System.EventArgs)

    End Sub
End Class


Roberto.  
ps. Thank you very much for your help.





On Friday, November 21, 2008 12:59:08 AM Roberto said:
Ok, I figured this and I do not have any errors.  However after I imput the password and click login, it says wrong password.  When I using a regular Login without pull down menu control works. So my user Name and Passwords are working fine. Any Idea?? Please

Roberto
On Friday, November 21, 2008 8:02:51 AM Imar Spaanjaars said:
Hi Roberto,

Difficult to say without seeing your code. Can you post this on http://p2p.wrox.com and provide the full source for the ASPX and code behind files?

Imar
On Friday, November 21, 2008 12:41:39 PM Roberto said:
Thank you, it works fine now. I had a permission issue for a particular directory.

Roberto
On Friday, March 06, 2009 4:38:03 PM pravin jha said:
nice article, but can you help me in designing a login control to accept 3 parameters :
   1)company name
   2)user name
& 3)password.

in standard sceanrio we need only user name and password. but in this case I want my to create users on the basis of company and after login I want to store and disply the logged in user with logged in comany name.

waiting for your response.

thanks

pravin
On Friday, March 06, 2009 6:05:55 PM Imar Spaanjaars said:
Hi pravin,

I'd say the concepts I presented in this article should be enough to get you started on this on your own. I am not going to build the control for you, so you'll need to code it yourself. What exactly are you having troubles with?

If this gets off-topic, may I suggest a public discussion forum as this one: http://p2p.wrox.com/index.php?referrerid=385

Cheers,

Imar
On Tuesday, January 26, 2010 2:30:42 PM david said:
Roberto hi,
I'm getting the same errors "Login1 is not declared" and
Handles clause requires a WithEvents ....

You only say you figured it
Please let me know how!

Thanks

David
On Tuesday, January 26, 2010 3:47:41 PM Imar Spaanjaars said:
Hi david,

Try posting this on a forum like http://p2p.wrox.com/index.php?referrerid=385

If you do post there, please provide lots of detail (code, setup, web server) and so on.

Cheers,

Imar

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.