How do I implement a password Reset Link - c#

I currently have a system where if a user has forgotten their password, they can reset it by clicking on a forgot password link. They will be taken to a page where they enter in their username/email and then an email will be sent to the user, I wanted to know how can I implement a password reset link in the email so once the user clicks on the link he/she is taken to a page which will allow them to reset their password.
This is the code in my controller
public ActionResult ForgotPassword()
{
//verify user id
string UserId = Request.Params ["txtUserName"];
string msg = "";
if (UserId == null)
{
msg = "You Have Entered An Invalid UserId - Try Again";
ViewData["ForgotPassword"] = msg;
return View("ForgotPassword");
}
SqlConnection lsql = null;
lsql = DBFactory.GetInstance().getMyConnection();
String sqlstring = "SELECT * from dbo.[USERS] where USERID = '" + UserId.ToString() + "'";
SqlCommand myCommand = new SqlCommand(sqlstring, lsql);
lsql.Open();
Boolean validUser;
using (SqlDataReader myReader = myCommand.ExecuteReader())
{
validUser = false;
while (myReader.Read())
{
validUser = true;
}
myReader.Close();
}
myCommand.Dispose();
if (!validUser)
{
msg = "You Have Entered An Invalid UserId - Try Again";
ViewData["ForgotPassword"] = msg;
lsql.Close();
return View("ForgotPassword");
}
//run store procedure
using (lsql)
{
SqlCommand cmd = new SqlCommand("Stock_Check_Test.dbo.RESET_PASSWORD", lsql);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter paramUsername = new SqlParameter("#var1", UserId);
cmd.Parameters.Add(paramUsername);
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
if (Convert.ToInt32(rdr["RC"]) == 99)
{
msg = "Unable to update password at this time";
ViewData["ForgotPassword"] = msg;
lsql.Close();
return View("ForgotPassword");
}
}
}
msg = "new password sent";
ViewData["ForgotPassword"] = msg;
lsql.Close();
return View("ForgotPassword");
}
This is my current stored procedure which sends the user an email
ALTER PROCEDURE [dbo].[A_SEND_MAIL]
#var1 varchar (200), -- userid
#var2 varchar (200) -- email address
AS
BEGIN
declare #bodytext varchar(200);
set #bodytext = 'Password Reset for user: ' +#var1 + ' #' + cast (getDate() as varchar) + ' ' ;
EXEC msdb.dbo.sp_send_dbmail
#profile_name='Test',
#recipients=#var2,
#subject='Password Reset',
#body=#bodytext
END
GO

Create a table that has a structure like
create table ResetTickets(
username varchar(200),
tokenHash varbinary(16),
expirationDate datetime,
tokenUsed bit)
Then in your code when the user clicks the reset password button you will generate a random token then put a entry in that table with the hashed value of that token and a expiration date of something like DATEADD(day, 1, GETDATE()) and appends that token value on the url you email to the user for the password reset page.
www.example.com/passwordReset?username=Karan&token=ZB71yObR
On the password reset page you take the username and token passed in, hash the token again then compare that with the ResetTickets table, and if the expiration date has not passed yet and the token has not been used yet then take the user to a page that lets them enter a new password.
Things to be careful about:
Make sure to expire the token, don't let a email from two years ago reset the password.
Make sure to mark the token as used, don't let other users of the computer use the browser's history to reset other users passwords.
Make sure you generate the random token safely. Don't use Rand and use it to generate the token, two users who reset at the same time would get the same token (I could reset my password and your password at the same time then use my token to reset your account). Instead make a static RNGCryptoServiceProvider and use the GetBytes method from that, the class is thread safe so you don't need to worry about two threads using the same instance.
Be sure to parameterize your queries. In your current code if I typed in the userid '; delete dbo.[USERS] -- it would delete all the users in your database. See the linked SO post for more info on how to fix it.
Be sure you hash the token, your passwordReset page only accepts the unhashed version, and you never store the unhashed version anywhere (including email logs of outgoing messages to users). This prevents an attacker who has read access to the database from making a token for some other user, reading the value that was sent in the email, then sending the same value himself (and perhaps getting access to an administrator user who can do more stuff than just read values).

here are 2 alternatives using HMAC or JWT (which i think provide better, more secure, email URLS)
https://neosmart.net/blog/2015/using-hmac-signatures-to-avoid-database-writes/
https://www.smashingmagazine.com/2017/11/safe-password-resets-with-json-web-tokens/

Related

BCrypt.Verify not working for c# asp.net web application

I am trying to verify a hashed password that is hashed in the register page, when I try to verify the hashed password with a entered password from the login, false is always returned.
I am hashing the password so:
string hashPassword = BCrypt.Net.BCrypt.HashPassword(Password);
The hashpassword is then saved to the database.
I try to verify the password so:
bool validPassword = BCrypt.Net.BCrypt.Verify(ProvidedPassword, StoredPassword);
Debug.WriteLine(validPassword);
if (validPassword)
{
Debug.WriteLine(ProvidedPassword + " is valid");
}
else
{
Debug.WriteLine("Passwords do not match");
}
I am using this source from github.
I have tried multiple methods and still always returns a false value.
I found the issue, my stored procedure parameters did not match my tables paremeters

