Check out a free preview of the full Build Go Apps That Scale on AWS course

The "Hashing Passwords" Lesson is part of the full, Build Go Apps That Scale on AWS course featured in this preview video. Here's what you'd learn in this lesson:

Melkey adds password hashing to the application. The bcrypt package is added to the project to both hash the plain text password and compare a plain text password with a hashed version.


Transcript from the "Hashing Passwords" Lesson

>> Let's actually start fixing our RegisterUser function. I'm gonna do that in our types.go. So there's one big, glaring issue, and that's we are storing plaintext passwords. That's not good, we don't want that. So how can we change this? If you go back, let's make a new type called User.

And it's gonna be a struct, and it's gonna look very similar. We're gonna have a Username field, Username string. It's gonna be a string, it's gonna have the same json: "username", okay? But this time, instead of just plaintext password, which we get from the request, we're gonna have Passworhash, still gonna be a string, okay?

And it's gonna be json, and it's gonna be password, and it should be password. Yeah, I don't think we need to change that, yeah, cool. Okay, so this new type looks exactly the same, password, not harsh. Let's change this, I think I'm typing too fast, PasswordHash, cool, okay?

And so where this user is gonna come into play is once we know there's no existing users, once we have confidence that there's no errors, before we insert user, we're not gonna be inserting of type RegisterUser, we're gonna be inserting of type User, okay? We're gonna do func NewUser, it's gonna take a registerUser of type RegisterUser.

And what it's going to return is a User and an error, okay? And why is that? Why am I doing all this work? Because this is where we're gonna create the hashedPassword function. Now, I would be an absolute fool if I were to stand in front of you and say, we're gonna make this our own.

I will never ever say I know enough about security to a level where I think I can hash my own password. That's why we're gonna use packages, it's gonna make our lives a little easier. So make sure you cd into your lambda, clear this. I'm gonna do go get, and the package we're gonna use is gonna be called bcrypt.

[00:01:55], so it's not Github, okay, x/crypto/bcrypt. Make sure I got that right, and bcrypt, sweet. Now, we got it. bcrypt is gonna do the heavy lifting for us, okay? You do not wanna be responsible for hashing passwords, I do not recommend that, but it's not financial advice.

Go here, we import that same thing,, cool. Imported and not used, that's the best message here. And then here, we're gonna do hashedPassword, and then it also returns an error. It's going to be bcrypt, and we're gonna do, not compare and hash. We're gonna use that afterwards, but we're gonna do GenerateFromPassword.

And this GenerateFromPassword takes a slice of bytes, which is another underlying data structure and essential type to Go. That's how a lot of the conversion between strings, different parameters are processed with functions within Go for performance reasons, efficiency reasons, and just natively being able to be more flexible with that implementation.

So here, how do we get a byte? How do we transform a string from a byte? We can just easily transform that with something like this. So first, it's gonna expect a slice of bytes, and it can transform the registerUser.Password. And this conversion transforms that string representation into a slice of bytes for us.

Okay, and the next parameter is this hashing algorithm, the salt, I think, if you will, we're gonna go with 10. The higher you go, the more it's going to take, processing-wise, to salt your password, to hash it correctly. But again, the higher you go, the more secure it is, but the longer it takes to create it.

10 is a good value, where we have a good security into the hashing, and it's not gonna take too long to generate, okay? And now if the error does not equal to nil, what I'm gonna do is return an empty User type, doesn't really matter. And I'm actually gonna talk about something.

So we can easily do something here. Remember how we were asked, when do we use this versus not? Well, this is an example of when you could just do nil, right? Cuz this zero value of a point of reference is nil. So there's applications where this matters, there's some applications where maybe it doesn't matter as much.

For our case, because I know I'm gonna be calling this NewUser, and where I'm calling it, I'm just gonna be checking on the error. I really don't care what this value is here, if it's nil or an empty User type. If I get an error, that's the only thing I'm focusing on in that block of code, okay?

Okay, and so now, once we have the password, all we're gonna do is return an instance of User, but this one's not gonna be empty. So our username is going to be from registerUser.Username. And then the password, PasswordHash, is gonna be the string. Cuz remember, this returns a byte, a slice of bytes generated from password, this bcrypt library.

So we have to do string, and we're gonna put in the hashedPassword, okay? And it's complaining, because we don't have enough return types, so it's gonna return User and nil. Cool, while we're here, it's interesting to kinda think about this from when you implement it versus when it's working, okay, wait.

Right now, if we look at our database, the passwords are plaintext passwords. Okay, but if we kinda think forward a little bit, once we use this NewUser function, every time we call that register function, invoke it, I should say, it's gonna be the username and the hash representation of that password.

And that's great, but then it's like, well, when we log in, which is something we're gonna build here, what do you do? Cuz no one is ever gonna have that hashed representation of your password, certainly not your client, right? So how do we go from the plaintext password to the hashed password?

And we could do it later on, I'm gonna do it right now just so we have it in our back pocket. But the function is gonna be just a ValidatePassword function, okay? This ValidatePassword function is gonna take two arguments, it's gonna be a hashedPassword. So this hashedPassword is gonna come from our database, that's the only place it's actually gonna come.

And then it's gonna be also plainTextPassword. plainTextPassword comes from a request from our clients. And this little short form notation, hashedPassword, if you note, they're both can be strings, you can just append string at the end. So instead of doing hashedPassword string, plainPassword string, you can just do both, and then string at the end, okay?

And the only thing that it's gonna return is a Boolean, true or false, only thing we care about. Here, I'm gonna declare an error. I'm gonna say, basically, we need bcrypt.CompareHashAndPassword. Be very careful here about the direction you put your arguments, cuz it does matter. The first argument has to be the hashed representation.

The second argument can be the plaintext password. And similar thing, we're gonna pass in a list of bytes. We're gonna transform our hashedPassword. List of bytes, it's gonna be the plainTextPassword, okay? And what we're gonna do is return error if our error basically equals nil, okay? So this is gonna be a truthy or falsey.

It's gonna basically return, do you have an error, yes, return True. I mean, if there's an error, return False, if there's no error, return True. Which is why we have that Boolean return in the ValidatePassword function. So we're not really doing too much of hashing, we're kind of leaving that up to a much smarter package that we're depending on, but we're still using it in our application code, okay?

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now