Skip to content

Commit

Permalink
Optionally send custom headers in authentication call
Browse files Browse the repository at this point in the history
Complex systems that offer Two Factor Authentication with their OAuth 2.0 implementation need to send additional context via the HTTP headers. This pattern has been observed in the wild by such systems such as GitHub. Because of the restrictions of OAuth 2.0 RFC, only headers can be used for additional context, not request/response bodies.

This could be seen as a counterpart to #1012, where using both features allow bi-directional context enabling 2FA, brute force lockouts, etc.
  • Loading branch information
Justin Bull committed Jul 8, 2016
1 parent e1910f8 commit 066062a
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 6 deletions.
20 changes: 14 additions & 6 deletions addon/authenticators/oauth2-password-grant.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const {
A,
$: jQuery,
testing,
warn
warn,
keys
} = Ember;
const assign = emberAssign || merge;

Expand Down Expand Up @@ -199,10 +200,11 @@ export default BaseAuthenticator.extend({
@param {String} identification The resource owner username
@param {String} password The resource owner password
@param {String|Array} scope The scope of the access request (see [RFC 6749, section 3.3](http://tools.ietf.org/html/rfc6749#section-3.3))
@param {Object} headers Optional headers that particular backends may require (for example sending 2FA challenge responses)
@return {Ember.RSVP.Promise} A promise that when it resolves results in the session becoming authenticated
@public
*/
authenticate(identification, password, scope = []) {
authenticate(identification, password, scope = [], headers = {}) {
return new RSVP.Promise((resolve, reject) => {
const data = { 'grant_type': 'password', username: identification, password };
const serverTokenEndpoint = this.get('serverTokenEndpoint');
Expand All @@ -211,7 +213,7 @@ export default BaseAuthenticator.extend({
if (!isEmpty(scopesString)) {
data.scope = scopesString;
}
this.makeRequest(serverTokenEndpoint, data).then((response) => {
this.makeRequest(serverTokenEndpoint, data, headers).then((response) => {
run(() => {
if (!this._validate(response)) {
reject('access_token is missing in server response');
Expand Down Expand Up @@ -279,21 +281,27 @@ export default BaseAuthenticator.extend({
@method makeRequest
@param {String} url The request URL
@param {Object} data The request data
@param {Object} headers Additional headers to send in request
@return {jQuery.Deferred} A promise like jQuery.Deferred as returned by `$.ajax`
@protected
*/
makeRequest(url, data) {
makeRequest(url, data, headers = {}) {
const options = {
url,
data,
type: 'POST',
dataType: 'json',
contentType: 'application/x-www-form-urlencoded'
contentType: 'application/x-www-form-urlencoded',
headers
};

const clientIdHeader = this.get('_clientIdHeader');
if (!isEmpty(clientIdHeader)) {
options.headers = clientIdHeader;
merge(options.headers, clientIdHeader);
}

if (keys(options.headers).length === 0) {
delete options.headers;
}

return jQuery.ajax(options);
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/authenticators/oauth2-password-grant-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ describe('OAuth2PasswordGrantAuthenticator', () => {
});
});

it('sends an AJAX request to the token endpoint with customized headers', function(done) {
authenticator.authenticate('username', 'password', [], { 'X-Custom-Context': 'foobar' });

next(() => {
expect(jQuery.ajax.getCall(0).args[0]).to.eql({
url: '/token',
type: 'POST',
data: { 'grant_type': 'password', username: 'username', password: 'password' },
dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
headers: { 'X-Custom-Context': 'foobar' }
});
done();
});
});

it('sends a single OAuth scope to the token endpoint', function(done) {
authenticator.authenticate('username', 'password', 'public');

Expand Down

0 comments on commit 066062a

Please sign in to comment.