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=/
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.