This post will explain how to sign up and login users using password based authentication in Go.

banner

Any application that stores passwords has to ensure that its passwords are stored securely. You can’t just store passwords as plain text, and it should ideally be impossible to guess your users passwords, even if you have access to their data.

In this post, we will explain the production-ready method for storing passwords, and build a web application in Go to demonstrate it in practice.

Overview

The Proper Way To Store Passwords

As mentioned before, we cannot store our users passwords as we receive them.

We need to transform each password in such a way that it is easy to verify, but not easy to guess. We achieve this using a one-way hashing function (which would be the bcrypt algorithm in this case).

The details of how this works deserve’s its own post, but in short: it is easy to convert a piece of text into a hash, but almost impossible to guess the piece of text given its hash.

one way hashing

During login, we can check if a password is correct for a given username by retrieving the password hash for that username, and using the bcrypt compare function to verifying the given password with the hash:

verification using bcrypt

Now, let’s see how we can implement signup and login using an HTTP web server.

HTTP Server Implementation

We are going to build an 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.
  • /signin will accept user credentials, and authenticate them by comparing them with the entries in the database.

server implentation diagram

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

// The "db" package level variable will hold the reference to our database instance
var db *sql.DB

func main() {
	// "Signin" and "Signup" are handler that we will implement
	http.HandleFunc("/signin", Signin)
	http.HandleFunc("/signup", Signup)
	// initialize our database connection
	initDB()
	// start the server on port 8000
	log.Fatal(http.ListenAndServe(":8000", nil))
}

func initDB(){
	var err error
	// Connect to the postgres db
	//you might have to change the connection string to add your database credentials
	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:

// Create a struct that models the structure of a user, both in the request body, and in the DB
type Credentials struct {
	Password string `json:"password", db:"password"`
	Username string `json:"username", db:"username"`
}

func Signup(w http.ResponseWriter, r *http.Request){
	// Parse and decode the request body into a new `Credentials` instance
	creds := &Credentials{}
	err := json.NewDecoder(r.Body).Decode(creds)
	if err != nil {
		// If there is something wrong with the request body, return a 400 status
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	// Salt and hash the password using the bcrypt algorithm
	// The second argument is the cost of hashing, which we arbitrarily set as 8 (this value can be more or less, depending on the computing power you wish to utilize)
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(creds.Password), 8)

	// Next, insert the username, along with the hashed password into the database
	if _, err = db.Query("insert into users values ($1, $2)", creds.Username, string(hashedPassword)); err != nil {
		// If there is any issue with inserting into the database, return a 500 error
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	// We reach this point if the credentials we correctly stored in the database, and the default status of 200 is sent back
}

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){
	// Parse and decode the request body into a new `Credentials` instance
	creds := &Credentials{}
	err := json.NewDecoder(r.Body).Decode(creds)
	if err != nil {
		// If there is something wrong with the request body, return a 400 status
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	// Get the existing entry present in the database for the given username
	result := db.QueryRow("select password from users where username=$1", creds.Username)
	if err != nil {
		// If there is an issue with the database, return a 500 error
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	// We create another instance of `Credentials` to store the credentials we get from the database
	storedCreds := &Credentials{}
	// Store the obtained password in `storedCreds`
	err = result.Scan(&storedCreds.Password)
	if err != nil {
		// If an entry with the username does not exist, send an "Unauthorized"(401) status
		if err == sql.ErrNoRows {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
		// If the error is of any other type, send a 500 status
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// Compare the stored hashed password, with the hashed version of the password that was received
	if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(creds.Password)); err != nil {
		// If the two passwords don't match, return a 401 status
		w.WriteHeader(http.StatusUnauthorized)
	}

	// If we reach this point, that means the users password was correct, and that they are authorized
	// The default 200 status is sent
}

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

Persisting User Login

In this example, we logged the user in and showed them a success message. However, in the real-world, most users would want to continue using your website.

In this case, we need to remember that the user has logged in even if they go to another page on our website. Otherwise, the user would have to login over and over again.

We can achieve this using sessions and cookies. A “cookie” is a piece of data that persists on your users browser that enables us to identify them throughout our website.

You can read my other post on session based authentication in Go to learn how to implement it.