UNPKG

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