Getting started with Golang web application (and authentication) Part 1

Getting started with Golang web application (and authentication) Part 1

How to implement an authentication system on a web application using Go

The purpose of this article is to create an authentication system using Golang. For weeks, I searched for tutorials that performed this kind of task but the results were the same: implementing Golang backend as JSON or Rest API. Therefore, I built mine and wrote a blog about it. This is part 1 of the article. I hope it is insightful and maybe you can learn a thing or two from it. The frontend will be in HTML and styled with Bootstrap, using a MySQL database and Go as the backend:

  • Setting up the project
  • Setting up the frontend
  • Setting up the backend

The finished project can be found here: Github

Setting up the Project

We start by creating a new folder called golangwebauth and cd into it. 1.png Create a templates folder, an errorpages folder inside the templates directory, initialize our go mod and create a main.go file. 2.png

Setting up the frontend - HTML and Bootstrap

We set up our frontend by creating some HTML pages in the templates directory. First, our index.html file serves as a landing page, dashboard.html serves as the logged-in page after the user logs in, login.html as the login page and register.html serving as the register page. Inside the errorpages directory: 401.html page for error 401 - http.StatusUnauthorized and 400.html for the error 400 - http.StatusBadRequest. 3.png

Insert the code for the files: index.html:

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
    integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">

  <title>Web App Authentication in Go</title>
</head>

<body>
  <div class="form-group">
    <div class="form-group col-md-2">
    </div>
    <div class="form-group col-md-5">
      <h1>Simple authentication in Go</h1>
    </div>
    <div class="form-group col-md-3">
    </div>
    <div class="form-group col-md-2">
    </div>
  </div>

  <div class="form-row form-group">
    <div class="form-group col-md-2">
    </div>
    <div class="form-group col-md-4">
      <a class="btn btn-primary" href="/register">Register</a>
      <a class="btn btn-outline-secondary" href="/login">Login</a>
    </div>
    <div class="form-group col-md-4">
    </div>
    <div class="form-group col-md-2">
    </div>
  </div>

  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
    integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous">
  </script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous">
  </script>
</body>

</html>


login.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Login - Go Auth</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
    integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
</head>

<body>
  <form method="POST">
    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <h1>Login - Go Auth</h1>
      </div>
      <div class="form-group col-md-4">

      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <label for="email">Email</label>
        <input type="email" class="form-control" id="email" name="email" placeholder="Email">
      </div>
      <div class="form-group col-md-4">

      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <label for="password">Password</label>
        <input type="password" class="form-control" name="password" id="password" placeholder="Password">
      </div>
      <div class="form-group col-md-4">
      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <button type="submit" class="btn btn-primary">Sign In</button>
        <a href="/register" type="submit" class="btn btn-outline-primary">Sign Up</a>
        <a href="/" type="submit" class="btn btn-outline-secondary">Home</a>
      </div>
      <div class="form-group col-md-4">
      </div>
      <div class="form-group col-md-2">
      </div>
    </div>
  </form>

</body>

</html>


register.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Register - Go Auth</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
    integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
</head>

<body>
  <form id="registerForm" method="POST">
    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <h1>Register - Go Auth</h1>
      </div>
      <div class="form-group col-md-4">

      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <label>First Name</label>
        <input type="text" class="form-control" id="FirstName" name="FirstName" placeholder="First Name" required>
      </div>
      <div class="form-group col-md-4">

      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <label>Last Name</label>
        <input type="text" class="form-control" id="LastName" name="LastName" placeholder="Last Name" required>
      </div>
      <div class="form-group col-md-4">
      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <label>Email</label>
        <input type="email" class="form-control" id="email" name="email" placeholder="Email" required>
      </div>
      <div class="form-group col-md-4">

      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <label for="password">Password</label>
        <input type="password" class="form-control" id="password" name="password" placeholder="Password" required>
      </div>
      <div class="form-group col-md-4">
      </div>
      <div class="form-group col-md-2">
      </div>
    </div>

    <div class="form-row form-group">
      <div class="form-group col-md-2">
      </div>
      <div class="form-group col-md-4">
        <button type="submit" class="btn btn-primary">Sign Up</button>

      </div>
      <div class="form-group col-md-4">
      </div>
      <div class="form-group col-md-2">
      </div>
    </div>
  </form>

  <a href="/login" type="submit" class="btn btn-outline-primary">Sign in</a>
  <a href="/" type="submit" class="btn btn-outline-secondary">Home</a>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.0/jquery.validate.js"></script>

