Skip to content

Commit aff188b

Browse files
authored
Merge commit from fork
fix: block cross-app token injection via HS256 /userinfo fallback
2 parents 9cad7a6 + 560e793 commit aff188b

2 files changed

Lines changed: 32 additions & 174 deletions

File tree

src/web-auth/index.js

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,7 @@ function WebAuth(options) {
207207
*
208208
* Only validates id_tokens signed by Auth0 using the RS256 algorithm using the public key exposed
209209
* by the `/.well-known/jwks.json` endpoint of your account.
210-
* Tokens signed with the HS256 algorithm cannot be properly validated.
211-
* Instead, a call to {@link userInfo} will be made with the parsed `access_token`.
212-
* If the {@link userInfo} call fails, the {@link userInfo} error will be passed to the callback.
210+
* Tokens signed with the HS256 algorithm cannot be properly validated and will result in an error.
213211
* Tokens signed with other algorithms will not be accepted.
214212
*
215213
* @example
@@ -310,9 +308,7 @@ WebAuth.prototype.parseHash = function (options, cb) {
310308
*
311309
* Only validates id_tokens signed by Auth0 using the RS256 algorithm using the public key exposed
312310
* by the `/.well-known/jwks.json` endpoint of your account.
313-
* Tokens signed with the HS256 algorithm cannot be properly validated.
314-
* Instead, a call to {@link userInfo} will be made with the parsed `access_token`.
315-
* If the {@link userInfo} call fails, the {@link userInfo} error will be passed to the callback.
311+
* Tokens signed with the HS256 algorithm cannot be properly validated and will result in an error.
316312
* Tokens signed with other algorithms will not be accepted.
317313
*
318314
* @method validateAuthenticationResponse
@@ -454,56 +450,7 @@ WebAuth.prototype.validateAuthenticationResponse = function (
454450
);
455451
}
456452

457-
if (
458-
validationError.error !== 'invalid_token' ||
459-
(validationError.errorDescription &&
460-
validationError.errorDescription.indexOf(
461-
'Nonce (nonce) claim value mismatch in the ID token'
462-
) > -1)
463-
) {
464-
return callback(validationError);
465-
}
466-
467-
// if it's an invalid_token error, decode the token
468-
var decodedToken = new IdTokenVerifier().decode(parsedHash.id_token);
469-
470-
// if the alg is not HS256, return the raw error
471-
if (decodedToken.header.alg !== 'HS256') {
472-
return callback(validationError);
473-
}
474-
475-
if ((decodedToken.payload.nonce || null) !== transactionNonce) {
476-
return callback({
477-
error: 'invalid_token',
478-
errorDescription:
479-
'Nonce (nonce) claim value mismatch in the ID token; expected "' +
480-
transactionNonce +
481-
'", found "' +
482-
decodedToken.payload.nonce +
483-
'"'
484-
});
485-
}
486-
487-
if (!parsedHash.access_token) {
488-
var noAccessTokenError = {
489-
error: 'invalid_token',
490-
description:
491-
'The id_token cannot be validated because it was signed with the HS256 algorithm and public clients (like a browser) can’t store secrets. Please read the associated doc for possible ways to fix this. Read more: https://auth0.com/docs/errors/libraries/auth0-js/invalid-token#parsing-an-hs256-signed-id-token-without-an-access-token'
492-
};
493-
return callback(noAccessTokenError);
494-
}
495-
496-
// if the alg is HS256, use the /userinfo endpoint to build the payload
497-
return _this.client.userInfo(
498-
parsedHash.access_token,
499-
function (errUserInfo, profile) {
500-
// if the /userinfo request fails, use the validationError instead
501-
if (errUserInfo) {
502-
return callback(errUserInfo);
503-
}
504-
return callback(null, profile);
505-
}
506-
);
453+
return callback(validationError);
507454
}
508455
);
509456
};

test/web-auth/web-auth.test.js

Lines changed: 29 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,101 +1547,59 @@ describe('auth0.WebAuth', function () {
15471547
});
15481548

15491549
afterEach(function () {
1550-
if (this.webAuth.client.userInfo.restore) {
1551-
this.webAuth.client.userInfo.restore();
1552-
}
15531550
if (IdTokenVerifier.prototype.verify.restore) {
15541551
IdTokenVerifier.prototype.verify.restore();
15551552
}
15561553
});
15571554

