Skip to main content

Lack of data validation - Token

Need

Implementation of proper token validation and verification

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 jwt-go for JSON Web Token (JWT) authentication and authorization

Description

Non compliant code

package main

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

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

r.GET("/token", func(c *gin.Context) {
token := jwt.New(jwt.SigningMethodHS256)

claims := token.Claims.(jwt.MapClaims)
claims["name"] = "John Doe"
claims["admin"] = true
claims["exp"] = time.Now().Add(time.Hour * 24).Unix()

t, _ := token.SignedString([]byte("secret"))

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

r.Run(":8080")
}

In the above code, we are generating a JWT token with a fixed payload containing user information and an expiration time. The token is then signed with a secret key using the HMAC SHA256 algorithm.

The problem here is that the generated token is not validated. Anyone can modify this token, change the payload, and the server will accept it. Even if someone removes the signature from the token, the server will still accept it.

This is a major security flaw, as it allows an attacker to impersonate any user, gain unauthorized access to data, and perform actions on behalf of the user. It's crucial that the server validates the token and checks the signature to ensure it was not tampered with.

Steps

  • Implement token validation to ensure the signature is valid.
  • Use a secure secret key for signing the token.
  • Verify the token's expiration time to prevent accepting expired tokens.
  • Ensure that the token payload is not tampered with by validating the token's integrity.
  • Consider using a JWT library that provides built-in validation and verification methods.

Compliant code

package main

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

var mySigningKey = []byte("secret")

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

r.GET("/token", func(c *gin.Context) {
token := jwt.New(jwt.SigningMethodHS256)

claims := token.Claims.(jwt.MapClaims)
claims["name"] = "John Doe"
claims["admin"] = true
claims["exp"] = time.Now().Add(time.Hour * 24).Unix()

t, _ := token.SignedString(mySigningKey)

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

r.POST("/validate", func(c *gin.Context) {
tokenString := c.PostForm("token")

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if jwt.GetSigningMethod("HS256") != token.Method {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return mySigningKey, nil
})

if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
c.JSON(http.StatusOK, gin.H{"valid": true, "claims": claims})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"valid": false, "message": err.Error()})
}
})

r.Run(":8080")
}

The updated code now includes a new endpoint /validate that validates the JWT token. The token is parsed and validated using the jwt.Parse function. This function takes the token string and a function that returns the key for validating the token.

The key function checks if the token's signing method matches the expected HS256 method. If it doesn't, an error is returned. If it does, the secret key used for signing the token is returned. This key is used to validate the token's signature.

If the token's claims are valid and the token is valid, a JSON response is returned with the validity status and the token's claims. If the token is not valid, a JSON response is returned with the validity status and the error message.

This way, the JWT token's signature is validated, ensuring that the token has not been tampered with. The token's expiration time is also checked during validation, preventing expired tokens from being accepted.

References