<script>
  $('#registerForm').validate({ // initialize the plugin
    rules: {
      "FirstName": {
        required: true,
        minlength: 3
      },
      "LastName": {
        required: true,
        minlength: 3
      },
      "email": {
        required: true,
        email: true
      },
      "password": {
        required: true,
        minlength: 8

      }
    },
    messages: {
      "FirstName": {
        required: "Please, enter your first name",
        minlength: "Minimum length should be 3 digits",
      },
      "LastName": {
        required: "Please, enter your last name",
        minlength: "Minimum length should be 3 digits",
      },
      "email": {
        required: "Please, enter an email",
        email: "Email is invalid"
      },
      "password": {
        required: "Please,enter a password",
        minlength: "Minimum length should be 8"
      }
    },
    submitHandler: function (form) {
      form.submit();
    }
  });
</script>

</html>


dashboard.html:


<!DOCTYPE html>
<html lang="en">

<head>
  <title>Dashboard</title>

  <meta charset="UTF-8" />
  <link href="https://fonts.googleapis.com/css?family=Nunito+Sans:400,400i,700,900&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
    integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
  <link rel="stylesheet" href="../asset/css/style.css" crossorigin="anonymous">
  <link rel="stylesheet" href="../asset/css/responsive.css" crossorigin="anonymous">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">


</head>

<body class="success-body">
  <div class="card">
    <div class="flex invite-user">
      <p>Welcome {{.FirstName}} {{.LastName}} !</p>
      <a href="/logouth">Log out</a>
    </div>

    <h1>Successfully logged in </h1>
  </div>
</body>

</html>


400.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bad Request</title>

    <!-- Google font -->
    <link href="https://fonts.googleapis.com/css?family=Josefin+Sans:400,700" rel="stylesheet">

    <!-- Custom stlylesheet -->
    <link type="text/css" rel="stylesheet" href="/static/css/style.css" />

</head>

<body>

    <div id="notfound">
        <div class="notfound">
            <div class="notfound-404">
                <h1>4<span>0</span>0</h1>
            </div>
            <p>Bad request.</p>
            <a href="/">home page</a>
        </div>
    </div>

</body><!-- This templates was made by Colorlib (https://colorlib.com) -->

</html>


401.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Unauthorized Access</title>

    <!-- Google font -->
    <link href="https://fonts.googleapis.com/css?family=Josefin+Sans:400,700" rel="stylesheet">

    <!-- Custom stlylesheet -->
    <link type="text/css" rel="stylesheet" href="/static/css/style.css" />


</head>

<body>

    <div id="notfound">
        <div class="notfound">
            <div class="notfound-404">
                <h1>4<span>0</span>1</h1>
            </div>
            <p>Unauthorized Access.</p>
            <a href="/">home page</a>
        </div>
    </div>

</body><!-- This templates was made by Colorlib (https://colorlib.com) -->

</html>


We create a static directory in our base directory, create a css folder and add a style.css file. Inside the style.css, put in: 4.png

style.css:

* {
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
}

body {
  padding: 0;
  margin: 0;
}

#notfound {
  position: relative;
  height: 100vh;
  background-color: #222;
}

#notfound .notfound {
  position: absolute;
  left: 50%;
  top: 50%;
  -webkit-transform: translate(-50%, -50%);
      -ms-transform: translate(-50%, -50%);
          transform: translate(-50%, -50%);
}

.notfound {
  max-width: 460px;
  width: 100%;
  text-align: center;
  line-height: 1.4;
}

.notfound .notfound-404 {
  height: 158px;
  line-height: 153px;
}

.notfound .notfound-404 h1 {
  font-family: 'Josefin Sans', sans-serif;
  color: #222;
  font-size: 220px;
  letter-spacing: 10px;
  margin: 0px;
  font-weight: 700;
  text-shadow: 2px 2px 0px #c9c9c9, -2px -2px 0px #c9c9c9;
}

.notfound .notfound-404 h1>span {
  text-shadow: 2px 2px 0px #ffab00, -2px -2px 0px #ffab00, 0px 0px 8px #ff8700;
}

