Skip to content

Commit b3846ef

Browse files
committedJul 28, 2017
Merge branch 'secret-key-provider' of https://github.com/gpoole/passport-jwt into pr108-secret-key-provider
2 parents 98a7a09 + a698173 commit b3846ef

5 files changed

+67
-20
lines changed
 

‎README.md

+9-6
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ The JWT authentication strategy is constructed as follows:
2626
`options` is an object literal containing options to control how the token is
2727
extracted from the request or verified.
2828

29-
* `secretOrKey` is a REQUIRED string or buffer containing the secret
29+
* `secretOrKey` is a string or buffer containing the secret
3030
(symmetric) or PEM-encoded public key (asymmetric) for verifying the token's
31-
signature.
32-
31+
signature. REQUIRED unless `secretOrKeyProvider` is provided.
32+
* `secretOrKeyProvider` is a callback in the format `function secretOrKeyProvider(token, done)`,
33+
which should call `done` with a secret or PEM-encoded public key (asymmetric) for the given key and request combination.
34+
`done` accepts arguments in the format `function done(err, secret)`.
35+
REQUIRED unless `secretOrKey` is provided.
3336
* `jwtFromRequest` (REQUIRED) Function that accepts a request as the only
34-
parameter and returns either the JWT as a string or *null*. See
37+
parameter and returns either the JWT as a string or *null*. See
3538
[Extracting the JWT from the request](#extracting-the-jwt-from-the-request) for
3639
more details.
3740
* `issuer`: If defined the token issuer (iss) will be verified against this
@@ -84,7 +87,7 @@ possible the JWT is parsed from the request by a user-supplied callback passed i
8487
`jwtFromRequest` parameter. This callback, from now on referred to as an extractor,
8588
accepts a request object as an argument and returns the encoded JWT string or *null*.
8689

87-
#### Included extractors
90+
#### Included extractors
8891

8992
A number of extractor factory functions are provided in passport-jwt.ExtractJwt. These factory
9093
functions return a new extractor configured with the given parameters.
@@ -105,7 +108,7 @@ functions return a new extractor configured with the given parameters.
105108
### Writing a custom extractor function
106109

107110
If the supplied extractors don't meet your needs you can easily provide your own callback. For
108-
example, if you are using the cookie-parser middleware and want to extract the JWT in a cookie
111+
example, if you are using the cookie-parser middleware and want to extract the JWT in a cookie
109112
you could use the following function as the argument to the jwtFromRequest option:
110113

111114
```

‎lib/strategy.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,18 @@ function JwtStrategy(options, verify) {
2525
passport.Strategy.call(this);
2626
this.name = 'jwt';
2727

28-
this._secretOrKey = options.secretOrKey;
29-
if (!this._secretOrKey) {
28+
this._secretOrKeyProvider = options.secretOrKeyProvider;
29+
30+
if (options.secretOrKey) {
31+
if (this._secretOrKeyProvider) {
32+
throw new TypeError('JwtStrategy has been given both a secretOrKey and a secretOrKeyProvider');
33+
}
34+
this._secretOrKeyProvider = function (token, done) {
35+
done(options.secretOrKey)
36+
};
37+
}
38+
39+
if (!this._secretOrKeyProvider) {
3040
throw new TypeError('JwtStrategy requires a secret or key');
3141
}
3242

@@ -82,7 +92,7 @@ JwtStrategy.prototype.authenticate = function(req, options) {
8292
}
8393

8494
// Verify the JWT
85-
JwtStrategy.JwtVerifier(token, this._secretOrKey, this._verifOpts, function(jwt_err, payload) {
95+
JwtStrategy.JwtVerifier(token, this._secretOrKeyProvider, this._verifOpts, function(jwt_err, payload) {
8696
if (jwt_err) {
8797
return self.fail(jwt_err);
8898
} else {

‎lib/verify_jwt.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
var jwt = require('jsonwebtoken');
22

3-
module.exports = function(token, secretOrKey, options, callback) {
4-
return jwt.verify(token, secretOrKey, options, callback);
3+
module.exports = function(token, secretOrKeyProvider, options, callback, verify) {
4+
if (!verify) {
5+
verify = jwt.verify;
6+
}
7+
return secretOrKeyProvider(token, function (err, secretOrKey) {
8+
if (err) {
9+
callback(err);
10+
return;
11+
}
12+
verify(token, secretOrKey, options, callback);
13+
});
514
};

‎test/strategy-validation-test.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ describe('Strategy', function() {
3939
});
4040

4141

42-
it('should call with the right secret as an argument', function() {
43-
expect(Strategy.JwtVerifier.args[0][1]).to.equal('secret');
42+
it('should be given a function that calls done with the secretOrKey when passed the token', function() {
43+
const secretOrKeyProvider = Strategy.JwtVerifier.args[0][1];
44+
const doneSpy = sinon.spy();
45+
secretOrKeyProvider(null, doneSpy);
46+
expect(doneSpy.calledWith('secret')).to.be.true;
4447
});
4548

46-
4749
it('should call with the right issuer option', function() {
4850
expect(Strategy.JwtVerifier.args[0][2]).to.be.an.object;
4951
expect(Strategy.JwtVerifier.args[0][2].issuer).to.equal('TestIssuer');

‎test/strategy-verify-test.js

+29-6
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ var chai = require('chai')
22
, Strategy = require('../lib/strategy')
33
, test_data = require('./testdata')
44
, sinon = require('sinon')
5+
, verify = require('../lib/verify_jwt')
56
, extract_jwt = require('../lib/extract_jwt');
67

78

89
describe('Strategy', function() {
910

1011
before(function() {
11-
Strategy.JwtVerifier = sinon.stub();
12+
Strategy.JwtVerifier = sinon.stub();
1213
Strategy.JwtVerifier.callsArgWith(3, null, test_data.valid_jwt.payload);
1314
});
1415

1516
describe('Handling a request with a valid JWT and succesful verification', function() {
1617

17-
var strategy, user, info;
18+
var strategy, user, info;
1819

1920
before(function(done) {
2021
strategy = new Strategy({jwtFromRequest:extract_jwt.fromAuthHeader(), secretOrKey: 'secret'}, function(jwt_paylod, next) {
@@ -63,7 +64,7 @@ describe('Strategy', function() {
6364
info = i;
6465
done();
6566
})
66-
.req(function(req) {
67+
.req(function(req) {
6768
req.headers['authorization'] = "JWT " + test_data.valid_jwt.token;
6869
})
6970
.authenticate();
@@ -74,7 +75,7 @@ describe('Strategy', function() {
7475
expect(info).to.be.an.object;
7576
expect(info.message).to.equal('invalid user');
7677
});
77-
78+
7879
});
7980

8081

@@ -93,7 +94,7 @@ describe('Strategy', function() {
9394
err = e;
9495
done();
9596
})
96-
.req(function(req) {
97+
.req(function(req) {
9798
req.headers['authorization'] = "JWT " + test_data.valid_jwt.token;
9899
})
99100
.authenticate();
@@ -122,7 +123,7 @@ describe('Strategy', function() {
122123
err = e;
123124
done();
124125
})
125-
.req(function(req) {
126+
.req(function(req) {
126127
req.headers['authorization'] = "JWT " + test_data.valid_jwt.token;
127128
})
128129
.authenticate();
@@ -169,4 +170,26 @@ describe('Strategy', function() {
169170

170171
});
171172

173+
describe('verify function', function() {
174+
it('should call the secretOrKeyProvider with the token and pass it to verify', function() {
175+
const provider = sinon.spy(function(token, done) {
176+
done(null, 'secret');
177+
});
178+
const verifyStub = sinon.stub();
179+
verify(test_data.valid_jwt.payload, provider, null, null, verifyStub);
180+
expect(provider.calledOnce).to.be.true;
181+
expect(provider.calledWith(test_data.valid_jwt.payload)).to.be.true;
182+
expect(verifyStub.calledWith(test_data.valid_jwt.payload, 'secret')).to.be.true;
183+
});
184+
185+
it('should call the callback with an error if the secretOrKeyProvider fails to return a key', function() {
186+
const providerStub = function(token, done) {
187+
done(new Error('invalid key'));
188+
};
189+
const callback = sinon.spy();
190+
verify(test_data.valid_jwt.payload, providerStub, null, callback);
191+
expect(callback.calledWith(sinon.match.instanceOf(Error)));
192+
});
193+
});
194+
172195
});

0 commit comments

Comments
 (0)
Please sign in to comment.