Skip to content

fix(verify): make regexp audience consistent with string audience for tokens without an aud claim#1038

Open
spokodev wants to merge 1 commit into
auth0:masterfrom
spokodev:fix/regexp-audience-missing-aud
Open

fix(verify): make regexp audience consistent with string audience for tokens without an aud claim#1038
spokodev wants to merge 1 commit into
auth0:masterfrom
spokodev:fix/regexp-audience-missing-aud

Conversation

@spokodev

Copy link
Copy Markdown

Description

When verify is given a regexp audience, a token that has no aud claim is treated differently from how a string audience treats the same token. A string rejects it; a regexp can accept it. This PR makes the two branches consistent.

Reproduction

const jwt = require('jsonwebtoken');
const token = jwt.sign({ foo: 'bar' }, 'shhhhh'); // no aud claim

jwt.verify(token, 'shhhhh', { audience: 'anything' }); // throws "jwt audience invalid" (correct)
jwt.verify(token, 'shhhhh', { audience: /.+/ });       // returns the payload (inconsistent)

A token with aud: "" or aud: [] is rejected by both branches, so only the missing-aud case diverges.

Existing test expectations

test/claim-aud.test.js already has a describe('without a "aud" value in payload') block asserting that a no-aud token must fail every audience check, including the regexp ones. Those existing tests pass only because they use patterns like /^urn:no-match$/ that happen not to match the coerced value. A pattern that matches any string exposes the gap, which is what the added test covers.

Root cause

In verify.js:

const target = Array.isArray(payload.aud) ? payload.aud : [payload.aud];

When aud is absent, payload.aud is undefined, so target becomes [undefined]. The regexp branch then runs:

audience.test(targetAudience) // RegExp.prototype.test(undefined)

RegExp.prototype.test coerces its argument to a string first, so this evaluates audience.test("undefined"). A permissive pattern matches the literal string "undefined" and the check passes. The string-equality branch (audience === targetAudience) does no coercion, so it rejects the same token. Per RFC 7519 section 4.1.3, a verifier that requires an audience should reject a token whose aud is absent.

Fix

Test the regexp only against string targets:

-          return audience instanceof RegExp ? audience.test(targetAudience) : audience === targetAudience;
+          return audience instanceof RegExp ? typeof targetAudience === 'string' && audience.test(targetAudience) : audience === targetAudience;

This brings the regexp branch in line with the string-equality branch and with the no-aud expectations already encoded in the test suite.

Testing

Added a test under the existing without a "aud" value in payload block: a regexp that matches any string must still reject a no-aud token. It fails on master (the token is accepted) and passes with the fix.

  • npm test: 512 passing, 1 pending, 0 failing
  • npx eslint verify.js test/claim-aud.test.js: clean

Checklist

  • I have added documentation for new/changed functionality in this PR or in auth0.com/docs
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not master

… tokens without an aud claim

A RegExp `audience` option matches a token that has no `aud` claim, while a
string `audience` correctly rejects it. The mismatch comes from the missing
`aud` being coerced to the string "undefined" before `RegExp.prototype.test`
runs, so a permissive pattern such as `/.+/` reports a match.

Guard the regexp branch to test only string targets, which lines up its
behavior with the string-equality branch and the existing no-aud test
expectations.
@spokodev spokodev requested a review from a team as a code owner June 25, 2026 19:04
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.

1 participant