From 370f76b0b4e70883ae716416190f8460e042c554 Mon Sep 17 00:00:00 2001 From: Charles Davison Date: Fri, 14 Nov 2014 16:22:45 +0800 Subject: [PATCH 1/3] Add xhrCahllengeType option --- lib/passport-http/strategies/basic.js | 20 ++++++++++----- test/strategies/basic-test.js | 35 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/passport-http/strategies/basic.js b/lib/passport-http/strategies/basic.js index 5b1f049..ff995f3 100644 --- a/lib/passport-http/strategies/basic.js +++ b/lib/passport-http/strategies/basic.js @@ -50,6 +50,7 @@ function BasicStrategy(options, verify) { this._verify = verify; this._realm = options.realm || 'Users'; this._passReqToCallback = options.passReqToCallback; + this._xhrChallengeType = options.xhrChallengeType || 'Basic'; } /** @@ -66,7 +67,7 @@ util.inherits(BasicStrategy, passport.Strategy); */ BasicStrategy.prototype.authenticate = function(req) { var authorization = req.headers['authorization']; - if (!authorization) { return this.fail(this._challenge()); } + if (!authorization) { return this.fail(this._challenge(req)); } var parts = authorization.split(' ') if (parts.length < 2) { return this.fail(400); } @@ -74,20 +75,20 @@ BasicStrategy.prototype.authenticate = function(req) { var scheme = parts[0] , credentials = new Buffer(parts[1], 'base64').toString().split(':'); - if (!/Basic/i.test(scheme)) { return this.fail(this._challenge()); } + if (!/Basic/i.test(scheme)) { return this.fail(this._challenge(req)); } if (credentials.length < 2) { return this.fail(400); } var userid = credentials[0]; var password = credentials[1]; if (!userid || !password) { - return this.fail(this._challenge()); + return this.fail(this._challenge(req)); } var self = this; function verified(err, user) { if (err) { return self.error(err); } - if (!user) { return self.fail(self._challenge()); } + if (!user) { return self.fail(self._challenge(req)); } self.success(user); } @@ -101,10 +102,17 @@ BasicStrategy.prototype.authenticate = function(req) { /** * Authentication challenge. * + * @param {Object} req * @api private */ -BasicStrategy.prototype._challenge = function() { - return 'Basic realm="' + this._realm + '"'; +BasicStrategy.prototype._challenge = function(req) { + var challengeType = 'Basic'; + + if (req.xhr) { + challengeType = this._xhrChallengeType; + } + + return challengeType + ' realm="' + this._realm + '"'; } diff --git a/test/strategies/basic-test.js b/test/strategies/basic-test.js index 6a5b453..3231cf8 100644 --- a/test/strategies/basic-test.js +++ b/test/strategies/basic-test.js @@ -440,4 +440,39 @@ vows.describe('BasicStrategy').addBatch({ }, }, + 'strategy with xhrChallengeType option on xhr request': { + topic: function() { + var strategy = new BasicStrategy({ xhrChallengeType: 'xBasic' }, function(userid, password, done) { + done(null, false); + }); + return strategy; + }, + + 'after augmenting with actions': { + topic: function(strategy) { + var self = this; + var req = {}; + strategy.success = function(user) { + self.callback(new Error('should not be called')); + } + strategy.fail = function(challenge) { + self.callback(null, challenge); + } + + req.headers = {}; + req.headers.authorization = 'Basic Ym9iOnNlY3JldA=='; + req.xhr = true; + process.nextTick(function () { + strategy.authenticate(req); + }); + }, + + 'should fail authentication with challenge' : function(err, challenge) { + // fail action was called, resulting in test callback + assert.isNull(err); + assert.equal(challenge, 'xBasic realm="Users"'); + }, + }, + }, + }).export(module); From 299d4cd95c1f8686b876dc05aeecbaa472d66858 Mon Sep 17 00:00:00 2001 From: Charles Davison Date: Fri, 14 Nov 2014 16:29:50 +0800 Subject: [PATCH 2/3] Add documentation on xhrChallengeType option --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 8673fe0..7789d76 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,21 @@ credentials and calls `done` providing a user. } )); +##### Avoid Basic Auth dialogs on XHR requests +Browsers such as Safari intercept `401 Unauthorized` responses with the `Basic` challenge. In client side apps using XHR requests this creates a browser authorization dialog. To work around this you can pass the `xhrChallengeType` option. This will set a different challenge type and avoid the popup dialog on XHR requests, letting you handle the error in your own code. For example: + + passport.use(new BasicStrategy( + { xhrCallengeType: 'xBasic' }, + function(userid, password, done) { + User.findOne({ username: userid }, function (err, user) { + if (err) { return done(err); } + if (!user) { return done(null, false); } + if (!user.verifyPassword(password)) { return done(null, false); } + return done(null, user); + }); + } + )); + #### Authenticate Requests Use `passport.authenticate()`, specifying the `'basic'` strategy, to From 1b7f126561e9fd226e3f16ec4ef869fe1096b1b0 Mon Sep 17 00:00:00 2001 From: Charles Davison Date: Tue, 10 Nov 2015 22:37:12 +0000 Subject: [PATCH 3/3] Fix readme typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 878c867..a4feb13 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ credentials and calls `done` providing a user. Browsers such as Safari intercept `401 Unauthorized` responses with the `Basic` challenge. In client side apps using XHR requests this creates a browser authorization dialog. To work around this you can pass the `xhrChallengeType` option. This will set a different challenge type and avoid the popup dialog on XHR requests, letting you handle the error in your own code. For example: passport.use(new BasicStrategy( - { xhrCallengeType: 'xBasic' }, + { xhrChallengeType: 'xBasic' }, function(userid, password, done) { User.findOne({ username: userid }, function (err, user) { if (err) { return done(err); }