Authentication with BCrypt

Make sure you have followed the guide on creating User Accounts before you continue onto this guide. You will need this before you can continue. You should already have your login form. The example we will use is nice and simple, includes 6 controls, 3 labels, 2 textboxes and a button.

image-20211012193106746

Now there are different ways to deal with Authentication in a C# desktop application, this is just one example. Feel free to take your own route here but here is an example.

Check the User Exists

The first thing we need to do is to check that the user exists in the database. We need this action to complete as we enter a Username and Password and click the Login button. So your first step is to double click that button to open the code view.

image-20211012193226583

I have marked where will be adding our code in the screenshot above, you should be used to this concept by now. Being event driven in this example we’re going to run the code we’re about to program only if the user clicks the button called btnLogin from our visual form.

Remember we want to access the database here, but let’s do a few checks first of all, as we may not need to access the database at all. The point i’m emphasizing here is why connect to the database and add extra load on it if the user hasn’t entered any details into the form. So let’s first check that both a Username and Password have been entered. We can do this using an if statement, the example statement below is a good place to start.

// check that we have a username and password
if(txtUsername.Text == "" || txtPassword.Text == "")
{
     // alert the user providing feedback on the issue
     MessageBox.Show("Please enter a Username & Password!");
       
     // return here, don't continue processing any more code in this code block
     return;
}

An important note to make here is that we have return; in this if statement code block. Esentially what this is doing is saying if there is a problem, i.e. the Username or Password are blank (we can’t login without both) then stop processing the code in this code block (or function). This will help prevent us from making unnecessary requests to the database.

If there isn’t an issue then the application will move past this block and continue to anything below the closing curly brace (}). So we can add our database code below this.

Add the code snippet below. We’ve seen this before remember, it allows us to interface with our database via the Model files we have created for each table in the database.

// get the database context
var context = new DataContext();

// if the connection to the database is successful
if (context.Database.CanConnect())
{

    // we will add our database actions here

}

We’ve also checked to make sure we can actually access the database again, now let’s add some logic which will allow us to check for a valid username and password in the SQLite database. Have a look at the code snippet below, we will break this down into chunks too.

// grab only the record which matches the username
Login loginObj =
     context.Login
     .Where(x => x.Username == txtUsername.Text)
     .FirstOrDefault();

// check the outcome
if (loginObj != null)
{
    // confirm the password is a match
    if(BCrypt.Net.BCrypt.Verify(txtPassword.Text, loginObj.Password))
    {
           // login successful
           MessageBox.Show("Login Successful");
                    
    }
    else
    {
       // we hit this point if the password is incorrect
       IncorrectDetails();
    }

    return;
}

// we hit this point if the database doesn't return a valid username
IncorrectDetails();

Login Logic Breakdown

The login logic is fairly straightforward once you get to grips with it, so let’s break it up a bit so you can get a better understanding of each section of the code snippet above.

// grab only the record which matches the username
Login loginObj =
     context.Login
     .Where(x => x.Username == txtUsername.Text)
     .FirstOrDefault();

This first section of the code checks for a match on the Username. Seems silly right, surely we should be checking both the username and password?! Well no, it’s absolutely correct. We can’t verify the password at the moment as BCrypt is irreversible as an encryption algorithm and if we attempt to match a plain text password e.g. password against our encrypted password e.g. $2a$11$rjbNA6HOUM5nI.s6U4w7XeiQhaJJhU6D/bcejzwe5ZU7MtOKsIfDm we’re not going to get very far.

In this case we need to collect the record for the user where the username matches, e.g. Marc. Once we have this match we have to encrypt the string the user entered in the password box at login (the plaintext version), we run this through the BCrypt.Verify function to check that it matches the encrypted version in the database. Queue the next part of our code….

// check the outcome
if (loginObj != null)
{
 	// code removed for brevity   
}

The next if statement checks if we have a result, this means we’re simply checking that the user has a record, but we’re not checking the password. An example would be checking for the username Marc. The screenshot below serves a reminder so you can see exactly what we have in the database.

image-20211012203415709

This user exists, therefore we will collect the rest of the information. If they don’t we run the code in the IncorrectDetails function, which we will discuss shortly. Lets look at the next if statement.

	// confirm the password is a match
	if(BCrypt.Net.BCrypt.Verify(txtPassword.Text, loginObj.Password))
	{
       	// login successful
       	MessageBox.Show("Login Successful");                  
	}
	else
	{
      	// we hit this point if the password is incorrect
      	IncorrectDetails();
	}

	return;
}

// we hit this point if the database doesn't return a valid username
IncorrectDetails();

In this if else statement we’re using the BCrypt Verify function which came part of our BCrypt package to check if the password that the user attempting to login with matches the encrypted version in the database. If it does then we show a Message Box letting them know this is the case.

If an incorrect password has been entered we run the IncorrectDetails() function. After this we close the previous if statement too - this is what the final closing curly brace is. Don’t worry it’s not rogue 🙂

Finally we call the IncorrectDetails() function once more, but only if the username doesn’t exist in the database. This brings us onto our user defined function which has been added to aid with code brevity.

private void IncorrectDetails()
{
	// clear the inputs so that they can try again?

	// alert the user that the details they provided are incorrect
	MessageBox.Show("Invalid Credentials Supplied!");
}

This function doesn’t return any data, so you can see the datatype for the function is void. Equally it doesn’t need to be access anywhere else, other than in the login functionality and so it has been set as private. The screenshot below has been provided to give you an idea of placement of this function.

image-20211012204402735

It’s created outside of the btnLogin_Click function, which is a little different to what we have seen so far.

What next?

At the moment we have our system checking login details, but nothing much happens other than a success or error message is displayed. You now need to think of what you should do next in terms of making this more functional. There are some ideas below to get you thinking?

  • Could you open up a hidden form, inaccessible unless they have passed this login form?
  • Could you clear the txtUsername and txtPassword inputs if the user enters incorrect details?
  • Why do we not show “your username is incorrect!” & “your password is incorrect!” as individual error messages?