Search code examples
gojwtjwt-go

JWT is always valid even when incorrect params passed


I'm working with the jwt-go library and I've written tests for implementing it in my application. However, no matter what token I create, it is returned as valid. I'm guessing I'm not checking for something. The documentation is out of date because claims no longer support indexing.

This is my application code:

// AuthService - provides authentication
type AuthService struct{}

// CreateToken - signs and encrypts auth token
func (a *AuthService) CreateToken(email, password string) (string, error) {
    token := jwt.New(jwt.SigningMethodHS256)
    token.Claims = buildClaims(email, password)

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

    return tokenString, nil
}

// VerifyToken - ensures that the token is valid
func (a *AuthService) VerifyToken(email, password, token string) bool {
    claims := buildClaims(email, password)
    parser := new(jwt.Parser)

    parsedClaims, _ := parser.ParseWithClaims(token, claims, getKey)

    return parsedClaims.Valid
}

func getKey(t *jwt.Token) (interface{}, error) {
    return []byte(os.Getenv("AUTH_SECRET")), nil
}

func buildClaims(email, password string) jwt.Claims {
    claims := make(jwt.MapClaims)

    claims["email"] = email
    claims["password"] = password
    claims["exp"] = time.Now().Add(time.Hour * 24).Unix()
    claims["iat"] = time.Now().Unix()

    return claims
}

And this is the failing test:

func TestAuthToken(t *testing.T) {
    // var err error
    var valid bool

    os.Setenv("AUTH_SECRET", "secret")

    email := "[email protected]"
    password := "password"

    auth := &AuthService{}

    token, err := auth.CreateToken(email, password)
    if err != nil {
        t.Errorf("AuthService failed to create a token: %v", err)
    }

    valid = auth.VerifyToken(email, password, token)
    if !valid {
        t.Errorf("AuthService failed to verify token: %v", err)
    }

    valid = auth.VerifyToken("invalid", "", token)
    fmt.Println(valid)
    if valid {
        t.Error("AuthService verified a bad password")
    }
}

What I do not understand is why the final test would return valid. In fact, I can put anything in the password or the email and they will be considered as valid.

Is it possible that this has something to do with the fact that password and email are not standard claims?


Solution

  • Yes, it's that because the email and password, are not the part of the standard Claims there is validation method which is used in your example

    func (m MapClaims) Valid() error {
        vErr := new(ValidationError)
        now := TimeFunc().Unix()
    
        if m.VerifyExpiresAt(now, false) == false {
            vErr.Inner = errors.New("Token is expired")
            vErr.Errors |= ValidationErrorExpired
        }
    
        if m.VerifyIssuedAt(now, false) == false {
            vErr.Inner = errors.New("Token used before issued")
            vErr.Errors |= ValidationErrorIssuedAt
        }
    
        if m.VerifyNotBefore(now, false) == false {
            vErr.Inner = errors.New("Token is not valid yet")
            vErr.Errors |= ValidationErrorNotValidYet
        }
    
        if vErr.valid() {
            return nil
        }
    
        return vErr
    }
    

    Do you really need to verifie password and username? Because it's already checked when whole token is passed. But if you anyway wanna check this data you can make some struct

    type CustomClaims struct {
        jwt.StandardClaims
        Email    string `json:"email,omitempty"`
        Password string `json:"password,omitempty"`
    }
    

    which you then can verify in your VerifyToken method token after parsing calims.

    if claims, ok := parsedClaims.Claims.(*CustomClaims); ok {
        if claims.Password != password {
            parsedClaims.Valid = false
        }
        if claims.Email != username {
            parsedClaims.Valid = false
        }
    }
    

    Some other things:

    1. You do not need to build calims in VerifeToken just pass Claims object or pass your own with your custom Valid method which will satisfying interface
    2. For creating token you can use NewWithClaims method