I've always admired Go, it's a simplistic language, and I finally decided to give it a try. After numerous failures to understand how Go actually works, I decided to install the compiler, and write a simple program.
package main
import "fmt"
func main() {
fmt.Println("Heck yes, it worked")
}
Now just a little go build, and I've got my first application written in Go. It didn't seem hard or complex at all, but I struggled to understand the most how the multiple files would compile.
Take a PHP app for example, we can have N number of folders, files, structures, etc. and just do a simple require_once($file_path) from an autoloader and bob's your uncle. This was a simple approach, that I've always enjoyed. With go, we don't do that traditionally, we can go get, and import - but this isn't the same in my understanding as the PHP equivalent. I found if I just ran "go build" in a directory without putting the file name, everything I wanted compiled properly. That was awesome
Let's say you've got 5 go files:
- webserver.go
- api_route_pages.go
- asset_pages.go
- main.go
- helpers.go
You use all these files, and they interact with methods together. If you try to run normally, go build main.go
you're going to hit some errors - because you're strictly trying to compile one file; now if you run go build
it won't error out.... why you may ask? Because you've included all files in your build.
Getting a little SQL with it
Now that I figured out how to get off the ground, I wanted to now bring in some SQL, with authentication logic, and here's how I did it.
Introducing the "Auth" service
I made a new file called "auth_service.go" - this would serve to have all my logical functions inside of it for authentication, including register, login, and validate session token.
We're going to need a few packages to do this:
import "golang.org/x/crypto/pbkdf2"
import (
"crypto/rand"
"crypto/sha512"
"encoding/hex"
"fmt"
"strings"
"crypto/subtle"
"log"
)
Creating Secure Password
We want to have our passwords encrypted, so we're using a salt, and doing pbkdf2, and finally hex encoding to string SALT:PASSWORD, and returning it.
func authMakePassword(password string) string {
salt := make([]byte, 16)
_, err := rand.Read(salt)
checkErr(err)
passwordHash := pbkdf2.Key([]byte(password), salt, 8192, 64, sha512.New)
return hex.EncodeToString(salt) + ":" + hex.EncodeToString(passwordHash)
}
Validating our password
In order to validate our password, we split the strings, run DecodeString for the hex parts, and we'll compare our actual password hash verses the provided password hash. If this is successful, we return true, otherwise we'll return false.
func authCheckPassword(password string, actualPasswordCombined string) bool {
passwordParts := strings.Split(actualPasswordCombined, ":")
salt, _ := hex.DecodeString(passwordParts[0])
actualPasswordHash, _ := hex.DecodeString(passwordParts[1])
providedPasswordHash := pbkdf2.Key([]byte(password), salt, 8192, 64, sha512.New)
return subtle.ConstantTimeCompare(actualPasswordHash, providedPasswordHash) == 1
}
It worked.
Putting this together, we can safely make our own authLogin function, which will simulate this:
func authLogin(username string, password string) (int, error) {
db := MakeDatabase()
if len(password) > 255 {
return 0, fmt.Errorf("password is too long, max length is %s", "255")
}
rows := db.Query("SELECT id, password FROM users WHERE username = ?", username)
if !rows.Next() {
log.Printf("Authentication failure on user=%s: bad username (%s)", username, username)
return 0, fmt.Errorf("invalid_username=%s", username)
}
var userId int
var actualPasswordCombined string
rows.Scan(&userId, &actualPasswordCombined)
rows.Close()
if authCheckPassword(password, actualPasswordCombined) {
log.Printf("Authentication successful for user=%s", username)
return userId, nil
} else {
log.Printf("Authentication failure on user=%s: bad username (%s)", username, username)
return 0, fmt.Errorf("invalid_username=%s", username)
}
}
We first make our database connection, check the length, if it's too long we reject it, if it's not we permit it. We then get the id, password from the users table, validate it, and then we return success or failure.
I'm still working on it, though
It's definitely a work in progress, you can follow my progress here
Top comments (3)
It is more idiomatic in Go to have the package name act as the domain/namespace indicator.
I noticed your functions all begin with "auth". It would be correct to put
auth_service.go
in a package calledauth
, so your functions actually becomeauth.CheckPassword()
,auth.Login()
etc. (Notice the capital letter on the function names. In Go, a function with a capital is public, otherwise it is package-private).I think you can use an more secure password derivation, the Argon2, which was the winner of PHC
The Golang has the Argon2i and Argon2id "natively" under "golang.org/x/crypto/argon2", godoc.org/golang.org/x/crypto/argon2. If you need to use the Argon2d, for some reason, you can modify the API too.
Nice post!