User Authentication in Go APIs: JWT, Password Hashing, and MySQL
Tags: go, api, authentication, jwt
Published: 2023-09-10 20:00:34

In this blog post, you'll learn how to build user authentication in Go APIs. We'll create user, store there password securely using `bcrypt` in `MySQL` database and create protected routes by using `JWT` auth tokens using `go-chi`.

### PreRequisites

- Basic of `Go`
- Basic of `MySQL`
- Basic understanding of [JWT Tokens](https://jwt.io/introduction)
- Should have gone through the previous blog posts for basic understanding
  - [Write APIs in Go using go-chi](https://dev.to/the-arcade-01/write-apis-in-go-using-go-chi-d3g)
  - [Storing and retrieving data from MySQL in Go APIs](https://dev.to/the-arcade-01/storing-and-retrieving-data-from-mysql-in-go-apis-3k33)

### Code

Full code of this blog can be found in my Github repo [the-arcade-01/go-auth-api](https://github.com/the-arcade-01/go-auth-api)

So, the first step is to create the database with the `User` table.<br>
For the sake of simplicity, we are only storing 3 fields for now.<br>
Password entered by user will be hashed using `bcrypt` and will be stored in `hash` field.

```sql
CREATE DATABASE IF NOT EXISTS `Practice`;

CREATE TABLE IF NOT EXISTS `Practice`.`User` (
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `email` VARCHAR(255) NOT NULL,
    `hash` VARCHAR(255) NOT NULL
);
```

Now create an environment file, add your MySQl `<user>`, `<password>` and `<database>` into it.
`.env`

```js
PORT=:5000
DATABASE_URL=<user>:<password>@tcp(localhost)/<database>?parseTime=true
DB_DRIVER=mysql
JWT_SECRET_KEY=<secret_key>
```

Now, this been done we will install some external libraries that we will be using in this project

```js
go get -u github.com/go-chi/chi/v5        // library for creating APIs in Go
go get golang.org/x/crypto/bcrypt         // for hashing password
go get github.com/joho/godotenv           // loading .env variables
go get github.com/go-sql-driver/mysql     // DB driver for MySQL
go get github.com/go-chi/jwtauth/v5       // JWT library
```

`main.go`

```go
package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/jwtauth/v5"
	"github.com/joho/godotenv"
	"golang.org/x/crypto/bcrypt"

	_ "github.com/go-sql-driver/mysql"
)

var (
	DATABASE_URL, DB_DRIVER, JWT_SECRET_KEY, PORT string
)

/*
 *  Loads enviroment variables for .env
*/
func init() {
	err := godotenv.Load()
	if err != nil {
		log.Fatalln("Coudn't load env file!!")
	}

	DATABASE_URL = os.Getenv("DATABASE_URL")
	DB_DRIVER = os.Getenv("DB_DRIVER")
	PORT = os.Getenv("PORT")
	JWT_SECRET_KEY = os.Getenv("JWT_SECRET_KEY")
}

/*
 *  DB connection is created in this function
*/
func DBClient() (*sql.DB, error) {
	db, err := sql.Open(DB_DRIVER, DATABASE_URL)
	if err != nil {
		return nil, err
	}

	if err := db.Ping(); err != nil {
		return nil, err
	}
	fmt.Println("Connected to DB!!")
	return db, nil
}

/*
 *  This funtion returns the JWT auth structure
 *  We passed our own JWT_SECRET_KEY to generate it
*/
func GenerateAuthToken() *jwtauth.JWTAuth {
	tokenAuth := jwtauth.New("HS256", []byte(JWT_SECRET_KEY), nil)
	return tokenAuth
}

type Server struct {
	Router    *chi.Mux
	DB        *sql.DB
	AuthToken *jwtauth.JWTAuth
}

func CreateServer(db *sql.DB) *Server {
	server := &Server{
		Router:    chi.NewRouter(),
		DB:        db,
		AuthToken: GenerateAuthToken(),
	}
	return server
}

/*
 *  go-chi's middleware library provides us with different inbuilt functionality
 *  like Clean path, logger, cors etc. read here (https://go-chi.io/#/pages/middleware)
 * Here we're using Logger which will log inbound requests
*/
func (server *Server) MountMiddleware() {
	server.Router.Use(middleware.Logger)
}

/*
 * Base url: http:localhost:5000/user
*/
func (server *Server) MountHandlers() {
	server.Router.Route("/user", func(userRouter chi.Router) {

    /*  These endpoins will be accessible to user without auth
     *  POST /user/login    =   login user
     *  POST /user          =   create user
    */
		userRouter.Post("/login", server.LoginUser)
		userRouter.Post("/", server.CreateUser)

		userRouter.Group(func(r chi.Router) {
      /*  We mount the Verifier and Authenticator to our request
       *  All APIs inside this group will require JWT Token in Auth Headers
      */
			r.Use(jwtauth.Verifier(server.AuthToken))
			r.Use(jwtauth.Authenticator)

      /*  GET /user/{id}    =   get user with specific ID
      */
			r.Get("/{id}", server.GetUser)
		})
	})
}

func main() {
	db, err := DBClient()
	if err != nil {
		log.Fatal(err)
	}

	server := CreateServer(db)
	server.MountMiddleware()
	server.MountHandlers()
	fmt.Printf("server running on port%v\n", PORT)
	http.ListenAndServe(PORT, server.Router)
}
```

Now, we'll create neccessary schemas that we'll use in this project.

```go
type User struct {
	Id    int    `json:"id"`
	Email string `json:"email"`
	Hash  string `json:"hash"`
}

type UserRequestBody struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

type Response struct {
	Id int `json:"id"`
}

func ScanRow(rows *sql.Rows) (*User, error) {
	user := new(User)
	err := rows.Scan(&user.Id, &user.Email, &user.Hash)
	if err != nil {
		return nil, err
	}
	return user, nil
}
```

Now, the actual implementation and the most important parts, the routes for creating user, login user and get users details.

Lets start with create user route first.

```go
/*
 * We pass user's password into this function and
 * call bcrypts function to give us the hashed password
*/
func getHashPassword(password string) (string, error) {
	bytePassword := []byte(password)
	hash, err := bcrypt.GenerateFromPassword(bytePassword, bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(hash), nil
}

/*
 * We parse the user request body, takes the email, password
 * hashes the password and then runs insert query to insert in DB
 * on success returns the Id of the record
*/
func (server *Server) CreateUser(w http.ResponseWriter, r *http.Request) {
	userReqBody := new(UserRequestBody)
	if err := json.NewDecoder(r.Body).Decode(userReqBody); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Please provide the correct input!!"))
		return
	}

	hashPassword, err := getHashPassword(userReqBody.Password)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Something bad happened on the server :("))
		return
	}

	query := `INSERT INTO User (email, hash) VALUES (?, ?)`
	result, err := server.DB.Exec(query, userReqBody.Email, hashPassword)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Something bad happened on the server :("))
		return
	}
	recordId, _ := result.LastInsertId()
	response := Response{
		Id: int(recordId),
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(response)
}
```

Now, we'll write the Login user endpoint

```go
/*
 * We compare the hashed password in DB with the password entered by user
*/
func checkPassword(hashPassword, password string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hashPassword), []byte(password))
	return err == nil
}

/*
 * We parse the request body, fetch user details based on email entered,
 * check the hashed password with user's entered password
 * generate claims and create token and return it in response
*/
func (server *Server) LoginUser(w http.ResponseWriter, r *http.Request) {
	userReqBody := new(UserRequestBody)
	if err := json.NewDecoder(r.Body).Decode(userReqBody); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Please provide the correct input!!"))
		return
	}
	query := `SELECT * FROM User where email = ?`
	rows, err := server.DB.Query(query, userReqBody.Email)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Please provide the correct input!!"))
		return
	}
	var user *User
	for rows.Next() {
		user, err = ScanRow(rows)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("Something bad happened on the server :("))
			return
		}
	}

	if !checkPassword(user.Hash, userReqBody.Password) {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Incorrect password please check again"))
		return
	}

  /* After verify password, we want to generate user specific token
   * for that we mask few of the user details in the jwt token
   * these are called as claims and are used for verifying user w.r.t token
  */
	claims := map[string]interface{}{"id": user.Id, "email": user.Email}
	_, tokenString, err := server.AuthToken.Encode(claims)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Something bad happened on the server :("))
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(tokenString))
}
```

Now, lets write the final endpoint get user by his id

```go
/* This GET /user/{id} will require the JWT token
 * generated from POST /user/login in auth headers
*/
func (server *Server) GetUser(w http.ResponseWriter, r *http.Request) {

  // We get the 'id' from URL parameters of the request
  id := chi.URLParam(r, "id")
	userId, err := strconv.Atoi(id)

  if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Please provide the correct input!!"))
		return
	}

  /* After the Verifier and Authenticator have successful validated this request
   * We destructure the claims from the request and get the userId from claims
   * We then check whether the userId from claims is same as the userId for which
   * the request has been hit (from url params), if not that means user is using
   * different JWT token and hence unauthorized.
  */
	_, claims, _ := jwtauth.FromContext(r.Context())
	userIdFromClaims := int(claims["id"].(float64))

	if userId != userIdFromClaims {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("You're not authorized >("))
		return
	}

	query := `SELECT * FROM User WHERE id = ?`

	rows, err := server.DB.Query(query, userId)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Please provide the correct input!!"))
		return
	}

	var user *User
	for rows.Next() {
		user, err = ScanRow(rows)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("Something bad happened on the server :("))
			return
		}
	}
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(user)
}
```

Now, thats been done, we can finally hit our endpoints and see the response

```js
➜  go-auth-api git:(main) ✗ curl -X POST 'http://localhost:5000/user' -d '{"email": "test@test.com", "password": "test"}'

{"id":1}

➜  go-auth-api git:(main) ✗ curl -X POST 'http://localhost:5000/user/login' -d '{"email": "test@test.com", "password": "test"}'

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpZCI6Mn0.M3MPy4rxJVDn_lxSnIXpfZavWC-gPr3325WnLT26NHw

➜  go-auth-api git:(main) ✗ curl -X GET 'http://localhost:5000/user/2' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpZCI6Mn0.M3MPy4rxJVDn_lxSnIXpfZavWC-gPr3325WnLT26NHw'

{"id":1,"email":"test@test.com","hash":"$2a$10$P7.mUSvVrjx3N6mPEAD19.N9RoGEtGXNuz.XfGzeKmB9fsFqaZUbq"}
```
Logs will look like this as we used `go-chi` middleware Logger

```js
➜  go-auth-api git:(main) ✗ make run
Connected to DB!!
server running on port:5000
2023/09/10 01:05:28 "POST http://localhost:5000/user HTTP/1.1" from 127.0.0.1:43244 - 200 9B in 146.277249ms
2023/09/10 01:06:55 "POST http://localhost:5000/user/login HTTP/1.1" from 127.0.0.1:49428 - 200 124B in 78.764985ms
2023/09/10 01:10:18 "GET http://localhost:5000/user/2 HTTP/1.1" from 127.0.0.1:42164 - 200 103B in 705.163µs
```
_I encourage you to create another user and check whether he is able to get different users details with his own token._

Same can be seen inside your `MySQL` db.

```sql
mysql> select * from User;
+----+---------------+--------------------------------------------------------------+
| id | email         | hash                                                         |
+----+---------------+--------------------------------------------------------------+
|  1 | test@test.com | $2a$10$P7.mUSvVrjx3N6mPEAD19.N9RoGEtGXNuz.XfGzeKmB9fsFqaZUbq |
+----+---------------+--------------------------------------------------------------+
1 row in set (0.00 sec)

```

### Conclusion

That's it, we covered a lot of stuff in this blog, I encourage you to write code own your own and explore different implementations as well.

**Github**: https://github.com/the-arcade-01/go-auth-api

Thanks for reading till the end, really appreciate it!!