Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comparison of token parsing errors #143

Closed
fresonn opened this issue Dec 23, 2021 · 7 comments · Fixed by #274
Closed

Comparison of token parsing errors #143

fresonn opened this issue Dec 23, 2021 · 7 comments · Fixed by #274

Comments

@fresonn
Copy link

fresonn commented Dec 23, 2021

My parsing of token:

token, err := jwt.ParseWithClaims(tokenString, &accessToken{}, func(token *jwt.Token) (interface{}, error) {
	return []byte(t.secretKey), nil
})

I took an example of how to distinguish between errors from the documentation

if err != nil {
	if err.(*jwt.ValidationError).Errors&jwt.ValidationErrorExpired != 0 {
		return &accessToken{
			UserId: token.Claims.(*accessToken).UserId,
		}, ErrTokenExpired
	}
	return nil, err
}

And here's what I noticed. This patch can fire even when the error is not a token expiration error.

if err.(*jwt.ValidationError).Errors&jwt.ValidationErrorExpired != 0 { ...

For example, this condition will work even on the "signature is invalid" error.
But only if the token has expired and it has been changed.
In other words:

Token Condition
Token has been changed AND has not expired yet doesn't work
Token changed AND expired It works

But the condition should not work on an error like "signature is invalid"?

P.S
Can I compare errors like this?

if err.(*jwt.ValidationError).Errors&jwt.ValidationErrorExpired == jwt.ValidationErrorExpired { ...
@fresonn
Copy link
Author

fresonn commented Dec 23, 2021

UPD
I supplemented the check:

if err.(*jwt.ValidationError).Errors&jwt.ValidationErrorExpired != 0 && err.(*jwt.ValidationError).Errors&jwt.ValidationErrorSignatureInvalid == 0

It seems to work!
Or tell me what is better to do?

@oxisto
Copy link
Collaborator

oxisto commented Feb 18, 2022

We implemented go 1.13 error style checking a while ago, you can see in the example how it works

jwt/example_test.go

Lines 97 to 117 in 6de17d3

func ExampleParse_errorChecking() {
// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if token.Valid {
fmt.Println("You look nice today")
} else if errors.Is(err, jwt.ErrTokenMalformed) {
fmt.Println("That's not even a token")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
// Output: Timing is everything
}

@mfridman
Copy link
Member

I wonder if we can capture this in a README, or publish lightweight docs capturing the various use-cases and types of errors.

@qeq66
Copy link

qeq66 commented Nov 15, 2022

我也遇到了该问题,目前依然无法实现过期判断

@johakoch
Copy link

We implemented go 1.13 error style checking a while ago, you can see in the example how it works

jwt/example_test.go

Lines 97 to 117 in 6de17d3

func ExampleParse_errorChecking() {
// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if token.Valid {
fmt.Println("You look nice today")
} else if errors.Is(err, jwt.ErrTokenMalformed) {
fmt.Println("That's not even a token")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
// Output: Timing is everything
}

If you remove some characters from the end of the token string, e.g.

	var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YC"

you still get the message "Timing is everything", which on one side is correct, because the token is still expired. But the more critical problem now is IMO the invalid signature.

This probably happens because claim validation is done first, followed by the signature check, which only ORs a ValidationErrorSignatureInvalid to the existing expiration error.

@oxisto
Copy link
Collaborator

oxisto commented Feb 17, 2023

We implemented go 1.13 error style checking a while ago, you can see in the example how it works

jwt/example_test.go

Lines 97 to 117 in 6de17d3

func ExampleParse_errorChecking() {
// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if token.Valid {
fmt.Println("You look nice today")
} else if errors.Is(err, jwt.ErrTokenMalformed) {
fmt.Println("That's not even a token")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
// Output: Timing is everything
}

If you remove some characters from the end of the token string, e.g.

	var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YC"

you still get the message "Timing is everything", which on one side is correct, because the token is still expired. But the more critical problem now is IMO the invalid signature.

Ah, I get your point now. We could / should adjust the example to the following then:

if token.Valid {
		fmt.Println("You look nice today")
	} else if errors.Is(err, jwt.ErrTokenMalformed) {
		fmt.Println("That's not even a token")
	} else if errors.Is(err, jwt.ErrTokenSignatureInvalid) {
		// Invalid signature
		fmt.Println("Invalid signature")
	} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
		// Token is either expired or not active yet
		fmt.Println("Timing is everything")
	} else {
		fmt.Println("Couldn't handle this token:", err)
	}

This is a basic problem of checking multiple wrapped errors. Maybe even the else/if construct is not the best, but it's basically just there to demonstrate the different errors. In a real application I highly suggest aborting whatever you are doing once you encounter any kind of error from Parse and/or if .Valid is false.

This probably happens because claim validation is done first, followed by the signature check, which only ORs a ValidationErrorSignatureInvalid to the existing expiration error.

Sort of; the issue is that we want to keep all the errors and not really have a hierarchy of errors, e.g. one that overrides all of them. One could however argue that if we encounter either a malformed token or an invalid signature we basically skip setting the rest of the errors.

oxisto added a commit that referenced this issue Feb 17, 2023
This PR adjusts the error checking example so that a check for an invalid signature is also included.

See discussion in #143
@oxisto oxisto linked a pull request Feb 20, 2023 that will close this issue
@oxisto
Copy link
Collaborator

oxisto commented Feb 21, 2023

Fixed by #234

@oxisto oxisto closed this as completed Feb 21, 2023
oxisto added a commit that referenced this issue Feb 21, 2023
This PR adjusts the error checking example so that a check for an invalid signature is also included.

See discussion in #143
oxisto added a commit that referenced this issue Mar 24, 2023
This PR adjusts the error checking example so that a check for an invalid signature is also included.

See discussion in #143
oxisto added a commit to twocs/jwt that referenced this issue Mar 29, 2023
This PR adjusts the error checking example so that a check for an invalid signature is also included.

See discussion in golang-jwt#143
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants