gojwtgolang-jwt

golang-jwt signature is invalid


Having some trouble validating jwt tokens using golang-jwt. Pretty sure I'm forming the tokens properly because I'm able to print them and they're being return fine, however when I try to parse them and extract the claims I'm getting an error saying signature is invalid.

I'm using the echo framework for my API, in case that matters.

This is my authentication middleware func


func validateJWT(tokenString string) (*jwt.Token, error) {
    // hard coded for now
    secretKey := "keepmesecret"

    return jwt.Parse(tokenString, 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"])
        }

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

func Authenticator(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        // Get the token from the header
        tokenString := strings.Split(c.Request().Header.Get("Authorization"), "Bearer ")[1]
        token, err := validateJWT(tokenString)
        if err != nil {
            fmt.Println(err)
            return c.JSON(401, map[string]interface{}{
                "error": "Invalid token",
            })
        }
        if !token.Valid {
            return c.JSON(401, map[string]interface{}{
                "error": "Invalid token",
            })
        }

        claims := token.Claims.(jwt.MapClaims)

        // Set the user in the context
        c.Set("user", claims["username"])

        return next(c)
    }
}

This is my token creation

func createToken(secretKey string, username string) (string, error) {
    claim := jwt.MapClaims{
        "username": username,
        "exp":      time.Now().Add(time.Hour * 24).Unix(),
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)

    tokenString, err := token.SignedString([]byte(secretKey))
    if err != nil {
        return "", err
    }

    return tokenString, nil
}

func sendToken(c echo.Context, secretKey string, username string) error {
    token, err := createToken(secretKey, username)

    if err != nil {
        return c.String(500, "Internal Server Error")
    }
    return c.String(200, token)
}

// login
func RouteLogin(c echo.Context) error {
    
    secretKey := "keepmesecret"

    username := c.FormValue("username")
    password := c.FormValue("password")

    // if username is not admin get all guest account info
    if username == "admin" {
        if password != settings.Accounts.Admin.Password {
            return c.String(401, "Unauthorized")
        } else {
            sendToken(c, secretKey, username)
        }
    } else {
        accounts := settings.Accounts.GuestAccounts
        account, ok := accounts[username]
        if !ok {
            return c.String(404, "Not Found")
        }
        if account.Password != password {
            return c.String(401, "Unauthorized")
        }
        sendToken(c, secretKey, username)
    }

    return c.String(200, "login")
}

Adding some log info

Sending token:  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTUwNjExMzUsInVzZXJuYW1lIjoiZ3Vlc3QifQ.hbF88eu6fGs6F0S9Ttvv6eu8_mT_Iv7rBHvGq8Epvrw using secret key:  Indisputably-Salty-Orbit-7260-07erijgpeirgjpejgptrjgpptgjpritgpi4rtnghi
2024-05-06T15:52:15+10:00 | 200 | 2.5633ms | 127.0.0.1 | POST /api/auth/login
Received token:  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTUwNjExMzUsInVzZXJuYW1lIjoiZ3Vlc3QifQ.hbF88eu6fGs6F0S9Ttvv6eu8_mT_Iv7rBHvGq8Epvrwlogin using secret key:  Indisputably-Salty-Orbit-7260-07erijgpeirgjpejgptrjgpptgjpritgpi4rtnghi
signature is invalid
2024-05-06T15:52:24+10:00 | 401 | 2.7255ms | 127.0.0.1 | GET /api/navigate/?pathname=/

Solution

  • As per the comments the issue was here :

        } else {
    ...
            sendToken(c, secretKey, username)
        }
    
        return c.String(200, "login")
    

    sendToken called c.String(200, token) which sends transmits the contents of the string to the client. The implementation of String calls Blob which writes the header (will warn if this has already been done) and then the data. Because String is being called twice (once in sendToken and then again in return c.String) the output will be the token with login appended (i.e. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTUwNjExMzUsInVzZXJuYW1lIjoiZ3Vlc3QifQ.hbF88eu6fGs6F0S9Ttvv6eu8_mT_Iv7rBHvGq8Epvrwlogin).

    So the token sent to the client has the text login appended and this means you will get an error when you attempt to parse it. The simplest way to find issues like this is often just adding logging to check that the value you have is what you expect.