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  };