Skip to content

Commit 9efbf37

Browse files
committed
fixup! feat: add JWT bearer authorization grant (RFC 7523)
1 parent ed5cda4 commit 9efbf37

2 files changed

Lines changed: 39 additions & 10 deletions

File tree

lib/grant-types/jwt-bearer-grant-type.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
3636
* - `getJWTBearerIssuer(issuer)` → `{ audience, jwks | jwksUri | secret }` (or
3737
* falsy for an untrusted issuer) — the verification key material and the
3838
* expected `aud`.
39-
* - `getJWTBearerUser({ issuer, subject, client, scope, jti, exp })` → the
39+
* - `getJWTBearerUser({ issuer, subject, client, scope, jti, assertionId, exp })` → the
4040
* authorized user (or falsy to deny). Replay (`jti`) can be enforced here.
4141
*
4242
* @example
@@ -162,22 +162,33 @@ class JwtBearerGrantType extends AbstractGrantType {
162162
* Resolve the verification key for the issuer (HMAC secret or asymmetric JWKS).
163163
*/
164164
async getKey(issuerData, header) {
165+
const hasSecret = typeof issuerData.secret === 'string' && issuerData.secret.length > 0;
166+
const hasJwks = !!issuerData.jwks || !!issuerData.jwksUri;
167+
168+
// No key material at all is a server-side misconfiguration of the issuer.
169+
if (!hasSecret && !hasJwks) {
170+
throw new ServerError('Server error: issuer has no key material for assertion verification');
171+
}
172+
165173
if (isHmac(header.alg)) {
166-
if (typeof issuerData.secret !== 'string' || issuerData.secret.length === 0) {
167-
throw new ServerError('Server error: issuer has no secret for HMAC assertion verification');
174+
// The issuer has key material, just not for this algorithm family — that
175+
// makes the assertion unverifiable against this issuer, i.e. the client's
176+
// fault, not the server's.
177+
if (!hasSecret) {
178+
throw new InvalidGrantError('Invalid grant: assertion algorithm does not match the issuer key material');
168179
}
169180
return new TextEncoder().encode(issuerData.secret);
170181
}
171182

172-
if (issuerData.jwks) {
173-
return createLocalJWKSet(issuerData.jwks);
183+
if (!hasJwks) {
184+
throw new InvalidGrantError('Invalid grant: assertion algorithm does not match the issuer key material');
174185
}
175186

176-
if (issuerData.jwksUri) {
177-
return getRemoteJwks(issuerData.jwksUri);
187+
if (issuerData.jwks) {
188+
return createLocalJWKSet(issuerData.jwks);
178189
}
179190

180-
throw new ServerError('Server error: issuer has no keys for assertion verification');
191+
return getRemoteJwks(issuerData.jwksUri);
181192
}
182193

183194
/**

test/integration/grant-types/jwt-bearer-grant-type_test.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ describe('JWT bearer authorization grant integration', function () {
333333
);
334334
});
335335

336-
it('throws server_error when the issuer has no HMAC secret', async function () {
336+
it('throws server_error when the issuer has no key material (HMAC alg)', async function () {
337337
await grant
338338
.getKey({}, { alg: 'HS256' })
339339
.then(() => {
@@ -342,14 +342,32 @@ describe('JWT bearer authorization grant integration', function () {
342342
.catch((e) => e.name.should.equal('server_error'));
343343
});
344344

345-
it('throws server_error when the issuer has no keys', async function () {
345+
it('throws server_error when the issuer has no key material (asymmetric alg)', async function () {
346346
await grant
347347
.getKey({}, { alg: 'RS256' })
348348
.then(() => {
349349
throw new Error('should throw');
350350
})
351351
.catch((e) => e.name.should.equal('server_error'));
352352
});
353+
354+
it('rejects (invalid_grant) an HS256 assertion when the issuer is asymmetric-only', async function () {
355+
await grant
356+
.getKey({ jwks: { keys: [] } }, { alg: 'HS256' })
357+
.then(() => {
358+
throw new Error('should throw');
359+
})
360+
.catch((e) => e.name.should.equal('invalid_grant'));
361+
});
362+
363+
it('rejects (invalid_grant) an RS256 assertion when the issuer is HMAC-only', async function () {
364+
await grant
365+
.getKey({ secret: 'shhh' }, { alg: 'RS256' })
366+
.then(() => {
367+
throw new Error('should throw');
368+
})
369+
.catch((e) => e.name.should.equal('invalid_grant'));
370+
});
353371
});
354372

355373
describe('constructor and argument guards', function () {

0 commit comments

Comments
 (0)