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