diff --git a/claims.go b/claims.go index f0228f02..150a2313 100644 --- a/claims.go +++ b/claims.go @@ -16,13 +16,14 @@ type Claims interface { // https://tools.ietf.org/html/rfc7519#section-4.1 // See examples for how to use this with your own claim types type StandardClaims struct { - Audience string `json:"aud,omitempty"` - ExpiresAt int64 `json:"exp,omitempty"` - Id string `json:"jti,omitempty"` - IssuedAt int64 `json:"iat,omitempty"` - Issuer string `json:"iss,omitempty"` - NotBefore int64 `json:"nbf,omitempty"` - Subject string `json:"sub,omitempty"` + // https://tools.ietf.org/html/rfc7519#section-4.1.3 + Audience interface{} `json:"aud,omitempty"` + ExpiresAt int64 `json:"exp,omitempty"` + Id string `json:"jti,omitempty"` + IssuedAt int64 `json:"iat,omitempty"` + Issuer string `json:"iss,omitempty"` + NotBefore int64 `json:"nbf,omitempty"` + Subject string `json:"sub,omitempty"` } // Validates time based claims "exp, iat, nbf". @@ -61,6 +62,10 @@ func (c StandardClaims) Valid() error { // Compares the aud claim against cmp. // If required is false, this method will return true if the value matches or is unset func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { + return verifyAud(c.Audience, []string{cmp}, req) +} + +func (c *StandardClaims) VerifyMultipleAudiences(cmp []string, req bool) bool { return verifyAud(c.Audience, cmp, req) } @@ -90,15 +95,49 @@ func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { // ----- helpers -func verifyAud(aud string, cmp string, required bool) bool { - if aud == "" { +func contains(aud []string, cmp string) bool { + for _, a := range aud { + if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 { + return true + } + } + return false +} + +func verifyAud(aud interface{}, cmp []string, required bool) bool { + + switch audVal := aud.(type) { + case []interface{}: + var audArray []string + for _, oneVal := range audVal { + if oneStr, ok := oneVal.(string); ok { + audArray = append(audArray, oneStr) + } else { + panic(fmt.Sprintf("Audience is type %T, but must be string or []string", audVal)) + } + } + return verifyAudDeep(audArray, cmp, required) + case []string: + return verifyAudDeep(audVal, cmp, required) + case string: + return verifyAudDeep([]string{audVal}, cmp, required) + default: + panic(fmt.Sprintf("Audience is type %T, but must be string or []string", audVal)) + } +} + +func verifyAudDeep(aud []string, cmp []string, required bool) bool { + if len(aud) < 1 { return !required } - if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 { - return true - } else { - return false + + for _, c := range cmp { + if !contains(aud, c) { + return false + } } + + return true } func verifyExp(exp int64, now int64, required bool) bool { diff --git a/hmac_example_test.go b/hmac_example_test.go index 00278314..0f7eece0 100644 --- a/hmac_example_test.go +++ b/hmac_example_test.go @@ -51,7 +51,7 @@ func ExampleParse_hmac() { 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 hmacSampleSecret, nil }) diff --git a/map_claims.go b/map_claims.go index 291213c4..bbcfabb0 100644 --- a/map_claims.go +++ b/map_claims.go @@ -13,8 +13,7 @@ type MapClaims map[string]interface{} // Compares the aud claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyAudience(cmp string, req bool) bool { - aud, _ := m["aud"].(string) - return verifyAud(aud, cmp, req) + return verifyAud(m["aud"], []string{cmp}, req) } // Compares the exp claim against cmp.