Get ID of current logged in user in ASP.NET Web Forms

I want to get id of current loggedin user. I can get Username: User.Identity.Name . I have two tables Users and Reservations. UserID is a foreign key at Reservations table.
I have build an function that return id of current logged-in user:
private Int32 ReturnUserID() {
string constr = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
SqlConnection con = new SqlConnection(constr);
con.Open();
SqlCommand cmd = new SqlCommand("ReturnUserID", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p1 = new SqlParameter("Username", User.Identity.Name);
cmd.Parameters.Add(p1);
SqlDataReader rd = cmd.ExecuteReader();
if (rd.Read()) {
int u = Convert.ToInt12(rd["UserID"]);
return u;
} else return 0;
}
Please tell me a specific way that how can I get and store the id of the current loggedin user?
The tutorial you're using looks like it's relying on a FormsIdentity and a GenericPrincipal, so you can discover their properties through casting:
var p = (GenericPrincipal)HttpContext.Current.User;
var i = (FormsIdentity)HttpContext.Current.User.Identity;
//look for claims or whatever you use to store the ID
If you have a custom IIdentity or IPrincipal, then change the cast to use your custom type and get the ID property that way.
EDIT: One way to do what I'm suggesting is to take this line from the tutorial you mentioned:
HttpContext.Current.User
= new System.Security.Principal.GenericPrincipal(identity, roles);
...and instead of using a GenericPrincipal, create your own IPrincipal that also stores the user ID. This approach would require a round trip to the database with each request.
Alternatives are to store the user ID in session, which is secure, but consumes memory.
Lastly, you can store the ID in the UserData property of the FormsAuthenticationTicket (along with the roles). The forms authentication ticket is encrypted, but sent to the client. This approach is probably the best-performing overall and decently secure.
To go with that approach, you'd need to change this part of the tutorial code to include the ID:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
userName,
DateTime.Now,
DateTime.Now.AddMinutes(50),
rememberUserName,
roles + "####" + userId, //<-******** This Line Changes ********
FormsAuthentication.FormsCookiePath);
//... then in Application_AuthenticateRequest() do something like this:
var userData = ticket.UserData.Split(new string[]{"####"}, StringSplitOptions.RemoveEmptyEntries);
var userIdAsString = userData[1];
var roles = userData[0].Split( new char[]{ ',' } );
If you are already using ASP.Net identity most straightforward way is to use user manager. Upon successful login, you can use the login model's email to retrieve the user object using the user manager's FindByEmail or FindByEmailAsync method. Further, if you want to find out the user's role then use can use the user manager's IsInRole role method by parsing the id of the user and the role name.
**** This code shows how to get the logged-in user at login.****
protected void LogIn(object sender, EventArgs e)
{
if (IsValid)
{
// Validate the user password
var userManager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
var signinManager = Context.GetOwinContext().GetUserManager<ApplicationSignInManager>();
// This doen't count login failures towards account lockout
// To enable password failures to trigger lockout, change to shouldLockout: true
var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
//If login success find the user record by email using user manager
var user = userManager.FindByEmail(Email.Text);
//Store logged in user in session
Session["CurrentUser"] = user;
//find out current user's role and save it in session
if(userManager.IsInRole(user.Id, "Admin"))
{
Session["CurrentUserRole"] = "Admin";
}
else
{
Session["CurrentUserRole"] = "Staff";
}
IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
break;
case SignInStatus.LockedOut:
Response.Redirect("/Account/Lockout");
break;
case SignInStatus.RequiresVerification:
Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}", Request.QueryString["ReturnUrl"],RememberMe.Checked),true);
break;
case SignInStatus.Failure:
default:
FailureText.Text = "Invalid login attempt";
ErrorMessage.Visible = true;
break;
}
}
}

SQL Authentication with Textbox and AD Authentication

Hi I am running two forms of Authentication. The first authentication I run is a an AD Authentication which works fine. The Second is to confirm that the username is in a SQL Database. I am using this code to verify the username exists in SQL.
I get the error
Operator '==' cannot be applied to operands of type 'string' and 'System.Data.SqlClient.SqlDataReader'
with this code....
using (SqlConnection con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString))
{
SqlCommand cmd = new SqlCommand("select Username from dbo.users", con);
con.Open();
SqlDataReader sqlresult = cmd.ExecuteReader();
if (txtLoginID.Text == sqlresult)
{
Response.Redirect("account/orders.aspx");// Authenticated user redirects to default.aspx
}
else
{
Response.Redirect("default.aspx");// Authenticated user redirects to default.aspx
}
con.Close();
}
}
You have several problems here. First off your query:
select Username from dbo.users
Is pulling every record in the table. This is not the correct way to check if the user name exists. You need something like this:
select Username from dbo.users WHERE UserName = #UserName
Where #UserName is a parameter that contains the value from txtLoginID.Text. Once you have done this you can use:
if(sqlresult.HasRows)
{
Response.Redirect("account/orders.aspx");// Authenticated user redirects to default.aspx
}
else
{
Response.Redirect("default.aspx");// Authenticated user redirects to default.aspx
}
You should also look at using statements.
While it isn't doesn't really matter if you use the above solution, the reason you are getting the error is pretty self explanatory. You are attempting to compare a string value to a SqlDataReader object. This is not possible.

