Sending email confirmation link in an ASP.Net application - asp.net

I want to allow people by registering with their email id, password and confirming password in my asp.net 4.0 application. How can I send the confirmation link to their entered email, and there by showing the profile strength in their individual profiles? with the backend as sql server 2010

You have to send the confirmation link with unique confirmation id as query string parameter in link. When user clicks on that link you can validate that link for -
Is link already used?
Is link has valid confirmation number.
Then if it is valid link then allow user to confirm there email id. The unique confirmation id needs to be stored in the database for given email id. So that you can validate for it. Also in the same database table you can have additional field where it will record the time stamp when that id is generated and email send to user.

Introduction
Authentication and authorization as fundamental part of a web application was tremendously simplified with arrival of .NET 2.0. Basic authentication and authorization can be performed using different DBMS systems. There are numerous examples implementing authentication using Access database. One such example can be found in the article ASP.NET 2.0 Authentication using Access Database. In this article the example implements bare minimum to achieve desired goal, namely authentication and authorization using access database. However, authentication and authorization is only first part of the user management system. Second fundamental part of user management is allowing users to create an account at will. In such instances, users can sign up and become members of the community without administrator intervention. Furthermore, the user should be able to provide contact information (email) so that he/she can be contacted if necessary. Contact information must be verified. This article focuses on very simple ways to achieve such goal.
CreateUserWizard
As already suspected, .NET 2.0 provides necessary tools for quick and effective implementation of user registration. The component of interest is CreateUserWizard control that can be found under Login components. CreateUserWizard component provides preformatted set of components that provide the user with opportunity to enter necessary information. By default, CreateUserWizard provides user with option to enter name, password, email, security question, and answer to security question. In addition, CreateUserWizard provides the user with function to reenter the password to confirm user intentions.
In our example, we will focus on minimalistic approach to implement user signup and email conformation. As such we will not implement security question, but rather focus on username, password, and email to be confirmed. This article is continuation of ASP.NET 2.0 authorization using Access Database and it reuses Role and Membership provider code as presented in that article. Here, I’ll only present additions to the above mentioned article. In addition, full source code is included and demonstration can be found in demo section.
Extending Roles and Membership Providers
As already mentioned, I’ll focus on additional implementation necessary for CreateUserWizard implementation. Methods implemented in ASP.NET 2.0 authentication and authorization using Access Database are explained in mentioned article. However, there are some modifications that will be stressed at relevant time.
Looking at AccessMembershipProvider, I quickly realized that there are multiple methods that must be implemented to correctly use CreateUserWizardcontrol. First we need to be able to eliminate security question option. In order to eliminate security question, membership provider must return false for RequiresQuestionAndAnswer property of membership provider. Implementation of this property is similar to implementation of connection string property. We simply add variable of type bool that retrieves its value from web configuration file. Web configuration file must contain requiresQuestionAndAnswer as shown below:
<membership defaultProvider="AccessMembershipProvider">
<providers>
<clear/>
<add name="AccessMembershipProvider"
type="AccessProvider.AccessMembershipProvider"
connectionStringName="UsersDB"
requiresQuestionAndAnswer="false"/>
</providers>
</membership>
And the setting is parsed within Initialize method as following:
public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(name, config);
m_strDBConnection =
ConfigurationManager.ConnectionStrings[config["connectionStringName"]].ConnectionString;
m_bRequiresQuestionAndAnswer =
(config["requiresQuestionAndAnswer"].ToLower().CompareTo("true") == 0);
}
As we can see, the code above simply extracts the setting from the web.config file and applies it to appropriate property. CreateUserWizard control reads the same property and displays security question according to the property value. Now that security question does not have to be entered, we can look at what happens when user enters the data and tries to create a user. Through experiment, I realized that the AccessMembershipProvider method CreateUser is called (obviously). Intuitively, I concluded that this method is responsible for the code that adds the user to the database. Following is method implementation:
public override MembershipUser CreateUser(string username, string password, string email,
string passwordQuestion, string passwordAnswer,
bool isApproved, object providerUserKey,
out MembershipCreateStatus status)
{
MembershipUser user = null;
using (OleDbConnection conn = new OleDbConnection(m_strDBConnection))
{
try
{
OleDbCommand command = new OleDbCommand("INSERT INTO Users " +
"(UUSERNAME, UPASSWORD, EMAIL, PROVIDER_KEY) "+
"VALUES (#Param1, #Param2, #Param3, #Param4)",
conn);
Guid guid = Guid.NewGuid();
command.Parameters.AddWithValue("#Param1", username);
command.Parameters.AddWithValue("#Param2", password);
command.Parameters.AddWithValue("#Param3", email);
command.Parameters.AddWithValue("#Param4", guid.ToString());
conn.Open();
command.ExecuteNonQuery();
string[] users = {username};
Roles.AddUsersToRole(users, "User");
conn.Close();
user = new MembershipUser("AccessMembershipProvider",
username, guid, email, null, null, false, false,
DateTime.Now, DateTime.Now, DateTime.Now,
DateTime.Now, DateTime.Now);
status = MembershipCreateStatus.Success;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
status = MembershipCreateStatus.UserRejected;
}
}
return user;
}
CreateUser method simply adds the user to the database and returns MembershipUser object filled with the information as entered in CreateUserWizard. My first implementation of the method did not include adding the user to the “User” group using role provider. However, later investigation showed that the user was simply added without group assignment. Realizing that the user must be in a group, I added the following implementation to AccessRoleProvider code:
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
using (OleDbConnection conn = new OleDbConnection(m_strDBConnection))
{
try
{
for (int i = 0; i < usernames.Length; ++i)
{
OleDbCommand command = new OleDbCommand("INSERT INTO UsersInRoles "+
"(ROLE_NAME, UUSERNAME) "+
"VALUES (#Param1, #Param2)",
conn);
command.Parameters.AddWithValue("#Param1", roleNames[i]);
command.Parameters.AddWithValue("#Param2", usernames[i]);
conn.Open();
command.ExecuteNonQuery();
conn.Close();
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
}
Now that the user is added to the database and assigned to appropriate group, we need to implement code to confirm user information, particularly email. Searching through MSDN documentation I found that CreateUserWizard provides method for automatic email notification. However, such approach was not appropriate because of lack of information provided within the event. Our conformation will utilize GUID that is written to DB when the user is created. The user will be activated only when the activation with such GUID is performed. Finding out GUID was impractical using send email event and easier solution was found, namely CreatedUser event of CreateUserWizard component.
Email Notification
As already mentioned, the user should be able to use the account only after successful email conformation. For conformation, the user should receive an email containing the link that will automatically activate the account. Once the account is activated, the account is ready to be used. Following is CreatedUser event implementation:
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
CreateUserWizard wizard = (CreateUserWizard)sender;
MembershipUser user = Membership.GetUser(wizard.UserName);
if (user != null)
{
try
{
SmtpClient smtpClient = new SmtpClient("smtpServer");
smtpClient.UseDefaultCredentials = true;
smtpClient.Send("yourEmail", user.Email, "Account Conformation Email",
"hst/Confirm.aspx?id="+user.ProviderUserKey.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
}
Implementation of email notification is very simple. This is due to the fact that .NET library provides very simple means to communicate with any SMTP server. It is important to note the line smtpClient.UseDefaultCredentials = true. In this way, smtpClient object looks into web.config for SMTP server settings, i.e. username, password, and the host to be used. Following section is expected by SmtpClient object to be present in web.config file:
<system.net>
<mailSettings>
<smtp>
<network
host="smtp.hst.com "
userName="username"
password="password" />
</smtp>
</mailSettings>
</system.net>
Settings are self explanatory. The smtpClient simply needs the host name, username, and password to be used. Once the user is created, CreatedUser event is raised and email is sent. The content of the email is simply a link to Confirm.aspx page with an id parameter. The id parameter is GUID assigned specifically for that user. It is important to note that username could be used too, but GUID provides slightly more security.
Another extension necessary for CreatedUser event to function properly is capability of AccessMembershipProvider object to be able to provide user information based on username. In order to achieve such functionality, AccessMembershipProvider contains method that takes username as parameter and reads user information from database. Following is implementation:
public override MembershipUser GetUser(string username, bool userIsOnline)
{
MembershipUser user = null;
using (OleDbConnection conn = new OleDbConnection(m_strDBConnection))
{
try
{
/* Create command */
OleDbCommand command =
new OleDbCommand("SELECT UUSERNAME, PROVIDER_KEY, EMAIL FROM Users " +
"WHERE UUSERNAME=#Param1", conn);
command.Parameters.AddWithValue("#Param1", username);
/* Open connection */
conn.Open();
/* Run query */
OleDbDataReader reader = command.ExecuteReader();
/* Check if we have something */
bool bResult = reader.HasRows;
if (bResult)
{
reader.Read();
user = new MembershipUser("AccessMembershipProvider", reader.GetString(0),
new Guid(reader.GetString(1)),
reader.GetString(2), "", "", true, false,
DateTime.Now, DateTime.Now, DateTime.Now,
DateTime.Now, DateTime.Now);
}
/* Close connection */
conn.Close();
return user;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
return user;
}
}
Once again, the implementation is trivial. We simply find info about user with specified username by running query against our users table.
Email Confirmation
Once the email is sent the user receives an email with a link to confirm his/her signup and email. The link is of the form http://"hst"/Confirm.aspx?id=XXXXXXXXXXXXXXXXXX, where XXXXXXXXXXXXXXXX is GUID determined in CreateUser method. Once the user clicks on the email the browser loads Confirm.aspx page with appropriate parameter. As suspected, Confirm.aspx is responsible for account activation. The account activation is performed by modifying ACTIVATED flag within user table. Following is Confirm.aspx implementation:
protected void Page_Load(object sender, EventArgs e)
{
if (Request.QueryString["id"] != null)
{
using (OleDbConnection conn = new OleDbConnection(
ConfigurationManager.ConnectionStrings["UsersDB"].ConnectionString))
{
try
{
OleDbCommand command =
new OleDbCommand("UPDATE Users SET ACTIVATED=#Param1 "+
"WHERE PROVIDER_KEY=#Param2", conn);
command.Parameters.AddWithValue("#Param1", true);
command.Parameters.AddWithValue("#Param2", Request.QueryString["id"]);
conn.Open();
command.ExecuteNonQuery();
conn.Close();
Response.Write("Your account has been activated. "+
"Please log in <a href='Default.aspx'>here</a>");
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
}
}
The implementation is self explanatory. The code simply modifies activated flag of the user with specified GUID.
At this point it is important to state somewhat obvious fact. The AccessMembershipProvider’s ValidateUser method must be modified to consider activated flag. In other words, only activated users can be validated and consequently logged in. For completeness, following is the implementation:
public override bool ValidateUser(string username, string password)
{
using (OleDbConnection conn = new OleDbConnection(m_strDBConnection))
{
try
{
/* Create command */
OleDbCommand command = new OleDbCommand("SELECT UUSERNAME, UPASSWORD FROM Users " +
"WHERE UUSERNAME=#Param1 " +
"AND UPASSWORD=#Param2 " +
"AND ACTIVATED=#Param3",
conn);
command.Parameters.AddWithValue("#Param1", username);
command.Parameters.AddWithValue("#Param2", password);
command.Parameters.AddWithValue("#Param3", true);
/* Open connection */
conn.Open();
/* Run query */
OleDbDataReader reader = command.ExecuteReader();
/* Check if we have something */
bool bResult = reader.HasRows;
/* Close connection */
conn.Close();
return bResult;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
return false;
}
}
Access Database
Access database utilized is extended version of the database utilized in ASP.NET 2.0 Forms Authentication article with addition of GUID and ACTIVATED fields. GUID is stored in PROVIDER_KEY field. In addition, some fields like username and password had to be renamed because of the conflict with Access DB system.
Source
Click here to demo

Related

Web app protected by single password for all clients

I was wondering if there is a standard way of protecting a ASP.Net web application with just a single password? In other words no username needed and all clients use the same password for authentication.
Or does anyone have their own solution?
You simply could use Identity framework to aim this propose. Actually you don't need any user or password to authenticate.
[HttpPost]
public ActionResult Login(string password)
{
if (password=="MyVerySecretPassword")
{
var ident = new ClaimsIdentity(
new[] {
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, "JustAnuniqueName"),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
new Claim(ClaimTypes.Name,"JustAnuniqueName"),
},
DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
return RedirectToAction("MyAction"); // auth succeed
}
// invalid password
ModelState.AddModelError("", "invalid username or password");
return View();
}
But it would be much better if you hash the password and check the hashed password instead of above simple if statement. To aim this you could use PasswordHasher class to hash and verify the password.
First hash your desired password and save it in preferred storage (DB, file, hard coded in code or everywhere else):
string hashedPassword = new PasswordHasher().HashPassword("MyVerySecretPassword");
Now since you have the hashed one. You could use VerifyHashedPassword() method to verify it.
if(new PasswordHasher()
.VerifyHashedPassword("myHashedPassword",password)==PasswordVerificationResult.Success)
{
// the password is correct do whatever you want
}
Also you could see my simple working example which I made to demonstrate it.

How to configure member ship with a database other than aspnetdb

I created one database and tables to store the user login values and credentials.
asp.net is providing aspnet_regsql tool to create a database for the membership related activities. But I dont want to use it. Thats why I created another database. Now I want to connect this database to my project. I changed in web.config file for the connectionstring parameter to my newly created database. But I am unable to login. It is giving following error message.
Could not find stored procedure 'dbo.aspnet_CheckSchemaVersion'
How to work with this. Is there any step by step procedures are there!! If so please provide.
Is there any thing to change rather than the connection string in the web.config file?
You need to create a membership provider to connect to your custom tables for authentication. MSDN has some documentation on the subject. You can also view a video on the subject at ASP.NET. Here are the links.
http://msdn.microsoft.com/en-us/library/f1kyba5e(v=vs.100).aspx
http://www.asp.net/web-forms/videos/how-do-i/how-do-i-create-a-custom-membership-provider
The main method for validation is going to be the ValidateUser method, you will override this method to provide authentication.
public sealed class CustomMembershipProvider : MembershipProvider
{
// implement other methods
public override bool ValidateUser(string username, string password)
{
try
{
var user = // GET USER OBJECT HERE
if (user != null)
{
string name = // set username
// Set your forms authentication ticket
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, user.ID.ToString(), DateTime.Now, DateTime.Now.AddMinutes(30), false, name, FormsAuthentication.FormsCookiePath);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
HttpContext.Current.Response.Cookies.Add(authCookie);
return true;
}
}
catch
{
}
return false;
}
// Other implementations
}
If you have roles in your application you may also want to implement a custom role provider:
http://msdn.microsoft.com/en-us/library/8fw7xh74(v=vs.100).aspx

Unknown username or bad password, LDAP Active Directory

I'm trying to authenticate against AD using application mode (ADAM), but keep getting unknown username or bad password. If I test the login in LDP.exe it logs in no problem, on simple bind. I've trawled through all similar posts with the same issue, but have not resolved it, any suggestions what I should be checking for?
private bool ValidateActiveDirectoryLogin(string Username, string Password)
{
bool Success = false;
System.DirectoryServices.DirectoryEntry Entry = new System.DirectoryServices.DirectoryEntry("LDAP://localhost:389/OU=Users,O=TestDirectory", Username, Password);
System.DirectoryServices.DirectorySearcher Searcher = new System.DirectoryServices.DirectorySearcher(Entry);
Searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree;
try
{
System.DirectoryServices.SearchResult Results = Searcher.FindOne();
Success = (Results != null);
}
catch (Exception ex)
{
Success = false;
throw;
}
return Success;
}
Determine what context your application is hitting AD with. If your ASP.NET application pool identity is one that is low privileged, it won't have enough permissions to query active directory. If you don't want to create a custom user to run the app pool as with appropriate permissions - you could use the LogonUser API to make your ValidateActiveDirectoryLogin call under the security context of that account.
Finally, you should consider using System.DirectoryServices.AccountManagement if you are using .NET 3.5 or above.
You can use code like
bool validCreds = false;
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
validCreds = context.ValidateCredentials( username, password );
}

Is it possible to change the username with the Membership API

I am using the default sql membership provider with ASP.NET and I would like to provide a page to change the user's username. I believe I am sure I could do this with a custom provider, but can this be done with the default provider?
Second part of my question is:
Should I allow users to change their username after the account is created?
It's true that the default SQL Membership Provider does not allow username changes. However, there's no intrinsic reason to prevent users from changing their usernames if you have a valid argument, on your site, to allow it. None of the tables in the SQL database have the username as a key, everything is based on the user's ID, so from an implementation perspective it would be fairly easy.
If you use SqlMembershipProvider, you can extend it - it also relates to this question.
Roadkill is a wiki engine not a description of my coding style.
using System;
using System.Web.Security;
using System.Configuration;
using System.Data.SqlClient;
using System.Data;
using System.Web.Configuration;
namespace Roadkill.Core
{
public class RoadkillMembershipProvider : SqlMembershipProvider
{
private string _connectionString;
protected string ConnectionString
{
get
{
if (string.IsNullOrWhiteSpace(_connectionString))
{
Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
MembershipSection section = config.SectionGroups["system.web"].Sections["membership"] as MembershipSection;
string defaultProvider = section.DefaultProvider;
string connstringName = section.Providers[defaultProvider].ElementInformation.Properties["connectionStringName"].Value.ToString();
_connectionString = config.ConnectionStrings.ConnectionStrings[connstringName].ConnectionString;
}
return _connectionString;
}
}
public bool ChangeUsername(string oldUsername, string newUsername)
{
if (string.IsNullOrWhiteSpace(oldUsername))
throw new ArgumentNullException("oldUsername cannot be null or empty");
if (string.IsNullOrWhiteSpace(newUsername))
throw new ArgumentNullException("newUsername cannot be null or empty");
if (oldUsername == newUsername)
return true;
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "UPDATE aspnet_Users SET UserName=#NewUsername,LoweredUserName=#LoweredNewUsername WHERE UserName=#OldUsername";
SqlParameter parameter = new SqlParameter("#OldUsername", SqlDbType.VarChar);
parameter.Value = oldUsername;
command.Parameters.Add(parameter);
parameter = new SqlParameter("#NewUsername", SqlDbType.VarChar);
parameter.Value = newUsername;
command.Parameters.Add(parameter);
parameter = new SqlParameter("#LoweredNewUsername", SqlDbType.VarChar);
parameter.Value = newUsername.ToLower();
command.Parameters.Add(parameter);
return command.ExecuteNonQuery() > 0;
}
}
}
}
}
Scott Mitchell has a great article describing how to handle this situation here: https://web.archive.org/web/20210927191559/http://www.4guysfromrolla.com/articles/070109-1.aspx
Important quote from his article:
Unfortunately, idealism and pragmatism only rarely intersect. In some cases - such as allowing a user to change their username - we have no choice but to work directly with the underlying data store.
He also shows how to re-authenticate the user after changing their username/email.
If you want to do that with the Membership API, it seems the right way would be like this:
http://omaralzabir.com/how_to_change_user_name_in_asp_net_2_0_membership_provider/
Basically, you have to do the following (I copied from the above link, for sake of completeness):
Create a new user using the new email address
Get the password of the old account and set it to the new account. If you can’t get the old password via Membership provider, then ask user.
Create a new profile for the new user account
Copy all the properties from the old profile to the new profile object.
Log out user from old account
Auto sign in to the new account so that user does not notice what an incredible thing just happened.
Since Membershiop API does not allow username modification directly, you can access directly the aspnet_Membership table in your database.
Here's a version that incorporates the Enterprise Libraries DAB and a couple other minor changes. Also, I don't see a point in returning a Boolean, since it's either going succeed or throw an exception.
public static void ChangeUsername(string oldUsername, string newUsername)
{
if (string.IsNullOrWhiteSpace(oldUsername))
{
throw new ArgumentNullException("oldUsername cannot be null or empty");
}
if (string.IsNullOrWhiteSpace(newUsername))
{
throw new ArgumentNullException("newUsername cannot be null or empty");
}
if (oldUsername.Equals(newUsername))
{
return;
}
Database db = DatabaseFactory.CreateDatabase();
using (DbCommand cmd = db.GetSqlStringCommand("UPDATE dbo.aspnet_Users SET UserName=#NewUsername, LoweredUserName=#LoweredNewUsername WHERE UserName=#OldUsername"))
{
db.AddInParameter(cmd, "#OldUsername", DbType.String, oldUsername);
db.AddInParameter(cmd, "#NewUsername", DbType.String, newUsername);
db.AddInParameter(cmd, "#LoweredNewUsername", DbType.String, newUsername.ToLower());
db.ExecuteNonQuery(cmd);
}
}
No, the MembershipUser class does not allow to modify the Username property so you cannot do it.
Practically you should not allow the username to change. If you allow to do so somehow then it will lose its purpose and nature.

How do you change a hashed password using asp.net membership provider if you don't know the current password?

Problem, there's no method:
bool ChangePassword(string newPassword);
You have to know the current password (which is probably hashed and forgotten).
This is an easy one that I wasted too much time on. Hopefully this post saves someone else the pain of slapping their forehead as hard as I did.
Solution, reset the password randomly and pass that into the change method.
MembershipUser u = Membership.GetUser();
u.ChangePassword(u.ResetPassword(), "myAwesomePassword");
You are not able to change the password if the requiresQuestionAndAnswer="true"
I got the work around for this
Created two membership providers in web.config
i am using the AspNetSqlMembershipProviderReset provider for reseting the password since it has the requiresQuestionAndAnswer= false where as AspNetSqlMembershipProvider is the default provider used.
i wrote the following code to reset the password for the user.
public bool ResetUserPassword(String psUserName, String psNewPassword)
{
try
{
// Get Membership user details using secound membership provider with required question answer set to false.
MembershipUser currentUser = Membership.Providers["AspNetSqlMembershipProviderReset"].GetUser(psUserName,false);
//Reset the user password.
String vsResetPassword = currentUser.ResetPassword();
//Change the User password with the required password
currentUser.ChangePassword(vsResetPassword, psNewPassword);
//Changed the comments to to force the user to change the password on next login attempt
currentUser.Comment = "CHANGEPASS";
//Check if the user is locked out and if yes unlock the user
if (currentUser.IsLockedOut == true)
{
currentUser.UnlockUser();
}
Membership.Providers["AspNetSqlMembershipProviderReset"].UpdateUser(currentUser); return true;
}
catch (Exception ex)
{
throw ex;
return false;
}
}

Resources