Skip to content

Commit

Permalink
Header typ check (#40)
Browse files Browse the repository at this point in the history
* skip header typ check

* change to checktyp

* fix lint

* add normalize and verify

* improve check
  • Loading branch information
ovhemert authored Sep 8, 2020
1 parent 18c56b0 commit 3df64d6
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 13 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ async function test() {
Create a decoder function by calling `createDecoder` and providing one or more of the following options:

- `complete`: Return an object with the decoded header, payload, signature and input (the token part before the signature), instead of just the content of the payload. Default is `false`.
- `checkTyp`: When validating the decoded header, setting this option forces the check of the `typ` property against this value. Example: `checkTyp: 'JWT'`. Default is `undefined`.

The decoder is a function which accepts a token (as Buffer or string) and returns the payload or the sections of the token.

Expand Down
9 changes: 5 additions & 4 deletions src/decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const TokenError = require('./error')

function decode({ complete }, token) {
function decode({ complete, checkTyp }, token) {
// Make sure the token is a string or a Buffer - Other cases make no sense to even try to validate
if (token instanceof Buffer) {
token = token.toString('utf-8')
Expand All @@ -22,8 +22,8 @@ function decode({ complete }, token) {
let validHeader = false
try {
const header = JSON.parse(Buffer.from(token.slice(0, firstSeparator), 'base64').toString('utf-8'))
if (header.typ !== 'JWT') {
throw new TokenError(TokenError.codes.invalidType, 'The type must be JWT.', { header })
if (checkTyp && header.typ !== checkTyp) {
throw new TokenError(TokenError.codes.invalidType, `The type must be "${checkTyp}".`, { header })
}
validHeader = true

Expand Down Expand Up @@ -54,6 +54,7 @@ function decode({ complete }, token) {

module.exports = function createDecoder(options = {}) {
const complete = options.complete || false
const checkTyp = options.checkTyp

return decode.bind(null, { complete })
return decode.bind(null, { complete, checkTyp })
}
5 changes: 4 additions & 1 deletion src/signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function sign(
expiresIn,
notBefore,
kid,
typ,
isAsync,
additionalHeader,
fixedPayload
Expand All @@ -79,7 +80,7 @@ function sign(
// Prepare the header
const header = {
alg: algorithm,
typ: 'JWT',
typ: typ || 'JWT',
kid,
...additionalHeader
}
Expand Down Expand Up @@ -180,6 +181,7 @@ module.exports = function createSigner(options) {
sub,
nonce,
kid,
typ,
header: additionalHeader
} = { clockTimestamp: 0, ...options }

Expand Down Expand Up @@ -281,6 +283,7 @@ module.exports = function createSigner(options) {
expiresIn,
notBefore,
kid,
typ,
isAsync: keyType === 'function',
additionalHeader,
fixedPayload
Expand Down
20 changes: 18 additions & 2 deletions src/verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ function validateClaimValue(value, modifier, now, greater, errorCode, errorVerb)
function verifyToken(
key,
{ input, header, payload, signature },
{ validators, allowedAlgorithms, clockTimestamp, clockTolerance }
{ validators, allowedAlgorithms, checkTyp, clockTimestamp, clockTolerance }
) {
// Verify the key
/* istanbul ignore next */
Expand All @@ -171,6 +171,14 @@ function verifyToken(

validateAlgorithmAndSignature(input, header, signature, key, allowedAlgorithms)

// Verify typ
if (checkTyp) {
const headerTyp = (header.typ || '').toLowerCase().replace(/^application\//, '')
if (checkTyp !== headerTyp) {
throw new TokenError(TokenError.codes.invalidType, 'Invalid typ.')
}
}

// Verify the payload
const now = (clockTimestamp || Date.now()) + clockTolerance

Expand Down Expand Up @@ -202,6 +210,7 @@ function verify(
allowedAlgorithms,
complete,
cacheTTL,
checkTyp,
clockTimestamp,
clockTolerance,
ignoreExpiration,
Expand Down Expand Up @@ -259,7 +268,7 @@ function verify(

const { header, payload, signature } = decoded
cacheContext.payload = payload
const validationContext = { validators, allowedAlgorithms, clockTimestamp, clockTolerance }
const validationContext = { validators, allowedAlgorithms, checkTyp, clockTimestamp, clockTolerance }

// We have the key
if (!callback) {
Expand Down Expand Up @@ -324,6 +333,7 @@ module.exports = function createVerifier(options) {
complete,
cache: cacheSize,
cacheTTL,
checkTyp,
clockTimestamp,
clockTolerance,
ignoreExpiration,
Expand Down Expand Up @@ -411,11 +421,17 @@ module.exports = function createVerifier(options) {
validators.push({ type: 'string', claim: 'nonce', allowed: ensureStringClaimMatcher(allowedNonce) })
}

let normalizedTyp = null
if (checkTyp) {
normalizedTyp = checkTyp.toLowerCase().replace(/^application\//, '')
}

const context = {
key,
allowedAlgorithms,
complete,
cacheTTL,
checkTyp: normalizedTyp,
clockTimestamp,
clockTolerance,
ignoreExpiration,
Expand Down
3 changes: 2 additions & 1 deletion test/decoder.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { createDecoder } = require('../src')

const defaultDecoder = createDecoder()
// const rawDecoder = createDecoder({ json: false })
const typDecoder = createDecoder({ checkTyp: 'JWT' })
const completeDecoder = createDecoder({ complete: true })

const token =
Expand Down Expand Up @@ -54,7 +55,7 @@ test('invalid header', t => {

t.throws(() => defaultDecoder('Zm9v.b.c'), { message: 'The token header is not a valid base64url serialized JSON.' })

t.throws(() => defaultDecoder(nonJwtToken), { message: 'The type must be JWT.' })
t.throws(() => typDecoder(nonJwtToken), { message: 'The type must be "JWT".' })

t.end()
})
Expand Down
30 changes: 25 additions & 5 deletions test/verifier.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,31 @@ test('it correctly verifies a token - sync', t => {
{ a: 1, iat: 2000000000, exp: 2100000000 }
)

t.throws(() => {
verify('eyJhbGciOiJIUzI1NiJ9.MTIz.UqiZ2LDYZqYB3xJgkHaihGQnJ_WPTz3hERDpA7bWYjA', { noTimestamp: true })
}, {
code: 'FAST_JWT_INVALID_TYPE'
})
t.strictDeepEqual(
verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', {
checkTyp: 'jwt',
noTimestamp: true
}),
{ a: 1 }
)

t.strictDeepEqual(
verify('eyJhbGciOiJIUzI1NiIsInR5cCI6ImFwcGxpY2F0aW9uL2p3dCJ9.eyJhIjoxfQ.1ptuaNj5R0owE-5663LpMknK3eRgZVDHkMkOKkxlteM', {
checkTyp: 'jwt'
}),
{ a: 1 }
)

t.throws(
() =>
verify(
'eyJhbGciOiJIUzI1NiJ9.eyJhIjoxfQ.LrlPmSL4FxrzAHJSYbKzsA997COXdYCeFKlt3zt5DIY',
{ checkTyp: 'test' }
),
{
message: 'Invalid typ.'
}
)

t.strictDeepEqual(
verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ.57TF7smP9XDhIexBqPC-F1toZReYZLWb_YRU5tv0sxM', {
Expand Down

0 comments on commit 3df64d6

Please sign in to comment.