Concurrent sessions - Go

Concurrent sessions - Go

Need

Enforcement of session limits and user notification

Context

  • Usage of Go 1.16 for building efficient and scalable applications
  • Usage of gin-gonic/gin for building web applications in Go

Description

Non compliant code

        package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.POST("/login", func(c *gin.Context) {
		username := c.PostForm("username")
		password := c.PostForm("password")

		// Assume that validateUser is a function that checks if the username and password are correct.
		if validateUser(username, password) {
			// If the user is valid, a new session is created without checking if there is already an active session.
			sessionID := createNewSession(username)
			c.JSON(200, gin.H{
				"message":   "Logged in successfully",
				"sessionID": sessionID,
			})
		} else {
			c.JSON(401, gin.H{
				"message": "Invalid username or password",
			})
		}
	})

	r.Run() // listen and serve on 0.0.0.0:8080
}
        
        

The above code is an example of a login route in a Go application using the Gin framework. When a POST request is made to the /login endpoint with a username and password, the validateUser function is called to check if the credentials are correct.

If the credentials are valid, the createNewSession function is called to create a new session for the user, and the session ID is returned in the response. However, the problem here is that there is no check to see if the user already has an active session. This means that a user can have multiple active sessions at the same time.

Furthermore, the application does not notify the user when a session has already been initiated nor when a second one commences from a different location. This could potentially lead to unauthorized access if a user's credentials are compromised, as the user would not be aware that someone else has logged in with their credentials.

Steps

  • Implement session management to keep track of active sessions for each user.
  • When a user logs in, check if there is already an active session for that user.
  • If there is an active session, notify the user and prevent them from initiating a new session.
  • If there is no active session, create a new session for the user.
  • When a user logs out or the session expires, remove the session from the active sessions list.

Compliant code

        package main

import (
	"sync"
	"github.com/gin-gonic/gin"
)

var sessionStore = make(map[string]string)
var mutex = &sync.Mutex{}

func main() {
	r := gin.Default()

	r.POST("/login", func(c *gin.Context) {
		username := c.PostForm("username")
		password := c.PostForm("password")

		// Assume that validateUser is a function that checks if the username and password are correct.
		if validateUser(username, password) {
			mutex.Lock()
			if _, ok := sessionStore[username]; ok {
				// If there is an active session, notify the user and prevent them from initiating a new session.
				c.JSON(403, gin.H{
					"message": "There is already an active session for this user",
				})
			} else {
				// If there is no active session, create a new session for the user.
				sessionID := createNewSession(username)
				sessionStore[username] = sessionID
				c.JSON(200, gin.H{
					"message":   "Logged in successfully",
					"sessionID": sessionID,
				})
			}
			mutex.Unlock()
		} else {
			c.JSON(401, gin.H{
				"message": "Invalid username or password",
			})
		}
	})

	r.Run() // listen and serve on 0.0.0.0:8080
}
        
        

The fixed code now includes a session management system that keeps track of active sessions for each user. This is done using a map called sessionStore where the key is the username and the value is the session ID. A mutex is also used to ensure that the sessionStore map is accessed in a thread-safe manner.

When a user tries to log in, the system first checks if the username and password are correct. If they are, it then checks if there is already an active session for that user by looking up the username in the sessionStore map. If there is an active session, the system responds with a 403 status code and a message indicating that there is already an active session for this user. If there is no active session, the system creates a new session, adds it to the sessionStore map, and responds with a 200 status code and a message indicating that the login was successful.

This way, the system prevents users from having multiple active sessions at the same time, thus fixing the concurrent sessions vulnerability.

References