ASP.NET Membership CAPTCHA C# Code Example
The ASP.NET Membership Captcha example project shows how to integrate BotDetect CAPTCHA validation with standard ASP.NET Membership functionality used in ASP.NET Login
and CreateUserWizard
controls.
First Time Here?
Check the BotDetect ASP.NET WebForms Captcha Quickstart for key integration steps.
To prevent bots from trying to guess the login info by brute force submission of a large number of common values, the visitor first has to prove they are human (by solving the Captcha), and only then is their username and password submission checked against the authentication data store.
Also, if they enter an invalid username + password combination three times, they have to solve the Captcha again. This prevents attempts in which the attacker would first solve the Captcha themselves, and then let a bot brute-force the authentication info.
To keep the example code simple, the example doesn't access a data store to authenticate the user, but accepts all logins with usernames and passwords at least 5 characters long as valid.
And to prevent bots from registering user accounts, the Register page Captcha has to be solved before user details are recorded.
Download the BotDetect ASP.NET CAPTCHA Generator archive to run this example- C#
- VB.NET
- Visual Studio 2017, 2015, 2013 / .NET 4.5.1 and onwards
- Visual Studio 2012, 2010, 2008, 2005 / .NET 4.5, .NET 4.0, .NET 3.5, .NET 2.0
Visual Studio 2017, 2015, 2013 / .NET 4.5.1 and onwards
Since ASP.NET Membership has been deprecated in Visual Studio 2013 and replaced with ASP.NET Identity, there is no Visual Studio 2013 and onwards version of the ASP.NET Membership Captcha code example. You can see how to integrate BotDetect Captcha validation into ASP.NET WebForms applications using ASP.NET Identity user management in the ASP.NET WebForms Application Template Captcha code example.
Visual Studio 2012, 2010, 2008, 2005 / .NET 4.5, .NET 4.0, .NET 3.5, .NET 2.0
Within this page, the root folder of the extracted archive is referred as the <BDC-DIR>
.
This example is in the <BDC-DIR>/lgcy-on-lgcy/examples/t_api-captcha-webforms-web.security.membership/csharp/
folder; and contains the following files:
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <title>BotDetect CAPTCHA ASP.NET Membership Example</title> <link type="text/css" rel="Stylesheet" href="StyleSheet.css" /> </head> <body> <form id="form1" runat="server"> <h1>BotDetect CAPTCHA ASP.NET Membership Example</h1> <fieldset> <legend>Page protected with ASP.NET authentication</legend> <p>This page can only be seen after successful Captcha validation and username/password authentication.</p> <asp:LinkButton Text="Sign out" runat="server" ID="SignOutButton" OnClick="SignOutButton_Click"></asp:LinkButton> </fieldset> </form> </body> </html>
This page has no special code, but is meant to only be seen by authenticated users. It will be displayed to users who pass both the Captcha validation and the username / password authentication.
Default.aspx.cs
using System; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void SignOutButton_Click(object sender, EventArgs e) { FormsAuthentication.SignOut(); Response.Redirect("Default.aspx?ref=signout", true); } }
Login.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Login</title> <link type="text/css" rel="Stylesheet" href="StyleSheet.css" /> </head> <body> <form id="form1" runat="server"> <h1>BotDetect CAPTCHA ASP.NET Membership Example</h1> <fieldset> <legend>CAPTCHA Validation in a <code>Login</code> control</legend> <asp:Login ID="ExampleLogin" runat="server" OnAuthenticate="ExampleLogin_Authenticate"> <LayoutTemplate> <table border="0" cellpadding="1" cellspacing="0" style="border-collapse: collapse;"> <tr> <td> <table border="0" cellpadding="0"> <tr> <td align="center" colspan="2">Log In</td> </tr> <tr> <td align="right" width="185"> <asp:Label ID="UserNameLabel" runat="server" AssociatedControlID="UserName">Username:</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="ExampleLogin">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td align="right"> <asp:Label ID="PasswordLabel" runat="server" AssociatedControlID="Password">Password:</asp:Label> </td> <td> <asp:TextBox ID="Password" runat="server" TextMode="Password"></asp:TextBox> <asp:RequiredFieldValidator ID="PasswordRequired" runat="server" ControlToValidate="Password" ErrorMessage="Password is required." ToolTip="Password is required." ValidationGroup="ExampleLogin">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td></td> <td> <BotDetect:WebFormsCaptcha ID="LoginCaptcha" UserInputID="CaptchaCodeTextBox" ImageSize="150, 50" CodeLength="3" runat="server" /> </td> </tr> <tr runat="server" id="CaptchaRow"> <td align="right"> <asp:Label ID="CaptchaLabel" runat="server" AssociatedControlID="CaptchaCodeTextBox">Code:</asp:Label> </td> <td> <asp:TextBox ID="CaptchaCodeTextBox" runat="server"></asp:TextBox> <asp:RequiredFieldValidator ID="CaptchaRequiredValidator" runat="server" ControlToValidate="CaptchaCodeTextBox" ErrorMessage="CAPTCHA code is required." ToolTip="CAPTCHA code is required." ValidationGroup="ExampleLogin">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td align="center" colspan="2" style="color: Red;"> <span> <asp:Literal ID="FailureText" runat="server" EnableViewState="False"></asp:Literal> </span> </td> </tr> <tr> <td align="right" colspan="2"> <asp:Button ID="LoginButton" runat="server" CommandName="Login" Text="Log In" ValidationGroup="ExampleLogin" /> </td> </tr> </table> </td> </tr> </table> </LayoutTemplate> </asp:Login> <p><a href="Register.aspx">Register</a></p> </fieldset> </form> </body> </html>
The <BotDetect:WebFormsCaptcha>
control is added to the Login form below the username and password fields. An <asp:RequiredFieldValidator>
is added and connected with the Captcha code textbox, to warn users not to submit the form without entering the Captcha code.
Login.aspx.cs
using System; using System.Collections; using System.Configuration; using System.Data; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using BotDetect; using BotDetect.Web; using BotDetect.Web.UI; public partial class Login : System.Web.UI.Page { protected void ExampleLogin_Authenticate(object sender, System.Web.UI.WebControls.AuthenticateEventArgs e) { TextBox CaptchaCodeTextBox = ExampleLogin.FindControl("CaptchaCodeTextBox") as TextBox; WebFormsCaptcha LoginCaptcha = ExampleLogin.FindControl("LoginCaptcha") as WebFormsCaptcha; // first, validate the Captcha to check we're not dealing with a bot if (!IsHuman) { string code = CaptchaCodeTextBox.Text.Trim(); IsHuman = LoginCaptcha.Validate(code); CaptchaCodeTextBox.Text = null; // clear previous user input if (!IsHuman) { ExampleLogin.FailureText = "Retype the characters from the image carefully."; e.Authenticated = false; return; } } HideCaptcha(); // hide the CAPTCHA once it's solved // only when we're sure the visitor is human, we try to authenticate them if (!Membership.ValidateUser(ExampleLogin.UserName, ExampleLogin.Password)) { ExampleLogin.FailureText = "Invalid login info."; e.Authenticated = false; FailedAuthAttempts++; if (ResetFailedAuthAttempts < FailedAuthAttempts) { // show the CAPTCHA again if the user enters invalid authentication // info three times in a row ShowCaptcha(); } return; } e.Authenticated = true; } protected void HideCaptcha() { WebFormsCaptcha LoginCaptcha = ExampleLogin.FindControl("LoginCaptcha") as WebFormsCaptcha; HtmlControl CaptchaRow = ExampleLogin.FindControl("CaptchaRow") as HtmlControl; CaptchaRow.Visible = false; LoginCaptcha.Visible = false; } protected void ShowCaptcha() { WebFormsCaptcha LoginCaptcha = ExampleLogin.FindControl("LoginCaptcha") as WebFormsCaptcha; HtmlControl CaptchaRow = ExampleLogin.FindControl("CaptchaRow") as HtmlControl; IsHuman = false; FailedAuthAttempts = 0; CaptchaRow.Visible = true; LoginCaptcha.Visible = true; } /// <summary> /// flag showing the user successfully passed the CAPTCHA test /// </summary> protected bool IsHuman { get { bool isHuman = false; try { if (null != Session["IsHuman"]) { isHuman = (bool)Session["IsHuman"]; } } catch (InvalidCastException) { /* ignore cast errors */ } return isHuman; } set { Session["IsHuman"] = value; } } protected const int ResetFailedAuthAttempts = 3; /// <summary> /// Failed authentication attempt counter /// </summary> protected int FailedAuthAttempts { get { int count = 0; try { if (null != Session["FailedAuthAttempts"]) { count = (int)Session["FailedAuthAttempts"]; } } catch (InvalidCastException) { /* ignore cast errors */ } return count; } set { Session["FailedAuthAttempts"] = value; } } }
Since we want to process login attempts as they are submitted, we handle the ExampleLogin_Authenticate
event. To get the Captcha code textbox and Captcha
control instances, we have to use ExampleLogin.FindControl()
calls (as they can't be accessed directly by ID in this event handler).
Since we want to remember when the user correctly solved the Captcha (so they don't have to immediately solve another one if the fail the first authentication attempt), we keep it in the IsHuman
property wrapping Session persistence. And since we want to reset the Captcha status after a number of failed authentications (3 by default, as defined in the ResetFailedAuthAttempts
constant), we keep the related counter in the FailedAuthAttempts
property.
This allows us to implement the proper Login form workflow:
- The user must first solve the Captcha to prove they are human. This keeps the bots away from the Membership provider, both conserving its resources and improving its security (since usernames and passwords will not be forwarded to the underlying data store if the Captcha is not solved first).
- When the user has proven they are human, they get 3 authentication attempts without new Captcha tests, which allows them to remember the right combination in most cases.
- If the user fails three authentication requests, they are shown a new Captcha which they must solve before continuing. This throttles authentication access, ensuring username + password combinations cannot be brute-forced, while real human users get theoretically unlimited authentication attempts (as long as they don't mind solving further Captchas).
Register.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Register.aspx.cs" Inherits="Register" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Register</title> <link type="text/css" rel="Stylesheet" href="StyleSheet.css" /> </head> <body> <form id="form1" runat="server"> <h1>BotDetect CAPTCHA ASP.NET Membership Example</h1> <fieldset> <legend>CAPTCHA Validation in a <code>CreateUserWizard</code> control< /legend> <asp:CreateUserWizard ID="RegisterUser" runat="server" OnNextButtonClick="RegisterUser_NextButtonClick" ActiveStepIndex="0"> <WizardSteps> <asp:CreateUserWizardStep runat="server"> <ContentTemplate> <table border="0"> <tr> <td align="center" colspan="2">Sign Up for Your New Account</td> </tr> <tr> <td align="right" width="185"> <asp:Label ID="UserNameLabel" runat="server" AssociatedControlID="UserName">Username:</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="RegisterUser">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td align="right"> <asp:Label ID="PasswordLabel" runat="server" AssociatedControlID="Password">Password:</asp:Label> </td> <td> <asp:TextBox ID="Password" runat="server" TextMode="Password"></asp:TextBox> <asp:RequiredFieldValidator ID="PasswordRequired" runat="server" ControlToValidate="Password" ErrorMessage="Password is required." ToolTip="Password is required." ValidationGroup="RegisterUser">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td align="right"> <asp:Label ID="ConfirmPasswordLabel" runat="server" AssociatedControlID="ConfirmPassword">Confirm Password:</asp:Label> </td> <td> <asp:TextBox ID="ConfirmPassword" runat="server" TextMode="Password"></asp:TextBox> <asp:RequiredFieldValidator ID="ConfirmPasswordRequired" runat="server" ControlToValidate="ConfirmPassword" ErrorMessage="Confirm Password is required." ToolTip="Confirm Password is required." ValidationGroup="RegisterUser">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td align="center" colspan="2"> <asp:CompareValidator ID="PasswordCompare" runat="server" ControlToCompare="Password" ControlToValidate="ConfirmPassword" Display="Dynamic" ErrorMessage="The Password and Confirmation Password must match." ValidationGroup="RegisterUser"></asp:CompareValidator> </td> </tr> <tr> <td align="right"> <asp:Label ID="EmailLabel" runat="server" AssociatedControlID="Email">E-mail:</asp:Label> </td> <td> <asp:TextBox ID="Email" runat="server"></asp:TextBox> <asp:RequiredFieldValidator ID="EmailRequired" runat="server" ControlToValidate="Email" ErrorMessage="E-mail is required." ToolTip="E-mail is required." ValidationGroup="RegisterUser">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td></td> <td> <BotDetect:WebFormsCaptcha ID="RegisterCaptcha" UserInputID="CaptchaCodeTextBox" ImageSize="150, 50" CodeLength="3" runat="server" /> </td> </tr> <tr> <td align="right"> <asp:Label ID="CaptchaLabel" runat="server" AssociatedControlID="CaptchaCodeTextBox">Code:</asp:Label> </td> <td> <asp:TextBox ID="CaptchaCodeTextBox" runat="server"></asp: TextBox> <asp:RequiredFieldValidator ID="CaptchaRequiredValidator" runat="server" ControlToValidate="CaptchaCodeTextBox" ErrorMessage="CAPTCHA code is required." ToolTip="CAPTCHA code is required." ValidationGroup="RegisterUser">*</asp:RequiredFieldValidator> </td> </tr> <tr> <td align="center" colspan="2" style="color: Red;"> <span> <asp:Literal ID="InvalidCaptchaInput" runat="server" EnableViewState="False" Visible="False" Text="Retype the characters from the image carefully."></asp:Literal> </span> </td> <td align="center" colspan="2" style="color: Red;"> <span> <asp:Literal ID="ErrorMessage" runat="server" EnableViewState="False"></asp:Literal> </span> </td> </tr> </table> </ContentTemplate> </asp:CreateUserWizardStep> <asp:CompleteWizardStep runat="server"> <ContentTemplate> <table border="0"> <tr> <td align="center" colspan="2">Complete</td> </tr> <tr> <td>Your account has been successfully created.</td> </tr> <tr> <td align="right" colspan="2"> <p><a href="Default.aspx">Continue</a></p> </td> </tr> </table> </ContentTemplate> </asp:CompleteWizardStep> </WizardSteps> </asp:CreateUserWizard> <p><a href="Login.aspx">Login</a></p> </fieldset> </form> </body> </html>
To protect the user registration field from automated submissions, a <BotDetect:WebFormsCaptcha>
control is added to the CreateUserWizard
template.
Register.aspx.cs
using System; using System.Collections; using System.Configuration; using System.Data; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using BotDetect; using BotDetect.Web; using BotDetect.Web.UI; public partial class Register : System.Web.UI.Page { protected void RegisterUser_NextButtonClick(object sender, WizardNavigationEventArgs e) { if (e.CurrentStepIndex == 0) // CreateUserStep { // get control references WebFormsCaptcha RegisterCaptcha = RegisterUser.CreateUserStep.ContentTemplateContainer.FindControl("RegisterCaptcha") as WebFormsCaptcha; TextBox CaptchaCodeTextBox = RegisterUser.CreateUserStep.ContentTemplateContainer.FindControl("CaptchaCodeTextBox") as TextBox; Literal CaptchaIncorrect = RegisterUser.CreateUserStep.ContentTemplateContainer.FindControl("InvalidCaptchaInput") as Literal; // validate the Captcha to check we're not dealing with a bot string code = CaptchaCodeTextBox.Text.Trim(); bool isHuman = RegisterCaptcha.Validate(code); CaptchaCodeTextBox.Text = null; // clear previous user input if (!isHuman) { CaptchaIncorrect.Visible = true; e.Cancel = true; } else { CaptchaIncorrect.Visible = false; } } } }
To process user creation attempts, we handle the RegisterUser_NextButtonClick
event and make sure the code is executed during the first wizard step where Captcha protection was added (if (e.CurrentStepIndex == 0)
).
We use ContentTemplateContainer.FindControl()
calls to get all necessary control references, and simply cancel account creation unless Captcha validation succeeds.
Web.config
<?xml version="1.0"?> <configuration> <configSections> <section name="botDetect" requirePermission="false" type="BotDetect.Configuration.BotDetectConfigurationSection, BotDetect"/> </configSections> <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> <add key="ValidationSettings:UnobtrusiveValidationMode" value="None" /> </appSettings> <system.web> <httpHandlers> <!-- Register the HttpHandler used for BotDetect Captcha requests --> <add verb="GET" path="BotDetectCaptcha.ashx" type="BotDetect.Web.CaptchaHandler, BotDetect"/> </httpHandlers> <!-- Register a custom SessionIDManager for BotDetect Captcha requests --> <sessionState mode="InProc" cookieless="AutoDetect" timeout="20" sessionIDManagerType="BotDetect.Web.CustomSessionIdManager, BotDetect"/> <!-- Session state is required for BotDetect storage; you can also turn if off globally and only enable for BotDetect-protected pages if you prefer --> <pages enableSessionState="true" controlRenderingCompatibilityVersion="4.5"> <controls> <!-- Register the BotDetect tag prefix for easier use in all pages --> <add assembly="BotDetect" namespace="BotDetect.Web.UI" tagPrefix="BotDetect"/> </controls> <namespaces> <add namespace="BotDetect"/> <add namespace="BotDetect.Web"/> <add namespace="BotDetect.Web.UI"/> </namespaces> </pages> <compilation debug="false" targetFramework="4.5" /> <httpRuntime requestValidationMode="4.5" targetFramework="4.5" encoderType="System.Web.Security.AntiXss.AntiXssEncoder, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <machineKey compatibilityMode="Framework45" /> <trace enabled="false" localOnly="true"/> <httpCookies httpOnlyCookies="true"/> <trust level="Full" originUrl=""/> <customErrors mode="RemoteOnly"/> <!-- Forms authentication --> <authentication mode="Forms"> <forms loginUrl="Login.aspx"/> </authentication> <!-- Deny access to all pages to unauthorized users --> <authorization> <deny users="?"/> <allow users="*"/> </authorization> <!-- Membership provider --> <membership defaultProvider="MockMembershipProvider"> <providers> <add name="MockMembershipProvider" applicationName="/" type="Sample.MockMembershipProvider"/> </providers> </membership> </system.web> <!-- Allow unauthorized access to the Login page --> <location path="Login.aspx"> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </location> <!-- Allow unauthorized access to the Register page --> <location path="Register.aspx"> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </location> <!-- Allow unauthorized access to BotDetect Captcha images and sounds --> <location path="BotDetectCaptcha.ashx"> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </location> <!-- Allow unauthorized access to the Stylesheet --> <location path="StyleSheet.css"> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </location> <system.webServer> <validation validateIntegratedModeConfiguration="false"/> <handlers> <!-- Register the HttpHandler used for BotDetect Captcha requests (IIS 7.0+) --> <remove name="BotDetectCaptchaHandler"/> <add name="BotDetectCaptchaHandler" preCondition="integratedMode" verb="GET" path="BotDetectCaptcha.ashx" type="BotDetect.Web.CaptchaHandler, BotDetect"/> </handlers> </system.webServer> <botDetect helpLinkEnabled="true" helpLinkMode="image" /> </configuration>
Besides the usual BotDetect-related web.config
changes (Captcha HttpHandler
registration, ASP.NET Session state configuration, and BotDetect tag prefix registration), the authentication
and authorization
elements define access restrictions, while the location
elements specify exceptions to those restrictions. In this case, unauthorized users can only access the Login and Register pages and BotDetect Captcha paths.
To keep the example as simple as possible, we use the MockMembershipProvider
instead of a real MembershipProvider. It doesn't access any data store to verify the user's credentials, but considers all authorization requests with usernames and passwords longer than 5 characters as valid. Since it doesn't affect Captcha protection and validation logic, MockMembershipProvider
is not shown (but is included in the example installation).
Current BotDetect Versions
-
BotDetect ASP.NET CAPTCHA
2019-07-22v4.4.2 -
BotDetect Java CAPTCHA
2019-07-22v4.0.Beta3.7 -
BotDetect PHP CAPTCHA
2019-07-22v4.2.5