How to add authentication to your Go Chat application (Part 4)
Tags: go, tutorial, vue
Published: 2020-11-30 08:22:55

---
title: How to add authentication to  your Go Chat application (Part 4)
published: true
date: 2020-11-30 08:22:47 UTC
tags: Go,Tutorial,Vue
canonical_url: https://www.whichdev.com/how-to-add-authentication-to-your-go-chat-application/?utm_source=rss&utm_medium=rss&utm_campaign=how-to-add-authentication-to-your-go-chat-application
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/nnsnbhlbpcyigp50o47m.png
series: Go Chat Application
---

In the last part of this tutorial, we will allow our users to use the chat application with a user account. To do this we will authenticate the users with the use of JWT (JSON web token). After authenticating, the user will use the chat as an authenticated user.

## **Posts overview**

- [Basic chat application with Go + Vue.JS](https://www.whichdev.com/go-vuejs-chat/)
- [Multi-room & 1 one 1 chats.](https://www.whichdev.com/building-a-multi-room-chat-application-with-websockets-in-go-and-vue-js-part-2/)
- [Using Redis Pub/Sub for scalability](https://www.whichdev.com/using-redis-pub-sub-in-go-chat-application-part-3/)
- _Adding authentication and allow users to log-in (This page)_

## **Preconditions**

To follow along you should have completed [part 1](https://www.whichdev.com/go-vuejs-chat/), [part 2](https://www.whichdev.com/building-a-multi-room-chat-application-with-websockets-in-go-and-vue-js-part-2/) and [part3](https://www.whichdev.com/using-redis-pub-sub-in-go-chat-application-part-3/) or grab the source from [here](https://github.com/jeroendk/go-vuejs-chat/tree/v3.0).

## Step 1: Storing credentials

First, we will create some utility functions for encoding and comparing passwords. For encoding the password we will use Argon2, get the Go library with:

```go
go get golang.org/x/crypto/argon2
```

Then create the utility class with the code below.

```go
// auth/encoder.go

package auth

import (
	"crypto/rand"
	"crypto/subtle"
	"encoding/base64"
	"fmt"
	"strings"

	"golang.org/x/crypto/argon2"
)

type PasswordConfig struct {
	time uint32
	memory uint32
	threads uint8
	keyLen uint32
}

// GeneratePassword is used to generate a new password hash for storing and
// comparing at a later date.
func GeneratePassword(password string) (string, error) {

	c := &PasswordConfig{
		time: 1,
		memory: 64 * 1024,
		threads: 4,
		keyLen: 32,
	}
	// Generate a Salt
	salt := make([]byte, 16)
	if _, err := rand.Read(salt); err != nil {
		return "", err
	}

	hash := argon2.IDKey([]byte(password), salt, c.time, c.memory, c.threads, c.keyLen)

	// Base64 encode the salt and hashed password.
	b64Salt := base64.RawStdEncoding.EncodeToString(salt)
	b64Hash := base64.RawStdEncoding.EncodeToString(hash)

	format := "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s"
	full := fmt.Sprintf(format, argon2.Version, c.memory, c.time, c.threads, b64Salt, b64Hash)
	return full, nil
}

// ComparePassword is used to compare a user-inputted password to a hash to see
// if the password matches or not.
func ComparePassword(password, hash string) (bool, error) {

	parts := strings.Split(hash, "$")

	c := &PasswordConfig{}
	_, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &c.memory, &c.time, &c.threads)
	if err != nil {
		return false, err
	}

	salt, err := base64.RawStdEncoding.DecodeString(parts[4])
	if err != nil {
		return false, err
	}

	decodedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
	if err != nil {
		return false, err
	}
	c.keyLen = uint32(len(decodedHash))

	comparisonHash := argon2.IDKey([]byte(password), salt, c.time, c.memory, c.threads, c.keyLen)

	return (subtle.ConstantTimeCompare(decodedHash, comparisonHash) == 1), nil
}
```

### Database scheme

With the password encoder in place, we can update/create our database schema. Delete the existing User table and create a new one, or rewrite the query below to update the table instead.

```go
// config/database.go

...
sqlStmt = `	
	CREATE TABLE IF NOT EXISTS user (
		id VARCHAR(255) NOT NULL PRIMARY KEY,
		name VARCHAR(255) NOT NULL,
		username VARCHAR(255) NULL,
		password VARCHARR(255) NULL
	);
	`
_, err = db.Exec(sqlStmt)
if err != nil {
  log.Fatal("%q: %s\n", err, sqlStmt)
}

password, _ := auth.GeneratePassword("password")

sqlStmt = `INSERT into user (id, name, username, password) VALUES
					('` + uuid.New().String() + `', 'john', 'john','` + password + `')`

_, err = db.Exec(sqlStmt)
if err != nil {
  log.Fatal("%q: %s\n", err, sqlStmt)
}

return db
```

The code above also includes an insert query to create a user, you can remove it after executing it once.

## Step 2: Authenticating the user

The next step includes an API endpoint to let the user authenticate himself. After authenticating, the user is provided with a JWT. Then this JWT can be used later on to use the Chat application as an authenticated user.

First create a Handler function thats handles the http request and returns a response.

```go
// api.go
package main

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

	"github.com/jeroendk/chatApplication/auth"
	"github.com/jeroendk/chatApplication/repository"
)

type LoginUser struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type API struct {
	UserRepository *repository.UserRepository
}

func (api *API) HandleLogin(w http.ResponseWriter, r *http.Request) {

	var user LoginUser

	// Try to decode the JSON request to a LoginUser
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Find the user in the database by username
	dbUser := api.UserRepository.FindUserByUsername(user.Username)
	if dbUser == nil {
		returnErrorResponse(w)
		return
	}

	// Check if the passwords match
	ok, err := auth.ComparePassword(user.Password, dbUser.Password)

	if !ok || err != nil {
		returnErrorResponse(w)
		return
	}

	// Create a JWT
	token, err := auth.CreateJWTToken(dbUser)

	if err != nil {
		returnErrorResponse(w)
		return
	}

	w.Write([]byte(token))

}

func returnErrorResponse(w http.ResponseWriter) {

	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte("{\"status\": \"error\"}"))
}

```

If the requested user is found and if the provided password is correct, the function above provides a JWT token that the user can use in further requests.

Now lets implement the functions the code above relies on.

```go
// repository/userRepository.go
...
type User struct {
	Id string `json:"id"`
	Name string `json:"name"`
	Username string `json:"username"`
	Password string `json:"password"`
}

...

func (repo *UserRepository) FindUserByUsername(username string) *User {

	row := repo.Db.QueryRow("SELECT id, name, username, password FROM user where username = ? LIMIT 1", username)

	var user User

	if err := row.Scan(&user.Id, &user.Name, &user.Username, &user.Password); err != nil {
		if err == sql.ErrNoRows {
			return nil
		}
		panic(err)
	}

	return &user
}
```

Our repository now includes a method to retrieve a user by his Username.  
The next file lets us create a JWT and decode the created JWT. To do this we need an external library, install it with:

```go
go get github.com/dgrijalva/jwt-go
```

Then add this file:

```go
// auth/jwt.go
package auth

import (
	"fmt"
	"time"

	"github.com/jeroendk/chatApplication/models"

	"github.com/dgrijalva/jwt-go"
)

const hmacSecret = "SecretValueReplaceThis"
const defaulExpireTime = 604800 // 1 week

type Claims struct {
	ID string `json:"id"`
	Name string `json:"name"`
	jwt.StandardClaims
}

func (c *Claims) GetId() string {
	return c.ID
}

func (c *Claims) GetName() string {
	return c.Name
}

// CreateJWTToken generates a JWT signed token for for the given user
func CreateJWTToken(user models.User) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"Id": user.GetId(),
		"Name": user.GetName(),
		"ExpiresAt": time.Now().Unix() + defaulExpireTime,
	})
	tokenString, err := token.SignedString([]byte(hmacSecret))

	return tokenString, err
}

func ValidateToken(tokenString string) (models.User, error) {
	token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}

		// hmacSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
		return []byte(hmacSecret), nil
	})

	if claims, ok := token.Claims.(*Claims); ok && token.Valid {
		return claims, nil
	} else {
		return nil, err
	}
}
```

Now wire up the API endpoint by adding a route for it in the **main.go**

```go
// main.go

import (
	...
	"github.com/jeroendk/chatApplication/auth"
  	...
)

...

func main() {
	...
    // Define the userRepo here, to use it in bothe the wsServer & the API
 	userRepository := &repository.UserRepository{Db: db}

	wsServer := NewWebsocketServer(&repository.RoomRepository{Db: db}, userRepository)
	go wsServer.Run()

	api := &API{UserRepository: userRepository}

	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		ServeWs(wsServer, w, r)
	})
  
	// Add the login route
	http.HandleFunc("/api/login", api.HandleLogin) 
```

With this in place you should be able to retrieve a JWT with a tool Like Postman or by performing the following cURL request:

```shell
curl --location --request GET 'localhost:8080/api/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "john",
    "Password":"password"
}'
```

### Step 3: Securing the WebSocket connection

Now it’s time to identify the user when the WebSocket connection is established. To make this happen we will create a **Middleware** function that we can add to the WebSocket HTTP endpoint.

In this example, we will allow known users, identified with a JWT and anonymous users identified by a name. You can easily change this to only allow known users.

```go
// auth/middleware.go

package auth

import (
	"context"
	"net/http"

	"github.com/google/uuid"
)

type contextKey string

const UserContextKey = contextKey("user")

type AnonUser struct {
	Id string `json:"id"`
	Name string `json:"name"`
}

func (user *AnonUser) GetId() string {
	return user.Id
}

func (user *AnonUser) GetName() string {
	return user.Name
}

func AuthMiddleware(f http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token, tok := r.URL.Query()["bearer"]
		name, nok := r.URL.Query()["name"]

		if tok && len(token) == 1 {            
			user, err := ValidateToken(token[0])
			if err != nil {
				http.Error(w, "Forbidden", http.StatusForbidden)

			} else {
				ctx := context.WithValue(r.Context(), UserContextKey, user)
				f(w, r.WithContext(ctx))
			}

		} else if nok && len(name) == 1 {
			// Continue with new Anon. user
			user := AnonUser{Id: uuid.New().String(), Name: name[0]}
			ctx := context.WithValue(r.Context(), UserContextKey, &user)
			f(w, r.WithContext(ctx))

		} else {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Please login or provide name"))
		}
	})
}
```

Then add the **Middleware** to the WebSocket HTTP endpoint

```go
// main.go
...
http.HandleFunc("/ws", auth.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
  ServeWs(wsServer, w, r)
}))
...
```

From now on, when connecting to the websocket server, the User will be set in de **context** by the middleware function. So let’s make the proper adjustments in the **ServeWs** function in **client.go**

```go
//client.go

func newClient(conn *websocket.Conn, wsServer *WsServer, name string, ID string) *Client {
	client := &Client{
		Name: name,
		conn: conn,
		wsServer: wsServer,
		send: make(chan []byte, 256),
		rooms: make(map[*Room]bool),
	}
	
    // Use existing User ID
	client.ID, _ = uuid.Parse(ID)
	return client
}

func ServeWs(wsServer *WsServer, w http.ResponseWriter, r *http.Request) {
	
    // Remove these lines
	- name, ok := r.URL.Query()["name"]

	- if !ok || len(name[0]) < 1 {
	-	log.Println("Url Param 'name' is missing")
	-	return
	- }

    // Instead get the User from the context
	userCtxValue := r.Context().Value(auth.UserContextKey)
	if userCtxValue == nil {
		log.Println("Not authenticated")
		return
	}

	user := userCtxValue.(models.User)

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	
    // Now user the context user when creating a new Client.
	client := newClient(conn, wsServer, user.GetName(), user.GetId())

	go client.writePump()
	go client.readPump()

	wsServer.register <- client
}
```

Because a user can log-in with multiple clients, the **ChatServer** needs some changes as well. Update the methods listed below:

```go
// chatServer.go
...
func (server *WsServer) registerClient(client *Client) {
	// First check if the user does not exist yet.
	if user := server.findUserByID(client.ID.String()); user == nil {
		// Add user to the repo
		server.userRepository.AddUser(client)
	}

	// Publish user in PubSub
	server.publishClientJoined(client)

	server.listOnlineClients(client)
	server.clients[client] = true
}

...

func (server *WsServer) unregisterClient(client *Client) {
	if _, ok := server.clients[client]; ok {
		delete(server.clients, client)

      	// Remove this line, We don't want to delete user accounts.
		- server.userRepository.RemoveUser(client)
 	
		// Publish user left in PubSub
		server.publishClientLeft(client)
	}
}

...

func (server *WsServer) handleUserLeft(message Message) {
	for i, user := range server.users {
		if user.GetId() == message.Sender.GetId() {
			server.users[i] = server.users[len(server.users)-1]
			server.users = server.users[:len(server.users)-1]
			break // added this break to only remove the first occurrence
		}
	}

	server.broadcastToClients(message.encode())
}

func (server *WsServer) handleUserJoinPrivate(message Message) {
	// Find client for given user, if found add the user to the room.
	// Expect multiple clients for one user now.
	targetClients := server.findClientsByID(message.Message)
	for _, targetClient := range targetClients {
		targetClient.joinRoom(message.Target.GetName(), message.Sender)
	}
}

func (server *WsServer) listOnlineClients(client *Client) {
	// Find unique users instead of returning all users.
	var uniqueUsers = make(map[string]bool)
	for _, user := range server.users {
		if ok := uniqueUsers[user.GetId()]; !ok {
			message := &Message{
				Action: UserJoinedAction,
				Sender: user,
			}
			uniqueUsers[user.GetId()] = true
			client.send <- message.encode()
		}
	}
}

...

func (server *WsServer) findClientsByID(ID string) []*Client {
	// Find all clients for given user ID.
	var foundClients []*Client
	for client := range server.clients {
		if client.GetId() == ID {
			foundClients = append(foundClients, client)
		}
	}

	return foundClients
}
```

### Front-end

The last thing we need to do is to add a log-in form and use the JWT to connect to the WebSocket.

Let’s update the HTML first:

```HTML
...
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
...

<div class="col-12 form" v-if="!ws">
  <h2>Anonymous log-in</h2>
  <div class="input-group">
    <input v-model="user.name" class="form-control name" placeholder="Please fill in your (nick)name"
           @keyup.enter.exact="connect"></input>
    <div class="input-group-append">
      <span class="input-group-text send_btn" @click="connect">
        >
      </span>
    </div>
  </div>
  <h2>Registered users</h2>

  <div class="input-group">
      <input v-model="user.username" class="form-control username" placeholder="username"></input>
      <input v-model="user.password" type="password" class="form-control password" placeholder="password"></input>
      <div class="input-group-append">
          <span class="input-group-text send_btn" @click="login">
          >
          </span>
      </div>
  </div>

  <div class="alert alert-danger" role="alert" v-show="loginError">
    {{loginError}}
  </div>

</div>
```

Then at last update the Javascript file:

```js
// public/assets/app.js
  ...
  data: {
    ws: null,
    serverUrl: "ws://" + location.host + "/ws",
    roomInput: null,
    rooms: [],
    user: {
      name: "",
	  // Add the new user properties
      username: "",
      password: "",
      token: ""
    },
    users: [],
    initialReconnectDelay: 1000,
    currentReconnectDelay: 0,
    maxReconnectDelay: 16000,
    loginError: "" // Login error message string
  },
    
  ...
  // The login function
  // on success save the token and connect to the WebSocket.
  async login() {
    try {
      const result = await axios.post("http://" + location.host + '/api/login', this.user);
      if (result.data.status !== "undefined" && result.data.status == "error") {
        this.loginError = "Login failed";
      } else {
        this.user.token = result.data;
        this.connectToWebsocket();
      }
    } catch (e) {
      this.loginError = "Login failed";
      console.log(e);
    }
  },
  connectToWebsocket() {
    // Use the token if available, else connect with a name. 
    if (this.user.token != "") {
      this.ws = new WebSocket(this.serverUrl + "?bearer=" + this.user.token);
    } else {
      this.ws = new WebSocket(this.serverUrl + "?name=" + this.user.name);
    }
    this.ws.addEventListener('open', (event) => { this.onWebsocketOpen(event) });
    this.ws.addEventListener('message', (event) => { this.handleNewMessage(event) });
    this.ws.addEventListener('close', (event) => { this.onWebsocketClose(event) });
  },
    
  ...
  // Make sure only one client of the user counts
  handleUserJoined(msg) {
    if(!this.userExists(msg.sender)) {
      this.users.push(msg.sender);
    }
  },
  userExists(user) {
    for (let i = 0; i < this.users.length; i++) {
      if (this.users[i].id == user.id) {
        return true;
      }
    }
    return false;
  }
```

## Result

You should now be able to log-in with a user account and use the chat as that user on different clients simultaneously.

This should be a good starting point for your application or at least give you some inspiration for the next time you need to implement a chat. From here on you can add functions such as a registration endpoint, saving sent messages, keeping track of online users, and many other things.

Thanks for following along and feel free to leave a comment when you have suggestions or questions!

![](https://www.whichdev.com/wp-content/uploads/2020/11/Peek-2020-11-29-19-21.gif)

The final source code of this part van be found here:  
[https://github.com/jeroendk/go-vuejs-chat/tree/v4.0](https://github.com/jeroendk/go-vuejs-chat/tree/v3.0)

The post [How to add authentication to your Go Chat application (Part 4)](https://www.whichdev.com/how-to-add-authentication-to-your-go-chat-application/) appeared first on [Which Dev](https://www.whichdev.com).