.notfound p {
  font-family: 'Josefin Sans', sans-serif;
  color: #c9c9c9;
  font-size: 16px;
  font-weight: 400;
  margin-top: 0px;
  margin-bottom: 15px;
}

.notfound a {
  font-family: 'Josefin Sans', sans-serif;
  font-size: 14px;
  text-decoration: none;
  text-transform: uppercase;
  background: transparent;
  color: #c9c9c9;
  border: 2px solid #c9c9c9;
  display: inline-block;
  padding: 10px 25px;
  font-weight: 700;
  -webkit-transition: 0.2s all;
  transition: 0.2s all;
}

.notfound a:hover {
  color: #ffab00;
  border-color: #ffab00;
}

@media only screen and (max-width: 480px) {
  .notfound .notfound-404 {
    height: 122px;
    line-height: 122px;
  }

  .notfound .notfound-404 h1 {
      font-size: 122px;
  }
}

Now, the frontend is complete. Some pages or specific features might not work (e.g. dashboard.html with the welcome {{.FirstName}}) until we connect the backend, which is our next task.


Setting up the backend

The backend is where the bulk of the work is. We would be connecting the routes to the pages, authenticating by registering our users, storing their details in the MySQL database and logging our users in. In our main.go file, we apply the following code:

package main

import (
    "html/template"
    "log"
    "net/http"

    "github.com/gorilla/context"
)

var tpl = template.Must(template.ParseGlob("templates/*.html"))

func indexHandler(w http.ResponseWriter, r *http.Request) {
    tpl.ExecuteTemplate(w, "index.html", nil)
}

func registerHandler(w http.ResponseWriter, r *http.Request) {
    tpl.ExecuteTemplate(w, "register.html", nil)
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
    tpl.ExecuteTemplate(w, "login.html", nil)
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, "/login", http.StatusPermanentRedirect)
}

func dashboardHandler(w http.ResponseWriter, r *http.Request) {
    tpl.ExecuteTemplate(w, "dashboard.html", nil)
}

func main() {
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/login", loginHandler)
    http.HandleFunc("/logouth", logoutHandler)
    http.HandleFunc("/register", registerHandler)
    http.HandleFunc("/dashboard", dashboardHandler)

    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

    log.Println("Server started on: http://localhost:8000")
    err := http.ListenAndServe(":8000", context.ClearHandler(http.DefaultServeMux)) // context to prevent memory leak
    if err != nil {
        log.Fatal(err)
    }
}

Our main function handles the routing, starts our server and use a context package to prevent memory leaks when running our server. The http.Handle("/static/"...) helps us serve our CSS to the frontend: 5.png The tpl variable holds our templates, and the functions execute the templates: 6.png

Before running our application, we run: go get github.com/gorilla/context to install the context package. Then run our server by running go run main.go which starts on port 8000: 7.png Our home page (localhost:8000) shows this: 8.png

We create a .env file in our root directory which houses some environmental variables: 10.png

Setting up the database
: I am using MySQL and the terminal for this, but you can use other databases such as Postgres or MongoDB and you can also use MySQL workbench if you do not want to use the terminal.

Log into the MySQL workspace and run the commands:

-- Creating our database
CREATE DATABASE golangwebauth;

USE DATABASE golangwebauth;

CREATE TABLE user(
  id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  firstname VARCHAR(255) NOT NULL,
  lastname VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL,
  createdDate TIMESTAMP
);

-- Checking the content of the user table
DESCRIBE user;

-- Getting all the records in the user table
SELECT * FROM user;
  • We create a function to connect our code to the MySQL database:
    func dbConn() (db *sql.DB) {
      err := godotenv.Load(".env")
      if err != nil {
          log.Fatal("Error loading .env file")
      }
      dbDriver := os.Getenv("DB_DRIVER")
      dbUser := os.Getenv("DB_USER")
      dbPass := os.Getenv("DB_PASSWORD")
      dbName := os.Getenv("DB_NAME")
      fmt.Println(dbDriver, dbUser, dbPass, dbName)
      db, err = sql.Open(dbDriver, dbUser+":"+dbPass+"@tcp(127.0.0.1:3306)/"+dbName+"?parseTime=true")
      if err != nil {
          panic(err.Error())
      }
      fmt.Println("DB Connected!!")
      return db
    }
    
    11.png We use this package: github.com/joho/godotenv to get the environmental variables from our .env file and do not forget to run go get github.com/joho/godotenv to download the package and remember to add it as an import.

