Building a Basic Auth System With Go and MySQL
Tags: go, mysql, backend
Published: 2024-07-09 06:18:31

[Go](https://go.dev/) is one of the few languages that have caught my attention in the past. So in our quest to do more, we will build a simple auth system using Go. I am starting to like writing Go because the syntax is quite elegant and helps to reduce the overall amount of code I have to write. 

Go is also very fast and as such it would make so much sense for us to build a backend service with Go, in today’s post we will explore how to build a simple authentication system using Go and as such we will cover the following talking points;

- Project Setup 
- Integration with MySQL and GORM
- Create Account
- Login

## Project Setup

The first thing we need to do is to set up a new project and for that, we’ll need to create a new directory “go_auth_app”. We’ll navigate into and run the go init command inside the directory. 

```bash
mkdir go_auth_app 
```

Now we need to navigate into the `go_auth_app` and run the `go mod init` command 

```bash
cd go_auth_app && go mod init
```

Let’s install the dependencies that we will need for this project starting with [Gorilla Mux](https://github.com/gorilla/mux) Http amongst others. 

```bash
go get -u github.com/gorilla/mux
```

Now let's create a `server.go` file in our project root. This file will contain our server so let's create a basic server.

```go
package main

import (
  "net/http"
  "github.com/gorilla/mux"
)

func main () {
  r := mux.NewRouter()
  http.handle("/", r)
  http.ListenAndServe(":8080", nil)
}
```

## Integration with MySQL and GORM
Now we need to install an ORM that will simplify the process of interacting with our database and for that, we will use [GORM](https://gorm.io/docs/). Let's go ahead and add that dependency to our project.

```bash
go get -u gorm.io/gorm
```

Now we have `GORM` installed we need to install a compatible SQL driver. 

```bash
go get -u gorm.io/driver/mysql
```

Now let's create a model for a user. To do that we need to create a new folder. `models` and inside this folder, we will create a new file `user.go` let's add the following code to the `user.go`.

```go
// go_auth_app/models/user.go

package models

import (
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name string
  Age uint
  Email string
  Phone string
  Password string
}
```

## Create User

Now that we have our model set up, we need to create a controller package to export some functions for interacting with our data. Let's make a folder called `controllers` and inside we'll create a new go file `user.go`.

```go

// go_auth_app/controllers/user.go

package controller

import (
  "net/http"
  "encoding/json"

  "go_auth_app/helper"

  "gorm.io/gorm"
  "golang.org/x/crypto/bcrypt"
)

func ReturnCreateUser(db *gorm.DB) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		user, err := helper.ParseRequestBody(r)

		if len(err) > 1 {
			http.Error(w, err, http.StatusBadRequest)
			return
		}

		toHash := []byte(user.Password)
		hashedPassword, hashErr := bcrypt.GenerateFromPassword(toHash, 14)
		if hashErr != nil {
			{
				http.Error(w, "Failed to hash password", http.StatusInternalServerError)
				return
			}
		}

		createdUser := models.User{Name: user.Name, Age: user.Age, Email: user.Email, Password: string(hashedPassword)}
		result := db.Create(&createdUser)

		if result == nil {
			http.Error(w, "Failed to create user", http.StatusInternalServerError)
			return
		}
		json.NewEncoder(w).Encode(createdUser)
	}
}

```
The code snippet above implements a function called `ReturnCreateUser` in the controller package. Here's a breakdown of what the code does: This function is designed to handle creating a new user in the system. It takes the database connection (db of type *gorm.DB) as input and returns another function that acts as the actual route handler. 

Inside the route handler function, we call `ParseRequestBody` on the helper package. The `ParseRequestBody` function is used to extract user data (name, age, email, password) from the request body (likely sent as JSON data). If there's an error parsing the request body (length of err string is greater than 1), then we send a bad request (400) error response. 

Next, we hash the user's Password by converting the user's password (assumed to be a string) to a byte slice ([]byte). We use the `bcrypt.GenerateFromPassword` function to securely hash the password. [Bcrypt](https://gorm.io/docs/) is a popular hashing algorithm for passwords. The function takes the password bytes and a cost factor (14 in this case) to control the hashing intensity. If there's an error during hashing (hashErr is not nil), it sends an internal server error (500) response with a message and exits. Run the following command to install the bcrypt library.

```bash
go get golang.org/x/crypto/bcrypt
```

Then we create a new `models.User` object by populating the user object with the parsed data (name, age, email) and the hashed password converted back to a string. Then we save the user to the Database using the database connection (db). We call the `Create` method on the database connection, passing the createdUser object as an argument. If the Create operation fails (result is nil), it sends an internal server error (500) response with a message and exits. If successful, we use the json.NewEncoder to encode the newly created user object (including the ID generated by the database) back into JSON format and sends it as the response. Now we need to implement the `helper` package and the `ParseRequestBody` function.

```go

// go_auth_app/helper/helper.go

package helper

import (
	"encoding/json"
	"io"
	"net/http"
)

type Payload struct {
	Name     string `json:"name"`
	Age      int    `json:"age"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

func ParseRequestBody[S string, P Payload](req *http.Request) (P, S) {
	body, err := io.ReadAll(req.Body)

	var payload P
	if err != nil {
		return payload, "Error reading request body"
	}
	err = json.Unmarshal(body, &payload)

	if err != nil {
		return payload, "Invalid JSON format in the request body"
	}

	return payload, ""

}

```
The code snippet above defines a package named helper containing functionalities for parsing request bodies in a Go web application. The package imports the necessary libraries: 
`encoding/json` for working with JSON data.
`io` for reading data from the request body.
`net/http` for accessing request information.

Then we define a struct named Payload. This struct represents the expected format of the data sent in the request body. It has fields for Name, Age (int), Email, and Password. The json tag specifies the corresponding JSON field names for each struct field during marshaling and unmarshalling. This function is designed to parse the request body and extract the user data. It takes an `http.Request` object as input and returns two values: `P` An instance of the Payload struct populated with the parsed user data (name, age, email, password). `S`: A string representing any error message encountered during parsing.

Now let's put all of this together in our `server.go` file

```go

// go_auth_app/server.go

package main

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"

	"go_auth_app/controller"
	"go_auth_app/models"
)

func main() {
	r := mux.NewRouter()

	db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: "root@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=true",
	}))

	if err != nil {
		panic("failed to connect database")
	}

	// Migrate the schema
	db.AutoMigrate(&models.User{})

	r.HandleFunc("/user", controller.ReturnCreateUser(db)).Methods("POST")
	r.HandleFunc("/login", controller.ReturnLoginUser(db)).Methods("POST")

	http.Handle("/", r)
	log.Println("server started on port 8080")
	http.ListenAndServe(":8080", nil) // Start server on port 8080
}

```
## Login

The code snippet in the main package ties together the previously explained controller and helper packages to create a functional web server application. Now let's add a new handler function to enable the user to log in to their account. We'll edit the `controller/user` file to add the handler function to enable the user login.

```go
// go_auth_app/controller/user.go

package controller

// cont'd

func ReturnLoginUser(db *gorm.DB) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		payload, err := helper.ParseRequestBody(r)

		if len(err) > 1 {
			http.Error(w, err, http.StatusBadRequest)
			return
		}

		var user models.User
		db.Where("email =?", payload.Email).First(&user) // Find user by email
		// if the user with that email does not exist throw an error
		if user.Email == "" {
			http.Error(w, "User not found", http.StatusNotFound)
			return
		}

		// compare their passwords
		compareErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(payload.Password))
		if compareErr != nil {
			http.Error(w, "Invalid credentials", http.StatusUnauthorized)
			return
		}
		// if the passwords match, return the user
		json.NewEncoder(w).Encode(user)
	}
}
```

That's going to be all for now, I hope you found this useful and leave your thoughts on the article and building a web server with Go in the comment section below.