Any application that involves password storage and authentication has to make sure that its passwords are safely stored. You cannot simply store the username and password of your users the way you store other types of data. In fact, it should be impossible for you to actually know the password of any of your users.

This post will go through how to securely store your users password by building a very simple web application in Go, and using a Postgres database to store your users credentials.

Overview

We are going to build a simple HTTP server with two routes: /signup and /signin , and use a Postgres DB to store user credentials.

/signup will accept user credentials, and securely store them in our database.

will accept user credentials, and securely store them in our database. /signin will accept user credentials, and authenticate them by comparing them with the entries in the database.

We will be using the bcrypt algorithm to hash and salt our passwords. If want to know more about hashing, salting, and the theory behind secure password storage, you can read my previous post.

If you just want to see the working source code of this implementation, you can view it here.

Initializing the web application

Before we implement password storage, let’s create our database and initialize our HTTP server:

Creating our database

Make a new database in postgres using the createdb command:

createdb mydb

Connect to the database:

psql mydb

Then, create the users table, with the username and password columns:

create table users ( username text primary key , password text ) ;

Initializing the HTTP server

var db * sql . DB func main ( ) { http . HandleFunc ( "/signin" , Signin ) http . HandleFunc ( "/signup" , Signup ) initDB ( ) log . Fatal ( http . ListenAndServe ( ":8000" , nil ) ) } func initDB ( ) { var err error db , err = sql . Open ( "postgres" , "dbname=mydb sslmode=disable" ) if err != nil { panic ( err ) } }

Implementing user sign up

In order to create a user, or sign them up, we will make a handler that accepts a POST request, with a JSON body of the form:

{ "username" : "johndoe" , "password" : "mysecurepassword" }

The handler will return a 200 status if the user has been signed up successfully:

type Credentials struct { Password string `json:"password", db:"password"` Username string `json:"username", db:"username"` } func Signup ( w http . ResponseWriter , r * http . Request ) { creds := & Credentials { } err := json . NewDecoder ( r . Body ) . Decode ( creds ) if err != nil { w . WriteHeader ( http . StatusBadRequest ) return } hashedPassword , err := bcrypt . GenerateFromPassword ( [ ] byte ( creds . Password ) , 8 ) if _ , err = db . Query ( "insert into users values ($1, $2)" , creds . Username , string ( hashedPassword ) ) ; err != nil { w . WriteHeader ( http . StatusInternalServerError ) return } }

At this point, we can start the server and attempt to send a request to store some credentials:

POST http://localhost:8000/signup { "username": "johndoe", "password": "mysecurepassword" }

If we inspect our database now, we can see that the password field does not contain the password that we sent just now:

mydb=# select * from users; username | password ----------+-------------------------------------------------------------- johndoe | $2a$08$2AH4glNU51oZY0fRMyhc7e/HyCG5.n37mqmuYdJnWiKMBcq1aXNtu (1 row)

Once a password is hashed with bcrypt, there is no way we can reverse the hash. Essentially, we cannot know the password of our own users, even though we have full access to the users table.

Implementing user login

We now have to create a handler that will authenticate a user given his username and password, against the entries in our database.

func Signin ( w http . ResponseWriter , r * http . Request ) { creds := & Credentials { } err := json . NewDecoder ( r . Body ) . Decode ( creds ) if err != nil { w . WriteHeader ( http . StatusBadRequest ) return } result := db . QueryRow ( "select password from users where username=$1" , creds . Username ) if err != nil { w . WriteHeader ( http . StatusInternalServerError ) return } storedCreds := & Credentials { } err = result . Scan ( & storedCreds . Password ) if err != nil { if err == sql . ErrNoRows { w . WriteHeader ( http . StatusUnauthorized ) return } w . WriteHeader ( http . StatusInternalServerError ) return } if err = bcrypt . CompareHashAndPassword ( [ ] byte ( storedCreds . Password ) , [ ] byte ( creds . Password ) ) ; err != nil { w . WriteHeader ( http . StatusUnauthorized ) } }

Now, we can try to log in by making a POST request to the /signin route:

POST http://localhost:8000/signin { "username": "johndoe", "password": "mysecurepassword" }

this will give you a 200 status code. If we make a request with an incorrect password, or with a username that does not exist, we’ll get a 401 status code:

POST http://localhost:8000/signin { "username": "johndoe", "password": "incorrect" }

If you want to run a working version of the server, you can view the source code here