DEV Community

loading...
Cover image for Learn and Build Web Authentication System (Universal Principles)

Learn and Build Web Authentication System (Universal Principles)

dpkahuja profile image Deepak Ahuja πŸ‘¨β€πŸ’» ・Updated on ・7 min read

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
}

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)

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)
}
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:

Discussion

pic
Editor guide
Collapse
alostboy profile image
A Lost Boy

I had to build an authentication system just like you described this weekend and there are lot of things that I have done in the same way.

One thing that it is worth noting is that bcrypt is not the most recommended way for salting & hashing passwords anymore, I will not try to get in details because I'm not and security expert, but there are several blogs and questions about this and the consensus today is to use Argon2. I suggest people to make a research to the their own conclusions.

ps: There is a typo on remember in the GenerateRemeberToken function.

Collapse
dpkahuja profile image
Deepak Ahuja πŸ‘¨β€πŸ’» Author

Thanks a ton for reading the article thoroughly! I get it there are many other better and worst alternatives, but for most cases (like person starting out web dev journey) with decent computer the compression algorithm to generate hash would be just fine. There are alternatives like scrypt, PBKDF2 and argon2 which are said to be better but i'd say not every platform i have seen supports scrypt, argon2 needs a GPU to churn out better results. The idea is to learn what hashing is and how it is different from encryption, Then these can be looked into. Thanks for pointing out the typo. I am truly grateful for guiding readers towards more curious driven solutions.

Collapse
raymag profile image
Carlos Magno

Nice post. It helped me to understand a few concepts I was in doubt. Thank you.

Collapse
dpkahuja profile image
Deepak Ahuja πŸ‘¨β€πŸ’» Author

Thanks a ton for reading it through! Please implement it on your own in a side project before using ready made authentication services. It gets real easy to understand and explain. One cool thing you could do is add some sort of caching so that you don't have to query database using rememeberTokenHash for each page visit.

Collapse
raymag profile image
Carlos Magno

On my last project, I encrypted the user ID and stored it on cache. So when the user access a authenticated page, the server takes the cached ID, decrypt it (only the server knows the key) and store it as a session variable, so the user will always be logged in. But I don't know if it was a good idea.

Thread Thread
dpkahuja profile image
Deepak Ahuja πŸ‘¨β€πŸ’» Author

Let me think about it for a while, I will get back to you with some cons of this approach which had chosen earlier. :)

Collapse
patarapolw profile image
Pacharapol Withayasakpunt

Web server can be made stateful by adding session storage. I don't know when it is required, though.

Collapse
rishidadheech profile image
Rishi Dadheech

It is very informative and valuable blog for me in order to understand "Web Authentication System By Universal Principles". But I would suggest one blog should be on "clean code core concept"

Collapse
dpkahuja profile image
Deepak Ahuja πŸ‘¨β€πŸ’» Author

Thanks a lot for reading it through! Yes Clean Code practices is a thing i wanted todo since a while now. I think a pocket guide is a good idea with examples. Cheat sheets comes out handy and people use it more often so it is retained in memory.

Collapse
ayaanraj profile image
ayaanraj

what do you think of JWT tokens

Collapse
dpkahuja profile image
Deepak Ahuja πŸ‘¨β€πŸ’» Author

Once you get a hang of these basic principles you can chose any readymade stuff you want. The idea is to learn why certain service exist and how to create our own. The ready made services are not much alter able :)