Validating a user's credentials remotely

I currently use LogonUser() to authenticate my user's username and password on my local domain at the office and it works great for what i need it to do.
Since I developed the app I now need to make it work over my VPN. It seems LogonUser() will not work with REMOTELY validating credentials. Or will it? Is it possible to use LogonUser() to validate a user's credentials on a REMOTE domain account?
I have read in some places that using LOGON32_LOGON_NEW_CREDENTIALS for the 4th param (login type) and LOGON32_PROVIDER_WINNT50 for the 5th param (provider) would do the trick. But every time I try that I ALWAYS get success... I can supply a bogas user and pass and it will work every time :(.
Ideas?
Edit - Added Notes
Tried to use this function but I kept getting the exception telling me the user/pass was bad.
public bool Win2kCredentialsIsValid(string domain, string username, string password)
{
string adPath = "LDAP://" + domain + "/rootDSE";
DirectoryEntry adRoot = new DirectoryEntry(adPath, domain + "\\" + username, password, AuthenticationTypes.ReadonlyServer);
try
{
object o = adRoot.Properties["defaultNamingContext"];
}
catch
{
return false;
}
return true;
}
--
Edit - Added More Notes
OK so I tried yet another example just to get it to work and started down this path, and there are a few things to note...
MyServerHostName is exactly that, my server's hostname. EX: 'Server01'.
My domain name in this example is 'MyDomain.local'
So that makes my FQN for the server 'Server01.MyDomain.local'
I tried to make this work and got the following error...
The supplied context type does not match the server contacted. The server type is Domain.
This errored out at : var context = new PrincipalContext(ContextType.ApplicationDirectory, "MyServerHostName:389", "DC=MyDomain,DC=local"))
private bool CheckADCredentials()
{
bool bResults;
using (var context = new PrincipalContext(ContextType.ApplicationDirectory,
"MyServerHostName:389",
"DC=MyDomain,DC=local"))
{
var username = "firstname.lastname";
var email = "firstname.lastname#MyServerHostName";
var password = "123456";
var user = new UserPrincipal(context)
{
Name = username,
EmailAddress = email
};
user.SetPassword(password);
user.Save();
if (context.ValidateCredentials(username, password, ContextOptions.SimpleBind))
{
bResults = true;
}
else
{
bResults = false;
}
user.Dispose();
}
return bResults;
}
I ended up going with a different solution. Instead of trying to validate a user's account on a domain that my PC was not connected to I ended up caching my domain credentials in the database and just built a salted MD5 type encrypt function so it would make it hard .. er.. for someone to crack it. ;)
Now I just validate against cached credentials in the database when working remotely... It just required the user to first login on the domain but then the user can use it remotely day and night. ;)
Thanks!

C# Populate drop down based on data from cookie

I will see if I can explain this clearly enough. I have 2 web forms. One is a basic Forms Authentication login page and the other form displays tasks from multiple servers. I am creating a cookie that stores the UserID. Here is the code for my cookie:
FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(1, txtUser.Text, DateTime.Now, DateTime.Now.AddMinutes(120), true, rdr.GetInt32(0).ToString(), FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(tkt);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
On my other form, I have a drop down box that displays all servers by Server IP from the Servers table.
public void Populate()
{
SqlConnection myConnection1 = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString);
myConnection1.Open();
SqlCommand cmd1 = new SqlCommand("SELECT ServerIP FROM Servers", myConnection1);
SqlDataReader dropReader;
dropReader = cmd1.ExecuteReader();
drpChoose.DataSource = dropReader;
drpChoose.DataTextField = "ServerIP";
drpChoose.DataValueField = "ServerIP";
drpChoose.DataBind();
}
I am calling Populate in Page Load. I have another table that stores permissions. It has UserID, ServerID, and Permission (read or execute). Let's say that UserID 1 is associated with only ServerID 1 which has an IP of 192.168.0.10. How can I get this one Server IP to display in the drop down? I am pretty sure if I pass the cookie into the second form that I can take the UserID from that but I do not know where to begin.
I apologize if I have not given enough information. I will provide more if need be.
Looks like you'll need to do a join to your permissions table something like
SELECT ServerIP from Servers s, Permissions p where p.serverid = s.serverid and p.userid = :userIdFromCookie
Then you'll need to pass in the user id from your cookie into the Populate method and use a DbParameter to pass the value into your Sql command.
something like (this is pseudocode by the way as I'm not at my dev machine)
cmd.AddInParameter(":userIdFromCookie",dbType.AnsiString, Request.Cookies["mycookie"]["userid"])

Categories

Resources