From f6eb27ae393710e6dcc595f42ac4a15dc31a88e4 Mon Sep 17 00:00:00 2001 From: Dimitrij Klesev Date: Tue, 27 Aug 2019 20:33:04 +0200 Subject: [PATCH] Add support for array value of aud --- claims.go | 62 ++++++++++++++++++++++++++++++++++++++++----------- map_claims.go | 8 ++++++- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/claims.go b/claims.go index f0228f02..0ebc2898 100644 --- a/claims.go +++ b/claims.go @@ -2,6 +2,7 @@ package jwt import ( "crypto/subtle" + "encoding/json" "fmt" "time" ) @@ -12,17 +13,34 @@ type Claims interface { Valid() error } +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +type Audience []string + // Structured version of Claims Section, as referenced at // 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"` + Audience Audience `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"` +} + +// To support string and []string +func (aud *Audience) UnmarshalJSON(b []byte) error { + if b[0] != '"' { + var a []string + if err := json.Unmarshal(b, &a); err != nil { + return err + } + *aud = a + } else { + *aud = Audience{string(b)} + } + return nil } // Validates time based claims "exp, iat, nbf". @@ -61,6 +79,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 +112,29 @@ func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { // ----- helpers -func verifyAud(aud string, cmp string, required bool) bool { - if aud == "" { +func contains(aud Audience, cmp string) bool { + for _, a := range aud { + if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 { + return true + } else { + return false + } + } + return false +} + +func verifyAud(aud Audience, 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/map_claims.go b/map_claims.go index 291213c4..a7bfd16c 100644 --- a/map_claims.go +++ b/map_claims.go @@ -13,7 +13,13 @@ 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) + aud, _ := m["aud"].(Audience) + return verifyAud(aud, []string{cmp}, req) +} + +// Compares the aud claim against cmp which is an array of strings +func (m MapClaims) VerifyMultipleAudiences(cmp []string, req bool) bool { + aud, _ := m["aud"].(Audience) return verifyAud(aud, cmp, req) }