verify.js
1 const JsonWebTokenError = require('./lib/JsonWebTokenError'); 2 const NotBeforeError = require('./lib/NotBeforeError'); 3 const TokenExpiredError = require('./lib/TokenExpiredError'); 4 const decode = require('./decode'); 5 const timespan = require('./lib/timespan'); 6 const validateAsymmetricKey = require('./lib/validateAsymmetricKey'); 7 const PS_SUPPORTED = require('./lib/psSupported'); 8 const jws = require('jws'); 9 const {KeyObject, createSecretKey, createPublicKey} = require("crypto"); 10 11 const PUB_KEY_ALGS = ['RS256', 'RS384', 'RS512']; 12 const EC_KEY_ALGS = ['ES256', 'ES384', 'ES512']; 13 const RSA_KEY_ALGS = ['RS256', 'RS384', 'RS512']; 14 const HS_ALGS = ['HS256', 'HS384', 'HS512']; 15 16 if (PS_SUPPORTED) { 17 PUB_KEY_ALGS.splice(PUB_KEY_ALGS.length, 0, 'PS256', 'PS384', 'PS512'); 18 RSA_KEY_ALGS.splice(RSA_KEY_ALGS.length, 0, 'PS256', 'PS384', 'PS512'); 19 } 20 21 module.exports = function (jwtString, secretOrPublicKey, options, callback) { 22 if ((typeof options === 'function') && !callback) { 23 callback = options; 24 options = {}; 25 } 26 27 if (!options) { 28 options = {}; 29 } 30 31 //clone this object since we are going to mutate it. 32 options = Object.assign({}, options); 33 34 let done; 35 36 if (callback) { 37 done = callback; 38 } else { 39 done = function(err, data) { 40 if (err) throw err; 41 return data; 42 }; 43 } 44 45 if (options.clockTimestamp && typeof options.clockTimestamp !== 'number') { 46 return done(new JsonWebTokenError('clockTimestamp must be a number')); 47 } 48 49 if (options.nonce !== undefined && (typeof options.nonce !== 'string' || options.nonce.trim() === '')) { 50 return done(new JsonWebTokenError('nonce must be a non-empty string')); 51 } 52 53 if (options.allowInvalidAsymmetricKeyTypes !== undefined && typeof options.allowInvalidAsymmetricKeyTypes !== 'boolean') { 54 return done(new JsonWebTokenError('allowInvalidAsymmetricKeyTypes must be a boolean')); 55 } 56 57 const clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1000); 58 59 if (!jwtString){ 60 return done(new JsonWebTokenError('jwt must be provided')); 61 } 62 63 if (typeof jwtString !== 'string') { 64 return done(new JsonWebTokenError('jwt must be a string')); 65 } 66 67 const parts = jwtString.split('.'); 68 69 if (parts.length !== 3){ 70 return done(new JsonWebTokenError('jwt malformed')); 71 } 72 73 let decodedToken; 74 75 try { 76 decodedToken = decode(jwtString, { complete: true }); 77 } catch(err) { 78 return done(err); 79 } 80 81 if (!decodedToken) { 82 return done(new JsonWebTokenError('invalid token')); 83 } 84 85 const header = decodedToken.header; 86 let getSecret; 87 88 if(typeof secretOrPublicKey === 'function') { 89 if(!callback) { 90 return done(new JsonWebTokenError('verify must be called asynchronous if secret or public key is provided as a callback')); 91 } 92 93 getSecret = secretOrPublicKey; 94 } 95 else { 96 getSecret = function(header, secretCallback) { 97 return secretCallback(null, secretOrPublicKey); 98 }; 99 } 100 101 return getSecret(header, function(err, secretOrPublicKey) { 102 if(err) { 103 return done(new JsonWebTokenError('error in secret or public key callback: ' + err.message)); 104 } 105 106 const hasSignature = parts[2].trim() !== ''; 107 108 if (!hasSignature && secretOrPublicKey){ 109 return done(new JsonWebTokenError('jwt signature is required')); 110 } 111 112 if (hasSignature && !secretOrPublicKey) { 113 return done(new JsonWebTokenError('secret or public key must be provided')); 114 } 115 116 if (!hasSignature && !options.algorithms) { 117 return done(new JsonWebTokenError('please specify "none" in "algorithms" to verify unsigned tokens')); 118 } 119 120 if (secretOrPublicKey != null && !(secretOrPublicKey instanceof KeyObject)) { 121 try { 122 secretOrPublicKey = createPublicKey(secretOrPublicKey); 123 } catch (_) { 124 try { 125 secretOrPublicKey = createSecretKey(typeof secretOrPublicKey === 'string' ? Buffer.from(secretOrPublicKey) : secretOrPublicKey); 126 } catch (_) { 127 return done(new JsonWebTokenError('secretOrPublicKey is not valid key material')) 128 } 129 } 130 } 131 132 if (!options.algorithms) { 133 if (secretOrPublicKey.type === 'secret') { 134 options.algorithms = HS_ALGS; 135 } else if (['rsa', 'rsa-pss'].includes(secretOrPublicKey.asymmetricKeyType)) { 136 options.algorithms = RSA_KEY_ALGS 137 } else if (secretOrPublicKey.asymmetricKeyType === 'ec') { 138 options.algorithms = EC_KEY_ALGS 139 } else { 140 options.algorithms = PUB_KEY_ALGS 141 } 142 } 143 144 if (options.algorithms.indexOf(decodedToken.header.alg) === -1) { 145 return done(new JsonWebTokenError('invalid algorithm')); 146 } 147 148 if (header.alg.startsWith('HS') && secretOrPublicKey.type !== 'secret') { 149 return done(new JsonWebTokenError((`secretOrPublicKey must be a symmetric key when using ${header.alg}`))) 150 } else if (/^(?:RS|PS|ES)/.test(header.alg) && secretOrPublicKey.type !== 'public') { 151 return done(new JsonWebTokenError((`secretOrPublicKey must be an asymmetric key when using ${header.alg}`))) 152 } 153 154 if (!options.allowInvalidAsymmetricKeyTypes) { 155 try { 156 validateAsymmetricKey(header.alg, secretOrPublicKey); 157 } catch (e) { 158 return done(e); 159 } 160 } 161 162 let valid; 163 164 try { 165 valid = jws.verify(jwtString, decodedToken.header.alg, secretOrPublicKey); 166 } catch (e) { 167 return done(e); 168 } 169 170 if (!valid) { 171 return done(new JsonWebTokenError('invalid signature')); 172 } 173 174 const payload = decodedToken.payload; 175 176 if (typeof payload.nbf !== 'undefined' && !options.ignoreNotBefore) { 177 if (typeof payload.nbf !== 'number') { 178 return done(new JsonWebTokenError('invalid nbf value')); 179 } 180 if (payload.nbf > clockTimestamp + (options.clockTolerance || 0)) { 181 return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000))); 182 } 183 } 184 185 if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) { 186 if (typeof payload.exp !== 'number') { 187 return done(new JsonWebTokenError('invalid exp value')); 188 } 189 if (clockTimestamp >= payload.exp + (options.clockTolerance || 0)) { 190 return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000))); 191 } 192 } 193 194 if (options.audience) { 195 const audiences = Array.isArray(options.audience) ? options.audience : [options.audience]; 196 const target = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; 197 198 const match = target.some(function (targetAudience) { 199 return audiences.some(function (audience) { 200 return audience instanceof RegExp ? audience.test(targetAudience) : audience === targetAudience; 201 }); 202 }); 203 204 if (!match) { 205 return done(new JsonWebTokenError('jwt audience invalid. expected: ' + audiences.join(' or '))); 206 } 207 } 208 209 if (options.issuer) { 210 const invalid_issuer = 211 (typeof options.issuer === 'string' && payload.iss !== options.issuer) || 212 (Array.isArray(options.issuer) && options.issuer.indexOf(payload.iss) === -1); 213 214 if (invalid_issuer) { 215 return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + options.issuer)); 216 } 217 } 218 219 if (options.subject) { 220 if (payload.sub !== options.subject) { 221 return done(new JsonWebTokenError('jwt subject invalid. expected: ' + options.subject)); 222 } 223 } 224 225 if (options.jwtid) { 226 if (payload.jti !== options.jwtid) { 227 return done(new JsonWebTokenError('jwt jwtid invalid. expected: ' + options.jwtid)); 228 } 229 } 230 231 if (options.nonce) { 232 if (payload.nonce !== options.nonce) { 233 return done(new JsonWebTokenError('jwt nonce invalid. expected: ' + options.nonce)); 234 } 235 } 236 237 if (options.maxAge) { 238 if (typeof payload.iat !== 'number') { 239 return done(new JsonWebTokenError('iat required when maxAge is specified')); 240 } 241 242 const maxAgeTimestamp = timespan(options.maxAge, payload.iat); 243 if (typeof maxAgeTimestamp === 'undefined') { 244 return done(new JsonWebTokenError('"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60')); 245 } 246 if (clockTimestamp >= maxAgeTimestamp + (options.clockTolerance || 0)) { 247 return done(new TokenExpiredError('maxAge exceeded', new Date(maxAgeTimestamp * 1000))); 248 } 249 } 250 251 if (options.complete === true) { 252 const signature = decodedToken.signature; 253 254 return done(null, { 255 header: header, 256 payload: payload, 257 signature: signature 258 }); 259 } 260 261 return done(null, payload); 262 }); 263 };