Skip to content

Reject non-string/array aud instead of silently discarding it (#6) - #22

Open
jaideeppyne wants to merge 1 commit into
kataras:mainfrom
jaideeppyne:fix/audience-reject-invalid-types
Open

Reject non-string/array aud instead of silently discarding it (#6)#22
jaideeppyne wants to merge 1 commit into
kataras:mainfrom
jaideeppyne:fix/audience-reject-invalid-types

Conversation

@jaideeppyne

Copy link
Copy Markdown

What

Fixes #6. Audience.UnmarshalJSON accepts an "aud" claim of an unexpected JSON type (number, boolean, object) without error and silently discards it.

var c jwt.Claims
_ = json.Unmarshal([]byte(`{"aud":1234}`), &c)
// before: err == nil, c.Audience == nil   ← malformed audience accepted & dropped

Why

The unmarshaler only handles two cases:

switch data[0] {
case '"':  // single string
    ...
case '[':  // array of strings
    var audStrings []string
    err = json.Unmarshal(data, &audStrings)
    *aud = audStrings
}

There is no default and no null handling, so a number/boolean/object matches no case, leaves err == nil, and returns with aud == nil. That is more permissive than encoding/json (which rejects those values when decoding into []string), and it drops a security-relevant claim: aud is how multi-tenant systems scope tokens, so a token whose audience is a number or object parses as if it had no audience at all. The '[' branch also assigns audStrings even when the unmarshal failed.

How

 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)
 }
  • Unexpected types now return ErrTokenForm (the package's existing sentinel, used the same way for nbf/iat/exp/iss/sub).
  • JSON null is treated as an absent audience (no error), matching an omitted claim.
  • The array branch no longer assigns on error.

Tests

Added to claims_time_test.go:

  • TestAudienceRejectsInvalidTypes{"aud":1234}, {"aud":true}, {"aud":{"x":1}} must fail with ErrTokenForm (this fails on main and passes with the fix);
  • TestAudienceValidFormsStillAccepted — string, array, explicit null, and omitted aud still parse to the expected audience.

The full suite stays green (go test ./...).


Disclosure: this change was prepared with AI assistance and reviewed/verified by me before submission.

…ataras#6)

Audience.UnmarshalJSON only handled the '"' (string) and '[' (array) cases.
Any other JSON type for "aud" -- a number, boolean, or object -- matched no
case, left err == nil, and returned with the audience silently set to nil. That
is more permissive than encoding/json (which rejects those when decoding into
[]string) and drops a security-relevant claim on the floor: a token whose
"aud" is a number or object parses as if it had no audience at all.

Reject unexpected JSON types with an ErrTokenForm error, treat JSON null as an
absent audience, and stop assigning the array result when its unmarshal failed.

Adds regression tests for the rejected types and for the still-accepted string,
array, null and omitted forms.
@jaideeppyne
jaideeppyne requested a review from kataras as a code owner August 19, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audience parsing doesn't reject unexpected items

1 participant