Skip to content
This repository has been archived by the owner on May 21, 2022. It is now read-only.

Commit

Permalink
“aud” claim can be string or []string
Browse files Browse the repository at this point in the history
  • Loading branch information
dgrijalva committed Apr 8, 2019
1 parent 32acca3 commit ec0a89a
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 13 deletions.
37 changes: 37 additions & 0 deletions claim_strings.go
@@ -0,0 +1,37 @@
package jwt

import (
"encoding/json"
"reflect"
)

// ClaimStrings is used for parsing claim properties that
// can be either a string or array of strings
type ClaimStrings []string

// UnmarshalJSON implements the json package's Unmarshaler interface
func (c *ClaimStrings) UnmarshalJSON(data []byte) error {
var value interface{}
err := json.Unmarshal(data, &value)
if err != nil {
return err
}
switch v := value.(type) {
case string:
*c = ClaimStrings{v}
case []interface{}:
result := make(ClaimStrings, 0, len(v))
for i, vv := range v {
if x, ok := vv.(string); ok {
result = append(result, x)
} else {
return &json.UnsupportedTypeError{Type: reflect.TypeOf(v[i])}
}
}
*c = result
case nil:
default:
return &json.UnsupportedTypeError{Type: reflect.TypeOf(v)}
}
return nil
}
60 changes: 60 additions & 0 deletions claim_strings_test.go
@@ -0,0 +1,60 @@
package jwt_test

import (
"encoding/json"
"reflect"
"testing"

"github.com/dgrijalva/jwt-go/v4"
)

var claimStringsTestData = []struct {
name string
input interface{}
output jwt.ClaimStrings
err error
}{
{
name: "null",
input: nil,
output: nil,
},
{
name: "single",
input: "foo",
output: jwt.ClaimStrings{"foo"},
},
{
name: "multi",
input: []string{"foo", "bar"},
output: jwt.ClaimStrings{"foo", "bar"},
},
{
name: "invalid",
input: float64(42),
output: nil,
err: &json.UnsupportedTypeError{Type: reflect.TypeOf(float64(42))},
},
{
name: "invalid multi",
input: []interface{}{"foo", float64(42)},
output: nil,
err: &json.UnsupportedTypeError{Type: reflect.TypeOf(float64(42))},
},
}

func TestClaimStrings(t *testing.T) {
for _, test := range claimStringsTestData {
var r *struct {
Value jwt.ClaimStrings `json:"value"`
}
data, _ := json.Marshal(map[string]interface{}{"value": test.input})
err := json.Unmarshal(data, &r)
if !reflect.DeepEqual(err, test.err) {
t.Errorf("[%v] Error didn't match expectation: %v != %v", test.name, test.err, err)
}
if !reflect.DeepEqual(test.output, r.Value) {
t.Errorf("[%v] Unmarshaled value didn't match expectation: %v != %v", test.name, test.output, r.Value)
}
}
}
24 changes: 13 additions & 11 deletions claims.go
Expand Up @@ -19,13 +19,13 @@ 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"`
Audience ClaimStrings `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"`
}

// Valid implements Valid from Claims
Expand Down Expand Up @@ -94,12 +94,14 @@ func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {

// ----- helpers

func verifyAud(aud string, cmp string, required bool) bool {
if aud == "" {
func verifyAud(aud ClaimStrings, cmp string, required bool) bool {
if len(aud) == 0 {
return !required
}
if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 {
return true
for _, audStr := range aud {
if subtle.ConstantTimeCompare([]byte(audStr), []byte(cmp)) != 0 {
return true
}
}
return false
}
Expand Down
15 changes: 13 additions & 2 deletions map_claims.go
Expand Up @@ -13,8 +13,19 @@ 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)
aud, ok := m["aud"]
if !ok {
return !req
}

switch v := aud.(type) {
case string:
return verifyAud(ClaimStrings{v}, cmp, req)
case []string:
return verifyAud(ClaimStrings(v), cmp, req)
default:
return false
}
}

// Compares the exp claim against cmp.
Expand Down

0 comments on commit ec0a89a

Please sign in to comment.