Login Error
As you can see, I want to catch the exception if the user is tampering the Login Button if there are no values in the fields or if it doesn't match info in the database.
For example:
The field has no values and I click Login button once, it says the error. After I clicked OK button, I click Login button again and now it says,
"ExecuteReader requires an open and available Connection. The connection's current state is closed."
I use 3 tier Architecture Windows Application.
BEL:
public SqlDataReader Login(BELLogin bellog)
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = Con.getcon();
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT username,password FROM tbl_login WHERE username = #Username AND password = #Password";
cmd.Parameters.AddWithValue("#Username", bellog.Acctname);
cmd.Parameters.AddWithValue("#Password", bellog.Password);
SqlDataReader dr = cmd.ExecuteReader();
return dr;
}
BAL:
public class BELLogin
{
public string Acctname { get; set; }
public string Password { get; set; }
}
DBConnection:
public SqlConnection getcon()
{
if (con.State == System.Data.ConnectionState.Closed)
con.Open();
else if (con.State == System.Data.ConnectionState.Open)
con.Close();
return con;
}
public DataTable ExeReader(SqlCommand cmd)
{
getcon();
cmd.Connection = getcon();
SqlDataReader dr = cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dr);
return dt;
}
GUI:
private void btn_login_Click(object sender, EventArgs e)
{
BELog.Acctname = txb_accName.Text;
BELog.Password = txb_password.Text;
SqlDataReader dr;
dr = BALog.Login(BELog);
if (txb_accName.Text == "" || txb_password.Text == "")
{
MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
if (dr.HasRows == true)
{
dr.Read();
Inventory Inv = new Inventory();
Inv.Show();
this.Hide();
}
else
{
MessageBox.Show("You have entered your password or account name incorrectly. Please check your password and account name and try again.", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
dr.Close();
}
Logging in is ok but what if the user tampering the button?
Thank you for helping me :D
You need to change your code in gui like this:
//Put code to get the reader inside else clause and close the reader in the same else clause. Also ideally you should return if you encounter. I have added it and commented it.
//Off course you would need to put more effort to make this code better. You will get to that as you get more experience. For now this should make your app work.
private void btn_login_Click(object sender, EventArgs e)
{
BELog.Acctname = txb_accName.Text;
BELog.Password = txb_password.Text;
if (txb_accName.Text == "" || txb_password.Text == "")
{
MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
//return;
}
else
{
SqlDataReader dr;
dr = BALog.Login(BELog);
if (dr.HasRows == true)
{
dr.Read();
Inventory Inv = new Inventory();
Inv.Show();
this.Hide();
}
else
{
MessageBox.Show("You have entered your password or account name incorrectly. Please check your password and account name and try again.", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
dr.Close();
}
}
Do not reuse connections like this, it is bad practice and unnecessary.
Wrap all type instances that implement IDisposable in using blocks so the resources are released. In your case SqlConnection, SqlCommand, SqlDataReader, DataTable.
From a security standpoint you should never store your user passwords (anywhere, not DB, not files, not registry, etc. just do not store them). You need to store the hash, not the password, and compare hashes
Adhere to loose the coupling / high cohesion principle. Essentially expose as little as possible (especially implementation details) from your methods / classes so they can be easily reused and changed. Currently you are passing around and sharing DB objects, this will make your code brittle and very difficult to track down where problems are. Here is your code with a little bit of refactoring, notice that if you have another issue with a connection during login it would now be very easy to figure out where that might be.
// place in new code file
public class UserManager{
public BELLogin FindLogin(string userName, string password){
if(string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
return null;
using(var connection = new SqlConnection("connectionStringPointerFromAppConfigHere"))
using(SqlCommand cmd = new SqlCommand("SELECT username,password FROM tbl_login WHERE username = #Username AND password = #Password", connection))
{
connection.Open();
cmd.Parameters.AddWithValue("#Username", bellog.Acctname).SqlDbType = SqlDbType.VarChar;
// BAD practice! Use a secure hash instead and store that not the password!
cmd.Parameters.AddWithValue("#Password", bellog.Password).SqlDbType = SqlDbType.VarChar;
using(SqlDataReader dr = cmd.ExecuteReader())
{
if(dr.Read())
return new BELLogin() {Acctname = dr.GetString(0), Password = dr.GetString(1)}; // passed in is same as in datareader
}
}
return null;
}
}
From your login form class
private void btn_login_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txb_accName.Text) || string.IsNullOrEmpty(txb_password.Text))
{
MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
var manager = new UserManager();
var user = manager.FindLogin(txb_accName.Text, txb_password.Text);
if (user != null)
{
Inventory Inv = new Inventory();
Inv.Show();
this.Hide();
}
else
{
MessageBox.Show("You have entered your password or account name incorrectly. Please check your password and account name and try again.", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
Your btn_login_Click method appears to call BALog.Login(BELog) prior to checking if there are any valid values in the username and password textboxes. Simply move the validation to the top of btn_login_Click method, and return if the fields are empty:
if (txb_accName.Text == "" || txb_password.Text == "")
{
MessageBox.Show("Some fields are empty. Please fill up all fields before clicking LOGIN button.", "Login Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
The code in the else portion of that if statement can stay where it is, just not inside an else. The method will exit due to return statement if there are no valid values in the username and password textboxes.
As others have suggested, you should review your code to ensure you really want this structure; but if you do want to keep it that way, this simple fix will solve your problem.
Just get rid of your DBConnection object, it's not really doing anything and just making your structure complex:
public BELLogin Login(BELLogin bellog)
{
SqlConnection conn = new SqlConnection(connectionsString);
try
{
using (SqlCommand cmd = new SqlCommand())
{
conn.Open();
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT username,password FROM tbl_login WHERE username = #Username AND password = #Password";
cmd.Parameters.AddWithValue("#Username", bellog.Acctname);
cmd.Parameters.AddWithValue("#Password", bellog.Password);
//really this should be in a using as well.
//You be better off reading your data
//into a class and returnig the class not the reader.
using (SqlDataReader dr = cmd.ExecuteReader())
{
BELLogin obj = new BELLogin();
while(dr.Read())
{
//populate obj
}
return obj;
}
}
}
finally
{
conn.Close();
conn.Dispose();
}
}
also the way you use it can cause memory leaks as your not explicitly disposing and closing your connections. Always dispose Sql objects in C#. Be wary of exceptions too. Any exceptions in your code will not close the connection, etc. This will result in memory leaks and connections locking
Related
Hi guys I'm really a beginner and need your help and advises.
So I was trying to create authentication in my Login form. The user role is in the same table with username and password but I don't have better idea to achieve authentication in my Winforms.
I want to do something like this
If user role id is == 1
then proceed to admin dashboard
and if user role id is == 2
then proceed to user dashboard
But I'm stuck up in the accessing my database.
So here is my code functions.
This is the LOGIN FUNCTION to the Database:
public static bool LOGIN(string user, string pass)
{
try
{
SqlCommand sqlCommand;
string Query = "SELECT COUNT(*) FROM tblBPAccounts WHERE username=#user AND passwd=#pass";
using (var SqlConn = new SqlConnection(DatabaseHelper.connection))
{
SqlConn.Open();
sqlCommand = new SqlCommand(Query, SqlConn);
sqlCommand.Parameters.AddWithValue("#user", user);
sqlCommand.Parameters.AddWithValue("#pass", pass);
int result = (int)sqlCommand.ExecuteScalar();
return (result > 0);
}
}
catch (Exception ex)
{
Console.WriteLine("{0}" , ex.Message);
return false;
}
}
this is where I want to put if user role is == id :
private void LoginButton_Click(object sender, EventArgs e)
{
if (DatabaseHelper.LOGIN(UsernameTextBox.Text, PasswordTextBox.Text))
{
//dialog box for successful login
MessageBox.Show("Login Successfully", "Login", MessageBoxButtons.OK, MessageBoxIcon.Information);
//to hide the first form when button pressed
this.Hide();
// Log in was successful, do something...
AdminDashboard admin = new AdminDashboard();
admin.ShowDialog();
}
else
{
// Log in was NOT successful, inform the user...
MessageBox.Show("Incorrect Username or Password", "Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
use sqlDataReader
SqlDataReader reader = sqlCommand.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
if (Convert.ToInt16(reader["TypeofUser"])==1)
else if Convert.ToInt16(reader["TypeofUser"]==2)
return true;
}
else
{
return false;
}
This might help you a bit
try{
string query ="SELECT USERNAME,PASSWORD,TYPE from ***** WHERE.....");
conn.Open();
MysqlCommand comm = new MysqlCommand(query,Myconnection);
MysqlDataReader read = comm.ExecuteReader();
if(read.HasRows){
while(read.Read())
{
string Type = read.GetString(2);//Get Value of Type
if(Type.EQuals("admin"))
{
//open admin
}else{
}
}
}
else
{
//Invalid user and pass
}
conn.Close();
}
catch(Exception eee)
{Console.WriteLine(eee.Message);}
Could somebody help me with my problem on my log-in form?
My username registered on the database is "admin" (all are in lowercase form). However, upon logging-in with username, "admiN" (considering N is capitalized), I still get logged-in successfully.
private void btnLogin_Click(object sender, EventArgs e)
{
Account account = new Account();
if (txtUserName.Text == "" || txtPassword.Text == "")
{
MessageBox.Show("Empty Fields Detected ! Please fill up all the fields");
return;
}
if (account.Authorize(txtUserName.Text, txtPassword.Text))
{
MessageBox.Show("Login Successfully!");
this.Hide();
main.showMeForm4(this);
}
else
{
txtPassword.Focus();
MessageBox.Show("Username or Password Is Incorrect");
txtUserName.Text = "";
txtPassword.Text = "";
}
}
//class Account
public bool Authorize(string userName, string userPassword)
{
Connection connection = new Connection();
string sql = "SELECT * FROM tbl_Account WHERE Username=#userName and Password=#userPassword";
MySqlConnection conn = new MySqlConnection(connection.ConnectionString);
MySqlCommand cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("#userName", userName);
cmd.Parameters.AddWithValue("#userPassword", userPassword);
conn.Open();
MySqlDataReader login = cmd.ExecuteReader();
if (login.Read())
{
conn.Close();
return true;
}
else
{
conn.Close();
return false;
}
}
Your query will not take case into account. (default SQL Server behavior)
SELECT * FROM tbl_Account WHERE Username=#userName and Password=#userPassword
You can change your query to
SELECT * FROM tbl_Account
WHERE Username=#userName COLLATE SQL_Latin1_General_CP1_CS_AS
AND Password=#userPassword COLLATE SQL_Latin1_General_CP1_CS_AS
By changing the collation, it will take into account the case.
You can do what you want by doing the comparison in C# (instead of SQL) since string comparisons are case sensitive in C#:
MySqlDataReader login = cmd.ExecuteReader();
if (login.Read())
{
var userNameFromDb = login["Username"].ToString();
var passwordFromDb = login["Password"].ToString();
conn.Close();
return userNameFromDb == userName && passwordFromDb == userPassword
}
That being said, if this is for a something more than just your personal use / learning, I would recommend you reconsider how you are storing passwords. Right now, it looks like you're storing them in clear text which is a huge security risk. You should look into hashing and salting passwords and use a pre-made framework for authorization / authentication.
Also, I agree with other commenters that probably want to ignore case for the username.
I am creating a C# windows login form using MS Access 2013.
Login form using User ID(Autonumber) and Password(Short text).
My problem here is, it always crash(or Syntax error I guess) every time I click the LOGIN button and I can't trace the problem since I'm still inexperience in programming.
Table User
Fields: user_Id(Auto Number), password(short text), name(short text), type(number)
private void btn_Login_Click(object sender, EventArgs e)
{
try
{
if (string.IsNullOrEmpty(txt_UserId.Text))
{
lbl_warningUser.Visible = true;
lbl_warningUser.Text = "User ID is Empty";
}
if (string.IsNullOrEmpty(txt_Password.Text))
{
lbl_warningPass.Visible = true;
lbl_warningPass.Text = "Password is Empty";
}
if (txt_UserId.Text !="" & txt_Password.Text != "")
{
string constring = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Database/Health.accdb;";
string query = ("SELECT COUNT(*) FROM User WHERE user_Id = #ID AND password = #Pass");
using (OleDbConnection con = new OleDbConnection(constring))
using (OleDbCommand cmd = new OleDbCommand(query, con))
{
con.Open();
cmd.Parameters.AddWithValue("#ID", txt_UserId.Text);
cmd.Parameters.AddWithValue("#Pass", txt_Password.Text);
int result = (int)cmd.ExecuteScalar();
if (result > 0)
{
MessageBox.Show("Successfully Login");
con.Close();
this.Hide();
MainUI m = new MainUI();
m.Show();
}
else
{
MessageBox.Show("Incorrect User ID or Password");
}
con.Close();
}
}
}
catch (Exception ex)
{
MessageBox.Show(" "+ex);
}
}
User is a reserved word in MS Access I think, so you need to wrap it in square bracket delimiters. Also, you likely need to specify an alias for the COUNT function result:
"SELECT COUNT(*) AS qtyUsers FROM [User] WHERE user_Id = #ID AND password = #Pass"
txt_UserId.Text has a default return value of string, did you convert the value to an integer then try actually autonumber is integer.
First convert your value and try
Convert.ToInt32(txt_UserId.Text)
My method for checking login details against a database doesn't seem to return anything. I don't see how it is faulty. Here is the code:
connection.Open();
if (chkRemember.Checked == true)
{
loginDetails();
}
string command = #"SELECT email, password FROM zc_users WHERE email = '#user';";
try
{
// COMMAND DETAILS
MySqlCommand email = new MySqlCommand(command, connection);
// PARAMETERS
email.Parameters.AddWithValue("#user", txtEmail.Text);
// READER DETAILS
MySqlDataReader dr;
// CHECK DETAILS
dr = email.ExecuteReader();
if (dr.Read())
{
string passwordC = dr.GetString(1);
string saltedPass = Security.HashSalt.CreateHash(txtPassword.Text, passwordC);
bool match = IsPasswordMatch(passwordC, saltedPass);
if (match == true)
{
connection.Close();
string email2 = txtEmail.Text;
frmZilent frm = new frmZilent(email2);
frm.Show();
this.Hide();
}
else
{
msgEx m = new msgEx();
m.Show();
connection.Close();
}
}
connection.Close();
}
catch (MySqlException ex)
{
MessageBox.Show("Zilent Error: Code ZCx001. Please report this to the Zilent Team. Thanks. " + ex.Message);
connection.Close();
}
}
Basically, the email and password are selected from the database and then the password in the txtPassword textbox is hashed using the one from the database. The method checks if they are the same, and if they are, it closes the connection and shows the next form.
I either get an error or nothing happens. Any ideas?
Your SQL has the parameter inside a string literal, therefore the parameter is being interpreted as a string literal, so the value won't be used
You have:
SELECT email, password FROM zc_users WHERE email = '#user';
Change to:
SELECT email, password FROM zc_users WHERE email = #user;
Note that the apostrophes are missing in the second version.
I would like to ask how do I block a user after a specified failed attempts. After 30 minutes or whatever time, the user will be able to log in again.. Here's my sample code for log in.
public partial class Login : System.Web.UI.Page
{
SimplerAES AES = new SimplerAES();
protected void Page_Load(object sender, EventArgs e)
{
Session.Clear();
}
protected void btnLogIn_Click(object sender, EventArgs e)
{
if (txtUsername.Text == "" || txtPassword.Text == "")
{
lblMessage.Visible = true;
txtUsername.Text = "";
txtPassword.Text = "";
lblMessage.Text = "Invalid Username/Password";
}
else
{
SqlConnection con = new SqlConnection(SeiboLMS.Helper.GetConnectionString());
con.Open();
SqlCommand com = new SqlCommand();
com.Connection = con;
com.CommandType = CommandType.Text;
com.CommandText = "SELECT * FROM Users WHERE UserName=#UserName";
com.Parameters.Add("#UserName", SqlDbType.NVarChar);
com.Parameters[0].Value = txtUsername.Text;
SqlDataReader data = com.ExecuteReader();
if (data != null)
{
while (data.Read())
{
if (txtPassword.Text == AES.Decrypt(data["Password"].ToString()))
{
if (data["UserTypeID"].ToString() == "1")
{
Session["userid"] = data["UserID"].ToString();
Session["usertypeid"] = data["UserTypeID"].ToString();
Session["username"] = data["UserName"].ToString();
Session["password"] = data["Password"].ToString();
Helper.Logs(int.Parse(data["UserID"].ToString()), 1, "Log In Successful");
Response.Redirect("Admin/Default.aspx");
}
else
{
Session["userid"] = data["UserID"].ToString();
Session["usertypeid"] = data["UserTypeID"].ToString();
Session["username"] = data["UserName"].ToString();
Session["password"] = data["Password"].ToString();
Helper.Logs(int.Parse(data["UserID"].ToString()), 1, "Log In Successful");
Response.Redirect("Employees/Default.aspx");
}
}
else
{
lblMessage.Text = "Invalid Username/Password.";
txtUsername.Text = "";
txtPassword.Text = "";
}
}
}
else
{
lblMessage.Text = "Invalid Username/Password.";
txtUsername.Text = "";
txtPassword.Text = "";
}
data.Close();
con.Close();
con.Dispose();
}
}
}
Please share with me what ideas do you have there.
Thank you in advance..
This question has been answered many times.
Search for c# login with failed attempts
This particular question is very similar.
Login - Allow only 3 attempts
The top voted answer by Willem has the following text:
use a MembershipProvider and in your web.config, in system.web you can
configure number of attempts and timeouts. Set
maxInvalidPasswordAttempts="3" and passwordAttemptWindow="5" for your
requirements.
I suggest that if you find this helpful, you go vote up Willems answer.
You can use the MembershipProvider or depending on your database structure, create your own. Lots of ways to accomplish this, but one way is to include login_attempt, lockflag and timestamp columns. I'm guessing you have the login_attempt counter and are incrementing this (and resetting it to zero when successful) Then at login you check the values and if the user is locked check the timestamp to see when and if enough time has lapsed for the account to be unlocked.
Well, what you could do is the following:
1) Add in your users table, a field (DateTime) indicating the time that he/she was blocked by exceeds the maximum login attempts.
2) Add a key in the web.config (AppSettings, perhaps) that indicate how long users that had access blocked must wait until they can try again.
3) When trying to authenticate the user, check if the lock field (DateTime) is null, if it is the user performs the login normally. Otherwise compare the current time with the time it is in this field from the table, if greater than the appsettings value, prevents the user to login.
Hope I helped!