Next, we work on the registration
: Our register.html looks like this: 9.png

We create a struct type to hold the fields in our database:

type User struct {
    ID          int
    FirstName   string    `json:"firstname" validate:"required, gte=3"`
    LastName    string    `json:"lastname" validate:"required, gte=3"`
    Email       string    `json:"email"`
    Password    string    `json:"password"`
    CreatedDate time.Time `json:"createdDate"`
}

12.png

We update our registerHandler function to add the database function and users into our database: 13.png

func registerHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        db := dbConn()
        firstName := r.FormValue("FirstName")
        lastName := r.FormValue("LastName")
        email := r.FormValue("email")
        fmt.Printf("%s, %s, %s\n", firstName, lastName, email)

        password, err := bcrypt.GenerateFromPassword([]byte(r.FormValue("password")), bcrypt.DefaultCost)
        if err != nil {
            fmt.Println(err)
            tpl.ExecuteTemplate(w, "Register", err)
        }

        dt := time.Now()

        createdDateString := dt.Format("2006-01-02 15:04:05")

        // Convert the time before inserting into the database
        createdDate, err := time.Parse("2006-01-02 15:04:05", createdDateString)
        if err != nil {
            log.Fatal("Error converting the time:", err)
        }

        _, err = db.Exec("INSERT INTO user(firstname, lastname,email,password,createdDate) VALUES(?,?,?,?,?)", firstName, lastName, email, password, createdDate)
        if err != nil {
            fmt.Println("Error when inserting: ", err.Error())
            panic(err.Error())
        }
        log.Println("=> Inserted: First Name: " + firstName + " | Last Name: " + lastName)

        http.Redirect(w, r, "/login", http.StatusMovedPermanently)
    }    else if r.Method == "GET"    {
        tpl.ExecuteTemplate(w, "register.html", nil)
    }
}

We add this package: golang.org/x/crypto/bcrypt to our imports for hashing the user's password and we run go get golang.org/x/crypto/bcrypt to install the package. Also, import the MySQL driver: github.com/go-sql-driver/mysql to aid in our MySQL connection.

What our import looks like: 14.png Stop the server and start again, then fill the registration form and viola, our user's details have been inserted into the database: 15.png

16.png

17.png

Next is the loginHandler function so the user can log in: We update the loginHandler function to check if the user is making a POST request, then we get the values of the email and password, validating the password and checking if it corresponds to the hashed password in the database, then we execute the dashboardHandler function if all things are correct. 18.png

func loginHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        db := dbConn()
        email := r.FormValue("email")
        password := r.FormValue("password")
        fmt.Printf("%s, %s\n", email, password)

        if strings.Trim(email, " ") == "" || strings.Trim(password, " ") == "" {
            fmt.Println("Parameter's can't be empty")
            http.Redirect(w, r, "/login", http.StatusMovedPermanently)
            return
        }

        checkUser, err := db.Query("SELECT id, createdDate, password, firstname, lastname, email FROM user WHERE email=?", email)
        if err != nil {
            panic(err.Error())
        }
        user := &User{}
        for checkUser.Next() {
            var id int
            var password, firstName, lastName, email string
            var createdDate time.Time
            err = checkUser.Scan(&id, &createdDate, &password, &firstName, &lastName, &email)
            if err != nil {
                panic(err.Error())
            }
            user.ID = id
            user.FirstName = firstName
            user.LastName = lastName
            user.Email = email
            user.Password = password
            user.CreatedDate = createdDate
        }

        errf := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
        if errf != nil && errf == bcrypt.ErrMismatchedHashAndPassword { //Password does not match!
            fmt.Println(errf)
            http.Redirect(w, r, "/login", http.StatusMovedPermanently)
        } else {
            tpl.ExecuteTemplate(w, "dashboard.html", user)
            return
        }
    } else if r.Method == "GET" {
        tpl.ExecuteTemplate(w, "login.html", nil)
    }
}

Restart our server : 19.png Our dashboard after user has logged in: 20.png

And we are done with part 1. Having completed this project, Part 2 will include the use of JWT(JSON Web Token) authentication which is used when a user logs in, they have access to the dashboard but if they aren't logged in, they do not have access (our error pages come into play here). Till next time, stay safe and happy coding.