Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,12 @@ func (aud *Audience) UnmarshalJSON(data []byte) (err error) {
case '[': // it's an array of strings.
var audStrings []string
err = json.Unmarshal(data, &audStrings)
*aud = audStrings
if err == nil {
*aud = audStrings
}
case 'n': // it's null, treat as an absent audience.
default: // any other JSON type (number, boolean, object) is not a valid audience.
err = fmt.Errorf("%w: aud: must be a string or an array of strings", ErrTokenForm)
}
}

Expand Down
49 changes: 49 additions & 0 deletions claims_time_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jwt

import (
"encoding/json"
"errors"
"strings"
"testing"
Expand Down Expand Up @@ -126,6 +127,54 @@ func TestRequireExpiry(t *testing.T) {
}
}

func TestAudienceRejectsInvalidTypes(t *testing.T) {
// Per RFC 7519 §4.1.3 the "aud" claim is a string or an array of strings.
// A number, boolean or object used to match no case in Audience.UnmarshalJSON
// and was accepted silently with the audience discarded -- more permissive than
// encoding/json, dropping a security-relevant claim on the floor. See issue #6.
for _, payload := range []string{
`{"aud":1234}`,
`{"aud":true}`,
`{"aud":{"x":1}}`,
} {
var claims Claims
err := json.Unmarshal([]byte(payload), &claims)
if err == nil {
t.Fatalf("%s: expected an error but it parsed with audience %#v", payload, claims.Audience)
}
if !errors.Is(err, ErrTokenForm) {
t.Fatalf("%s: expected ErrTokenForm but got: %v", payload, err)
}
}
}

func TestAudienceValidFormsStillAccepted(t *testing.T) {
for _, tc := range []struct {
payload string
want Audience
}{
{`{"aud":"api"}`, Audience{"api"}},
{`{"aud":["api","web"]}`, Audience{"api", "web"}},
{`{"aud":null}`, nil}, // explicit null -> absent audience
{`{"sub":"user"}`, nil}, // omitted -> absent audience
} {
var claims Claims
if err := json.Unmarshal([]byte(tc.payload), &claims); err != nil {
t.Fatalf("%s: unexpected error: %v", tc.payload, err)
}

got := claims.Audience
if len(got) != len(tc.want) {
t.Fatalf("%s: expected audience %#v but got %#v", tc.payload, tc.want, got)
}
for i := range tc.want {
if got[i] != tc.want[i] {
t.Fatalf("%s: expected audience %#v but got %#v", tc.payload, tc.want, got)
}
}
}
}

func TestAudienceContains(t *testing.T) {
aud := Audience{"api.example.com", "web.example.com"}

Expand Down