diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js index 0d2b33d81..d8df93582 100644 --- a/src/auth/OAuthAuthenticator.js +++ b/src/auth/OAuthAuthenticator.js @@ -179,6 +179,60 @@ OAuthAuthenticator.prototype.passwordGrant = function(userData, cb) { return this.oauthWithIDTokenValidation.create(params, data); }; +/** + * Sign in using a refresh token + * + * @method refreshToken + * @memberOf module:auth.OAuthAuthenticator.prototype + * + * @example + * Given a refresh token from a previous authentication request + * it will return a JSON with the access_token and id_token. + * More information in the + * + * API Docs + * . + * + * + * var data = { + * client_id: '{CLIENT_ID}', // Optional field. + * refresh_token: '{REFRESH_TOKEN}', + * }; + * + * auth0.oauth.refreshToken(data, function (err, userData) { + * if (err) { + * // Handle error. + * } + * + * console.log(userData); + * }); + * + * @param {Object} userData User credentials object. + * @param {String} userData.refresh_token Refresh token. + * + * @return {Promise|undefined} + */ +OAuthAuthenticator.prototype.refreshToken = function(userData, cb) { + var params = { + type: 'token' + }; + var defaultFields = { + client_id: this.clientId, + grant_type: 'refresh_token' + }; + var data = extend(defaultFields, userData); + if (!userData || typeof userData !== 'object') { + throw new ArgumentError('Missing user data object'); + } + if (typeof data.refresh_token !== 'string' || data.refresh_token.split().length === 0) { + throw new ArgumentError('refresh_token is required'); + } + if (cb && cb instanceof Function) { + return this.oauthWithIDTokenValidation.create(params, data, cb); + } + return this.oauthWithIDTokenValidation.create(params, data); +}; + /** * Sign in using a social provider access token. * diff --git a/src/auth/index.js b/src/auth/index.js index fa410aa9c..acb70f253 100644 --- a/src/auth/index.js +++ b/src/auth/index.js @@ -543,4 +543,39 @@ utils.wrapPropertyMethod( */ utils.wrapPropertyMethod(AuthenticationClient, 'passwordGrant', 'oauth.passwordGrant'); +/** + * Sign in using a refresh token + * + * @method refreshToken + * @memberOf module:auth.AuthenticationClient.prototype + * + * @example + * Given a refresh token from a previous authentication request, + * it will return a JSON with the access_token and id_token. + * More information in the + * + * API Docs + * . + * + * + * var data = { + * client_id: '{CLIENT_ID}', // Optional field. + * refresh_token: '{REFRESH_TOKEN}', + * }; + * + * auth0.refreshToken(data, function (err, userData) { + * if (err) { + * // Handle error. + * } + * + * console.log(userData); + * }); + * + * @param {Object} userData User credentials object. + * @param {String} userData.refresh_token Refresh token. + * + * @return {Promise|undefined} + */ +utils.wrapPropertyMethod(AuthenticationClient, 'refreshToken', 'oauth.refreshToken'); + module.exports = AuthenticationClient; diff --git a/test/auth/oauth.tests.js b/test/auth/oauth.tests.js index 3dcb436a2..f91d4104e 100644 --- a/test/auth/oauth.tests.js +++ b/test/auth/oauth.tests.js @@ -44,7 +44,13 @@ describe('OAuthAuthenticator', function() { }); describe('instance', function() { - var methods = ['signIn', 'socialSignIn', 'passwordGrant', 'authorizationCodeGrant']; + var methods = [ + 'signIn', + 'socialSignIn', + 'passwordGrant', + 'authorizationCodeGrant', + 'refreshToken' + ]; var authenticator = new Authenticator(validOptions); methods.forEach(function(method) { @@ -405,6 +411,96 @@ describe('OAuthAuthenticator', function() { }); }); + describe('#refreshToken', function() { + var path = '/oauth/token'; + var userData = { + refresh_token: 'refresh_token' + }; + 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.refreshToken).to.throw(ArgumentError, 'Missing user data object'); + }); + it('should require a refreshToken', function() { + var auth = this.authenticator; + var refresh = auth.refreshToken.bind(auth, {}); + expect(refresh).to.throw(ArgumentError, 'refresh_token is required'); + }); + it('should accept a callback', function(done) { + this.authenticator.refreshToken(userData, done.bind(null, null)); + }); + it('should return a promise when no callback is provided', function(done) { + this.authenticator + .refreshToken(userData) + .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 + .refreshToken(userData) + .then(function() { + expect(request.isDone()).to.be.true; + done(); + }) + .catch(done); + }); + it('should include the user data in the request', function(done) { + nock.cleanAll(); + var request = nock(API_URL) + .post(path, function(body) { + for (var property in userData) { + if (userData[property] !== body[property]) { + return false; + } + } + return true; + }) + .reply(200); + this.authenticator + .refreshToken(userData) + .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 + .refreshToken(userData) + .then(function() { + expect(request.isDone()).to.be.true; + done(); + }) + .catch(done); + }); + it('should use refresh_token as default grant type', function(done) { + nock.cleanAll(); + var request = nock(API_URL) + .post(path, function(body) { + return body.grant_type === 'refresh_token'; + }) + .reply(200); + this.authenticator + .refreshToken(userData) + .then(function() { + expect(request.isDone()).to.be.true; + done(); + }) + .catch(done); + }); + }); + describe('#socialSignIn', function() { var path = '/oauth/access_token'; var userData = {