Skip to main content

Insecure authentication method - Basic

Need

Secure authentication method

Context

  • Usage of Go 1.16 for building efficient and scalable applications
  • Usage of gin-gonic/gin for building web applications in Go
  • Usage of net/http for creating HTTP servers in a Node.js application

Description

Non compliant code

package main

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

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

// Basic Auth middleware
r.Use(gin.BasicAuth(gin.Accounts{
"user": "pass",
}))

r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Authenticated")
})

r.Run(":8080")
}

The above code is an example of a Go backend application using the Gin framework that has an insecure authentication method vulnerability.

The server uses Basic authentication over an insecure channel. This means that the user credentials are sent as base64 encoded strings over the network. If the channel is not secure (not using HTTPS for example), then these credentials can be easily intercepted and decoded by an attacker.

In the code, the gin.BasicAuth middleware is used which provides Basic authentication. The credentials are hard-coded as "user" and "pass". These credentials are sent with each request from the client to the server. If the channel is insecure, then these credentials can be intercepted.

The server listens on port 8080 and any request to the root ("/") path is authenticated using the provided credentials. If the credentials match, then a 200 OK response with the string "Authenticated" is sent back to the client. Otherwise, a 401 Unauthorized response is sent.

This type of authentication is not recommended over an insecure channel due to the risk of credential interception and should be replaced with a more secure method such as token-based authentication over a secure channel (HTTPS).

Steps

  • Step 1: Replace Basic authentication with a more secure authentication method, such as JWT (JSON Web Tokens) or OAuth.
  • Step 2: Implement SSL/TLS to encrypt the communication channel between the client and the server.
  • Step 3: Use secure password hashing algorithms, such as bcrypt, to store user passwords securely.
  • Step 4: Implement strong password policies, such as enforcing minimum password length and complexity requirements.
  • Step 5: Implement rate limiting and account lockout mechanisms to prevent brute-force attacks.
  • Step 6: Regularly update and patch the server and its dependencies to address any security vulnerabilities.
  • Step 7: Implement session management to handle user authentication and authorization securely.
  • Step 8: Perform regular security audits and penetration testing to identify and fix any security vulnerabilities.

Compliant code

package main

import (
"github.com/gin-gonic/gin"
"github.com/dgrijalva/jwt-go"
"net/http"
"time"
)

var (
// NOTE: Replace these with your own values
jwtSecret = []byte("your-secret-key")
)

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

r.POST("/login", func(c *gin.Context) {
var credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}

if err := c.ShouldBindJSON(&credentials); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Bad request"})
return
}

// NOTE: Replace this with your own logic
if credentials.Username != "user" || credentials.Password != "pass" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": credentials.Username,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})

tokenString, err := token.SignedString(jwtSecret)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
return
}

c.JSON(http.StatusOK, gin.H{"token": tokenString})
})

r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Authenticated")
})

r.Run(":8080")
}

The above code replaces the Basic authentication with JWT (JSON Web Tokens) authentication. The /login endpoint is added to authenticate users and generate a JWT if the authentication is successful. The JWT includes the username and an expiration time of 24 hours.

The JWT is signed with a secret key (jwtSecret). This key should be kept secret and should be unique to your application. The JWT is then returned to the client in the response.

The client should include this token in the Authorization header of subsequent requests. The server can then verify the token to authenticate the user.

This code does not include SSL/TLS encryption, password hashing, password policies, rate limiting, account lockout mechanisms, session management, or regular security audits and penetration testing. These should be implemented as well to further secure your application.

References