Skip to content

Commit

Permalink
perf: base64url decode, JWT.verify, JWK.Key instance re-use
Browse files Browse the repository at this point in the history
I'm done trying to educate other JOSE producers about interoperability
so i'm going to be accepting their non-conform base64url so that users
of this module don't suffer performance loss.
  • Loading branch information
panva committed Jan 29, 2020
1 parent 9551717 commit 470b4c7
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 95 deletions.
17 changes: 17 additions & 0 deletions lib/jwa/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,22 @@ require('./pbes2')(JWA, JWK)
require('./ecdh/dir')(JWA, JWK)
require('./ecdh/kw')(JWA, JWK)

const map = new WeakMap()

const i = (ctx) => {
if (!map.has(ctx)) {
map.set(ctx, {})
}
return map.get(ctx)
}

const check = (key, op, alg) => {
const cache = i(key)

if (cache[`${op}${alg}`]) {
return true
}

let label
let keyOp
if (op === 'keyManagementEncrypt') {
Expand All @@ -43,6 +58,8 @@ const check = (key, op, alg) => {
} else {
throw new JOSENotSupported(`unsupported ${label || op} alg: ${alg}`)
}

cache[`${op}${alg}`] = true
}

module.exports = {
Expand Down
2 changes: 0 additions & 2 deletions lib/jwe/decrypt.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ const jweDecrypt = (skipValidateHeaders, serialization, jwe, key, { crit = [], c

if (!serialization) {
serialization = resolveSerialization(jwe)
} else if (serialization !== resolveSerialization(jwe)) {
throw new errors.JWEInvalid()
}

let alg, ciphertext, enc, encryptedKey, iv, opts, prot, tag, unprotected, cek, aad, header
Expand Down
2 changes: 1 addition & 1 deletion lib/jws/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const Sign = require('./sign')
const verify = require('./verify')
const { verify } = require('./verify')

const single = (serialization, payload, key, protectedHeader, unprotectedHeader) => {
const jws = new Sign(payload)
Expand Down
41 changes: 26 additions & 15 deletions lib/jws/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { check, verify } = require('../jwa')
const { detect: resolveSerialization } = require('./serializers')

validateCrit = validateCrit.bind(undefined, errors.JWSInvalid)
const SINGLE_RECIPIENT = new Set(['compact', 'flattened'])
const SINGLE_RECIPIENT = new Set(['compact', 'flattened', 'preparsed'])

/*
* @public
Expand All @@ -29,8 +29,6 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp

if (!serialization) {
serialization = resolveSerialization(jws)
} else if (serialization !== resolveSerialization(jws)) {
throw new errors.JWSInvalid()
}

let prot // protected header
Expand All @@ -47,26 +45,35 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
jws = { ...root, ...signatures[0] }
}

let decoded

if (SINGLE_RECIPIENT.has(serialization)) {
if (serialization === 'compact') { // compact serialization format
([prot, payload, signature] = jws.split('.'))
} else { // flattened serialization format
({ protected: prot, payload, signature, header } = jws)
let parsedProt = {}

switch (serialization) {
case 'compact': // compact serialization format
([prot, payload, signature] = jws.split('.'))
break
case 'flattened': // flattened serialization format
({ protected: prot, payload, signature, header } = jws)
break
case 'preparsed': { // from the JWT module
({ decoded } = jws);
([prot, payload, signature] = jws.token.split('.'))
break
}
}

if (!header) {
skipDisjointCheck = true
}

let parsedProt = {}
if (prot) {
if (decoded) {
parsedProt = decoded.header
} else if (prot) {
try {
parsedProt = base64url.JSON.decode(prot)
} catch (err) {
if (err instanceof errors.JOSEError) {
throw err
}

throw new errors.JWSInvalid('could not parse JWS protected header')
}
} else {
Expand Down Expand Up @@ -125,13 +132,14 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
Buffer.from('.'),
Buffer.isBuffer(payload) ? payload : Buffer.from(payload)
])

if (!verify(alg, key, toBeVerified, base64url.decodeToBuffer(signature))) {
throw new errors.JWSVerificationFailed()
}

if (!combinedHeader.crit || !combinedHeader.crit.includes('b64') || combinedHeader.b64) {
if (parse) {
payload = base64url.JSON.decode.try(payload, encoding)
payload = decoded ? decoded.payload : base64url.JSON.decode.try(payload, encoding)
} else {
payload = base64url.decodeToBuffer(payload)
}
Expand Down Expand Up @@ -168,4 +176,7 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
throw multi
}

module.exports = jwsVerify.bind(undefined, false, undefined)
module.exports = {
bare: jwsVerify,
verify: jwsVerify.bind(undefined, false, undefined)
}
24 changes: 12 additions & 12 deletions lib/jwt/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const isObject = require('../help/is_object')
const epoch = require('../help/epoch')
const secs = require('../help/secs')
const getKey = require('../help/get_key')
const JWS = require('../jws')
const { bare: verify } = require('../jws/verify')
const { KeyStore } = require('../jwks')
const { JWTClaimInvalid, JWTExpired } = require('../errors')

Expand Down Expand Up @@ -225,9 +225,17 @@ module.exports = (token, key, options = {}) => {
jti, maxAuthAge, maxTokenAge, nonce, now, profile, subject
} = options = validateOptions(options)

const unix = epoch(now)

const decoded = decode(token, { complete: true })
key = getKey(key, true)

if (complete) {
({ key } = verify(true, 'preparsed', { decoded, token }, key, { crit, algorithms, complete: true }))
decoded.key = key
} else {
verify(true, 'preparsed', { decoded, token }, key, { crit, algorithms })
}

const unix = epoch(now)
validateTypes(decoded, profile, options)

if (issuer && decoded.payload.iss !== issuer) {
Expand Down Expand Up @@ -292,13 +300,5 @@ module.exports = (token, key, options = {}) => {
throw new JWTClaimInvalid('invalid JWT typ header value for the used validation profile', 'typ', 'check_failed')
}

key = getKey(key, true)

if (complete && key instanceof KeyStore) {
({ key } = JWS.verify(token, key, { crit, algorithms, complete: true }))
} else {
JWS.verify(token, key, { crit, algorithms })
}

return complete ? { ...decoded, key } : decoded.payload
return complete ? decoded : decoded.payload
}
6 changes: 0 additions & 6 deletions test/help/base64url.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,3 @@ test('.JSON.decode.try (valid json)', t => {
test('.JSON.decode.try (invalid json)', t => {
t.is(base64url.JSON.decode.try('Zm9v'), 'foo')
})

test('decode input with invalid encoding throws', t => {
t.throws(() => {
base64url.decode(testStr)
}, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
})
3 changes: 2 additions & 1 deletion test/jwa/sanity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const test = require('ava')

const { errors } = require('../..')
const JWA = require('../../lib/jwa')
const JWK = require('../../lib/jwk')

;['sign', 'verify', 'keyManagementEncrypt', 'keyManagementDecrypt', 'encrypt', 'decrypt'].forEach((op) => {
let label
Expand All @@ -10,7 +11,7 @@ const JWA = require('../../lib/jwa')
}
test(`JWA.${op} will not accept an "unimplemented" algorithm`, t => {
t.throws(() => {
JWA[op]('foo')
JWA[op]('foo', JWK.generateSync('oct'))
}, { instanceOf: errors.JOSENotSupported, code: 'ERR_JOSE_NOT_SUPPORTED', message: `unsupported ${label || op} alg: foo` })
})
})
20 changes: 0 additions & 20 deletions test/jwk/import.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,26 +145,6 @@ test('fails to import JWK RSA with oth', async t => {
}, { instanceOf: errors.JOSENotSupported, code: 'ERR_JOSE_NOT_SUPPORTED', message: 'Private RSA keys with more than two primes are not supported' })
})

test('invalid encoded jwk import', async t => {
const jwk = (await generate('oct')).toJWK(true)

jwk.k = base64url.decodeToBuffer(jwk.k).toString('base64')

t.throws(() => {
asKey(jwk)
}, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
})

test('invalid encoded oct jwk import', async t => {
const jwk = (await generate('EC')).toJWK(true)

jwk.d = base64url.decodeToBuffer(jwk.d).toString('base64')

t.throws(() => {
asKey(jwk)
}, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
})

const cert = `-----BEGIN CERTIFICATE-----
MIIC4DCCAcgCCQDO8JBSH914NDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJD
WjEPMA0GA1UEBwwGUHJhZ3VlMRIwEAYDVQQDDAlwa210bHN0d28wHhcNMTkwNjE4
Expand Down
9 changes: 0 additions & 9 deletions test/jws/sanity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,6 @@ test('JWS verify algorithms whitelist (multi-recipient)', t => {
})
})

test('invalid tokens', t => {
t.throws(() => {
JWS.verify(
'eyJ0eXAiOiJKV1QiLCJraWQiOiIyZTFkYjRmMC1mYmY5LTQxZjYtOGMxYi1hMzczYjgwZmNhYTEiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHktc3RhZ2luZy5kZWxpdmVyb28uY29tLyIsImNsaWVudCI6ImIyM2I0ZjM1YzIyMTI5NDQxZjMwZDMyYmI5ZmM4ZWYyIiwic2lnbmVyIjoiYXJuOmF3czplbGFzdGljbG9hZGJhbGFuY2luZzpldS13ZXN0LTE6NTE3OTAyNjYzOTE1OmxvYWRiYWxhbmNlci9hcHAvcGF5bWVudHMtZGFzaGJvYXJkLXdlYi80YzA4ZGI2NDMyMDIyOWEyIiwiZXhwIjoxNTYyNjkxNTg1fQ==.eyJlbWFpbCI6ImpvYW8udmllaXJhQGRlbGl2ZXJvby5jby51ayIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmYW1pbHlfbmFtZSI6Ikd1ZXJyYSBWaWVpcmEiLCJnaXZlbl9uYW1lIjoiSm9hbyIsIm5hbWUiOiJKb2FvIEd1ZXJyYSBWaWVpcmEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1sTUpXTXV3R1dpYy9BQUFBQUFBQUFBSS9BQUFBQUFBQUFCVS9lNGtkTDg5UjlqZy9zOTYtYy9waG90by5qcGciLCJzdWIiOiIxMWE1YmFmMGRjNzcwNWRmMzk1ZTMzYWFkZjU2MDk4OCIsImV4cCI6MTU2MjY5MTU4NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS1zdGFnaW5nLmRlbGl2ZXJvby5jb20vIn0=.DSHLJXLOfLJ-ZYcX0Vlii6Ak_jcDSkKOvNRj_rvtAyY9uYXtwo798ZrR35fgut-LuCdx0aKz2SgK0KJqw5q6dA==',
generateSync('EC')
)
}, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
})

test('"enc" key is not usable for signing', t => {
const k = generateSync('oct', 256, { use: 'enc' })
t.throws(() => {
Expand Down
6 changes: 0 additions & 6 deletions test/jwt/decode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,3 @@ test('returns the payload', t => {
foo: 'bar'
})
})

test('invalid tokens', t => {
t.throws(() => {
JWT.decode('eyJ0eXAiOiJKV1QiLCJraWQiOiIyZTFkYjRmMC1mYmY5LTQxZjYtOGMxYi1hMzczYjgwZmNhYTEiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHktc3RhZ2luZy5kZWxpdmVyb28uY29tLyIsImNsaWVudCI6ImIyM2I0ZjM1YzIyMTI5NDQxZjMwZDMyYmI5ZmM4ZWYyIiwic2lnbmVyIjoiYXJuOmF3czplbGFzdGljbG9hZGJhbGFuY2luZzpldS13ZXN0LTE6NTE3OTAyNjYzOTE1OmxvYWRiYWxhbmNlci9hcHAvcGF5bWVudHMtZGFzaGJvYXJkLXdlYi80YzA4ZGI2NDMyMDIyOWEyIiwiZXhwIjoxNTYyNjkxNTg1fQ==.eyJlbWFpbCI6ImpvYW8udmllaXJhQGRlbGl2ZXJvby5jby51ayIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmYW1pbHlfbmFtZSI6Ikd1ZXJyYSBWaWVpcmEiLCJnaXZlbl9uYW1lIjoiSm9hbyIsIm5hbWUiOiJKb2FvIEd1ZXJyYSBWaWVpcmEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1sTUpXTXV3R1dpYy9BQUFBQUFBQUFBSS9BQUFBQUFBQUFCVS9lNGtkTDg5UjlqZy9zOTYtYy9waG90by5qcGciLCJzdWIiOiIxMWE1YmFmMGRjNzcwNWRmMzk1ZTMzYWFkZjU2MDk4OCIsImV4cCI6MTU2MjY5MTU4NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS1zdGFnaW5nLmRlbGl2ZXJvby5jb20vIn0=.DSHLJXLOfLJ-ZYcX0Vlii6Ak_jcDSkKOvNRj_rvtAyY9uYXtwo798ZrR35fgut-LuCdx0aKz2SgK0KJqw5q6dA==')
}, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
})
36 changes: 13 additions & 23 deletions test/jwt/verify.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const test = require('ava')

const { JWT, JWK, JWKS, errors } = require('../..')
const base64url = require('../../lib/help/base64url')
const { JWS, JWT, JWK, JWKS, errors } = require('../..')

const key = JWK.generateSync('oct')
const token = JWT.sign({}, key, { iat: false })
Expand Down Expand Up @@ -107,7 +106,7 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
test(`"${claim} must be a timestamp when provided"`, t => {
;['', 'foo', true, null, [], {}].forEach((val) => {
const err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: val })}.`
const invalid = JWS.sign({ [claim]: val }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a unix timestamp` })

Expand All @@ -121,7 +120,7 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
test(`"${claim} must be a string when provided"`, t => {
;['', 0, 1, true, null, [], {}].forEach((val) => {
const err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: val })}.`
const invalid = JWS.sign({ [claim]: val }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a string` })

Expand All @@ -136,14 +135,14 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
;['', 0, 1, true, null, [], {}].forEach((val) => {
let err
err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: val })}.`
const invalid = JWS.sign({ [claim]: val }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a string or array of strings` })
t.is(err.claim, claim)
t.is(err.reason, 'invalid')

err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: [val] })}.`
const invalid = JWS.sign({ [claim]: [val] }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a string or array of strings` })
t.is(err.claim, claim)
Expand All @@ -161,14 +160,14 @@ Object.entries({
test(`option.${option} validation fails`, t => {
let err
err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: 'foo' })}.`
const invalid = JWS.sign({ [claim]: 'foo' }, key)
JWT.verify(invalid, key, { [option]: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: `unexpected "${claim}" claim value` })
t.is(err.claim, claim)
t.is(err.reason, 'check_failed')

err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: undefined })}.`
const invalid = JWS.sign({ [claim]: undefined }, key)
JWT.verify(invalid, key, { [option]: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim is missing` })
t.is(err.claim, claim)
Expand All @@ -185,14 +184,14 @@ Object.entries({
test('option.audience validation fails', t => {
let err
err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ aud: 'foo' })}.`
const invalid = JWS.sign({ aud: 'foo' }, key)
JWT.verify(invalid, key, { audience: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: 'unexpected "aud" claim value' })
t.is(err.claim, 'aud')
t.is(err.reason, 'check_failed')

err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ aud: ['foo'] })}.`
const invalid = JWS.sign({ aud: ['foo'] }, key)
JWT.verify(invalid, key, { audience: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: 'unexpected "aud" claim value' })
t.is(err.claim, 'aud')
Expand All @@ -218,7 +217,7 @@ test('option.audience validation success', t => {

test('option.maxAuthAge requires iat to be in the payload', t => {
const err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({})}.`
const invalid = JWS.sign({}, key)
JWT.verify(invalid, key, { maxAuthAge: '30s' })
}, { instanceOf: errors.JWTClaimInvalid, message: '"auth_time" claim is missing' })
t.is(err.claim, 'auth_time')
Expand All @@ -230,7 +229,7 @@ const now = new Date(epoch * 1000)

test('option.maxAuthAge checks auth_time', t => {
const err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ auth_time: epoch - 31 })}.`
const invalid = JWS.sign({ auth_time: epoch - 31 }, key)
JWT.verify(invalid, key, { maxAuthAge: '30s', now })
}, { instanceOf: errors.JWTClaimInvalid, message: '"auth_time" claim timestamp check failed (too much time has elapsed since the last End-User authentication)' })
t.is(err.claim, 'auth_time')
Expand All @@ -245,7 +244,7 @@ test('option.maxAuthAge checks auth_time (with tolerance)', t => {

test('option.maxTokenAge requires iat to be in the payload', t => {
const err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({})}.`
const invalid = JWS.sign({}, key)
JWT.verify(invalid, key, { maxTokenAge: '30s' })
}, { instanceOf: errors.JWTClaimInvalid, message: '"iat" claim is missing' })
t.is(err.claim, 'iat')
Expand All @@ -254,7 +253,7 @@ test('option.maxTokenAge requires iat to be in the payload', t => {

test('option.maxTokenAge checks iat elapsed time', t => {
const err = t.throws(() => {
const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ iat: epoch - 31 })}.`
const invalid = JWS.sign({ iat: epoch - 31 }, key)
JWT.verify(invalid, key, { maxTokenAge: '30s', now })
}, { instanceOf: errors.JWTExpired, code: 'ERR_JWT_EXPIRED', message: '"iat" claim timestamp check failed (too far in the past)' })
t.true(err instanceof errors.JWTClaimInvalid)
Expand Down Expand Up @@ -828,12 +827,3 @@ test('must be a supported value', t => {
t.is(err.reason, 'check_failed')
})
}

test('invalid tokens', t => {
t.throws(() => {
JWT.verify(
'eyJ0eXAiOiJKV1QiLCJraWQiOiIyZTFkYjRmMC1mYmY5LTQxZjYtOGMxYi1hMzczYjgwZmNhYTEiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHktc3RhZ2luZy5kZWxpdmVyb28uY29tLyIsImNsaWVudCI6ImIyM2I0ZjM1YzIyMTI5NDQxZjMwZDMyYmI5ZmM4ZWYyIiwic2lnbmVyIjoiYXJuOmF3czplbGFzdGljbG9hZGJhbGFuY2luZzpldS13ZXN0LTE6NTE3OTAyNjYzOTE1OmxvYWRiYWxhbmNlci9hcHAvcGF5bWVudHMtZGFzaGJvYXJkLXdlYi80YzA4ZGI2NDMyMDIyOWEyIiwiZXhwIjoxNTYyNjkxNTg1fQ==.eyJlbWFpbCI6ImpvYW8udmllaXJhQGRlbGl2ZXJvby5jby51ayIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmYW1pbHlfbmFtZSI6Ikd1ZXJyYSBWaWVpcmEiLCJnaXZlbl9uYW1lIjoiSm9hbyIsIm5hbWUiOiJKb2FvIEd1ZXJyYSBWaWVpcmEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1sTUpXTXV3R1dpYy9BQUFBQUFBQUFBSS9BQUFBQUFBQUFCVS9lNGtkTDg5UjlqZy9zOTYtYy9waG90by5qcGciLCJzdWIiOiIxMWE1YmFmMGRjNzcwNWRmMzk1ZTMzYWFkZjU2MDk4OCIsImV4cCI6MTU2MjY5MTU4NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS1zdGFnaW5nLmRlbGl2ZXJvby5jb20vIn0=.DSHLJXLOfLJ-ZYcX0Vlii6Ak_jcDSkKOvNRj_rvtAyY9uYXtwo798ZrR35fgut-LuCdx0aKz2SgK0KJqw5q6dA==',
key
)
}, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
})

0 comments on commit 470b4c7

Please sign in to comment.