From 9a52fcd1a2e40a21a2ca150f41310fe9913563ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Rudge?= Date: Thu, 22 Mar 2018 15:47:50 -0300 Subject: [PATCH] Adding access_token validation for RS256 id_token's (#709) * validating tokens according to response_type and access_tokens * add missing test * test naming --- example/index.html | 2 +- package.json | 2 +- src/helper/error.js | 4 +- src/web-auth/index.js | 19 ++++- test/plugins/cordova.test.js | 5 ++ test/web-auth/web-auth.test.js | 127 ++++++++++++++++++++++++++++++++- yarn.lock | 6 +- 7 files changed, 154 insertions(+), 11 deletions(-) diff --git a/example/index.html b/example/index.html index 8b3691c8..98b47ead 100644 --- a/example/index.html +++ b/example/index.html @@ -203,7 +203,7 @@

Console:

domain: 'brucke.auth0.com', redirectUri: 'https://localhost:3000/example/', clientID: 'k5u3o2fiAA8XweXEEX604KCwCjzjtMU6', - responseType: 'token', + responseType: 'token id_token', plugins: [ new CordovaAuth0Plugin() ] diff --git a/package.json b/package.json index a1884b01..a588d74a 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "license": "MIT", "dependencies": { "base64-js": "^1.2.0", - "idtoken-verifier": "^1.1.2", + "idtoken-verifier": "^1.2.0", "qs": "^6.4.0", "superagent": "^3.8.2", "url-join": "^1.1.0", diff --git a/src/helper/error.js b/src/helper/error.js index c146e4c4..bf367424 100644 --- a/src/helper/error.js +++ b/src/helper/error.js @@ -5,11 +5,11 @@ function buildResponse(error, description) { }; } -function invalidJwt(description) { +function invalidToken(description) { return buildResponse('invalid_token', description); } module.exports = { buildResponse: buildResponse, - invalidJwt: invalidJwt + invalidToken: invalidToken }; diff --git a/src/web-auth/index.js b/src/web-auth/index.js index 0b4e20e5..af373df6 100644 --- a/src/web-auth/index.js +++ b/src/web-auth/index.js @@ -246,7 +246,22 @@ WebAuth.prototype.validateAuthenticationResponse = function(options, parsedHash, payload ) { if (!validationError) { - return callback(null, payload); + if (!parsedHash.access_token) { + return callback(null, payload); + } + // here we're absolutely sure that the id_token's alg is RS256 + // and that the id_token is valid, so we can check the access_token + return new IdTokenVerifier().validateAccessToken( + parsedHash.access_token, + 'RS256', + payload.at_hash, + function(err) { + if (err) { + return callback(error.invalidToken(err.message)); + } + return callback(null, payload); + } + ); } if (validationError.error !== 'invalid_token') { return callback(validationError); @@ -307,7 +322,7 @@ WebAuth.prototype.validateToken = function(token, nonce, cb) { verifier.verify(token, nonce, function(err, payload) { if (err) { - return cb(error.invalidJwt(err.message)); + return cb(error.invalidToken(err.message)); } cb(null, payload); diff --git a/test/plugins/cordova.test.js b/test/plugins/cordova.test.js index 400870c8..b03f8410 100644 --- a/test/plugins/cordova.test.js +++ b/test/plugins/cordova.test.js @@ -1,3 +1,4 @@ +var IdTokenVerifier = require('idtoken-verifier'); var expect = require('expect.js'); var stub = require('sinon').stub; @@ -100,6 +101,9 @@ describe('auth0.plugins.cordova', function() { context('PopupHandler', function() { beforeEach(function() { + stub(IdTokenVerifier.prototype, 'validateAccessToken', function(at, alg, atHash, cb) { + cb(null); + }); var _this = this; this.events = {}; var webAuth = new WebAuth({ @@ -132,6 +136,7 @@ describe('auth0.plugins.cordova', function() { }); afterEach(function() { + IdTokenVerifier.prototype.validateAccessToken.restore(); delete global.window; this.events = null; this.popupHandler = null; diff --git a/test/web-auth/web-auth.test.js b/test/web-auth/web-auth.test.js index 4836d303..05740594 100644 --- a/test/web-auth/web-auth.test.js +++ b/test/web-auth/web-auth.test.js @@ -2,6 +2,7 @@ var expect = require('expect.js'); var stub = require('sinon').stub; var spy = require('sinon').spy; var request = require('superagent'); +var IdTokenVerifier = require('idtoken-verifier'); var storage = require('../../src/helper/storage'); var windowHelper = require('../../src/helper/window'); @@ -271,9 +272,18 @@ describe('auth0.WebAuth', function() { beforeEach(function() { spy(ssodata, 'set'); + stub(IdTokenVerifier.prototype, 'validateAccessToken', function(at, alg, atHash, cb) { + cb(null); + }); }); afterEach(function() { ssodata.set.restore(); + if (IdTokenVerifier.prototype.validateAccessToken.restore) { + IdTokenVerifier.prototype.validateAccessToken.restore(); + } + if (WebAuth.prototype.validateToken.restore) { + WebAuth.prototype.validateToken.restore(); + } }); it('should parse a valid hash without id_token', function(done) { @@ -307,6 +317,120 @@ describe('auth0.WebAuth', function() { } ); // eslint-disable-line }); + it('should return the id_token payload when there is no access_token', function(done) { + var webAuth = new WebAuth({ + domain: 'brucke.auth0.com', + redirectUri: 'http://example.com/callback', + clientID: 'k5u3o2fiAA8XweXEEX604KCwCjzjtMU6', + responseType: 'id_token', + __disableExpirationCheck: true + }); + + var data = webAuth.parseHash( + { + hash: '#state=foo&token_type=Bearer&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wMy0xNFQxNjozNDo1Ni40MjNaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6Ims1dTNvMmZpQUE4WHdlWEVFWDYwNEtDd0Nqemp0TVU2IiwiaWF0IjoxNTIxMDQ1Mjk2LCJleHAiOjE1MjEwODEyOTYsImF0X2hhc2giOiJjZHVrb2FVc3dNOWJvX3l6cmdWY3J3Iiwibm9uY2UiOiJsRkNuSTguY3JSVGRIZmRvNWsuek1YZlIzMTg1NmdLeiJ9.U4_F5Zw6xYVoHGiiem1wjz7i9eRaSOrt-L1e6hlu3wmqA-oNuVqf1tEYD9u0z5AbXXbQSr491A3VvUbLKjws13XETcljhaqigZ9q4HBpmzPlrUGmPreBLVQgGOaq5NVAViFTvORxYCMFLlc-SE6QI6xWF0AhFpoW7-hkOcOzXWAXqhkMgwAfjJ9aeOzSBgblmtx4duyNESBRefd3XPQrakWjGIqH3dFdc-lDFbY76eSLYfBi4AH-yim4egzB6LYOC-e2huZcHdmRAmEQaKZ7D7COBiGsgAPVGyjZtqfSQ2CRwNrAbxDwi8BqlLhQePOs6d3hqV-3OPLfdE6dUFh2DQ', + nonce: 'lFCnI8.crRTdHfdo5k.zMXfR31856gKz' + }, + function(err, data) { + expect(err).to.be(null); + expect(data).to.be.eql({ + accessToken: null, + idToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wMy0xNFQxNjozNDo1Ni40MjNaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6Ims1dTNvMmZpQUE4WHdlWEVFWDYwNEtDd0Nqemp0TVU2IiwiaWF0IjoxNTIxMDQ1Mjk2LCJleHAiOjE1MjEwODEyOTYsImF0X2hhc2giOiJjZHVrb2FVc3dNOWJvX3l6cmdWY3J3Iiwibm9uY2UiOiJsRkNuSTguY3JSVGRIZmRvNWsuek1YZlIzMTg1NmdLeiJ9.U4_F5Zw6xYVoHGiiem1wjz7i9eRaSOrt-L1e6hlu3wmqA-oNuVqf1tEYD9u0z5AbXXbQSr491A3VvUbLKjws13XETcljhaqigZ9q4HBpmzPlrUGmPreBLVQgGOaq5NVAViFTvORxYCMFLlc-SE6QI6xWF0AhFpoW7-hkOcOzXWAXqhkMgwAfjJ9aeOzSBgblmtx4duyNESBRefd3XPQrakWjGIqH3dFdc-lDFbY76eSLYfBi4AH-yim4egzB6LYOC-e2huZcHdmRAmEQaKZ7D7COBiGsgAPVGyjZtqfSQ2CRwNrAbxDwi8BqlLhQePOs6d3hqV-3OPLfdE6dUFh2DQ', + idTokenPayload: { + nickname: 'johnfoo', + name: 'johnfoo@gmail.com', + picture: 'https://s.gravatar.com/avatar/38fa002423bd8c941c6ed0588b60ffed?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fjo.png', + updated_at: '2018-03-14T16:34:56.423Z', + email: 'johnfoo@gmail.com', + email_verified: false, + iss: 'https://brucke.auth0.com/', + sub: 'auth0|5a2054ff45157711be8182f4', + aud: 'k5u3o2fiAA8XweXEEX604KCwCjzjtMU6', + iat: 1521045296, + exp: 1521081296, + at_hash: 'cdukoaUswM9bo_yzrgVcrw', + nonce: 'lFCnI8.crRTdHfdo5k.zMXfR31856gKz' + }, + appState: null, + refreshToken: null, + state: 'foo', + expiresIn: null, + tokenType: 'Bearer', + scope: null + }); + done(); + } + ); // eslint-disable-line + }); + it('should return the id_token payload when there is a valid access_token', function(done) { + var webAuth = new WebAuth({ + domain: 'brucke.auth0.com', + redirectUri: 'http://example.com/callback', + clientID: 'k5u3o2fiAA8XweXEEX604KCwCjzjtMU6', + responseType: 'token id_token', + __disableExpirationCheck: true + }); + + var data = webAuth.parseHash( + { + hash: '#state=foo&token_type=Bearer&access_token=L11oiFDHj3zmZid1AnsEuggXcMfjqe0X&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wMy0yMVQyMDowNDo0Mi40OTNaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6Ims1dTNvMmZpQUE4WHdlWEVFWDYwNEtDd0Nqemp0TVU2IiwiaWF0IjoxNTIxNjYyNjgyLCJleHAiOjE1MjE2OTg2ODIsImF0X2hhc2giOiJKS2NaM3hTQ2NGVEE5NkxuQ3lJX0FRIiwibm9uY2UiOiJLdlhoc1VIc2VJSEl5emF1X2JVflJHQ2t1RUFDTE5HaiJ9.UbiWFikCkoX-m22mFnXJhKMY8M9BGMDJqZZ5J-iUAQwOmD-33-zX-AjSbD6zL6sOJoKJratJLtLa90tE3sDeokI9c8GE_JonfeF95knVPAx99tD5eCIJabV8HN_K1rfcgI_ed9v8RKQD9_dRkwUMHgXyceWeijnA9k8jG-pe1iXAtnn386G5s6fj-do8SUvC2MFWNmD5VhkW-CyEg_Chui8BoOSM9d7liMZRQkgKA2aGl5t2qqvOu0ZNJwaWoeQ5T0R-h2Yk6Om_alFKyLdZXsZY2LRYQdbk4nEgxY59241HPZGHYOTJN5uLlbcxKyouTyM7Gt4dE76wyRh9kBr47A', + nonce: 'KvXhsUHseIHIyzau_bU~RGCkuEACLNGj' + }, + function(err, data) { + expect(err).to.be(null); + expect(data).to.be.eql({ + accessToken: 'L11oiFDHj3zmZid1AnsEuggXcMfjqe0X', + idToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wMy0yMVQyMDowNDo0Mi40OTNaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6Ims1dTNvMmZpQUE4WHdlWEVFWDYwNEtDd0Nqemp0TVU2IiwiaWF0IjoxNTIxNjYyNjgyLCJleHAiOjE1MjE2OTg2ODIsImF0X2hhc2giOiJKS2NaM3hTQ2NGVEE5NkxuQ3lJX0FRIiwibm9uY2UiOiJLdlhoc1VIc2VJSEl5emF1X2JVflJHQ2t1RUFDTE5HaiJ9.UbiWFikCkoX-m22mFnXJhKMY8M9BGMDJqZZ5J-iUAQwOmD-33-zX-AjSbD6zL6sOJoKJratJLtLa90tE3sDeokI9c8GE_JonfeF95knVPAx99tD5eCIJabV8HN_K1rfcgI_ed9v8RKQD9_dRkwUMHgXyceWeijnA9k8jG-pe1iXAtnn386G5s6fj-do8SUvC2MFWNmD5VhkW-CyEg_Chui8BoOSM9d7liMZRQkgKA2aGl5t2qqvOu0ZNJwaWoeQ5T0R-h2Yk6Om_alFKyLdZXsZY2LRYQdbk4nEgxY59241HPZGHYOTJN5uLlbcxKyouTyM7Gt4dE76wyRh9kBr47A', + idTokenPayload: { + nickname: 'johnfoo', + name: 'johnfoo@gmail.com', + picture: 'https://s.gravatar.com/avatar/38fa002423bd8c941c6ed0588b60ffed?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fjo.png', + updated_at: '2018-03-21T20:04:42.493Z', + email: 'johnfoo@gmail.com', + email_verified: false, + iss: 'https://brucke.auth0.com/', + sub: 'auth0|5a2054ff45157711be8182f4', + aud: 'k5u3o2fiAA8XweXEEX604KCwCjzjtMU6', + iat: 1521662682, + exp: 1521698682, + at_hash: 'JKcZ3xSCcFTA96LnCyI_AQ', + nonce: 'KvXhsUHseIHIyzau_bU~RGCkuEACLNGj' + }, + appState: null, + refreshToken: null, + state: 'foo', + expiresIn: null, + tokenType: 'Bearer', + scope: null + }); + done(); + } + ); // eslint-disable-line + }); + it('should validate an access_token when available', function(done) { + var webAuth = new WebAuth({ + domain: 'brucke.auth0.com', + redirectUri: 'http://example.com/callback', + clientID: 'k5u3o2fiAA8XweXEEX604KCwCjzjtMU6', + responseType: 'token id_token', + __disableExpirationCheck: true + }); + IdTokenVerifier.prototype.validateAccessToken.restore(); + + var data = webAuth.parseHash( + { + hash: '#state=foo&token_type=Bearer&access_token=YTvJcYrrZYHUXLZK5leLnfmD5ZIA_EA&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FVkJOVU5CT1RneFJrRTVOa1F6UXpjNE9UQkVNRUZGUkRRNU4wUTJRamswUmtRMU1qRkdNUSJ9.eyJuaWNrbmFtZSI6ImpvaG5mb28iLCJuYW1lIjoiam9obmZvb0BnbWFpbC5jb20iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLmdyYXZhdGFyLmNvbS9hdmF0YXIvMzhmYTAwMjQyM2JkOGM5NDFjNmVkMDU4OGI2MGZmZWQ_cz00ODAmcj1wZyZkPWh0dHBzJTNBJTJGJTJGY2RuLmF1dGgwLmNvbSUyRmF2YXRhcnMlMkZqby5wbmciLCJ1cGRhdGVkX2F0IjoiMjAxOC0wMy0xNFQxNjozNDo1Ni40MjNaIiwiZW1haWwiOiJqb2huZm9vQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9icnVja2UuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVhMjA1NGZmNDUxNTc3MTFiZTgxODJmNCIsImF1ZCI6Ims1dTNvMmZpQUE4WHdlWEVFWDYwNEtDd0Nqemp0TVU2IiwiaWF0IjoxNTIxMDQ1Mjk2LCJleHAiOjE1MjEwODEyOTYsImF0X2hhc2giOiJjZHVrb2FVc3dNOWJvX3l6cmdWY3J3Iiwibm9uY2UiOiJsRkNuSTguY3JSVGRIZmRvNWsuek1YZlIzMTg1NmdLeiJ9.U4_F5Zw6xYVoHGiiem1wjz7i9eRaSOrt-L1e6hlu3wmqA-oNuVqf1tEYD9u0z5AbXXbQSr491A3VvUbLKjws13XETcljhaqigZ9q4HBpmzPlrUGmPreBLVQgGOaq5NVAViFTvORxYCMFLlc-SE6QI6xWF0AhFpoW7-hkOcOzXWAXqhkMgwAfjJ9aeOzSBgblmtx4duyNESBRefd3XPQrakWjGIqH3dFdc-lDFbY76eSLYfBi4AH-yim4egzB6LYOC-e2huZcHdmRAmEQaKZ7D7COBiGsgAPVGyjZtqfSQ2CRwNrAbxDwi8BqlLhQePOs6d3hqV-3OPLfdE6dUFh2DQ', + nonce: 'lFCnI8.crRTdHfdo5k.zMXfR31856gKz' + }, + function(err, data) { + expect(err).to.be.eql({ + error: 'invalid_token', + errorDescription: 'Invalid access_token' + }); + done(); + } + ); // eslint-disable-line + }); context('when there is a transaction', function() { it('should return transaction.appState', function(done) { @@ -824,7 +948,7 @@ describe('auth0.WebAuth', function() { domain: 'mdocs_2.auth0.com', redirectUri: 'http://example.com/callback', clientID: '0HP71GSd6PuoRYJ3DXKdiXCUUdGmBbup', - responseType: 'token' + responseType: 'id_token' }); var data = webAuth.parseHash( @@ -880,7 +1004,6 @@ describe('auth0.WebAuth', function() { }, function(err, data) { expect(err).to.be.eql(expectedError); - WebAuth.prototype.validateToken.restore(); done(); } ); diff --git a/yarn.lock b/yarn.lock index bdcbf396..15fc2fdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1956,9 +1956,9 @@ husky@^0.13.3: is-ci "^1.0.9" normalize-path "^1.0.0" -idtoken-verifier@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-1.1.2.tgz#bd5125aaccc221c1e0c57c9393dc68c0f7134b69" +idtoken-verifier@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-1.2.0.tgz#4654f1f07ab7a803fc9b1b8b36057e2a87ad8b09" dependencies: base64-js "^1.2.0" crypto-js "^3.1.9-1"