Skip to content

Commit

Permalink
Merge pull request #290 from cwurtz/feature/authorizationCodeGrant
Browse files Browse the repository at this point in the history
Added authorizationCodeGrant method to OAuthAuthenticator
  • Loading branch information
luisrudge authored Aug 8, 2018
2 parents d608e7a + 73bcd13 commit fcf3e71
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 1 deletion.
69 changes: 69 additions & 0 deletions src/auth/OAuthAuthenticator.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,73 @@ OAuthAuthenticator.prototype.clientCredentialsGrant = function(options, cb) {
return this.oauth.create(params, data);
};

/**
* Sign in using an authorization code
*
* @method authorizationCodeGrant
* @memberOf module:auth.OAuthAuthenticator.prototype
*
* @example <caption>
* 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
* <a href="https://auth0.com/docs/api/authentication#authorization-code-grant">
* API Docs
* </a>.
* </caption>
*
* var data = {
* code: '{CODE}',
* redirect_uri: '{REDIRECT_URI}',
* client_id: '{CLIENT_ID}', // Optional field.
* client_secret: '{CLIENT_SECRET}', // Optional field.
* };
*
* auth0.oauth.authorizationCodeGrant(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.authorizationCodeGrant = 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;
144 changes: 143 additions & 1 deletion test/auth/oauth.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('OAuthAuthenticator', function() {
});

describe('instance', function() {
var methods = ['signIn', 'socialSignIn', 'passwordGrant'];
var methods = ['signIn', 'socialSignIn', 'passwordGrant', 'authorizationCodeGrant'];
var authenticator = new Authenticator(validOptions);

methods.forEach(function(method) {
Expand Down Expand Up @@ -674,4 +674,146 @@ describe('OAuthAuthenticator', function() {
});
});
});

describe('#authorizationCodeGrant', 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.authorizationCodeGrant).to.throw(
ArgumentError,
'Missing options object'
);
});

it('should require a code', function() {
var auth = this.authenticator;
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.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.authorizationCodeGrant(data, done.bind(null, null));
});

it('should return a promise when no callback is provided', function(done) {
this.authenticator
.authorizationCodeGrant(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
.authorizationCodeGrant(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
.authorizationCodeGrant(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
.authorizationCodeGrant(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
.authorizationCodeGrant(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
.authorizationCodeGrant(data)
.then(function() {
expect(request.isDone()).to.be.true;

done();
})
.catch(done);
});
});
});

0 comments on commit fcf3e71

Please sign in to comment.