Learn and Build Web Authentication System (Universal Principles)

dpkahuja

Deepak Ahuja πŸ‘¨β€πŸ’»

Posted on September 2, 2019

Learn and Build Web Authentication System (Universal Principles)

What's an Authentication?

Servers are basically stupid computer programs who cannot remember who and what made a request after serving it once. The communication between clients and servers over HTTP model is stateless in the sense that server cannot confirm the identity of client (user-agent) for each request without some sort of Authentication. Also, they have been over burdened with responsibilities like not showing your personal Twitter DM(s) to someone else, remembering stuff you added to your cart on Amazon, keeping your drafts on DEV articles safe from being copied by other people, preventing you from hacking your Ex's Facebook. All of this is going to need a way to tell server who you are and what should be served to you.

Authentication is more than storing an "user email". Emails and usernames are public facing digital identity you create for yourself on the web. Other people can see and use it too. So we also use passwords and tokens to protect your non-public resources.

Passwords

The most basic and unsafe approach for storing passwords is just to save them as they are. Once saved, you query and match them with password provided by user. This approach is extremely bad because the passwords can be stolen over wire as well as when database is hacked. Most people use same password for multiple services, You are likely to expose all of them for all the users signed up on your website.

"Storing passwords in a plain text is a sin." - J ✝️

One approach would be to encrypt the password and then store it.

encryption
Encryption and Descryption

  1. You chose a key with which you will mix the password to generate a random string using an algorithm.
  2. This password (gibberish text) will be stored in the database.
  3. At the time of authentication:
  • you can decrypt the password from database using the same key to generate a value and match it with password provided by user.
  • Or you can encrypt the password at input with same key and match it with value stored in the database.

There are many encryption algorithms which are available as go libraries to work with. You can find same in other languages of your choice. The drawback with this approach is that if you can decrypt a password to it's original text, so can a hacker. if they are able to guess a key, every other user in your db is compromised too.

Hashing

To compare password for authentication without decrypting them is made possible using a hash function. Hash function converts strings of random length to a fixed length string using some predefined algorithms.

hashing
Hashing

  1. Text generated by hashing functions is not reversible unlike encryption.
  2. The output will be of fixed length for inputs of variable lengths.
  3. Even a small change in input text would generate a totally different hash.
  4. For same input same hashes is generated. We can prevent this using salt and pepper.

Salt and Pepper

We would need to add a some bytes to the password before passing in to a hashing function. As hashes cannot be decrypted, but still a person can generate a rainbow table which is a precomputed table of commonly used passwords and their hashing functions. The hacker can match the hashes to the database hashes and will be able to tell the password. This would be prevented if a unique and random string is added to password which before saving a hash.
saltedhash(password) = hash(password || salt)

  1. The salt would be unique for each password. Hence, all the hashes would be unique.
  2. The salt is not a private entity, it can be saved along with hash as a part of hash or in a different field.
  3. If two users use the same password, when added with salts, their generated hash would be different.

Pepper are also random strings that are added to passwords, they differ from the salt in the fact that they are not unique per user, they are same across all application. They are not stored in database necessarily. We will use them as environment variable in our application demo.

Hands On

  1. Signup for an online free postgres database service and get host, port, username, dbname and password.
  2. Fork and clone the project from github here.
  3. Edit database credentials (or use provided).
  4. Run in the root of the project go run main.go.
  5. The project consists of home, login, signup, profile and accounts page. To navigate to profile and accounts page you would need to have a token (explained shortly).
  6. On every restart of server, the database would reset. You can comment out the code setUpDB for so in main.go at root. Preview

To understand application of hashing we would first need to have fields like password and a password-hash.

User Model

The gorm tag (gorm:"-") ignores the password field because we never store password in the database. We would store explicitly defined password hash.

Sign Up Process

Sign up process
Use bcrypt.GenerateFromPassword(password, cost) to get a hash for the password. The second argument is the cost that is how much work is needed to hash the password. It would change in future when computer gets more powerful. Right now Default Cost is 10.


The code snippet above uses the sign up steps. You can find full working in project repository at path /dev-blog/services/signup.go.

Login Process

Login Process
Use bcrypt.CompareHashAndPassword(password, cost) to compare hashed password to it's plain text equivalent.


The code snippet above uses the sign up steps. You can find full working in project repository at path /dev-blog/services/login.go.

Web server are stateless

The servers handles each request independently. It does not save any data from client requests to do stuff and responds. Each request has everything it needs from server and get a response.
How to make server remember what you did some time ago on the website?
Frankly, we don't. we let clients tell in each request who they are and what resources they need. Login each time while browsing is a tedious task, so after login once, we sign in a cookie (data stored in the computer), so each time you browse a website, the browser sends the cookie with each request to the linked website. We will use this cookie data to verify user. This authentication data stored in cookie is called a Remember Token. We have added this in the user schema too earlier.

Remember token is a series random bytes of a certain length.
We create this using following snippet:

// GenerateRememberToken returns a 32 bytes random token string using
// crypto/rand packages
func GenerateRememberToken() string {
    b := make([]byte, 32) // create a placeholder of 32 bytes (big enough)
    _, err := rand.Read(b) // Fill it with random bytes
    Must(err)
    return base64.URLEncoding.EncodeToString(b) // encoded string
}
Enter fullscreen mode Exit fullscreen mode

Add this token to field in user object (RememberToken) and save to database.

The following snippet helps in setting up cookie for the website.

    cookie := http.Cookie{
        Name:     "remember_token",
        Value:    user.RememberToken,
        HttpOnly: true,
                Expires: time.Now().Add(24 * time.Hour),
    }
    http.SetCookie(w, &cookie)
Enter fullscreen mode Exit fullscreen mode

It is very easy to see cookie in a browser and temper it. To protect our cookie from temporary we can use some options like HttpOnly (disallow javascript to temper cookie) or not store the remember token in plain text at all.

Here's your editable cookie: Cookie in browser

We would rather save a hash of same token and on each request compare it with token provided from cookie.
If we use bcrypt hashing, we would:

  1. Lookup a user from database using email
  2. Hash the user's password with salt which is part of PasswordHash field
  3. Compare But in case of remember token we cannot lookup a user from database since we are not storing RememberToken in db (only it's hash), we need a way to hash value from cookie first, then find the user. The simple hashing function like crypto/hmac would work.
// Hash generates hash for given input with secret key of hmac object
func Hash(token string) string {
       // sha256 is hashing algorithm
       // key can be taken from env variable too
    h := hmac.New(sha256.New, []byte("somekey"))
    h.Reset() // Clear previous leftover bytes
    h.Write([]byte(token))
    b := h.Sum(nil)
    return base64.URLEncoding.EncodeToString(b)
}
Enter fullscreen mode Exit fullscreen mode
utils/utils.go

Here's how we will use all of this:



These signIn method will be called once after login or sign up and a remember token would be stored in the browser as cookie and it's hash version would be saved to the database.

Now when user visits authenticated pages like profile or accounts. We can use the token came into request and compare it with the hashed version stored in the database.


Since we know in hashing for same input string same output is generated. We can hash the remember token came in cookie and compare.

These are a few parts of building an authentication system. The full working project is available here. This also has html templates parsing in go about which i wrote an in depth guide here:

The project structure is simplified for easier understanding with error handling, separate handler file for each route etc. Thanks for making it till last.Yay
Please feel free to comment any doubts about unclear things or say hello on twitter.
My other work:
πŸ’– πŸ’ͺ πŸ™… 🚩
dpkahuja
Deepak Ahuja πŸ‘¨β€πŸ’»

Posted on September 2, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related