Skip to content

Commit 0128a41

Browse files
committed
Disallow use of HTTP in JWKS URL
1 parent 1790748 commit 0128a41

File tree

4 files changed

+92
-64
lines changed

4 files changed

+92
-64
lines changed

src/strategies/bearer-token.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import TokenValidationError from '../utils/tokenValidationError.js';
2727
export default class extends Strategy {
2828
constructor({algorithms, audience, issuer, jwksUrl, serviceAuthHeader}) {
2929
super();
30+
31+
const jwksUsesHttps = jwksUrl.startsWith('https://');
32+
if (!jwksUsesHttps) {
33+
throw new Error('JWKS URL must use HTTPS');
34+
}
35+
3036
this.name = 'keycloak-jwt-bearer';
3137
this.jwksUrl = jwksUrl;
3238
this.verifyOpts = {algorithms, audience, issuer, ignoreExpiration: false};
@@ -55,7 +61,6 @@ export default class extends Strategy {
5561
}
5662
}
5763

58-
// eslint-disable-next-line max-statements
5964
async authenticate(req) {
6065
const debug = createDebugLogger('@natlibfi/passport-keycloak-js/bearer-token:authenticate');
6166

src/strategies/bearer-token.test.js

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*
1616
*/
1717

18-
/* eslint-disable max-lines */
19-
2018
import chai from 'chai';
2119
import chaiPassportStrategy from 'chai-passport-strategy';
2220
import assert from 'node:assert';
@@ -40,7 +38,7 @@ describe('strategies/bearer-token', () => {
4038
});
4139

4240
it('Should call success() when token is valid', () => {
43-
const scope = nock('http://localhost')
41+
const scope = nock('https://localhost')
4442
.get('/realms/foo/protocol/openid-connect/certs')
4543
.times(1)
4644
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -56,7 +54,7 @@ describe('strategies/bearer-token', () => {
5654
const token = jwt.sign(payload, privateKey, signOpts);
5755

5856
const strategy = new Strategy({
59-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
57+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
6058
algorithms: ['RS256'],
6159
audience: 'foo.audience',
6260
issuer: 'foo.issuer'
@@ -83,7 +81,7 @@ describe('strategies/bearer-token', () => {
8381
});
8482

8583
it('Should call fail() because of invalid token', () => {
86-
const scope = nock('http://localhost')
84+
const scope = nock('https://localhost')
8785
.get('/realms/foo/protocol/openid-connect/certs')
8886
.times(1)
8987
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -101,7 +99,7 @@ describe('strategies/bearer-token', () => {
10199
const token = jwt.sign(payload, anotherPrivateKey, signOpts);
102100

103101
const strategy = new Strategy({
104-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
102+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
105103
algorithms: ['RS256'],
106104
audience: 'foo.audience',
107105
issuer: 'foo.issuer'
@@ -123,7 +121,7 @@ describe('strategies/bearer-token', () => {
123121
});
124122

125123
it('Should call fail() when token audience is not valid', () => {
126-
const scope = nock('http://localhost')
124+
const scope = nock('https://localhost')
127125
.get('/realms/foo/protocol/openid-connect/certs')
128126
.times(1)
129127
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -139,7 +137,7 @@ describe('strategies/bearer-token', () => {
139137
const token = jwt.sign(payload, privateKey, signOpts);
140138

141139
const strategy = new Strategy({
142-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
140+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
143141
algorithms: ['RS256'],
144142
audience: 'foo.audience',
145143
issuer: 'foo.issuer'
@@ -161,7 +159,7 @@ describe('strategies/bearer-token', () => {
161159
});
162160

163161
it('Should call fail() when token issuer is not valid', () => {
164-
const scope = nock('http://localhost')
162+
const scope = nock('https://localhost')
165163
.get('/realms/foo/protocol/openid-connect/certs')
166164
.times(1)
167165
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -177,7 +175,7 @@ describe('strategies/bearer-token', () => {
177175
const token = jwt.sign(payload, privateKey, signOpts);
178176

179177
const strategy = new Strategy({
180-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
178+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
181179
algorithms: ['RS256'],
182180
audience: 'foo.audience',
183181
issuer: 'foo.issuer'
@@ -200,12 +198,12 @@ describe('strategies/bearer-token', () => {
200198

201199

202200
it('Should call fail() because of missing token. JWKS endpoint was not queried.', () => {
203-
const scope = nock('http://localhost')
201+
const scope = nock('https://localhost')
204202
.get('/realms/foo/protocol/openid-connect/certs')
205203
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
206204

207205
const strategy = new Strategy({
208-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
206+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
209207
algorithms: ['RS256'],
210208
audience: 'foo.audience',
211209
issuer: 'foo.issuer'
@@ -226,7 +224,7 @@ describe('strategies/bearer-token', () => {
226224
});
227225

228226
it('Should call fail() because of expired token', () => {
229-
const scope = nock('http://localhost')
227+
const scope = nock('https://localhost')
230228
.get('/realms/foo/protocol/openid-connect/certs')
231229
.times(1)
232230
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -243,7 +241,7 @@ describe('strategies/bearer-token', () => {
243241
const token = jwt.sign(payload, privateKey, signOpts);
244242

245243
const strategy = new Strategy({
246-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
244+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
247245
algorithms: ['RS256'],
248246
audience: 'foo.audience',
249247
issuer: 'foo.issuer'
@@ -265,7 +263,7 @@ describe('strategies/bearer-token', () => {
265263
});
266264

267265
it('Should call fail() when alg is not valid', () => {
268-
const scope = nock('http://localhost')
266+
const scope = nock('https://localhost')
269267
.get('/realms/foo/protocol/openid-connect/certs')
270268
.times(1)
271269
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -281,7 +279,7 @@ describe('strategies/bearer-token', () => {
281279
const token = jwt.sign(payload, privateKey, signOpts);
282280

283281
const strategy = new Strategy({
284-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
282+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
285283
algorithms: ['RS512'],
286284
audience: 'foo.audience',
287285
issuer: 'foo.issuer'
@@ -303,7 +301,7 @@ describe('strategies/bearer-token', () => {
303301
});
304302

305303
it('Service token option enabled: calls success when both tokens are valid()', () => {
306-
const scope = nock('http://localhost')
304+
const scope = nock('https://localhost')
307305
.get('/realms/foo/protocol/openid-connect/certs')
308306
.times(1)
309307
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -328,7 +326,7 @@ describe('strategies/bearer-token', () => {
328326
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
329327

330328
const strategy = new Strategy({
331-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
329+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
332330
algorithms: ['RS256'],
333331
audience: 'foo.audience',
334332
issuer: 'foo.issuer',
@@ -358,7 +356,7 @@ describe('strategies/bearer-token', () => {
358356
});
359357

360358
it('Service token option enabled: calls fail() when service token is invalid', () => {
361-
const scope = nock('http://localhost')
359+
const scope = nock('https://localhost')
362360
.get('/realms/foo/protocol/openid-connect/certs')
363361
.times(1)
364362
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -385,7 +383,7 @@ describe('strategies/bearer-token', () => {
385383
const serviceToken = jwt.sign(servicePayload, anotherPrivateKey, signOpts);
386384

387385
const strategy = new Strategy({
388-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
386+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
389387
algorithms: ['RS256'],
390388
audience: 'foo.audience',
391389
issuer: 'foo.issuer',
@@ -409,7 +407,7 @@ describe('strategies/bearer-token', () => {
409407
});
410408

411409
it('Service token option enabled: calls fail() when user token is invalid', () => {
412-
const scope = nock('http://localhost')
410+
const scope = nock('https://localhost')
413411
.get('/realms/foo/protocol/openid-connect/certs')
414412
.times(1)
415413
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -436,7 +434,7 @@ describe('strategies/bearer-token', () => {
436434
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
437435

438436
const strategy = new Strategy({
439-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
437+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
440438
algorithms: ['RS256'],
441439
audience: 'foo.audience',
442440
issuer: 'foo.issuer',
@@ -460,7 +458,7 @@ describe('strategies/bearer-token', () => {
460458
});
461459

462460
it('Service token option enabled: should call fail() when service token audience is not valid', () => {
463-
const scope = nock('http://localhost')
461+
const scope = nock('https://localhost')
464462
.get('/realms/foo/protocol/openid-connect/certs')
465463
.times(1)
466464
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -485,7 +483,7 @@ describe('strategies/bearer-token', () => {
485483
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
486484

487485
const strategy = new Strategy({
488-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
486+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
489487
algorithms: ['RS256'],
490488
audience: 'foo.audience',
491489
issuer: 'foo.issuer',
@@ -509,7 +507,7 @@ describe('strategies/bearer-token', () => {
509507
});
510508

511509
it('Service token option enabled: should call fail() when user token audience is not valid', () => {
512-
const scope = nock('http://localhost')
510+
const scope = nock('https://localhost')
513511
.get('/realms/foo/protocol/openid-connect/certs')
514512
.times(1)
515513
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -534,7 +532,7 @@ describe('strategies/bearer-token', () => {
534532
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
535533

536534
const strategy = new Strategy({
537-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
535+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
538536
algorithms: ['RS256'],
539537
audience: 'foo.audience',
540538
issuer: 'foo.issuer',
@@ -558,7 +556,7 @@ describe('strategies/bearer-token', () => {
558556
});
559557

560558
it('Service token option enabled: should call fail() when service token issuer is not valid', () => {
561-
const scope = nock('http://localhost')
559+
const scope = nock('https://localhost')
562560
.get('/realms/foo/protocol/openid-connect/certs')
563561
.times(1)
564562
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -583,7 +581,7 @@ describe('strategies/bearer-token', () => {
583581
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
584582

585583
const strategy = new Strategy({
586-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
584+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
587585
algorithms: ['RS256'],
588586
audience: 'foo.audience',
589587
issuer: 'foo.issuer',
@@ -607,7 +605,7 @@ describe('strategies/bearer-token', () => {
607605
});
608606

609607
it('Service token option enabled: should call fail() when user token issuer is not valid', () => {
610-
const scope = nock('http://localhost')
608+
const scope = nock('https://localhost')
611609
.get('/realms/foo/protocol/openid-connect/certs')
612610
.times(1)
613611
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -632,7 +630,7 @@ describe('strategies/bearer-token', () => {
632630
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
633631

634632
const strategy = new Strategy({
635-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
633+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
636634
algorithms: ['RS256'],
637635
audience: 'foo.audience',
638636
issuer: 'foo.issuer',
@@ -656,7 +654,7 @@ describe('strategies/bearer-token', () => {
656654
});
657655

658656
it('Service token option enabled: Should call fail() because of missing service token. Does not make call to JWKS endpoint.', () => {
659-
const scope = nock('http://localhost')
657+
const scope = nock('https://localhost')
660658
.get('/realms/foo/protocol/openid-connect/certs')
661659
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
662660

@@ -671,7 +669,7 @@ describe('strategies/bearer-token', () => {
671669
const token = jwt.sign(payload, privateKey, signOpts);
672670

673671
const strategy = new Strategy({
674-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
672+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
675673
algorithms: ['RS256'],
676674
audience: 'foo.audience',
677675
issuer: 'foo.issuer',
@@ -696,7 +694,7 @@ describe('strategies/bearer-token', () => {
696694
});
697695

698696
it('Service token option enabled: Should call fail() because of missing user token. Does not make call to JWKS endpoint.', () => {
699-
const scope = nock('http://localhost')
697+
const scope = nock('https://localhost')
700698
.get('/realms/foo/protocol/openid-connect/certs')
701699
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
702700

@@ -711,7 +709,7 @@ describe('strategies/bearer-token', () => {
711709
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
712710

713711
const strategy = new Strategy({
714-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
712+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
715713
algorithms: ['RS256'],
716714
audience: 'foo.audience',
717715
issuer: 'foo.issuer',
@@ -736,7 +734,7 @@ describe('strategies/bearer-token', () => {
736734
});
737735

738736
it('Service token option enabled: Should call fail() because of expired service token', () => {
739-
const scope = nock('http://localhost')
737+
const scope = nock('https://localhost')
740738
.get('/realms/foo/protocol/openid-connect/certs')
741739
.times(1)
742740
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -762,7 +760,7 @@ describe('strategies/bearer-token', () => {
762760
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
763761

764762
const strategy = new Strategy({
765-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
763+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
766764
algorithms: ['RS256'],
767765
audience: 'foo.audience',
768766
issuer: 'foo.issuer',
@@ -786,7 +784,7 @@ describe('strategies/bearer-token', () => {
786784
});
787785

788786
it('Service token option enabled: Should call fail() because of expired user token', () => {
789-
const scope = nock('http://localhost')
787+
const scope = nock('https://localhost')
790788
.get('/realms/foo/protocol/openid-connect/certs')
791789
.times(1)
792790
.reply(200, {keys: [{...publicKey, kid: 'foo.keyid', alg: 'RS256'}]});
@@ -812,7 +810,7 @@ describe('strategies/bearer-token', () => {
812810
const serviceToken = jwt.sign(servicePayload, privateKey, signOpts);
813811

814812
const strategy = new Strategy({
815-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
813+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
816814
algorithms: ['RS256'],
817815
audience: 'foo.audience',
818816
issuer: 'foo.issuer',
@@ -838,7 +836,7 @@ describe('strategies/bearer-token', () => {
838836
it('Should disallow use of algorithm that is not in allowed algorithms list (one algorithm)', () => {
839837
assert.throws(() => {
840838
new Strategy({
841-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
839+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
842840
algorithms: ['HS256'],
843841
audience: 'foo.audience',
844842
issuer: 'foo.issuer',
@@ -850,7 +848,7 @@ describe('strategies/bearer-token', () => {
850848
it('Should disallow use of algorithm that is not in allowed algorithms list (multiple algorithms, one disallowed)', () => {
851849
assert.throws(() => {
852850
new Strategy({
853-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
851+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
854852
algorithms: ['RS256', 'HS256'],
855853
audience: 'foo.audience',
856854
issuer: 'foo.issuer',
@@ -862,12 +860,24 @@ describe('strategies/bearer-token', () => {
862860
it('Should disallow use of empty algorithm array', () => {
863861
assert.throws(() => {
864862
new Strategy({
865-
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
863+
jwksUrl: 'https://localhost/realms/foo/protocol/openid-connect/certs',
866864
algorithms: [],
867865
audience: 'foo.audience',
868866
issuer: 'foo.issuer',
869867
serviceAuthHeader: 'customHeader'
870868
});
871869
}, new Error('Algorithm definitions are missing. Define at least one approved algorithm.'))
872870
})
871+
872+
it('Should disallow use of JWKS url that does not use HTTPS', () => {
873+
assert.throws(() => {
874+
new Strategy({
875+
jwksUrl: 'http://localhost/realms/foo/protocol/openid-connect/certs',
876+
algorithms: ['RS256'],
877+
audience: 'foo.audience',
878+
issuer: 'foo.issuer',
879+
serviceAuthHeader: 'customHeader'
880+
});
881+
}, new Error('JWKS URL must use HTTPS'))
882+
})
873883
});

0 commit comments

Comments
 (0)