From cb449064d8792e54e655d58dc6f62589e6871a77 Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Fri, 27 Jul 2018 17:22:11 -0400 Subject: [PATCH 1/2] Added authorizeCodeGrant() to OAuthAuthenticator Added method to exchange an authorization code for a Bearer token containing the access_token & id_token. --- src/auth/OAuthAuthenticator.js | 69 ++++++++++++++++ test/auth/oauth.tests.js | 144 ++++++++++++++++++++++++++++++++- 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js index 125b8878e..1b96d4f36 100644 --- a/src/auth/OAuthAuthenticator.js +++ b/src/auth/OAuthAuthenticator.js @@ -243,4 +243,73 @@ OAuthAuthenticator.prototype.clientCredentialsGrant = function(options, cb) { return this.oauth.create(params, data); }; +/** + * Sign in using an authorization code + * + * @method authorizeCodeGrant + * @memberOf module:auth.OAuthAuthenticator.prototype + * + * @example + * Given the code returned in the URL params after the redirect + * from successful authentication, exchange the code for auth0 + * credentials. It will return JSON with the access_token and id_token. + * More information in the + * + * API Docs + * . + * + * + * var data = { + * code: '{CODE}', + * redirect_uri: '{REDIRECT_URI}', + * client_id: '{CLIENT_ID}', // Optional field. + * client_secret: '{CLIENT_SECRET}', // Optional field. + * }; + * + * auth0.oauth.authorizeCodeGrant(data, function (err, userData) { + * if (err) { + * // Handle error. + * } + * + * console.log(userData); + * }); + * + * @param {Object} data Authorization code payload + * @param {String} userData.code Code in URL returned after authentication + * @param {String} userData.redirect_uri The URL to which Auth0 will redirect the browser after authorization has been granted by the user. + * + * @return {Promise|undefined} + */ +OAuthAuthenticator.prototype.authorizeCodeGrant = function(options, cb) { + var params = { + type: 'token' + }; + + var defaultFields = { + grant_type: 'authorization_code', + client_id: this.clientId, + client_secret: this.clientSecret + }; + + var data = extend(defaultFields, options); + + if (!options || typeof options !== 'object') { + throw new ArgumentError('Missing options object'); + } + + if (!data.code || data.code.trim().length === 0) { + throw new ArgumentError('code field is required'); + } + + if (!data.redirect_uri || data.redirect_uri.trim().length === 0) { + throw new ArgumentError('redirect_uri field is required'); + } + + if (cb && cb instanceof Function) { + return this.oauth.create(params, data, cb); + } + + return this.oauth.create(params, data); +}; + module.exports = OAuthAuthenticator; diff --git a/test/auth/oauth.tests.js b/test/auth/oauth.tests.js index fdd5f3e83..0de3ca61f 100644 --- a/test/auth/oauth.tests.js +++ b/test/auth/oauth.tests.js @@ -38,7 +38,7 @@ describe('OAuthAuthenticator', function() { }); describe('instance', function() { - var methods = ['signIn', 'socialSignIn', 'passwordGrant']; + var methods = ['signIn', 'socialSignIn', 'passwordGrant', 'authorizeCodeGrant']; var authenticator = new Authenticator(validOptions); methods.forEach(function(method) { @@ -674,4 +674,146 @@ describe('OAuthAuthenticator', function() { }); }); }); + + describe('#authorizeCodeGrant', function() { + var path = '/oauth/token'; + var data = { + code: 'auth_code', + redirect_uri: API_URL + }; + + beforeEach(function() { + this.authenticator = new Authenticator(validOptions); + this.request = nock(API_URL) + .post(path) + .reply(200); + }); + + it('should require an object as first argument', function() { + expect(this.authenticator.authorizeCodeGrant).to.throw( + ArgumentError, + 'Missing options object' + ); + }); + + it('should require a code', function() { + var auth = this.authenticator; + var signIn = auth.authorizeCodeGrant.bind(auth, { redirect: API_URL }); + + expect(signIn).to.throw(ArgumentError, 'code field is required'); + }); + + it('should require a redirect_uri', function() { + var auth = this.authenticator; + var signIn = auth.authorizeCodeGrant.bind(auth, { code: 'auth_code' }); + + expect(signIn).to.throw(ArgumentError, 'redirect_uri field is required'); + }); + + it('should accept a callback', function(done) { + this.authenticator.authorizeCodeGrant(data, done.bind(null, null)); + }); + + it('should return a promise when no callback is provided', function(done) { + this.authenticator + .authorizeCodeGrant(data) + .then(done.bind(null, null)) + .catch(done.bind(null, null)); + }); + + it('should perform a POST request to ' + path, function(done) { + var request = this.request; + + this.authenticator + .authorizeCodeGrant(data) + .then(function() { + expect(request.isDone()).to.be.true; + + done(); + }) + .catch(done); + }); + + it('should include the data in the request', function(done) { + nock.cleanAll(); + + var request = nock(API_URL) + .post(path, function(body) { + for (var property in data) { + if (data[property] !== body[property]) { + return false; + } + } + + return true; + }) + .reply(200); + + this.authenticator + .authorizeCodeGrant(data) + .then(function() { + expect(request.isDone()).to.be.true; + + done(); + }) + .catch(done); + }); + + it('should include the Auth0 client ID in the request', function(done) { + nock.cleanAll(); + + var request = nock(API_URL) + .post(path, function(body) { + return body.client_id === CLIENT_ID; + }) + .reply(200); + + this.authenticator + .authorizeCodeGrant(data) + .then(function() { + expect(request.isDone()).to.be.true; + + done(); + }) + .catch(done); + }); + + it('should include the Auth0 client secret in the request', function(done) { + nock.cleanAll(); + + var request = nock(API_URL) + .post(path, function(body) { + return body.client_secret === CLIENT_SECRET; + }) + .reply(200); + + this.authenticator + .authorizeCodeGrant(data) + .then(function() { + expect(request.isDone()).to.be.true; + + done(); + }) + .catch(done); + }); + + it('should use authorization_code as default grant type', function(done) { + nock.cleanAll(); + + var request = nock(API_URL) + .post(path, function(body) { + return body.grant_type === 'authorization_code'; + }) + .reply(200); + + this.authenticator + .authorizeCodeGrant(data) + .then(function() { + expect(request.isDone()).to.be.true; + + done(); + }) + .catch(done); + }); + }); }); From 73bcd1347621391597dbf98fd5c00cb7e8ee266f Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Wed, 8 Aug 2018 14:33:39 -0400 Subject: [PATCH 2/2] Updated authorizeCodeGrant function name Changed the function name to authorizationCodeGrant for all usages and comments --- src/auth/OAuthAuthenticator.js | 6 +++--- test/auth/oauth.tests.js | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js index 631f07c6e..0af66bdf1 100644 --- a/src/auth/OAuthAuthenticator.js +++ b/src/auth/OAuthAuthenticator.js @@ -246,7 +246,7 @@ OAuthAuthenticator.prototype.clientCredentialsGrant = function(options, cb) { /** * Sign in using an authorization code * - * @method authorizeCodeGrant + * @method authorizationCodeGrant * @memberOf module:auth.OAuthAuthenticator.prototype * * @example @@ -266,7 +266,7 @@ OAuthAuthenticator.prototype.clientCredentialsGrant = function(options, cb) { * client_secret: '{CLIENT_SECRET}', // Optional field. * }; * - * auth0.oauth.authorizeCodeGrant(data, function (err, userData) { + * auth0.oauth.authorizationCodeGrant(data, function (err, userData) { * if (err) { * // Handle error. * } @@ -280,7 +280,7 @@ OAuthAuthenticator.prototype.clientCredentialsGrant = function(options, cb) { * * @return {Promise|undefined} */ -OAuthAuthenticator.prototype.authorizeCodeGrant = function(options, cb) { +OAuthAuthenticator.prototype.authorizationCodeGrant = function(options, cb) { var params = { type: 'token' }; diff --git a/test/auth/oauth.tests.js b/test/auth/oauth.tests.js index 0de3ca61f..03e652f1d 100644 --- a/test/auth/oauth.tests.js +++ b/test/auth/oauth.tests.js @@ -38,7 +38,7 @@ describe('OAuthAuthenticator', function() { }); describe('instance', function() { - var methods = ['signIn', 'socialSignIn', 'passwordGrant', 'authorizeCodeGrant']; + var methods = ['signIn', 'socialSignIn', 'passwordGrant', 'authorizationCodeGrant']; var authenticator = new Authenticator(validOptions); methods.forEach(function(method) { @@ -675,7 +675,7 @@ describe('OAuthAuthenticator', function() { }); }); - describe('#authorizeCodeGrant', function() { + describe('#authorizationCodeGrant', function() { var path = '/oauth/token'; var data = { code: 'auth_code', @@ -690,7 +690,7 @@ describe('OAuthAuthenticator', function() { }); it('should require an object as first argument', function() { - expect(this.authenticator.authorizeCodeGrant).to.throw( + expect(this.authenticator.authorizationCodeGrant).to.throw( ArgumentError, 'Missing options object' ); @@ -698,25 +698,25 @@ describe('OAuthAuthenticator', function() { it('should require a code', function() { var auth = this.authenticator; - var signIn = auth.authorizeCodeGrant.bind(auth, { redirect: API_URL }); + var signIn = auth.authorizationCodeGrant.bind(auth, { redirect: API_URL }); expect(signIn).to.throw(ArgumentError, 'code field is required'); }); it('should require a redirect_uri', function() { var auth = this.authenticator; - var signIn = auth.authorizeCodeGrant.bind(auth, { code: 'auth_code' }); + var signIn = auth.authorizationCodeGrant.bind(auth, { code: 'auth_code' }); expect(signIn).to.throw(ArgumentError, 'redirect_uri field is required'); }); it('should accept a callback', function(done) { - this.authenticator.authorizeCodeGrant(data, done.bind(null, null)); + this.authenticator.authorizationCodeGrant(data, done.bind(null, null)); }); it('should return a promise when no callback is provided', function(done) { this.authenticator - .authorizeCodeGrant(data) + .authorizationCodeGrant(data) .then(done.bind(null, null)) .catch(done.bind(null, null)); }); @@ -725,7 +725,7 @@ describe('OAuthAuthenticator', function() { var request = this.request; this.authenticator - .authorizeCodeGrant(data) + .authorizationCodeGrant(data) .then(function() { expect(request.isDone()).to.be.true; @@ -750,7 +750,7 @@ describe('OAuthAuthenticator', function() { .reply(200); this.authenticator - .authorizeCodeGrant(data) + .authorizationCodeGrant(data) .then(function() { expect(request.isDone()).to.be.true; @@ -769,7 +769,7 @@ describe('OAuthAuthenticator', function() { .reply(200); this.authenticator - .authorizeCodeGrant(data) + .authorizationCodeGrant(data) .then(function() { expect(request.isDone()).to.be.true; @@ -788,7 +788,7 @@ describe('OAuthAuthenticator', function() { .reply(200); this.authenticator - .authorizeCodeGrant(data) + .authorizationCodeGrant(data) .then(function() { expect(request.isDone()).to.be.true; @@ -807,7 +807,7 @@ describe('OAuthAuthenticator', function() { .reply(200); this.authenticator - .authorizeCodeGrant(data) + .authorizationCodeGrant(data) .then(function() { expect(request.isDone()).to.be.true;