2021-05-29 04:45:11 +03:00
package jwt
New Validation API
Some guidelines in designing the new validation API
* Previously, the `Valid` method was placed on the claim, which was always not entirely semantically correct, since the validity is concerning the token, not the claims. Although the validity of the token is based on the processing of the claims (such as `exp`). Therefore, the function `Valid` was removed from the `Claims` interface and the single canonical way to retrieve the validity of the token is to retrieve the `Valid` property of the `Token` struct.
* The previous fact was enhanced by the fact that most claims implementations had additional exported `VerifyXXX` functions, which are now removed
* All validation errors should be comparable with `errors.Is` to determine, why a particular validation has failed
* Developers want to adjust validation options. Popular options include:
* Leeway when processing exp, nbf, iat
* Not verifying `iat`, since this is actually just an informational claim. When purely looking at the standard, this should probably the default
* Verifying `aud` by default, which actually the standard sort of demands. We need to see how strong we want to enforce this
* Developers want to create their own claim types, mostly by embedding one of the existing types such as `RegisteredClaims`.
* Sometimes there is the need to further tweak the validation of a token by checking the value of a custom claim. Previously, this was possibly by overriding `Valid`. However, this was error-prone, e.g., if the original `Valid` was not called. Therefore, we should provide an easy way for *additional* checks, without by-passing the necessary validations
This leads to the following two major changes:
* The `Claims` interface now represents a set of functions that return the mandatory claims represented in a token, rather than just a `Valid` function. This is also more semantically correct.
* All validation tasks are offloaded to a new (optional) `Validator`, which can also be configured with appropriate options. If no custom validator was supplied, a default one is used.
2022-08-27 13:07:09 +03:00
/ *
TODO ( oxisto ) : Re - enable tests with validation API
2021-05-29 04:45:11 +03:00
func TestVerifyAud ( t * testing . T ) {
var nilInterface interface { }
var nilListInterface [ ] interface { }
2021-08-21 02:43:08 +03:00
var intListInterface interface { } = [ ] int { 1 , 2 , 3 }
type test struct {
Name string
MapClaims MapClaims
Expected bool
2021-05-29 04:45:11 +03:00
Comparison string
2021-08-21 02:43:08 +03:00
Required bool
2021-05-29 04:45:11 +03:00
}
tests := [ ] test {
// Matching Claim in aud
// Required = true
2021-08-21 02:43:08 +03:00
{ Name : "String Aud matching required" , MapClaims : MapClaims { "aud" : "example.com" } , Expected : true , Required : true , Comparison : "example.com" } ,
{ Name : "[]String Aud with match required" , MapClaims : MapClaims { "aud" : [ ] string { "example.com" , "example.example.com" } } , Expected : true , Required : true , Comparison : "example.com" } ,
2021-05-29 04:45:11 +03:00
// Required = false
2021-08-21 02:43:08 +03:00
{ Name : "String Aud with match not required" , MapClaims : MapClaims { "aud" : "example.com" } , Expected : true , Required : false , Comparison : "example.com" } ,
{ Name : "Empty String Aud with match not required" , MapClaims : MapClaims { } , Expected : true , Required : false , Comparison : "example.com" } ,
{ Name : "Empty String Aud with match not required" , MapClaims : MapClaims { "aud" : "" } , Expected : true , Required : false , Comparison : "example.com" } ,
{ Name : "Nil String Aud with match not required" , MapClaims : MapClaims { "aud" : nil } , Expected : true , Required : false , Comparison : "example.com" } ,
2021-05-29 04:45:11 +03:00
2021-08-21 02:43:08 +03:00
{ Name : "[]String Aud with match not required" , MapClaims : MapClaims { "aud" : [ ] string { "example.com" , "example.example.com" } } , Expected : true , Required : false , Comparison : "example.com" } ,
{ Name : "Empty []String Aud with match not required" , MapClaims : MapClaims { "aud" : [ ] string { } } , Expected : true , Required : false , Comparison : "example.com" } ,
2021-05-29 04:45:11 +03:00
// Non-Matching Claim in aud
// Required = true
2021-08-21 02:43:08 +03:00
{ Name : "String Aud without match required" , MapClaims : MapClaims { "aud" : "not.example.com" } , Expected : false , Required : true , Comparison : "example.com" } ,
{ Name : "Empty String Aud without match required" , MapClaims : MapClaims { "aud" : "" } , Expected : false , Required : true , Comparison : "example.com" } ,
{ Name : "[]String Aud without match required" , MapClaims : MapClaims { "aud" : [ ] string { "not.example.com" , "example.example.com" } } , Expected : false , Required : true , Comparison : "example.com" } ,
{ Name : "Empty []String Aud without match required" , MapClaims : MapClaims { "aud" : [ ] string { "" } } , Expected : false , Required : true , Comparison : "example.com" } ,
{ Name : "String Aud without match not required" , MapClaims : MapClaims { "aud" : "not.example.com" } , Expected : false , Required : true , Comparison : "example.com" } ,
{ Name : "Empty String Aud without match not required" , MapClaims : MapClaims { "aud" : "" } , Expected : false , Required : true , Comparison : "example.com" } ,
{ Name : "[]String Aud without match not required" , MapClaims : MapClaims { "aud" : [ ] string { "not.example.com" , "example.example.com" } } , Expected : false , Required : true , Comparison : "example.com" } ,
2021-05-29 04:45:11 +03:00
// Required = false
2021-08-21 02:43:08 +03:00
{ Name : "Empty []String Aud without match required" , MapClaims : MapClaims { "aud" : [ ] string { "" } } , Expected : false , Required : true , Comparison : "example.com" } ,
2021-05-29 04:45:11 +03:00
// []interface{}
2021-08-21 02:43:08 +03:00
{ Name : "Empty []interface{} Aud without match required" , MapClaims : MapClaims { "aud" : nilListInterface } , Expected : true , Required : false , Comparison : "example.com" } ,
{ Name : "[]interface{} Aud wit match required" , MapClaims : MapClaims { "aud" : [ ] interface { } { "a" , "foo" , "example.com" } } , Expected : true , Required : true , Comparison : "example.com" } ,
{ Name : "[]interface{} Aud wit match but invalid types" , MapClaims : MapClaims { "aud" : [ ] interface { } { "a" , 5 , "example.com" } } , Expected : false , Required : true , Comparison : "example.com" } ,
{ Name : "[]interface{} Aud int wit match required" , MapClaims : MapClaims { "aud" : intListInterface } , Expected : false , Required : true , Comparison : "example.com" } ,
2021-05-29 04:45:11 +03:00
// interface{}
2021-08-21 02:43:08 +03:00
{ Name : "Empty interface{} Aud without match not required" , MapClaims : MapClaims { "aud" : nilInterface } , Expected : true , Required : false , Comparison : "example.com" } ,
2021-05-29 04:45:11 +03:00
}
for _ , test := range tests {
t . Run ( test . Name , func ( t * testing . T ) {
got := test . MapClaims . VerifyAudience ( test . Comparison , test . Required )
if got != test . Expected {
t . Errorf ( "Expected %v, got %v" , test . Expected , got )
}
} )
}
}
2021-07-30 23:27:54 +03:00
func TestMapclaimsVerifyIssuedAtInvalidTypeString ( t * testing . T ) {
mapClaims := MapClaims {
"iat" : "foo" ,
}
want := false
got := mapClaims . VerifyIssuedAt ( 0 , false )
if want != got {
t . Fatalf ( "Failed to verify claims, wanted: %v got %v" , want , got )
}
}
func TestMapclaimsVerifyNotBeforeInvalidTypeString ( t * testing . T ) {
mapClaims := MapClaims {
"nbf" : "foo" ,
}
want := false
got := mapClaims . VerifyNotBefore ( 0 , false )
if want != got {
t . Fatalf ( "Failed to verify claims, wanted: %v got %v" , want , got )
}
}
func TestMapclaimsVerifyExpiresAtInvalidTypeString ( t * testing . T ) {
mapClaims := MapClaims {
"exp" : "foo" ,
}
want := false
got := mapClaims . VerifyExpiresAt ( 0 , false )
if want != got {
t . Fatalf ( "Failed to verify claims, wanted: %v got %v" , want , got )
}
}
2021-09-11 00:44:55 +03:00
func TestMapClaimsVerifyExpiresAtExpire ( t * testing . T ) {
exp := time . Now ( ) . Unix ( )
mapClaims := MapClaims {
"exp" : float64 ( exp ) ,
}
want := false
got := mapClaims . VerifyExpiresAt ( exp , true )
if want != got {
t . Fatalf ( "Failed to verify claims, wanted: %v got %v" , want , got )
}
2022-05-28 17:03:15 +03:00
got = mapClaims . VerifyExpiresAt ( exp + 1 , true )
2021-09-11 00:44:55 +03:00
if want != got {
t . Fatalf ( "Failed to verify claims, wanted: %v got %v" , want , got )
}
want = true
2022-05-28 17:03:15 +03:00
got = mapClaims . VerifyExpiresAt ( exp - 1 , true )
2021-09-11 00:44:55 +03:00
if want != got {
t . Fatalf ( "Failed to verify claims, wanted: %v got %v" , want , got )
}
}
New Validation API
Some guidelines in designing the new validation API
* Previously, the `Valid` method was placed on the claim, which was always not entirely semantically correct, since the validity is concerning the token, not the claims. Although the validity of the token is based on the processing of the claims (such as `exp`). Therefore, the function `Valid` was removed from the `Claims` interface and the single canonical way to retrieve the validity of the token is to retrieve the `Valid` property of the `Token` struct.
* The previous fact was enhanced by the fact that most claims implementations had additional exported `VerifyXXX` functions, which are now removed
* All validation errors should be comparable with `errors.Is` to determine, why a particular validation has failed
* Developers want to adjust validation options. Popular options include:
* Leeway when processing exp, nbf, iat
* Not verifying `iat`, since this is actually just an informational claim. When purely looking at the standard, this should probably the default
* Verifying `aud` by default, which actually the standard sort of demands. We need to see how strong we want to enforce this
* Developers want to create their own claim types, mostly by embedding one of the existing types such as `RegisteredClaims`.
* Sometimes there is the need to further tweak the validation of a token by checking the value of a custom claim. Previously, this was possibly by overriding `Valid`. However, this was error-prone, e.g., if the original `Valid` was not called. Therefore, we should provide an easy way for *additional* checks, without by-passing the necessary validations
This leads to the following two major changes:
* The `Claims` interface now represents a set of functions that return the mandatory claims represented in a token, rather than just a `Valid` function. This is also more semantically correct.
* All validation tasks are offloaded to a new (optional) `Validator`, which can also be configured with appropriate options. If no custom validator was supplied, a default one is used.
2022-08-27 13:07:09 +03:00
* /