Back
Obsidian

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

May 31, 2021·11 min read·2526 views
Getting started with Golang web application (and authentication) Part 1

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

Setting up the frontend - HTML and Bootstrap

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 - Go Auth

Login - Go Auth

  </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>
```
`register.html`: ``` Register - Go Auth

Register - Go Auth

  </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>

Sign inHome

```
`dashboard.html`: ``` Dashboard

Welcome {{.FirstName}} {{.LastName}} !

Log out
<h1>Successfully logged in </h1>
```
`400.html`: ``` Bad Request
<!-- 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" />
<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>
```
`401.html`: ``` Unauthorized Access
<!-- 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" />
<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>
```
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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FCckeBidw1-6ffabb0271.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 ) 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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FtSoYcCm3r-b36eaf857a.png)
The tpl variable holds our templates, and the functions execute the templates:
![6.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FRvzyB0kt4-56449a358c.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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2Fnz_Y7V4Pw-d2a93482a2.png)
Our home page (localhost:8000) shows this:
![8.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2F_Zz1PoUnF-b69ad58194.png)

We create a `.env` file in our root directory which  houses some environmental variables:
![10.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2Fu7LHvwGPC-9616bad9e0.png)

<h5>Setting up the database<h5>:
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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FO3hOWAjTS-09a6141176.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.

<h5>Next, we work on the registration<h5>:
Our register.html looks like this:
![9.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FPFWdTfSqV-167a17a381.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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2F_y1UKd44N-1d27ebf962.png)

We update our registerHandler function to add the database function and users into our database:
![13.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FyZGSveQZz-189868ed07.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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FVMRuLM13H-de3fb591a4.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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FW5_fxA_Lf-050f63e20e.png)

![16.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FepnqmOHOv-4808737eb4.png)

![17.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FIjAfMP_63-53e9431b7d.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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FdAdeFqEEp-0fe4d188aa.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](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FOcAvW2xte-7ede847ce8.png)
Our dashboard after user has logged in:
![20.png](/api/blog-image?path=Blogs%20-%20Published%2Fimages%2Fhashnode%2Fgetting-started-with-golang-web-application-and-authentication-part-1%2FzjqBT1t_3-a233f64b0c.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.