1558-
it('should use result from /userinfo as idTokenPayload', function (done) {
1555+
it('should return a validation error instead of falling back to /userinfo', function (done) {
15591556
sinon
1560-
.stub(this.webAuth.client, 'userInfo')
1561-
.callsFake(function (accessToken, cb) {
1562-
expect(accessToken).to.be('VjubIMBmpgQ2W2');
1563-
cb(null, { from: 'userinfo' });
1557+
.stub(IdTokenVerifier.prototype, 'verify')
1558+
.callsFake(function (_, __, cb) {
1559+
cb({ message: 'Signature verification failed' });
15641560
});
15651561

15661562
this.webAuth.parseHash(
15671563
{
15681564
nonce: 'the-nonce',
1569-
hash: '#state=foo&access_token=VjubIMBmpgQ2W2&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjA4ODU1NzgsImV4cCI6MTU5MjQyMTU3OCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5vbmNlIjoidGhlLW5vbmNlIn0.jb9aG21kGibxKPIyfn8FfvjQ3ykJGiBGcep2hDHHfqk&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
1565+
hash: '#state=foo&access_token=VjubIMBmpgQ2W2&id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJhdWQiOiJpeGVPSEZoRDdOU1B4RVFLNkNGY3N3alVzYTVZa2NYUyIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJub25jZSI6InRoZS1ub25jZSIsImlhdCI6MTU2MDg4NTU3OCwiZXhwIjo0NTkyNDIxNTc4fQ.79KMRcgyMPF0IGv-bTv58pb6HCSRy8fDjcLgDRDqeFc&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
15701566
},
15711567
function (err, data) {
1572-
expect(err).to.be(null);
1573-
expect(data).to.be.eql({
1574-
accessToken: 'VjubIMBmpgQ2W2',
1575-
idToken:
1576-
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjA4ODU1NzgsImV4cCI6MTU5MjQyMTU3OCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5vbmNlIjoidGhlLW5vbmNlIn0.jb9aG21kGibxKPIyfn8FfvjQ3ykJGiBGcep2hDHHfqk',
1577-
idTokenPayload: { from: 'userinfo' },
1578-
appState: null,
1579-
refreshToken: 'kajshdgfkasdjhgfas',
1580-
state: 'foo',
1581-
expiresIn: null,
1582-
tokenType: 'Bearer',
1583-
scope: null
1568+
expect(err).to.be.eql({
1569+
error: 'invalid_token',
1570+
errorDescription: 'Signature verification failed'
15841571
});
1572+
expect(data).to.be(undefined);
15851573
done();
15861574
}
15871575
);
15881576
});
15891577

1590-
it('should not throw an error when the payload.nonce is undefined and transactionNonce is null', function (done) {
1591-
TransactionManager.prototype.getStoredTransaction.restore();
1592-
sinon
1593-
.stub(TransactionManager.prototype, 'getStoredTransaction')
1594-
.callsFake(function () {
1595-
return {
1596-
nonce: null,
1597-
state: 'foo'
1598-
};
1599-
});
1600-
var webAuth = new WebAuth({
1601-
domain: 'auth0-tests-lock.auth0.com',
1602-
redirectUri: 'http://example.com/callback',
1603-
clientID: 'ixeOHFhD7NSPxEQK6CFcswjUsa5YkcXS',
1604-
responseType: 'id_token'
1605-
});
1606-
1607-
sinon
1608-
.stub(webAuth.client, 'userInfo')
1609-
.callsFake(function (accessToken, cb) {
1610-
expect(accessToken).to.be('VjubIMBmpgQ2W2');
1611-
cb(null, { from: 'userinfo' });
1612-
});
1613-
1578+
it('should not call /userinfo when id_token verification fails', function (done) {
16141579
sinon
16151580
.stub(IdTokenVerifier.prototype, 'verify')
16161581
.callsFake(function (_, __, cb) {
1617-
cb({ error: true });
1582+
cb({ message: 'Signature verification failed' });
16181583
});
16191584

1620-
//nonce: undefined
1621-
webAuth.parseHash(
1585+
var userInfoSpy = sinon.spy(this.webAuth.client, 'userInfo');
1586+
1587+
this.webAuth.parseHash(
16221588
{
1623-
hash: '#state=foo&access_token=VjubIMBmpgQ2W2&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjE2NjM3ODMsImV4cCI6MTU5MzE5OTc4MywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSJ9.Hoq1Go3McuHgSMg9rWVxQsEenoDWYi5MEumc32Ah9CQ&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
1589+
nonce: 'the-nonce',
1590+
hash: '#state=foo&access_token=VjubIMBmpgQ2W2&id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJhdWQiOiJpeGVPSEZoRDdOU1B4RVFLNkNGY3N3alVzYTVZa2NYUyIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJub25jZSI6InRoZS1ub25jZSIsImlhdCI6MTU2MDg4NTU3OCwiZXhwIjo0NTkyNDIxNTc4fQ.79KMRcgyMPF0IGv-bTv58pb6HCSRy8fDjcLgDRDqeFc&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
16241591
},
16251592
function (err, data) {
1626-
expect(err).to.be(null);
1627-
expect(data).to.be.eql({
1628-
accessToken: 'VjubIMBmpgQ2W2',
1629-
idToken:
1630-
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjE2NjM3ODMsImV4cCI6MTU5MzE5OTc4MywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSJ9.Hoq1Go3McuHgSMg9rWVxQsEenoDWYi5MEumc32Ah9CQ',
1631-
idTokenPayload: { from: 'userinfo' },
1632-
appState: null,
1633-
refreshToken: 'kajshdgfkasdjhgfas',
1634-
state: 'foo',
1635-
expiresIn: null,
1636-
tokenType: 'Bearer',
1637-
scope: null
1638-
});
1593+
expect(err).to.not.be(null);
1594+
expect(err.error).to.be('invalid_token');
1595+
expect(userInfoSpy.called).to.be(false);
1596+
userInfoSpy.restore();
16391597
done();
16401598
}
16411599
);
16421600
});
16431601

1644-
it('should still throw an error with an invalid nonce', function (done) {
1602+
it('should return validation error for HS256 token without access_token', function (done) {
16451603
var webAuth = new WebAuth({
16461604
domain: 'auth0-tests-lock.auth0.com',
16471605
redirectUri: 'http://example.com/callback',
@@ -1651,20 +1609,20 @@ describe('auth0.WebAuth', function () {
16511609
sinon
16521610
.stub(IdTokenVerifier.prototype, 'verify')
16531611
.callsFake(function (_, __, cb) {
1654-
cb({ error: true });
1612+
cb({ message: 'Signature verification failed' });
16551613
});
16561614

1657-
//nonce: the-nonce
16581615
webAuth.parseHash(
16591616
{
1660-
hash: '#state=foo&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjA4ODU1NzgsImV4cCI6MTU5MjQyMTU3OCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5vbmNlIjoidGhlLW5vbmNlIn0.jb9aG21kGibxKPIyfn8FfvjQ3ykJGiBGcep2hDHHfqk&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
1617+
nonce: 'the-nonce',
1618+
hash: '#state=foo&id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJhdWQiOiJpeGVPSEZoRDdOU1B4RVFLNkNGY3N3alVzYTVZa2NYUyIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJub25jZSI6InRoZS1ub25jZSIsImlhdCI6MTU2MDg4NTU3OCwiZXhwIjo0NTkyNDIxNTc4fQ.79KMRcgyMPF0IGv-bTv58pb6HCSRy8fDjcLgDRDqeFc&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
16611619
},
16621620
function (err, data) {
16631621
expect(err).to.be.eql({
16641622
error: 'invalid_token',
1665-
errorDescription:
1666-
'Nonce (nonce) claim value mismatch in the ID token; expected "asfd", found "the-nonce"'
1623+
errorDescription: 'Signature verification failed'
16671624
});
1625+
expect(data).to.be(undefined);
16681626
done();
16691627
}
16701628
);
@@ -1680,13 +1638,12 @@ describe('auth0.WebAuth', function () {
16801638
sinon
16811639
.stub(IdTokenVerifier.prototype, 'verify')
16821640
.callsFake(function (_, __, cb) {
1683-
cb({ error: true });
1641+
cb({ message: 'Signature verification failed' });
16841642
});
16851643

1686-
//nonce: the-nonce
16871644
webAuth.parseHash(
16881645
{
1689-
hash: '#state=ignore-test-state-check&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjA4ODU1NzgsImV4cCI6MTU5MjQyMTU3OCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5vbmNlIjoidGhlLW5vbmNlIn0.jb9aG21kGibxKPIyfn8FfvjQ3ykJGiBGcep2hDHHfqk&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
1646+
hash: '#state=ignore-test-state-check&id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJhdWQiOiJpeGVPSEZoRDdOU1B4RVFLNkNGY3N3alVzYTVZa2NYUyIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJub25jZSI6InRoZS1ub25jZSIsImlhdCI6MTU2MDg4NTU3OCwiZXhwIjo0NTkyNDIxNTc4fQ.79KMRcgyMPF0IGv-bTv58pb6HCSRy8fDjcLgDRDqeFc&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
16901647
},
16911648
function (err, data) {
16921649
expect(err).to.be.eql({
@@ -1697,52 +1654,6 @@ describe('auth0.WebAuth', function () {
16971654
}
16981655
);
16991656
});
1700-
it('should throw an error when there is no access_token to call /userinfo', function (done) {
1701-
var webAuth = new WebAuth({
1702-
domain: 'auth0-tests-lock.auth0.com',
1703-
redirectUri: 'http://example.com/callback',
1704-
clientID: 'ixeOHFhD7NSPxEQK6CFcswjUsa5YkcXS',
1705-
responseType: 'id_token'
1706-
});
1707-
sinon
1708-
.stub(webAuth.client, 'userInfo')
1709-
.callsFake(function (accessToken, cb) {
1710-
cb({ any: 'error' });
1711-
});
1712-
1713-
webAuth.parseHash(
1714-
{
1715-
nonce: 'the-nonce',
1716-
hash: '#state=foo&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjA4ODU1NzgsImV4cCI6MTU5MjQyMTU3OCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5vbmNlIjoidGhlLW5vbmNlIn0.jb9aG21kGibxKPIyfn8FfvjQ3ykJGiBGcep2hDHHfqk&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
1717-
},
1718-
function (err, data) {
1719-
expect(err).to.be.eql({
1720-
error: 'invalid_token',
1721-
description:
1722-
'The id_token cannot be validated because it was signed with the HS256 algorithm and public clients (like a browser) can’t store secrets. Please read the associated doc for possible ways to fix this. Read more: https://auth0.com/docs/errors/libraries/auth0-js/invalid-token#parsing-an-hs256-signed-id-token-without-an-access-token'
1723-
});
1724-
done();
1725-
}
1726-
);
1727-
});
1728-
it('should throw original userinfo error when /userinfo call has an error', function (done) {
1729-
sinon
1730-
.stub(this.webAuth.client, 'userInfo')
1731-
.callsFake(function (accessToken, cb) {
1732-
cb({ any: 'error' });
1733-
});
1734-
1735-
this.webAuth.parseHash(
1736-
{
1737-
nonce: 'the-nonce',
1738-
hash: '#state=foo&access_token=VjubIMBmpgQ2W2&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1NjA4ODU1NzgsImV4cCI6MTU5MjQyMTU3OCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5vbmNlIjoidGhlLW5vbmNlIn0.jb9aG21kGibxKPIyfn8FfvjQ3ykJGiBGcep2hDHHfqk&token_type=Bearer&refresh_token=kajshdgfkasdjhgfas'
1739-
},
1740-
function (err, data) {
1741-
expect(err).to.be.eql({ any: 'error' });
1742-
done();
1743-
}
1744-
);
1745-
});
17461657
});
17471658
});
17481659

@@ -3499,7 +3410,7 @@ describe('auth0.WebAuth', function () {
34993410
expect(function () {
35003411
auth0.customTokenExchange(
35013412
{ subjectTokenType: 'urn:acme:token' },
3502-
function () {}
3413+
function () { }
35033414
);
35043415
}).to.throwException(function (e) {
35053416
expect(e.message).to.be('subjectToken option is required');

0 commit comments

Comments
 (0)