Skip to content

Commit

Permalink
feat: support for validating issuer from a list of values (#91)
Browse files Browse the repository at this point in the history
Co-authored-by: Filip Skokan <[email protected]>
  • Loading branch information
sboys3 and panva authored Aug 10, 2020
1 parent db6254e commit ce6836a
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 9 deletions.
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,8 @@ Verifies the claims and signature of a JSON Web Token.
past from now if expiration is not present. **Default:** 'false'
- `ignoreNbf`: `<Boolean>` When true will not be validating the "nbf" claim value to be in the
past from now. **Default:** 'false'
- `issuer`: `<string>` Expected issuer value. An exact match must be found in the payload.
- `issuer`: `<string>` &vert; `string[]` Expected issuer value(s). When string an exact match must
be found in the payload, when array at least one must be matched.
- `jti`: `<string>` Expected jti value. An exact match must be found in the payload.
- `maxAuthAge`: `<string>` When provided the payload is checked to have the "auth_time" claim and
its value is validated, provided as timespan string e.g. `30m`, `24 hours`. See
Expand Down
9 changes: 6 additions & 3 deletions lib/jwt/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,15 @@ const validateOptions = ({

isOptionString(maxTokenAge, 'options.maxTokenAge')
isOptionString(subject, 'options.subject')
isOptionString(issuer, 'options.issuer')
isOptionString(maxAuthAge, 'options.maxAuthAge')
isOptionString(jti, 'options.jti')
isOptionString(clockTolerance, 'options.clockTolerance')
isOptionString(typ, 'options.typ')

if (issuer !== undefined && (isNotString(issuer) && isNotArrayOfStrings(issuer))) {
throw new TypeError('options.issuer must be a string or an array of strings')
}

if (audience !== undefined && (isNotString(audience) && isNotArrayOfStrings(audience))) {
throw new TypeError('options.audience must be a string or an array of strings')
}
Expand Down Expand Up @@ -161,7 +164,7 @@ const validateTypes = ({ header, payload }, profile, options) => {
isPayloadString(payload.jti, '"jti" claim', 'jti', profile === LOGOUTTOKEN || profile === ATJWT || !!options.jti)
isPayloadString(payload.acr, '"acr" claim', 'acr')
isPayloadString(payload.nonce, '"nonce" claim', 'nonce', !!options.nonce)
isPayloadString(payload.iss, '"iss" claim', 'iss', !!options.issuer)
isStringOrArrayOfStrings(payload.iss, 'iss', !!options.issuer)
isPayloadString(payload.sub, '"sub" claim', 'sub', profile === IDTOKEN || profile === ATJWT || !!options.subject)
isStringOrArrayOfStrings(payload.aud, 'aud', !!options.audience)
isPayloadString(payload.azp, '"azp" claim', 'azp', profile === IDTOKEN && Array.isArray(payload.aud) && payload.aud.length > 1)
Expand Down Expand Up @@ -235,7 +238,7 @@ module.exports = (token, key, options = {}) => {
const unix = epoch(now)
validateTypes(decoded, profile, options)

if (issuer && decoded.payload.iss !== issuer) {
if (issuer && (typeof decoded.payload.iss !== 'string' || !(typeof issuer === 'string' ? [issuer] : issuer).includes(decoded.payload.iss))) {
throw new JWTClaimInvalid('unexpected "iss" claim value', 'iss', 'check_failed')
}

Expand Down
16 changes: 13 additions & 3 deletions test/jwt/verify.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ test('options must be an object', t => {
})

test('options.clockTolerance must be a string', string, 'clockTolerance')
test('options.issuer must be a string', string, 'issuer')
test('options.jti must be a string', string, 'jti')
test('options.profile must be a string', string, 'profile')
test('options.maxAuthAge must be a string', string, 'maxAuthAge')
Expand All @@ -55,6 +54,17 @@ test('options.ignoreExp must be boolean', boolean, 'ignoreExp')
test('options.ignoreNbf must be boolean', boolean, 'ignoreNbf')
test('options.ignoreIat must be boolean', boolean, 'ignoreIat')

test('options.issuer must be string or array of strings', t => {
;['', false, [], Buffer, Buffer.from('foo'), 0, Infinity].forEach((val) => {
t.throws(() => {
JWT.verify(token, key, { issuer: val })
}, { instanceOf: TypeError, message: 'options.issuer must be a string or an array of strings' })
t.throws(() => {
JWT.verify(token, key, { issuer: [val] })
}, { instanceOf: TypeError, message: 'options.issuer must be a string or an array of strings' })
})
})

test('options.audience must be string or array of strings', t => {
;['', false, [], Buffer, Buffer.from('foo'), 0, Infinity].forEach((val) => {
t.throws(() => {
Expand Down Expand Up @@ -116,7 +126,7 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
})
})

;['jti', 'acr', 'iss', 'nonce', 'sub', 'azp'].forEach((claim) => {
;['jti', 'acr', 'nonce', 'sub', 'azp'].forEach((claim) => {
test(`"${claim} must be a string when provided"`, t => {
;['', 0, 1, true, null, [], {}].forEach((val) => {
const err = t.throws(() => {
Expand All @@ -130,7 +140,7 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
})
})

;['aud', 'amr'].forEach((claim) => {
;['aud', 'amr', 'iss'].forEach((claim) => {
test(`"${claim} must be a string when provided"`, t => {
;['', 0, 1, true, null, [], {}].forEach((val) => {
let err
Expand Down
4 changes: 2 additions & 2 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ export namespace JWT {
ignoreIat?: boolean;
maxTokenAge?: string;
subject?: string;
issuer?: string;
issuer?: string | string[];
maxAuthAge?: string;
jti?: string;
clockTolerance?: string;
Expand Down Expand Up @@ -479,7 +479,7 @@ export namespace JWT {
function sign(payload: object, key: ProduceKeyInputWithNone, options?: SignOptions): string;

interface VerifyProfileOptions<profile> {
issuer: string;
issuer: string | string[];
audience: string | string[];
profile?: profile;
}
Expand Down

0 comments on commit ce6836a

Please sign in to comment.