[Pkg-javascript-commits] [passportjs] 09/15: Imported Upstream version 0.1.17

Jérémy Lal kapouer at alioth.debian.org
Sat Oct 26 14:37:39 UTC 2013


This is an automated email from the git hooks/post-receive script.

kapouer pushed a commit to branch master
in repository passportjs.

commit 3153b0291dab232f024258c74e9bc8fa655d5903
Author: Jérémy Lal <kapouer at melix.org>
Date:   Sat Oct 26 16:14:51 2013 +0200

    Imported Upstream version 0.1.17
---
 .gitignore                              |    4 +
 .npmignore                              |   18 +-
 .travis.yml                             |    1 +
 LICENSE                                 |    2 +-
 Makefile                                |   29 +-
 README.md                               |  152 +--
 lib/passport/context/http/actions.js    |   19 +-
 lib/passport/index.js                   |  153 ++-
 lib/passport/middleware/authenticate.js |  215 ++-
 lib/passport/strategies/session.js      |   30 +-
 package.json                            |   23 +-
 test/context/http/actions-test.js       |   68 +-
 test/index-test.js                      |  181 +++
 test/middleware/authenticate-test.js    | 2192 +++++++++++++++++++++++++------
 test/strategies/session-test.js         |   43 +
 15 files changed, 2549 insertions(+), 581 deletions(-)

diff --git a/.gitignore b/.gitignore
index 9daa824..d95c77f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
+# Mac OS X
 .DS_Store
+
+# Node.js
 node_modules
+npm-debug.log
diff --git a/.npmignore b/.npmignore
index d9ceb36..2bdd885 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,8 +1,16 @@
-*.md
-.DS_Store
-.git*
+README.md
 Makefile
-docs/
+doc/
 examples/
-support/
 test/
+
+# Mac OS X
+.DS_Store
+
+# Node.js
+.npmignore
+node_modules/
+npm-debug.log
+
+# Git
+.git*
diff --git a/.travis.yml b/.travis.yml
index 2644170..a57e4db 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,3 +2,4 @@ language: "node_js"
 node_js:
   - 0.4
   - 0.6
+  - 0.8
diff --git a/LICENSE b/LICENSE
index 74524ce..ec885b5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 (The MIT License)
 
-Copyright (c) 2011 Jared Hanson
+Copyright (c) 2011-2013 Jared Hanson
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/Makefile b/Makefile
index 30eb841..eec846c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,19 +1,24 @@
-NODE = node
-TEST = ./node_modules/.bin/vows
+SOURCES = lib/**/*.js
+
+# ==============================================================================
+# Node Tests
+# ==============================================================================
+
+VOWS = ./node_modules/.bin/vows
 TESTS ?= test/*-test.js test/**/*-test.js test/context/http/*-test.js
 
 test:
-	@NODE_ENV=test NODE_PATH=lib $(TEST) $(TEST_FLAGS) $(TESTS)
+	@NODE_ENV=test NODE_PATH=lib $(VOWS) $(TESTS)
+
+# ==============================================================================
+# Static Analysis
+# ==============================================================================
 
-docs: docs/api.html
+JSHINT = jshint
 
-docs/api.html: lib/passport/*.js
-	dox \
-		--title Passport \
-		--desc "Authentication framework for Connect and Express" \
-		$(shell find lib/passport/* -type f) > $@
+hint: lint
+lint:
+	$(JSHINT) $(SOURCES)
 
-docclean:
-	rm -f docs/*.{1,html}
 
-.PHONY: test docs docclean
+.PHONY: test hint lint
diff --git a/README.md b/README.md
index be52cdc..eedb3a7 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,16 @@
 # Passport
-[http://passportjs.org](http://passportjs.org)
 
-Passport is an authentication framework for [Connect](http://senchalabs.github.com/connect/)
-and [Express](http://expressjs.com/), which is extensible through "plugins"
-known as _strategies_.
+Passport is [Express](http://expressjs.com/)-compatible authentication
+middleware for [Node.js](http://nodejs.org/).
 
-Passport is designed to be a general-purpose, yet simple, modular, and
-unobtrusive, authentication framework.  Passport's sole purpose is to
-authenticate requests.  In being modular, it doesn't force any particular
-authentication strategy on your application.  In being unobtrusive, it doesn't
-mount routes in your application.  The API is simple: you give Passport a
-request to authenticate, and Passport provides hooks for controlling what occurs
-when authentication succeeds or fails.
+Passport's sole purpose is to authenticate requests, which it does through an
+extensible set of plugins known as _strategies_.  Passport does not mount
+routes or assume any particular database schema, which maximizes flexiblity and
+allows application-level decisions to be made by the developer.  The API is
+simple: you provide Passport a request to authenticate, and Passport provides
+hooks for controlling what occurs when authentication succeeds or fails.
 
-## Installation
+## Install
 
     $ npm install passport
 
@@ -26,8 +23,8 @@ can range from verifying username and password credentials, delegated
 authentication using [OAuth](http://oauth.net/) (for example, via [Facebook](http://www.facebook.com/)
 or [Twitter](http://twitter.com/)), or federated authentication using [OpenID](http://openid.net/).
 
-Before asking passport to authenticate a request, the strategy (or strategies)
-used by an application must be configured.
+Before authenticating requests, the strategy (or strategies) used by an
+application must be configured.
 
     passport.use(new LocalStrategy(
       function(username, password, done) {
@@ -44,8 +41,8 @@ sessions to work, the authenticated user must be serialized to the session, and
 deserialized when subsequent requests are made.
 
 Passport does not impose any restrictions on how your user records are stored.
-Instead, you provide a function to Passport which implements the necessary
-serialization and deserialization logic.  In typical applications, this will be
+Instead, you provide functions to Passport which implements the necessary
+serialization and deserialization logic.  In a typical application, this will be
 as simple as serializing the user ID, and finding the user by ID when
 deserializing.
 
@@ -59,30 +56,28 @@ deserializing.
       });
     });
 
-#### Connect/Express Middleware
+#### Middleware
 
-To use Passport in a [Connect](http://senchalabs.github.com/connect/) or
-[Express](http://expressjs.com/)-based application, configure it with the
-required `passport.initialize()` middleware.  If your applications uses
+To use Passport in an [Express](http://expressjs.com/) or
+[Connect](http://senchalabs.github.com/connect/)-based application, configure it
+with the required `passport.initialize()` middleware.  If your application uses
 persistent login sessions (recommended, but not required), `passport.session()`
 middleware must also be used.
 
     app.configure(function() {
+      app.use(express.static(__dirname + '/../../public'));
       app.use(express.cookieParser());
       app.use(express.bodyParser());
       app.use(express.session({ secret: 'keyboard cat' }));
       app.use(passport.initialize());
       app.use(passport.session());
       app.use(app.router);
-      app.use(express.static(__dirname + '/../../public'));
     });
 
 #### Authenticate Requests
 
-Passport provides an `authenticate()` function (which is standard
-Connect/Express middleware), which is utilized to authenticate requests.
-
-For example, it can be used as route middleware in an Express application:
+Passport provides an `authenticate()` function, which is used as route
+middleware to authenticate requests.
 
     app.post('/login', 
       passport.authenticate('local', { failureRedirect: '/login' }),
@@ -93,67 +88,37 @@ For example, it can be used as route middleware in an Express application:
 ## Examples
 
 For a complete, working example, refer to the [login example](https://github.com/jaredhanson/passport-local/tree/master/examples/login)
-included in [Passport-Local](https://github.com/jaredhanson/passport-local).
+included in [passport-local](https://github.com/jaredhanson/passport-local).
 
 ## Strategies
 
-<table>
-  <thead>
-    <tr><th>Strategy</th><th>Description</th><th>Developer</th></tr>
-  </thead>
-  <tbody>
-    <tr><td><a href="https://github.com/jaredhanson/passport-local">Local</a></td><td>Local username and password authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-openid">OpenID</a></td><td>OpenID authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-oauth">OAuth</a></td><td>OAuth 1.0 and 2.0 authentication strategies.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-browserid">BrowserID</a></td><td>BrowserID authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/magnetik/passport-webid">WebID</a></td><td>WebID authentication strategy.</td><td><a href="https://github.com/magnetik">Baptiste Lafontaine</a></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-37signals">37signals</a></td><td>37signals authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-angellist">AngelList</a></td><td>AngelList authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-bitbucket">Bitbucket</a></td><td>Bitbucket authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-digg">Digg</a></td><td>Digg authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-dropbox">Dropbox</a></td><td>Dropbox authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-dwolla">Dwolla</a></td><td>Dwolla authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-evernote">Evernote</a></td><td>Evernote authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-facebook">Facebook</a></td><td>Facebook authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-fitbit">Fitbit</a></td><td>Fitbit authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/freenerd/passport-flattr">Flattr</a></td><td>Flattr authentication strategy.</td><td><a href="https://github.com/freenerd">Johan Uhle</a></td></tr>
-    <tr><td><a href="https://github.com/johnnyhalife/passport-flickr">Flickr</a></td><td>Flickr authentication strategy.</td><td><a href="https://github.com/johnnyhalife">Johnny Halife</a></td></tr>
-    <tr><td><a href="https://github.com/joshbirk/passport-forcedotcom">Force.com</a></td><td>Force.com (Salesforce, Database.com) authentication strategy.</td><td><a href="https://github.com/joshbirk">Joshua Birk</a></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-foursquare">Foursquare</a></td><td>Foursquare authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-geoloqi">Geoloqi</a></td><td>Geoloqi authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-github">GitHub</a></td><td>GitHub authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-goodreads">Goodreads</a></td><td>Goodreads authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-google">Google</a></td><td>Google authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-google-oauth">Google</a> (OAuth)</td><td>Google (OAuth 1.0 and OAuth 2.0) authentication strategies.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-gowalla">Gowalla</a></td><td>Gowalla authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-instagram">Instagram</a></td><td>Instagram authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-justintv">Justin.tv</a></td><td>Justin.tv authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-linkedin">LinkedIn</a></td><td>LinkedIn authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-meetup">Meetup</a></td><td>Meetup authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-netflix">Netflix</a></td><td>Netflix authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-ohloh">Ohloh</a></td><td>Ohloh authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-openstreetmap">OpenStreetMap</a></td><td>OpenStreetMap authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-picplz">picplz</a></td><td>picplz authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-rdio">Rdio</a></td><td>Rdio authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-readability">Readability</a></td><td>Readability authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-runkeeper">RunKeeper</a></td><td>RunKeeper authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-smugmug">SmugMug</a></td><td>SmugMug authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-soundcloud">SoundCloud</a></td><td>SoundCloud authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/zoowar/passport-statusnet">StatusNet</a></td><td>StatusNet authentication strategy.</td><td><a href="https://github.com/zoowar">ZooWar</a></td></tr>
-    <tr><td><a href="https://github.com/liamcurry/passport-steam">Steam</a></td><td>Steam authentication strategy.</td><td><a href="https://github.com/liamcurry">Liam Curry</a></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-tripit">TripIt</a></td><td>TripIt authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-tumblr">Tumblr</a></td><td>Tumblr authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-twitter">Twitter</a></td><td>Twitter authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-vimeo">Vimeo</a></td><td>Vimeo authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-windowslive">Windows Live</a></td><td>Windows Live authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-yahoo">Yahoo!</a></td><td>Yahoo! authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-yahoo-oauth">Yahoo!</a> (OAuth)</td><td>Yahoo! (OAuth 1.0) authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-yammer">Yammer</a></td><td>Yammer authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-http">HTTP</a></td><td>HTTP Basic and Digest authentication strategies.</td><td></td></tr>
-    <tr><td><a href="https://github.com/jaredhanson/passport-http-bearer">HTTP-Bearer</a></td><td>HTTP Bearer authentication strategy.</td><td></td></tr>
-    <tr><td><a href="https://github.com/developmentseed/passport-dummy">Dummy</a></td><td>Dummy authentication strategy.</td><td><a href="https://github.com/developmentseed">Development Seed</a></td></tr>
-  </tbody>
-</table>
+Passport has a comprehensive set of **over 120** authentication strategies
+covering social networking, enterprise integration, API services, and more.
+The [complete list](https://github.com/jaredhanson/passport/wiki/Strategies) is
+available on the [wiki](https://github.com/jaredhanson/passport/wiki).
+
+The following table lists commonly used strategies:
+
+|Strategy                                                       | Protocol                 |Developer                                       |
+|---------------------------------------------------------------|--------------------------|------------------------------------------------|
+|[Local](https://github.com/jaredhanson/passport-local)         | HTML form                |[Jared Hanson](https://github.com/jaredhanson)  |
+|[OpenID](https://github.com/jaredhanson/passport-openid)       | OpenID                   |[Jared Hanson](https://github.com/jaredhanson)  |
+|[BrowserID](https://github.com/jaredhanson/passport-browserid) | BrowserID                |[Jared Hanson](https://github.com/jaredhanson)  |
+|[Facebook](https://github.com/jaredhanson/passport-facebook)   | OAuth 2.0                |[Jared Hanson](https://github.com/jaredhanson)  |
+|[Google](https://github.com/jaredhanson/passport-google)       | OpenID                   |[Jared Hanson](https://github.com/jaredhanson)  |
+|[Google](https://github.com/jaredhanson/passport-google-oauth) | OAuth / OAuth 2.0        |[Jared Hanson](https://github.com/jaredhanson)  |
+|[Twitter](https://github.com/jaredhanson/passport-twitter)     | OAuth                    |[Jared Hanson](https://github.com/jaredhanson)  |
+
+## Related Modules
+
+- [Locomotive](https://github.com/jaredhanson/locomotive) — Powerful MVC web framework
+- [OAuthorize](https://github.com/jaredhanson/oauthorize) — OAuth service provider toolkit
+- [OAuth2orize](https://github.com/jaredhanson/oauth2orize) — OAuth 2.0 authorization server toolkit
+- [connect-ensure-login](https://github.com/jaredhanson/connect-ensure-login)  — middleware to ensure login sessions
+
+The [modules](https://github.com/jaredhanson/passport/wiki/Modules) page on the
+[wiki](https://github.com/jaredhanson/passport/wiki) lists other useful modules
+that build upon or integrate with Passport.
 
 ## Tests
 
@@ -168,23 +133,6 @@ included in [Passport-Local](https://github.com/jaredhanson/passport-local).
 
 ## License
 
-(The MIT License)
-
-Copyright (c) 2011 Jared Hanson
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+[The MIT License](http://opensource.org/licenses/MIT)
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+Copyright (c) 2011-2013 Jared Hanson <[http://jaredhanson.net/](http://jaredhanson.net/)>
diff --git a/lib/passport/context/http/actions.js b/lib/passport/context/http/actions.js
index c13a57b..1182027 100644
--- a/lib/passport/context/http/actions.js
+++ b/lib/passport/context/http/actions.js
@@ -46,9 +46,22 @@ actions.fail = function(challenge, status) {
  * @api public
  */
 actions.redirect = function(url, status) {
-  this.res.statusCode = status || 302;
-  this.res.setHeader('Location', url);
-  this.res.end();
+  var res = this.res;
+  if (typeof res.redirect == 'function') {
+    // If possible use redirect method on the response
+    // Assume Express API, optional status param comes first
+    if (status) {
+      res.redirect(status, url);
+    } else {
+      res.redirect(url);
+    }
+  } else {
+    // Otherwise fall back to native methods
+    res.statusCode = status || 302;
+    res.setHeader('Location', url);
+    res.setHeader('Content-Length', '0');
+    res.end();
+  }
 }
 
 /**
diff --git a/lib/passport/index.js b/lib/passport/index.js
index 1319be0..33063a1 100644
--- a/lib/passport/index.js
+++ b/lib/passport/index.js
@@ -20,6 +20,8 @@ function Passport() {
   this._strategies = {};
   this._serializers = [];
   this._deserializers = [];
+  this._infoTransformers = [];
+  this._framework = null;
   
   this._userProperty = 'user';
   
@@ -53,6 +55,54 @@ Passport.prototype.use = function(name, strategy) {
 };
 
 /**
+ * Un-utilize the `strategy` with given `name`.
+ *
+ * In typical applications, the necessary authentication strategies are static,
+ * configured once and always available.  As such, there is often no need to
+ * invoke this function.
+ *
+ * However, in certain situations, applications may need dynamically configure
+ * and de-configure authentication strategies.  The `use()`/`unuse()`
+ * combination satisfies these scenarios.
+ *
+ * Examples:
+ *
+ *     passport.unuse('legacy-api');
+ *
+ * @param {String} name
+ * @return {Passport} for chaining
+ * @api public
+ */
+Passport.prototype.unuse = function(name) {
+  delete this._strategies[name];
+  return this;
+}
+
+/**
+ * Setup Passport to be used under framework.
+ *
+ * By default, Passport exposes middleware that operate using Connect-style
+ * middleware using a `fn(req, res, next)` signature.  Other popular frameworks
+ * have different expectations, and this function allows Passport to be adapted
+ * to operate within such environments.
+ *
+ * If you are using a Connect-compatible framework, including Express, there is
+ * no need to invoke this function.
+ *
+ * Examples:
+ *
+ *     passport.framework(require('hapi-passport')());
+ *
+ * @param {Object} name
+ * @return {Passport} for chaining
+ * @api public
+ */
+Passport.prototype.framework = function(fw) {
+  this._framework = fw;
+  return this;
+}
+
+/**
  * Passport's primary initialization middleware.
  *
  * This middleware must be in use by the Connect/Express application for
@@ -79,6 +129,10 @@ Passport.prototype.initialize = function(options) {
   options = options || {};
   this._userProperty = options.userProperty || 'user';
   
+  if (this._framework && this._framework.initialize) {
+    return this._framework.initialize().bind(this);
+  }
+  
   return initialize().bind(this);
 }
 
@@ -109,11 +163,19 @@ Passport.prototype.initialize = function(options) {
  *       app.use(passport.session());
  *     });
  *
+ * Options:
+ *   - `pauseStream`      Pause the request stream before deserializing the user
+ *                        object from the session.  Defaults to _false_.  Should
+ *                        be set to true in cases where middleware consuming the
+ *                        request body is configured after passport and the
+ *                        deserializeUser method is asynchronous.
+ *
+ * @param {Object} options
  * @return {Function} middleware
  * @api public
  */
-Passport.prototype.session = function() {
-  return this.authenticate('session');
+Passport.prototype.session = function(options) {
+  return this.authenticate('session', options);
 }
 
 /**
@@ -145,6 +207,10 @@ Passport.prototype.session = function() {
  * @api public
  */
 Passport.prototype.authenticate = function(strategy, options, callback) {
+  if (this._framework && this._framework.authenticate) {
+    return this._framework.authenticate(strategy, options, callback).bind(this);
+  }
+  
   return authenticate(strategy, options, callback).bind(this);
 }
 
@@ -202,7 +268,7 @@ Passport.prototype.serializeUser = function(fn, done) {
       err = undefined;
     }
     // an error or serialized object was obtained, done
-    if (err || obj) { return done(err, obj); }
+    if (err || obj || obj === 0) { return done(err, obj); }
     
     var layer = stack[i];
     if (!layer) {
@@ -265,6 +331,85 @@ Passport.prototype.deserializeUser = function(fn, done) {
 }
 
 /**
+ * Registers a function used to transform auth info.
+ *
+ * In some circumstances authorization details are contained in authentication
+ * credentials or loaded as part of verification.
+ *
+ * For example, when using bearer tokens for API authentication, the tokens may
+ * encode (either directly or indirectly in a database), details such as scope
+ * of access or the client to which the token was issued.
+ *
+ * Such authorization details should be enforced separately from authentication.
+ * Because Passport deals only with the latter, this is the responsiblity of
+ * middleware or routes further along the chain.  However, it is not optimal to
+ * decode the same data or execute the same database query later.  To avoid
+ * this, Passport accepts optional `info` along with the authenticated `user`
+ * in a strategy's `success()` action.  This info is set at `req.authInfo`,
+ * where said later middlware or routes can access it.
+ *
+ * Optionally, applications can register transforms to proccess this info,
+ * which take effect prior to `req.authInfo` being set.  This is useful, for
+ * example, when the info contains a client ID.  The transform can load the
+ * client from the database and include the instance in the transformed info,
+ * allowing the full set of client properties to be convieniently accessed.
+ *
+ * If no transforms are registered, `info` supplied by the strategy will be left
+ * unmodified.
+ *
+ * Examples:
+ *
+ *     passport.transformAuthInfo(function(info, done) {
+ *       Client.findById(info.clientID, function (err, client) {
+ *         info.client = client;
+ *         done(err, info);
+ *       });
+ *     });
+ *
+ * @api public
+ */
+Passport.prototype.transformAuthInfo = function(fn, done) {
+  if (typeof fn === 'function') {
+    return this._infoTransformers.push(fn);
+  }
+  
+  // private implementation that traverses the chain of transformers,
+  // attempting to transform auth info
+  var info = fn;
+  
+  var stack = this._infoTransformers;
+  (function pass(i, err, tinfo) {
+    // transformers use 'pass' as an error to skip processing
+    if ('pass' === err) {
+      err = undefined;
+    }
+    // an error or transformed info was obtained, done
+    if (err || tinfo) { return done(err, tinfo); }
+    
+    var layer = stack[i];
+    if (!layer) {
+      // if no transformers are registered (or they all pass), the default
+      // behavior is to use the un-transformed info as-is
+      return done(null, info);
+    }
+    
+    try {
+      var arity = layer.length;
+      if (arity == 1) {
+        // sync
+        var t = layer(info);
+        pass(i + 1, null, t);
+      } else {
+        // async
+        layer(info, function(e, t) { pass(i + 1, e, t); } )
+      }
+    } catch(e) {
+      return done(e);
+    }
+  })(0);
+}
+
+/**
  * Return strategy with given `name`. 
  *
  * @param {String} name
@@ -305,4 +450,4 @@ exports.strategies.SessionStrategy = SessionStrategy;
 /**
  * HTTP extensions.
  */
-require('./http/request');
+require('./http/request');
\ No newline at end of file
diff --git a/lib/passport/middleware/authenticate.js b/lib/passport/middleware/authenticate.js
index 56cf10c..d02f498 100644
--- a/lib/passport/middleware/authenticate.js
+++ b/lib/passport/middleware/authenticate.js
@@ -61,92 +61,187 @@ module.exports = function authenticate(name, options, callback) {
   }
   options = options || {};
   
-  // TODO: Implement support for authenticting with multiple strategies.
+  // Cast `name` to an array, allowing authentication to pass through a chain of
+  // strategies.  The first strategy to succeed, redirect, or error will halt
+  // the chain.  Authentication failures will proceed through each strategy in
+  // series, ultimately failing if all strategies fail.
+  //
+  // This is typically used on API endpoints to allow clients to authenticate
+  // using their preferred choice of Basic, Digest, token-based schemes, etc.
+  // It is not feasible to construct a chain of multiple strategies that involve
+  // redirection (for example both Facebook and Twitter), since the first one to
+  // redirect will halt the chain.
+  if (!Array.isArray(name)) {
+    name = [ name ];
+  }
   
   return function authenticate(req, res, next) {
     var passport = this;
-    var delegate = {};
-    delegate.success = function(user, info) {
+    
+    // accumulator for failures from each strategy in the chain
+    var failures = [];
+    
+    function allFailed() {
       if (callback) {
-        return callback(null, user, info);
+        if (failures.length == 1) {
+          return callback(null, false, failures[0].challenge, failures[0].status);
+        } else {
+          var challenges = failures.map(function(f) { return f.challenge; });
+          var statuses = failures.map(function(f) { return f.status; })
+          return callback(null, false, challenges, statuses);
+        }
       }
-      if (options.successFlash && info) {
-        var option = options.successFlash;
-        if (typeof option == 'string') {
-          option = { type: 'success', message: option };
+      
+      // Strategies are ordered by priority.  For the purpose of flashing a
+      // message, the first failure will be displayed.
+      var failure = failures[0] || {}
+        , challenge = failure.challenge || {};
+    
+      if (options.failureFlash) {
+        var flash = options.failureFlash;
+        if (typeof flash == 'string') {
+          flash = { type: 'error', message: flash };
         }
-        option.type = option.type || 'success';
-        
-        var type = option.type || info.type || 'success';
-        var msg = option.message || info.message || info;
-        
+        flash.type = flash.type || 'error';
+      
+        var type = flash.type || challenge.type || 'error';
+        var msg = flash.message || challenge.message || challenge;
         if (typeof msg == 'string') {
           req.flash(type, msg);
         }
       }
-      if (options.assignProperty) {
-        req[options.assignProperty] = user;
-        return next();
-      }
-      
-      req.logIn(user, options, function(err) {
-        if (err) { return next(err); }
-        if (options.successRedirect) {
-          return res.redirect(options.successRedirect);
+      if (options.failureMessage) {
+        var msg = options.failureMessage;
+        if (typeof msg == 'boolean') {
+          msg = challenge.message || challenge;
         }
-        next();
-      });
-    }
-    delegate.fail = function(challenge, status) {
-      if (callback) {
-        return callback(null, false, challenge, status);
-      }
-      if (options.failureFlash && challenge) {
-        var option = options.failureFlash;
-        if (typeof option == 'string') {
-          option = { type: 'error', message: option };
-        }
-        option.type = option.type || 'error';
-        
-        var type = option.type || challenge.type || 'error';
-        var msg = option.message || challenge.message || challenge;
-        
         if (typeof msg == 'string') {
-          req.flash(type, msg);
+          req.session.messages = req.session.messages || [];
+          req.session.messages.push(msg);
         }
       }
       if (options.failureRedirect) {
         return res.redirect(options.failureRedirect);
       }
-      
-      
-      if (typeof challenge == 'number') {
-        status = challenge;
-        challenge = null;
-      }
-      
+    
       // When failure handling is not delegated to the application, the default
       // is to respond with 401 Unauthorized.  Note that the WWW-Authenticate
       // header will be set according to the strategies in use (see
-      // actions#fail).
-      res.statusCode = status || 401;
-      if (typeof challenge == 'string') {
-        this.res.setHeader('WWW-Authenticate', challenge);
+      // actions#fail).  If multiple strategies failed, each of their challenges
+      // will be included in the response.
+      var rchallenge = []
+        , rstatus;
+      
+      for (var j = 0, len = failures.length; j < len; j++) {
+        var failure = failures[j]
+          , challenge = failure.challenge || {}
+          , status = failure.status;
+        if (typeof challenge == 'number') {
+          status = challenge;
+          challenge = null;
+        }
+          
+        rstatus = rstatus || status;
+        if (typeof challenge == 'string') {
+          rchallenge.push(challenge)
+        }
+      }
+    
+      res.statusCode = rstatus || 401;
+      if (rchallenge.length) {
+        res.setHeader('WWW-Authenticate', rchallenge);
       }
       res.end('Unauthorized');
     }
     
-    // Get the strategy, which will be used as prototype from which to create
-    // a new instance.  Action functions will then be bound to the strategy
-    // within the context of the HTTP request/response pair.
-    var prototype = passport._strategy(name);
-    if (!prototype) { return next(new Error('no strategy registered under name: ' + name)); }
+    (function attempt(i) {
+      var delegate = {};
+      delegate.success = function(user, info) {
+        if (callback) {
+          return callback(null, user, info);
+        }
+      
+        info = info || {}
+      
+        if (options.successFlash) {
+          var flash = options.successFlash;
+          if (typeof flash == 'string') {
+            flash = { type: 'success', message: flash };
+          }
+          flash.type = flash.type || 'success';
+        
+          var type = flash.type || info.type || 'success';
+          var msg = flash.message || info.message || info;
+          if (typeof msg == 'string') {
+            req.flash(type, msg);
+          }
+        }
+        if (options.successMessage) {
+          var msg = options.successMessage;
+          if (typeof msg == 'boolean') {
+            msg = info.message || info;
+          }
+          if (typeof msg == 'string') {
+            req.session.messages = req.session.messages || [];
+            req.session.messages.push(msg);
+          }
+        }
+        if (options.assignProperty) {
+          req[options.assignProperty] = user;
+          return next();
+        }
+      
+        req.logIn(user, options, function(err) {
+          if (err) { return next(err); }
+          if (options.authInfo || options.authInfo === undefined) {
+            passport.transformAuthInfo(info, function(err, tinfo) {
+              if (err) { return next(err); }
+              req.authInfo = tinfo;
+              complete();
+            });
+          } else {
+            complete();
+          }
+        
+          function complete() {
+            if (options.successReturnToOrRedirect) {
+              var url = options.successReturnToOrRedirect;
+              if (req.session && req.session.returnTo) {
+                url = req.session.returnTo;
+                delete req.session.returnTo;
+              }
+              return res.redirect(url);
+            }
+            if (options.successRedirect) {
+              return res.redirect(options.successRedirect);
+            }
+            next();
+          }
+        });
+      }
+      delegate.fail = function(challenge, status) {
+        // push this failure into the accumulator and attempt authentication
+        // using the next strategy
+        failures.push({ challenge: challenge, status: status });
+        attempt(i + 1);
+      }
+    
+      var layer = name[i];
+      // If no more strategies exist in the chain, authentication has failed.
+      if (!layer) { return allFailed(); }
+    
+      // Get the strategy, which will be used as prototype from which to create
+      // a new instance.  Action functions will then be bound to the strategy
+      // within the context of the HTTP request/response pair.
+      var prototype = passport._strategy(layer);
+      if (!prototype) { return next(new Error('no strategy registered under name: ' + layer)); }
     
-    var strategy = Object.create(prototype);
-    var context = new Context(delegate, req, res, next);
-    augment(strategy, actions, context);
+      var strategy = Object.create(prototype);
+      var context = new Context(delegate, req, res, next);
+      augment(strategy, actions, context);
     
-    strategy.authenticate(req, options);
+      strategy.authenticate(req, options);
+    })(0); // attempt
   }
 }
 
diff --git a/lib/passport/strategies/session.js b/lib/passport/strategies/session.js
index 9806498..190720d 100644
--- a/lib/passport/strategies/session.js
+++ b/lib/passport/strategies/session.js
@@ -1,7 +1,8 @@
 /**
  * Module dependencies.
  */
-var util = require('util')
+var pause = require('pause')
+  , util = require('util')
   , Strategy = require('../strategy');
 
 
@@ -30,22 +31,37 @@ util.inherits(SessionStrategy, Strategy);
  * This strategy is registered automatically by Passport.
  *
  * @param {Object} req
+ * @param {Object} options
  * @api protected
  */
-SessionStrategy.prototype.authenticate = function(req) {
+SessionStrategy.prototype.authenticate = function(req, options) {
   if (!req._passport) { return this.error(new Error('passport.initialize() middleware not in use')); }
-  
-  var self = this;
-  if (req._passport.session.user) {
-    req._passport.instance.deserializeUser(req._passport.session.user, function(err, user) {
+  options = options || {};
+
+  var self = this
+    , su = req._passport.session.user;
+  if (su || su === 0) {
+    // NOTE: Stream pausing is desirable in the case where later middleware is
+    //       listening for events emitted from request.  For discussion on the
+    //       matter, refer to: https://github.com/jaredhanson/passport/pull/106
+    
+    var paused = options.pauseStream ? pause(req) : null;
+    req._passport.instance.deserializeUser(su, function(err, user) {
       if (err) { return self.error(err); }
       if (!user) {
         delete req._passport.session.user;
-        return self.pass();
+        self.pass();
+        if (paused) {
+          paused.resume();
+        }
+        return;
       };
       var property = req._passport.instance._userProperty || 'user';
       req[property] = user;
       self.pass();
+      if (paused) {
+        paused.resume();
+      }
     });
   } else {
     self.pass();
diff --git a/package.json b/package.json
index e92b6f2..cf50209 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
   "name": "passport",
-  "version": "0.1.8",
+  "version": "0.1.17",
   "description": "Simple, unobtrusive authentication for Node.js.",
-  "author": { "name": "Jared Hanson", "email": "jaredhanson at gmail.com", "url": "http://www.jaredhanson.net/" },
+  "keywords": ["express", "connect", "auth", "authn", "authentication"],
   "homepage": "http://passportjs.org/",
   "repository": {
     "type": "git",
@@ -11,9 +11,19 @@
   "bugs": {
     "url": "http://github.com/jaredhanson/passport/issues"
   },
+  "author": {
+    "name": "Jared Hanson",
+    "email": "jaredhanson at gmail.com",
+    "url": "http://www.jaredhanson.net/"
+  },
+  "licenses": [ {
+    "type": "MIT",
+    "url": "http://www.opensource.org/licenses/MIT" 
+  } ],
   "main": "./lib/passport",
   "dependencies": {
-    "pkginfo": "0.2.x"
+    "pkginfo": "0.2.x",
+    "pause": "0.0.1"
   },
   "devDependencies": {
     "vows": "0.6.x"
@@ -21,10 +31,5 @@
   "scripts": {
     "test": "NODE_PATH=lib node_modules/.bin/vows test/*-test.js test/**/*-test.js test/context/http/*-test.js"
   },
-  "engines": { "node": ">= 0.4.0" },
-  "licenses": [ {
-    "type": "MIT",
-    "url": "http://www.opensource.org/licenses/MIT" 
-  } ],
-  "keywords": ["express", "connect", "auth", "authn", "authentication"]
+  "engines": { "node": ">= 0.4.0" }
 }
diff --git a/test/context/http/actions-test.js b/test/context/http/actions-test.js
index 5b8e72a..bff0d58 100644
--- a/test/context/http/actions-test.js
+++ b/test/context/http/actions-test.js
@@ -94,7 +94,8 @@ vows.describe('actions').addBatch({
       var self = this;
       var mockRes = {};
       mockRes.setHeader = function(field, value) {
-        this.header = field + ': ' + value;
+        this.header = this.header || {};
+        this.header[field] = value;
       }
       mockRes.end = function() {
         self.callback(null, this);
@@ -111,7 +112,8 @@ vows.describe('actions').addBatch({
     
     'should redirect to url': function (err, res) {
       assert.equal(res.statusCode, 302);
-      assert.equal(res.header, 'Location: http://www.example.com/login');
+      assert.equal(res.header['Location'], 'http://www.example.com/login');
+      assert.equal(res.header['Content-Length'], '0');
     },
   },
   
@@ -120,7 +122,8 @@ vows.describe('actions').addBatch({
       var self = this;
       var mockRes = {};
       mockRes.setHeader = function(field, value) {
-        this.header = field + ': ' + value;
+        this.header = this.header || {};
+        this.header[field] = value;
       }
       mockRes.end = function() {
         self.callback(null, this);
@@ -137,6 +140,65 @@ vows.describe('actions').addBatch({
     
     'should redirect to url': function (err, res) {
       assert.equal(res.statusCode, 303);
+      assert.equal(res.header['Location'], 'http://www.example.com/login');
+      assert.equal(res.header['Content-Length'], '0');
+    },
+  },
+  
+  'redirect using framework function': {
+    topic: function() {
+      var self = this;
+      var mockRes = {};
+      mockRes.redirect = function(status, url) {
+        if (!url) {
+          url = status;
+          status = 302;
+        }
+        this.statusCode = status;
+        this.header = 'Location: ' + url;
+        self.callback(null, this);
+      }
+      
+      var context = {};
+      context.res = mockRes;
+      
+      var redirect = actions.redirect.bind(context);
+      process.nextTick(function () {
+        redirect('http://www.example.com/login', 303);
+      });
+    },
+    
+    'should redirect to url': function (err, res) {
+      assert.equal(res.statusCode, 303);
+      assert.equal(res.header, 'Location: http://www.example.com/login');
+    },
+  },
+  
+  'redirect with status code using framework function': {
+    topic: function() {
+      var self = this;
+      var mockRes = {};
+      mockRes.redirect = function(status, url) {
+        if (!url) {
+          url = status;
+          status = 302;
+        }
+        this.statusCode = status;
+        this.header = 'Location: ' + url;
+        self.callback(null, this);
+      }
+      
+      var context = {};
+      context.res = mockRes;
+      
+      var redirect = actions.redirect.bind(context);
+      process.nextTick(function () {
+        redirect('http://www.example.com/login');
+      });
+    },
+    
+    'should redirect to url': function (err, res) {
+      assert.equal(res.statusCode, 302);
       assert.equal(res.header, 'Location: http://www.example.com/login');
     },
   },
diff --git a/test/index-test.js b/test/index-test.js
index 8c5b3cd..cbfa6f2 100644
--- a/test/index-test.js
+++ b/test/index-test.js
@@ -72,6 +72,31 @@ vows.describe('passport').addBatch({
     },
   },
   
+  'passport with strategies to unuse': {
+    topic: function() {
+      return new Passport();
+    },
+    
+    'should unuse strategies': function (passport) {
+      var strategyOne = {};
+      strategyOne.name = 'one';
+      passport.use(strategyOne);
+      var strategyTwo = {};
+      strategyTwo.name = 'two';
+      passport.use(strategyTwo);
+      
+      // session is implicitly used
+      assert.lengthOf(Object.keys(passport._strategies), 3);
+      assert.isObject(passport._strategies['one']);
+      assert.isObject(passport._strategies['two']);
+      
+      passport.unuse('one');
+      assert.lengthOf(Object.keys(passport._strategies), 2);
+      assert.isUndefined(passport._strategies['one']);
+      assert.isObject(passport._strategies['two']);
+    },
+  },
+  
   'passport with no serializers': {
     topic: function() {
       var self = this;
@@ -138,6 +163,69 @@ vows.describe('passport').addBatch({
     },
   },
   
+  'passport with one serializer that sets user to 0': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.serializeUser(function(user, done) {
+        done(null, 0);
+      });
+      function serialized(err, obj) {
+        self.callback(err, obj);
+      }
+      process.nextTick(function () {
+        passport.serializeUser({ id: '1', username: 'jared' }, serialized);
+      });
+    },
+    
+    'should serialize user': function (err, obj) {
+      assert.isNull(err);
+      assert.equal(obj, 0);
+    },
+  },
+  
+  'passport with one serializer that sets user to null': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.serializeUser(function(user, done) {
+        done(null, null);
+      });
+      function serialized(err, obj) {
+        self.callback(err, obj);
+      }
+      process.nextTick(function () {
+        passport.serializeUser({ id: '1', username: 'jared' }, serialized);
+      });
+    },
+    
+    'should fail to serialize user': function (err, obj) {
+      assert.instanceOf(err, Error);
+      assert.isUndefined(obj);
+    },
+  },
+  
+  'passport with one serializer that sets user to false': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.serializeUser(function(user, done) {
+        done(null, false);
+      });
+      function serialized(err, obj) {
+        self.callback(err, obj);
+      }
+      process.nextTick(function () {
+        passport.serializeUser({ id: '1', username: 'jared' }, serialized);
+      });
+    },
+    
+    'should fail to serialize user': function (err, obj) {
+      assert.instanceOf(err, Error);
+      assert.isUndefined(obj);
+    },
+  },
+  
   'passport with a serializer that throws an error': {
     topic: function() {
       var self = this;
@@ -343,5 +431,98 @@ vows.describe('passport').addBatch({
       assert.isUndefined(obj);
     },
   },
+  
+  'passport with no auth info transformers': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      function transformed(err, obj) {
+        self.callback(err, obj);
+      }
+      process.nextTick(function () {
+        passport.transformAuthInfo({ clientId: '1', scope: 'write' }, transformed);
+      });
+    },
+    
+    'should leave info untransformed': function (err, obj) {
+      assert.isNull(err);
+      assert.equal(obj.clientId, '1');
+      assert.equal(obj.scope, 'write');
+    },
+  },
+  
+  'passport with one auth info transformer': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.transformAuthInfo(function(info, done) {
+        done(null, { clientId: info.clientId, client: { name: 'foo' }});
+      });
+      function transformed(err, obj) {
+        self.callback(err, obj);
+      }
+      process.nextTick(function () {
+        passport.transformAuthInfo({ clientId: '1', scope: 'write' }, transformed);
+      });
+    },
+    
+    'should transform info': function (err, obj) {
+      assert.isNull(err);
+      assert.equal(obj.clientId, '1');
+      assert.equal(obj.client.name, 'foo');
+      assert.isUndefined(obj.scope);
+    },
+  },
+  
+  'passport with multiple auth info transformers': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.transformAuthInfo(function(info, done) {
+        done('pass');
+      });
+      passport.transformAuthInfo(function(info, done) {
+        done(null, { clientId: info.clientId, client: { name: 'bar' }});
+      });
+      passport.transformAuthInfo(function(info, done) {
+        done(null, { clientId: info.clientId, client: { name: 'not-bar' }});
+      });
+      function transformed(err, obj) {
+        self.callback(err, obj);
+      }
+      process.nextTick(function () {
+        passport.transformAuthInfo({ clientId: '1', scope: 'write' }, transformed);
+      });
+    },
+    
+    'should transform info': function (err, obj) {
+      assert.isNull(err);
+      assert.equal(obj.clientId, '1');
+      assert.equal(obj.client.name, 'bar');
+      assert.isUndefined(obj.scope);
+    },
+  },
+  
+  'passport with an auth info transformer that throws an error': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.transformAuthInfo(function(user, done) {
+        // throws ReferenceError: wtf is not defined
+        wtf
+      });
+      function transformed(err, obj) {
+        self.callback(err, obj);
+      }
+      process.nextTick(function () {
+        passport.serializeUser({ clientId: '1', scope: 'write' }, transformed);
+      });
+    },
+    
+    'should fail to transform info': function (err, obj) {
+      assert.instanceOf(err, Error);
+      assert.isUndefined(obj);
+    },
+  },
 
 }).export(module);
diff --git a/test/middleware/authenticate-test.js b/test/middleware/authenticate-test.js
index fcafd05..b061b1d 100644
--- a/test/middleware/authenticate-test.js
+++ b/test/middleware/authenticate-test.js
@@ -41,6 +41,14 @@ MockSuccessStringMessageStrategy.prototype.authenticate = function(req) {
   this.success(user, 'Greetings');
 }
 
+function MockSuccessTokenStrategy() {
+}
+
+MockSuccessTokenStrategy.prototype.authenticate = function(req) {
+  var user = { id: '1', username: 'jaredhanson' };
+  this.success(user, { token: 'abcd', clientId: '123' });
+}
+
 function MockFailureStrategy() {
 }
 
@@ -90,6 +98,68 @@ MockBadRequestStrategy.prototype.authenticate = function(req) {
   this.fail(400);
 }
 
+
+function MockLocalStrategy(options) {
+  this.options = options || {};
+}
+
+MockLocalStrategy.prototype.authenticate = function(req) {
+  if (!this.options.fail) {
+    this.success({ username: 'bob-local' });
+  } else {
+    this.fail('Bad username or password');
+  }
+}
+
+function MockSingleUseTokenStrategy(options) {
+  this.options = options || {};
+}
+
+MockSingleUseTokenStrategy.prototype.authenticate = function(req) {
+  if (!this.options.fail) {
+    this.success({ username: 'bob-sut' });
+  } else {
+    this.fail('Bad token');
+  }
+}
+
+function MockBasicStrategy(options) {
+  this.options = options || {};
+}
+
+MockBasicStrategy.prototype.authenticate = function(req) {
+  if (!this.options.fail) {
+    this.success({ username: 'bob-basic' });
+  } else {
+    this.fail('Basic foo', this.options.statusCode);
+  }
+}
+
+function MockDigestStrategy(options) {
+  this.options = options || {};
+}
+
+MockDigestStrategy.prototype.authenticate = function(req) {
+  if (!this.options.fail) {
+    this.success({ username: 'bob-digest' });
+  } else {
+    this.fail('Digest foo', this.options.statusCode);
+  }
+}
+
+function MockNoChallengeStrategy(options) {
+  this.options = options || {};
+}
+
+MockNoChallengeStrategy.prototype.authenticate = function(req) {
+  if (!this.options.fail) {
+    this.success({ username: 'bob-nc' });
+  } else {
+    this.fail(this.options.statusCode);
+  }
+}
+
+
 function MockRequest() {
 }
 
@@ -146,6 +216,10 @@ vows.describe('authenticate').addBatch({
       'should not set email on user according to scope' : function(err, req, res) {
         assert.isUndefined(req.user.email);
       },
+      'should have empty auth info' : function(err, req, res) {
+        assert.isObject(req.authInfo);
+        assert.lengthOf(Object.keys(req.authInfo), 0);
+      },
     },
   },
   
@@ -182,6 +256,10 @@ vows.describe('authenticate').addBatch({
       'should set email on user according to scope' : function(err, req, res) {
         assert.equal(req.user.email, 'jaredhanson at example.com');
       },
+      'should have empty auth info' : function(err, req, res) {
+        assert.isObject(req.authInfo);
+        assert.lengthOf(Object.keys(req.authInfo), 0);
+      },
     },
   },
   
@@ -214,6 +292,9 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
+      'should not set auth info on request' : function(err, req, res) {
+        assert.isUndefined(req.authInfo);
+      },
     },
   },
   
@@ -251,29 +332,30 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
+      'should have empty auth info' : function(err, req, res) {
+        assert.isObject(req.authInfo);
+        assert.lengthOf(Object.keys(req.authInfo), 0);
+      },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
       },
     },
   },
   
-  'with a successful authentication containing info message using boolean flash option': {
+  'with a successful authentication and return to or redirect option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoMessageStrategy);
-      return passport.authenticate('success', { successFlash: true,
-                                                successRedirect: 'http://www.example.com/account' });
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { successReturnToOrRedirect: 'http://www.example.com/default' });
     },
     
     'when handling a request': {
       topic: function(authenticate) {
         var self = this;
         var req = new MockRequest();
+        req.session = { returnTo: 'http://www.example.com/return' }
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
@@ -295,23 +377,21 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'success');
-        assert.equal(req.message.msg, 'Welcome!');
-      },
       'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/account');
+        assert.equal(res.location, 'http://www.example.com/return');
+      },
+      'should remove returnTo from session' : function(err, req, res) {
+        assert.isUndefined(req.session.returnTo);
       },
     },
   },
   
-  'with a successful authentication containing info message using string flash option': {
+  'with a successful authentication and return to or redirect option with no return to set in session': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoMessageStrategy);
-      return passport.authenticate('success', { successFlash: 'Login complete',
-                                                successRedirect: 'http://www.example.com/account' });
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { successReturnToOrRedirect: 'http://www.example.com/default' });
     },
     
     'when handling a request': {
@@ -319,9 +399,6 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
@@ -343,23 +420,18 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'success');
-        assert.equal(req.message.msg, 'Login complete');
-      },
       'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/account');
+        assert.equal(res.location, 'http://www.example.com/default');
       },
     },
   },
   
-  'with a successful authentication containing info message using flash option': {
+  'with a successful authentication containing token info and no transforms': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoMessageStrategy);
-      return passport.authenticate('success', { successFlash: { type: 'notice', message: 'Last login was yesterday' },
-                                                successRedirect: 'http://www.example.com/account' });
+      passport.use('token', new MockSuccessTokenStrategy);
+      return passport.authenticate('token');
     },
     
     'when handling a request': {
@@ -367,16 +439,9 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
-        }
         
         function next(err) {
-          self.callback(new Error('should not be called'));
+          self.callback(err, req, res);
         }
         process.nextTick(function () {
           authenticate(req, res, next)
@@ -391,23 +456,24 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'notice');
-        assert.equal(req.message.msg, 'Last login was yesterday');
-      },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/account');
+      'should set authInfo on request' : function(err, req, res) {
+        assert.isObject(req.authInfo);
+        assert.lengthOf(Object.keys(req.authInfo), 2);
+        assert.equal(req.authInfo.token, 'abcd');
+        assert.equal(req.authInfo.clientId, '123');
       },
     },
   },
   
-  'with a successful authentication containing info message using flash option with message only': {
+  'with a successful authentication containing token info and a transform': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoMessageStrategy);
-      return passport.authenticate('success', { successFlash: { message: 'OK' },
-                                                successRedirect: 'http://www.example.com/account' });
+      passport.use('token', new MockSuccessTokenStrategy);
+      passport.transformAuthInfo(function(info, done) {
+        done(null, { clientId: info.clientId, client: { name: 'foo' }});
+      });
+      return passport.authenticate('token');
     },
     
     'when handling a request': {
@@ -415,16 +481,9 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
-        }
         
         function next(err) {
-          self.callback(new Error('should not be called'));
+          self.callback(err, req, res);
         }
         process.nextTick(function () {
           authenticate(req, res, next)
@@ -439,23 +498,25 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'success');
-        assert.equal(req.message.msg, 'OK');
-      },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/account');
+      'should set authInfo on request' : function(err, req, res) {
+        assert.isObject(req.authInfo);
+        assert.lengthOf(Object.keys(req.authInfo), 2);
+        assert.isUndefined(req.authInfo.token);
+        assert.equal(req.authInfo.clientId, '123');
+        assert.equal(req.authInfo.client.name, 'foo');
       },
     },
   },
   
-  'with a successful authentication containing info message using flash option with type only': {
+  'with a successful authentication containing token info and a transform that errors': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoMessageStrategy);
-      return passport.authenticate('success', { successFlash: { type: 'info' },
-                                                successRedirect: 'http://www.example.com/account' });
+      passport.use('token', new MockSuccessTokenStrategy);
+      passport.transformAuthInfo(function(info, done) {
+        done(new Error('something went wrong'));
+      });
+      return passport.authenticate('token');
     },
     
     'when handling a request': {
@@ -463,47 +524,35 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
-        }
         
         function next(err) {
-          self.callback(new Error('should not be called'));
+          self.callback(err, req, res);
         }
         process.nextTick(function () {
           authenticate(req, res, next)
         });
       },
       
-      'should not generate an error' : function(err, req, res) {
-        assert.isNull(err);
+      'should generate an error' : function(err, req, res) {
+        assert.instanceOf(err, Error);
       },
       'should set user on request' : function(err, req, res) {
         assert.isObject(req.user);
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'info');
-        assert.equal(req.message.msg, 'Welcome!');
-      },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/account');
+      'should not set authInfo on request' : function(err, req, res) {
+        assert.isUndefined(req.authInfo);
       },
     },
   },
   
-  'with a successful authentication containing info type and message using boolean flash option': {
+  'with a successful authentication containing token info and authInfo option set to false': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
-      return passport.authenticate('success', { successFlash: true,
-                                                successRedirect: 'http://www.example.com/account' });
+      passport.use('token', new MockSuccessTokenStrategy);
+      return passport.authenticate('token', { authInfo: false });
     },
     
     'when handling a request': {
@@ -511,16 +560,9 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
-        }
         
         function next(err) {
-          self.callback(new Error('should not be called'));
+          self.callback(err, req, res);
         }
         process.nextTick(function () {
           authenticate(req, res, next)
@@ -535,22 +577,18 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'info');
-        assert.equal(req.message.msg, 'Hello');
-      },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/account');
+      'should not set authInfo on request' : function(err, req, res) {
+        assert.isUndefined(req.authInfo);
       },
     },
   },
   
-  'with a successful authentication containing info type and message using string flash option': {
+  'with a successful authentication containing info message using string message option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
-      return passport.authenticate('success', { successFlash: 'Success!',
+      passport.use('success', new MockSuccessInfoMessageStrategy);
+      return passport.authenticate('success', { successMessage: 'Login complete',
                                                 successRedirect: 'http://www.example.com/account' });
     },
     
@@ -559,9 +597,7 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
+        req.session = {};
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
@@ -583,9 +619,9 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'success');
-        assert.equal(req.message.msg, 'Success!');
+      'should set message on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 1);
+        assert.equal(req.session.messages[0], 'Login complete');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
@@ -593,12 +629,12 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing info type and message using flash option': {
+  'with a successful authentication containing info message using string message option with existing messages': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
-      return passport.authenticate('success', { successFlash: { type: 'warn', message: 'Last login from far away place' },
+      passport.use('success', new MockSuccessInfoMessageStrategy);
+      return passport.authenticate('success', { successMessage: 'Login complete',
                                                 successRedirect: 'http://www.example.com/account' });
     },
     
@@ -607,9 +643,8 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
+        req.session = {};
+        req.session.messages = [ 'I exist!' ];
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
@@ -631,9 +666,10 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'warn');
-        assert.equal(req.message.msg, 'Last login from far away place');
+      'should set message on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 2);
+        assert.equal(req.session.messages[0], 'I exist!');
+        assert.equal(req.session.messages[1], 'Login complete');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
@@ -641,12 +677,12 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing info type and message using flash option with message only': {
+  'with a successful authentication containing info message using boolean message option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
-      return passport.authenticate('success', { successFlash: { message: 'Okay' },
+      passport.use('success', new MockSuccessInfoMessageStrategy);
+      return passport.authenticate('success', { successMessage: true,
                                                 successRedirect: 'http://www.example.com/account' });
     },
     
@@ -655,9 +691,7 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
+        req.session = {};
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
@@ -679,9 +713,9 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'success');
-        assert.equal(req.message.msg, 'Okay');
+      'should set message on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 1);
+        assert.equal(req.session.messages[0], 'Welcome!');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
@@ -689,12 +723,12 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing info type and message using flash option with type only': {
+  'with a successful authentication containing info type and message using boolean message option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
       passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
-      return passport.authenticate('success', { successFlash: { type: 'ok' },
+      return passport.authenticate('success', { successMessage: true,
                                                 successRedirect: 'http://www.example.com/account' });
     },
     
@@ -703,9 +737,7 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
+        req.session = {};
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
@@ -727,9 +759,9 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'ok');
-        assert.equal(req.message.msg, 'Hello');
+      'should set message on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 1);
+        assert.equal(req.session.messages[0], 'Hello');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
@@ -737,11 +769,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing string message using boolean flash option': {
+  'with a successful authentication containing info message using boolean flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessStringMessageStrategy);
+      passport.use('success', new MockSuccessInfoMessageStrategy);
       return passport.authenticate('success', { successFlash: true,
                                                 successRedirect: 'http://www.example.com/account' });
     },
@@ -775,9 +807,14 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
+      'should set message in auth info' : function(err, req, res) {
+        assert.isObject(req.authInfo);
+        assert.lengthOf(Object.keys(req.authInfo), 1);
+        assert.equal(req.authInfo.message, 'Welcome!');
+      },
       'should set flash on request' : function(err, req, res) {
         assert.equal(req.message.type, 'success');
-        assert.equal(req.message.msg, 'Greetings');
+        assert.equal(req.message.msg, 'Welcome!');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
@@ -785,11 +822,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing string message using string flash option': {
+  'with a successful authentication containing info message using string flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessStringMessageStrategy);
+      passport.use('success', new MockSuccessInfoMessageStrategy);
       return passport.authenticate('success', { successFlash: 'Login complete',
                                                 successRedirect: 'http://www.example.com/account' });
     },
@@ -833,11 +870,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing string message using flash option': {
+  'with a successful authentication containing info message using flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessStringMessageStrategy);
+      passport.use('success', new MockSuccessInfoMessageStrategy);
       return passport.authenticate('success', { successFlash: { type: 'notice', message: 'Last login was yesterday' },
                                                 successRedirect: 'http://www.example.com/account' });
     },
@@ -881,11 +918,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing string message using flash option with message only': {
+  'with a successful authentication containing info message using flash option with message only': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessStringMessageStrategy);
+      passport.use('success', new MockSuccessInfoMessageStrategy);
       return passport.authenticate('success', { successFlash: { message: 'OK' },
                                                 successRedirect: 'http://www.example.com/account' });
     },
@@ -929,11 +966,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication containing string message using flash option with type only': {
+  'with a successful authentication containing info message using flash option with type only': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessStringMessageStrategy);
+      passport.use('success', new MockSuccessInfoMessageStrategy);
       return passport.authenticate('success', { successFlash: { type: 'info' },
                                                 successRedirect: 'http://www.example.com/account' });
     },
@@ -969,7 +1006,7 @@ vows.describe('authenticate').addBatch({
       },
       'should set flash on request' : function(err, req, res) {
         assert.equal(req.message.type, 'info');
-        assert.equal(req.message.msg, 'Greetings');
+        assert.equal(req.message.msg, 'Welcome!');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
@@ -977,11 +1014,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication lacking info message using boolean flash option': {
+  'with a successful authentication containing info type and message using boolean flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessStrategy);
+      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
       return passport.authenticate('success', { successFlash: true,
                                                 successRedirect: 'http://www.example.com/account' });
     },
@@ -1015,8 +1052,9 @@ vows.describe('authenticate').addBatch({
         assert.equal(req.user.id, '1');
         assert.equal(req.user.username, 'jaredhanson');
       },
-      'should not set flash on request' : function(err, req, res) {
-        assert.isUndefined(req.message);
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'info');
+        assert.equal(req.message.msg, 'Hello');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/account');
@@ -1024,12 +1062,13 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a successful authentication and assignProperty option': {
+  'with a successful authentication containing info type and message using string flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessStrategy);
-      return passport.authenticate('success', { assignProperty: 'account' });
+      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
+      return passport.authenticate('success', { successFlash: 'Success!',
+                                                successRedirect: 'http://www.example.com/account' });
     },
     
     'when handling a request': {
@@ -1037,9 +1076,16 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
         
         function next(err) {
-          self.callback(err, req, res);
+          self.callback(new Error('should not be called'));
         }
         process.nextTick(function () {
           authenticate(req, res, next)
@@ -1049,40 +1095,1282 @@ vows.describe('authenticate').addBatch({
       'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res) {
-        assert.isUndefined(req.user);
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
       },
-      'should set account on request' : function(err, req, res) {
-        assert.isObject(req.account);
-        assert.equal(req.account.id, '1');
-        assert.equal(req.account.username, 'jaredhanson');
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'success');
+        assert.equal(req.message.msg, 'Success!');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
       },
     },
   },
   
-  'with a successful authentication and callback': {
+  'with a successful authentication containing info type and message using flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('success', new MockSuccessInfoMessageStrategy);
+      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
+      return passport.authenticate('success', { successFlash: { type: 'warn', message: 'Last login from far away place' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'warn');
+        assert.equal(req.message.msg, 'Last login from far away place');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication containing info type and message using flash option with message only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
+      return passport.authenticate('success', { successFlash: { message: 'Okay' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'success');
+        assert.equal(req.message.msg, 'Okay');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication containing info type and message using flash option with type only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessInfoTypeAndMessageStrategy);
+      return passport.authenticate('success', { successFlash: { type: 'ok' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'ok');
+        assert.equal(req.message.msg, 'Hello');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication containing string message using boolean flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStringMessageStrategy);
+      return passport.authenticate('success', { successFlash: true,
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'success');
+        assert.equal(req.message.msg, 'Greetings');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication containing string message using string flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStringMessageStrategy);
+      return passport.authenticate('success', { successFlash: 'Login complete',
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'success');
+        assert.equal(req.message.msg, 'Login complete');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication containing string message using flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStringMessageStrategy);
+      return passport.authenticate('success', { successFlash: { type: 'notice', message: 'Last login was yesterday' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'notice');
+        assert.equal(req.message.msg, 'Last login was yesterday');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication containing string message using flash option with message only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStringMessageStrategy);
+      return passport.authenticate('success', { successFlash: { message: 'OK' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'success');
+        assert.equal(req.message.msg, 'OK');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication containing string message using flash option with type only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStringMessageStrategy);
+      return passport.authenticate('success', { successFlash: { type: 'info' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'info');
+        assert.equal(req.message.msg, 'Greetings');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication lacking info message using boolean flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { successFlash: true,
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should not set flash on request' : function(err, req, res) {
+        assert.isUndefined(req.message);
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication lacking info message using string flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { successFlash: 'Login complete',
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'success');
+        assert.equal(req.message.msg, 'Login complete');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication lacking info message using flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { successFlash: { type: 'notice', message: 'Last login was yesterday' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'notice');
+        assert.equal(req.message.msg, 'Last login was yesterday');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication lacking info message using flash option with message only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { successFlash: { message: 'OK' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'success');
+        assert.equal(req.message.msg, 'OK');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication lacking info message using flash option with type only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { successFlash: { type: 'info' },
+                                                successRedirect: 'http://www.example.com/account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, '1');
+        assert.equal(req.user.username, 'jaredhanson');
+      },
+      'should not set flash on request' : function(err, req, res) {
+        assert.isUndefined(req.message);
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/account');
+      },
+    },
+  },
+  
+  'with a successful authentication and assignProperty option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessStrategy);
+      return passport.authenticate('success', { assignProperty: 'account' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        
+        function next(err) {
+          self.callback(err, req, res);
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set account on request' : function(err, req, res) {
+        assert.isObject(req.account);
+        assert.equal(req.account.id, '1');
+        assert.equal(req.account.username, 'jaredhanson');
+      },
+    },
+  },
+  
+  'with a successful authentication and callback': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('success', new MockSuccessInfoMessageStrategy);
+      var callback = function(err, user, info) {
+        this.done(err, user, info);
+      }
+      var context = {};
+      
+      var authenticate = passport.authenticate('success', callback.bind(context));
+      process.nextTick(function () {
+        self.callback(null, authenticate, context);
+      });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate, context) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        context.done = function(err, user, info) {
+          self.callback(err, req, res, user, info);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res, user, info) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res, user, info) {
+        assert.isUndefined(req.user);
+      },
+      'should pass user to callback' : function(err, req, res, user, info) {
+        assert.isObject(user);
+        assert.equal(user.id, '1');
+        assert.equal(user.username, 'jaredhanson');
+      },
+      'should pass profile to callback' : function(err, req, res, user, info) {
+        assert.isObject(info);
+        assert.equal(info.message, 'Welcome!');
+      },
+    },
+  },
+  
+  
+  'with a failed authentication': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureStrategy());
+      return passport.authenticate('failure');
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        res.end = function() {
+          self.callback(null, req, res)
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set status code to unauthorized' : function(err, req, res) {
+        assert.equal(res.statusCode, 401);
+      },
+    },
+  },
+  
+  'with a failed authentication and redirect option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureStrategy());
+      return passport.authenticate('failure', { failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication and callback': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureStrategy());
+      var callback = function(err, user) {
+        this.done(err, user);
+      }
+      var context = {};
+      
+      var authenticate = passport.authenticate('failure', callback.bind(context));
+      process.nextTick(function () {
+        self.callback(null, authenticate, context);
+      });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate, context) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        context.done = function(err, user) {
+          self.callback(err, req, res, user);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res, user) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res, user) {
+        assert.isUndefined(req.user);
+      },
+      'should pass user to callback as false' : function(err, req, res, user) {
+        assert.isFalse(user);
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using callback': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
       var callback = function(err, user, info) {
         this.done(err, user, info);
       }
       var context = {};
       
-      var authenticate = passport.authenticate('success', callback.bind(context));
-      process.nextTick(function () {
-        self.callback(null, authenticate, context);
-      });
+      var authenticate = passport.authenticate('failure', callback.bind(context));
+      process.nextTick(function () {
+        self.callback(null, authenticate, context);
+      });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate, context) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        context.done = function(err, user, info) {
+          self.callback(err, req, res, user, info);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res, user) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res, user) {
+        assert.isUndefined(req.user);
+      },
+      'should pass user to callback as false' : function(err, req, res, user) {
+        assert.isFalse(user);
+      },
+      'should pass info to callback' : function(err, req, res, user, info) {
+        assert.isObject(info);
+        assert.equal(info.message, 'Invalid password');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using string message option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureMessage: 'Wrong credentials',
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.session = {};
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 1);
+        assert.equal(req.session.messages[0], 'Wrong credentials');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using string message option with existing messages': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureMessage: 'Wrong credentials',
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.session = {};
+        req.session.messages = [ 'I exist!' ];
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 2);
+        assert.equal(req.session.messages[0], 'I exist!');
+        assert.equal(req.session.messages[1], 'Wrong credentials');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using boolean message option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureMessage: true,
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.session = {};
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 1);
+        assert.equal(req.session.messages[0], 'Invalid password');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info type and message using boolean message option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      return passport.authenticate('failure', { failureMessage: true,
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.session = {};
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.lengthOf(req.session.messages, 1);
+        assert.equal(req.session.messages[0], 'Invite required');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using boolean flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: true,
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'error');
+        assert.equal(req.message.msg, 'Invalid password');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using string flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: 'Wrong credentials',
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'error');
+        assert.equal(req.message.msg, 'Wrong credentials');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: { type: 'notice', message: 'Try again' },
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'notice');
+        assert.equal(req.message.msg, 'Try again');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using flash option with message only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: { message: 'Try again' },
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'error');
+        assert.equal(req.message.msg, 'Try again');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info message using flash option with type only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: { type: 'info' },
+                                                failureRedirect: 'http://www.example.com/login' });
     },
     
     'when handling a request': {
-      topic: function(authenticate, context) {
+      topic: function(authenticate) {
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        context.done = function(err, user, info) {
-          self.callback(err, req, res, user, info);
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
         }
         
         function next(err) {
@@ -1093,31 +2381,75 @@ vows.describe('authenticate').addBatch({
         });
       },
       
-      'should not generate an error' : function(err, req, res, user, info) {
+      'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res, user, info) {
+      'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should pass user to callback' : function(err, req, res, user, info) {
-        assert.isObject(user);
-        assert.equal(user.id, '1');
-        assert.equal(user.username, 'jaredhanson');
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'info');
+        assert.equal(req.message.msg, 'Invalid password');
       },
-      'should pass profile to callback' : function(err, req, res, user, info) {
-        assert.isObject(info);
-        assert.equal(info.message, 'Welcome!');
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
       },
     },
   },
   
+  'with a failed authentication containing info type and message using boolean flash option': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: true,
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'notice');
+        assert.equal(req.message.msg, 'Invite required');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
   
-  'with a failed authentication': {
+  'with a failed authentication containing info type and message using string flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStrategy());
-      return passport.authenticate('failure');
+      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: 'Wrong credentials',
+                                                failureRedirect: 'http://www.example.com/login' });
     },
     
     'when handling a request': {
@@ -1125,8 +2457,12 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        res.end = function() {
-          self.callback(null, req, res)
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
         }
         
         function next(err) {
@@ -1143,18 +2479,23 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should set status code to unauthorized' : function(err, req, res) {
-        assert.equal(res.statusCode, 401);
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'error');
+        assert.equal(req.message.msg, 'Wrong credentials');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
       },
     },
   },
   
-  'with a failed authentication and redirect option': {
+  'with a failed authentication containing info type and message using flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStrategy());
-      return passport.authenticate('failure', { failureRedirect: 'http://www.example.com/login' });
+      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: { type: 'info', message: 'Try again' },
+                                                failureRedirect: 'http://www.example.com/login' });
     },
     
     'when handling a request': {
@@ -1162,6 +2503,9 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
@@ -1181,35 +2525,82 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'info');
+        assert.equal(req.message.msg, 'Try again');
+      },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/login');
       },
     },
   },
   
-  'with a failed authentication and callback': {
+  'with a failed authentication containing info type and message using flash option with message only': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStrategy());
-      var callback = function(err, user) {
-        this.done(err, user);
-      }
-      var context = {};
+      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: { message: 'Try again' },
+                                                failureRedirect: 'http://www.example.com/login' });
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
+        }
+        
+        function next(err) {
+          self.callback(new Error('should not be called'));
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
+      },
       
-      var authenticate = passport.authenticate('failure', callback.bind(context));
-      process.nextTick(function () {
-        self.callback(null, authenticate, context);
-      });
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should not set user on request' : function(err, req, res) {
+        assert.isUndefined(req.user);
+      },
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'error');
+        assert.equal(req.message.msg, 'Try again');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
+      },
+    },
+  },
+  
+  'with a failed authentication containing info type and message using flash option with type only': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      return passport.authenticate('failure', { failureFlash: { type: 'info' },
+                                                failureRedirect: 'http://www.example.com/login' });
     },
     
     'when handling a request': {
-      topic: function(authenticate, context) {
+      topic: function(authenticate) {
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        context.done = function(err, user) {
-          self.callback(err, req, res, user);
+        req.flash = function(type, msg) {
+          this.message = { type: type, msg: msg }
+        }
+        res.redirect = function(url) {
+          this.location = url;
+          self.callback(null, req, res);
         }
         
         function next(err) {
@@ -1220,23 +2611,27 @@ vows.describe('authenticate').addBatch({
         });
       },
       
-      'should not generate an error' : function(err, req, res, user) {
+      'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res, user) {
+      'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should pass user to callback as false' : function(err, req, res, user) {
-        assert.isFalse(user);
+      'should set flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'info');
+        assert.equal(req.message.msg, 'Invite required');
+      },
+      'should redirect response' : function(err, req, res) {
+        assert.equal(res.location, 'http://www.example.com/login');
       },
     },
   },
   
-  'with a failed authentication containing info message using boolean flash option': {
+  'with a failed authentication containing string message using boolean flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoMessageStrategy());
+      passport.use('failure', new MockFailureStringMessageStrategy());
       return passport.authenticate('failure', { failureFlash: true,
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1270,7 +2665,7 @@ vows.describe('authenticate').addBatch({
       },
       'should set flash on request' : function(err, req, res) {
         assert.equal(req.message.type, 'error');
-        assert.equal(req.message.msg, 'Invalid password');
+        assert.equal(req.message.msg, 'Access denied');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/login');
@@ -1278,11 +2673,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info message using string flash option': {
+  'with a failed authentication containing string message using string flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoMessageStrategy());
+      passport.use('failure', new MockFailureStringMessageStrategy());
       return passport.authenticate('failure', { failureFlash: 'Wrong credentials',
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1324,11 +2719,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info message using flash option': {
+  'with a failed authentication containing string message using flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoMessageStrategy());
+      passport.use('failure', new MockFailureStringMessageStrategy());
       return passport.authenticate('failure', { failureFlash: { type: 'notice', message: 'Try again' },
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1370,11 +2765,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info message using flash option with message only': {
+  'with a failed authentication containing string message using flash option with message only': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoMessageStrategy());
+      passport.use('failure', new MockFailureStringMessageStrategy());
       return passport.authenticate('failure', { failureFlash: { message: 'Try again' },
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1416,11 +2811,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info message using flash option with type only': {
+  'with a failed authentication containing string message using flash option with type only': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoMessageStrategy());
+      passport.use('failure', new MockFailureStringMessageStrategy());
       return passport.authenticate('failure', { failureFlash: { type: 'info' },
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1454,7 +2849,7 @@ vows.describe('authenticate').addBatch({
       },
       'should set flash on request' : function(err, req, res) {
         assert.equal(req.message.type, 'info');
-        assert.equal(req.message.msg, 'Invalid password');
+        assert.equal(req.message.msg, 'Access denied');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/login');
@@ -1462,11 +2857,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info type and message using boolean flash option': {
+  'with a failed authentication without info message and boolean flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      passport.use('failure', new MockFailureStrategy());
       return passport.authenticate('failure', { failureFlash: true,
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1498,9 +2893,8 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'notice');
-        assert.equal(req.message.msg, 'Invite required');
+      'should not set flash on request' : function(err, req, res) {
+        assert.isUndefined(req.message);
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/login');
@@ -1508,11 +2902,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info type and message using string flash option': {
+  'with a failed authentication without info message and string flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      passport.use('failure', new MockFailureStrategy());
       return passport.authenticate('failure', { failureFlash: 'Wrong credentials',
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1554,12 +2948,12 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info type and message using flash option': {
+  'with a failed authentication without info message and flash option': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
-      return passport.authenticate('failure', { failureFlash: { type: 'info', message: 'Try again' },
+      passport.use('failure', new MockFailureStrategy());
+      return passport.authenticate('failure', { failureFlash: { type: 'notice', message: 'Try again' },
                                                 failureRedirect: 'http://www.example.com/login' });
     },
     
@@ -1591,7 +2985,7 @@ vows.describe('authenticate').addBatch({
         assert.isUndefined(req.user);
       },
       'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'info');
+        assert.equal(req.message.type, 'notice');
         assert.equal(req.message.msg, 'Try again');
       },
       'should redirect response' : function(err, req, res) {
@@ -1600,11 +2994,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info type and message using flash option with message only': {
+  'with a failed authentication without info message and flash option with message only': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      passport.use('failure', new MockFailureStrategy());
       return passport.authenticate('failure', { failureFlash: { message: 'Try again' },
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1646,11 +3040,11 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing info type and message using flash option with type only': {
+  'with a failed authentication without info message and flash option with type only': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoTypeAndMessageStrategy());
+      passport.use('failure', new MockFailureStrategy());
       return passport.authenticate('failure', { failureFlash: { type: 'info' },
                                                 failureRedirect: 'http://www.example.com/login' });
     },
@@ -1682,9 +3076,8 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'info');
-        assert.equal(req.message.msg, 'Invite required');
+      'should not set flash on request' : function(err, req, res) {
+        assert.isUndefined(req.message);
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/login');
@@ -1692,13 +3085,12 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication containing string message using boolean flash option': {
+  'with a challenged authentication': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStringMessageStrategy());
-      return passport.authenticate('failure', { failureFlash: true,
-                                                failureRedirect: 'http://www.example.com/login' });
+      passport.use('challenge', new MockChallengeStrategy());
+      return passport.authenticate('challenge');
     },
     
     'when handling a request': {
@@ -1706,12 +3098,8 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
+        res.end = function() {
+          self.callback(null, req, res)
         }
         
         function next(err) {
@@ -1728,23 +3116,21 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'error');
-        assert.equal(req.message.msg, 'Access denied');
+      'should set status code to unauthorized' : function(err, req, res) {
+        assert.equal(res.statusCode, 401);
       },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/login');
+      'should set WWW-Authenticate to challenge' : function(err, req, res) {
+        assert.equal(res._headers['WWW-Authenticate'], 'Mock challenge');
       },
     },
   },
   
-  'with a failed authentication containing string message using string flash option': {
+  'with a challenged authentication and status code': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStringMessageStrategy());
-      return passport.authenticate('failure', { failureFlash: 'Wrong credentials',
-                                                failureRedirect: 'http://www.example.com/login' });
+      passport.use('forbidden', new MockForbiddenStrategy());
+      return passport.authenticate('forbidden');
     },
     
     'when handling a request': {
@@ -1752,12 +3138,8 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
+        res.end = function() {
+          self.callback(null, req, res)
         }
         
         function next(err) {
@@ -1774,23 +3156,21 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'error');
-        assert.equal(req.message.msg, 'Wrong credentials');
+      'should set status code to unauthorized' : function(err, req, res) {
+        assert.equal(res.statusCode, 403);
       },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/login');
+      'should set WWW-Authenticate to challenge' : function(err, req, res) {
+        assert.equal(res._headers['WWW-Authenticate'], 'Mock challenge');
       },
     },
   },
   
-  'with a failed authentication containing string message using flash option': {
+  'with a failed authentication due to bad request': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStringMessageStrategy());
-      return passport.authenticate('failure', { failureFlash: { type: 'notice', message: 'Try again' },
-                                                failureRedirect: 'http://www.example.com/login' });
+      passport.use('bad-request', new MockBadRequestStrategy());
+      return passport.authenticate('bad-request');
     },
     
     'when handling a request': {
@@ -1798,12 +3178,8 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
+        res.end = function() {
+          self.callback(null, req, res)
         }
         
         function next(err) {
@@ -1820,23 +3196,22 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'notice');
-        assert.equal(req.message.msg, 'Try again');
+      'should set status code to bad request' : function(err, req, res) {
+        assert.equal(res.statusCode, 400);
       },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/login');
+      'should not set WWW-Authenticate header' : function(err, req, res) {
+        assert.isUndefined(res._headers['WWW-Authenticate']);
       },
     },
   },
   
-  'with a failed authentication containing string message using flash option with message only': {
+  'with a multiple UI strategies with the first one succeeding': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStringMessageStrategy());
-      return passport.authenticate('failure', { failureFlash: { message: 'Try again' },
-                                                failureRedirect: 'http://www.example.com/login' });
+      passport.use('local', new MockLocalStrategy());
+      passport.use('single-use-token', new MockSingleUseTokenStrategy());
+      return passport.authenticate(['local', 'single-use-token']);
     },
     
     'when handling a request': {
@@ -1844,16 +3219,9 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          this.message = { type: type, msg: msg }
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
-        }
         
         function next(err) {
-          self.callback(new Error('should not be called'));
+          self.callback(err, req, res);
         }
         process.nextTick(function () {
           authenticate(req, res, next)
@@ -1863,43 +3231,70 @@ vows.describe('authenticate').addBatch({
       'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res) {
-        assert.isUndefined(req.user);
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.username, 'bob-local');
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'error');
-        assert.equal(req.message.msg, 'Try again');
+    },
+  },
+  
+  'with a multiple UI strategies with the second one succeeding': {
+    topic: function() {
+      var self = this;
+      var passport = new Passport();
+      passport.use('local', new MockLocalStrategy({ fail: true }));
+      passport.use('single-use-token', new MockSingleUseTokenStrategy());
+      return passport.authenticate(['local', 'single-use-token']);
+    },
+    
+    'when handling a request': {
+      topic: function(authenticate) {
+        var self = this;
+        var req = new MockRequest();
+        var res = new MockResponse();
+        
+        function next(err) {
+          self.callback(err, req, res);
+        }
+        process.nextTick(function () {
+          authenticate(req, res, next)
+        });
       },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/login');
+      
+      'should not generate an error' : function(err, req, res) {
+        assert.isNull(err);
+      },
+      'should set user on request' : function(err, req, res) {
+        assert.isObject(req.user);
+        assert.equal(req.user.username, 'bob-sut');
       },
     },
   },
   
-  'with a failed authentication containing string message using flash option with type only': {
+  'with a multiple UI strategies with the both failing with flash message': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStringMessageStrategy());
-      return passport.authenticate('failure', { failureFlash: { type: 'info' },
-                                                failureRedirect: 'http://www.example.com/login' });
+      passport.use('local', new MockLocalStrategy({ fail: true }));
+      passport.use('single-use-token', new MockSingleUseTokenStrategy({ fail: true }));
+      return passport.authenticate(['local', 'single-use-token'], { failureFlash: true, failureRedirect: 'http://www.example.com/login' });
     },
     
     'when handling a request': {
       topic: function(authenticate) {
         var self = this;
         var req = new MockRequest();
-        var res = new MockResponse();
         req.flash = function(type, msg) {
           this.message = { type: type, msg: msg }
         }
+        var res = new MockResponse();
         res.redirect = function(url) {
           this.location = url;
           self.callback(null, req, res);
         }
         
         function next(err) {
-          self.callback(new Error('should not be called'));
+          self.callback(err, req, res);
         }
         process.nextTick(function () {
           authenticate(req, res, next)
@@ -1912,9 +3307,9 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should set flash on request' : function(err, req, res) {
-        assert.equal(req.message.type, 'info');
-        assert.equal(req.message.msg, 'Access denied');
+      'should set first flash on request' : function(err, req, res) {
+        assert.equal(req.message.type, 'error');
+        assert.equal(req.message.msg, 'Bad username or password');
       },
       'should redirect response' : function(err, req, res) {
         assert.equal(res.location, 'http://www.example.com/login');
@@ -1922,13 +3317,14 @@ vows.describe('authenticate').addBatch({
     },
   },
   
-  'with a failed authentication without info message and boolean flash option': {
+  'with a multiple API strategies failing with default status': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureStrategy());
-      return passport.authenticate('failure', { failureFlash: true,
-                                                failureRedirect: 'http://www.example.com/login' });
+      passport.use('basic', new MockBasicStrategy({ fail: true }));
+      passport.use('digest', new MockDigestStrategy({ fail: true }));
+      passport.use('nc', new MockNoChallengeStrategy({ fail: true }));
+      return passport.authenticate(['basic', 'nc', 'digest'], { session: false });
     },
     
     'when handling a request': {
@@ -1936,12 +3332,8 @@ vows.describe('authenticate').addBatch({
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        req.flash = function(type, msg) {
-          self.callback(new Error('should not be called'));
-        }
-        res.redirect = function(url) {
-          this.location = url;
-          self.callback(null, req, res);
+        res.end = function() {
+          self.callback(null, req, res)
         }
         
         function next(err) {
@@ -1958,38 +3350,34 @@ vows.describe('authenticate').addBatch({
       'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should not set flash on request' : function(err, req, res) {
-        assert.isUndefined(req.message);
+      'should set status code to unauthorized' : function(err, req, res) {
+        assert.equal(res.statusCode, 401);
       },
-      'should redirect response' : function(err, req, res) {
-        assert.equal(res.location, 'http://www.example.com/login');
+      'should set multiple WWW-Authenticate headers' : function(err, req, res) {
+        assert.lengthOf(res._headers['WWW-Authenticate'], 2);
+        assert.equal(res._headers['WWW-Authenticate'][0], 'Basic foo');
+        assert.equal(res._headers['WWW-Authenticate'][1], 'Digest foo');
       },
     },
   },
   
-  'with a failed authentication containing info message using callback': {
+  'with a multiple API strategies failing with different status': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('failure', new MockFailureInfoMessageStrategy());
-      var callback = function(err, user, info) {
-        this.done(err, user, info);
-      }
-      var context = {};
-      
-      var authenticate = passport.authenticate('failure', callback.bind(context));
-      process.nextTick(function () {
-        self.callback(null, authenticate, context);
-      });
+      passport.use('basic', new MockBasicStrategy({ fail: true, statusCode: 400 }));
+      passport.use('digest', new MockDigestStrategy({ fail: true, statusCode: 403 }));
+      passport.use('nc', new MockNoChallengeStrategy({ fail: true, statusCode: 402 }));
+      return passport.authenticate(['basic', 'nc', 'digest'], { session: false });
     },
     
     'when handling a request': {
-      topic: function(authenticate, context) {
+      topic: function(authenticate) {
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        context.done = function(err, user, info) {
-          self.callback(err, req, res, user, info);
+        res.end = function() {
+          self.callback(null, req, res)
         }
         
         function next(err) {
@@ -2000,37 +3388,46 @@ vows.describe('authenticate').addBatch({
         });
       },
       
-      'should not generate an error' : function(err, req, res, user) {
+      'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res, user) {
+      'should not set user on request' : function(err, req, res) {
         assert.isUndefined(req.user);
       },
-      'should pass user to callback as false' : function(err, req, res, user) {
-        assert.isFalse(user);
+      'should set status code to bad request' : function(err, req, res) {
+        assert.equal(res.statusCode, 400);
       },
-      'should pass info to callback' : function(err, req, res, user, info) {
-        assert.isObject(info);
-        assert.equal(info.message, 'Invalid password');
+      'should set multiple WWW-Authenticate headers' : function(err, req, res) {
+        assert.lengthOf(res._headers['WWW-Authenticate'], 2);
+        assert.equal(res._headers['WWW-Authenticate'][0], 'Basic foo');
+        assert.equal(res._headers['WWW-Authenticate'][1], 'Digest foo');
       },
     },
   },
   
-  'with a challenged authentication': {
+  'with a single API strategy failing with challenge and status using custom callback': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('challenge', new MockChallengeStrategy());
-      return passport.authenticate('challenge');
+      passport.use('basic', new MockBasicStrategy({ fail: true, statusCode: 400 }));
+      var callback = function(err, user, challenge, status) {
+        this.done(err, user, challenge, status);
+      }
+      var context = {};
+      
+      var authenticate = passport.authenticate('basic', callback.bind(context));
+      process.nextTick(function () {
+        self.callback(null, authenticate, context);
+      });
     },
     
     'when handling a request': {
-      topic: function(authenticate) {
+      topic: function(authenticate, context) {
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        res.end = function() {
-          self.callback(null, req, res)
+        context.done = function(err, user, challenge, status) {
+          self.callback(err, req, res, user, challenge, status);
         }
         
         function next(err) {
@@ -2044,33 +3441,46 @@ vows.describe('authenticate').addBatch({
       'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res) {
+      'should not set user on request' : function(err, req, res, user, challenge, status) {
         assert.isUndefined(req.user);
       },
-      'should set status code to unauthorized' : function(err, req, res) {
-        assert.equal(res.statusCode, 401);
+      'should pass user to callback as false' : function(err, req, res, user, challenge, status) {
+        assert.isFalse(user);
       },
-      'should set WWW-Authenticate to challenge' : function(err, req, res) {
-        assert.equal(res._headers['WWW-Authenticate'], 'Mock challenge');
+      'should pass challenge to callback' : function(err, req, res, user, challenge, status) {
+        assert.strictEqual(challenge, 'Basic foo');
+      },
+      'should pass status to callback' : function(err, req, res, user, challenge, status) {
+        assert.strictEqual(status, 400);
       },
     },
   },
   
-  'with a challenged authentication and status code': {
+  'with a multiple API strategies failing with default status using custom callback': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('forbidden', new MockForbiddenStrategy());
-      return passport.authenticate('forbidden');
+      passport.use('basic', new MockBasicStrategy({ fail: true }));
+      passport.use('digest', new MockDigestStrategy({ fail: true }));
+      passport.use('nc', new MockNoChallengeStrategy({ fail: true }));
+      var callback = function(err, user, challenge, status) {
+        this.done(err, user, challenge, status);
+      }
+      var context = {};
+      
+      var authenticate = passport.authenticate(['basic', 'nc', 'digest'], callback.bind(context));
+      process.nextTick(function () {
+        self.callback(null, authenticate, context);
+      });
     },
     
     'when handling a request': {
-      topic: function(authenticate) {
+      topic: function(authenticate, context) {
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        res.end = function() {
-          self.callback(null, req, res)
+        context.done = function(err, user, challenge, status) {
+          self.callback(err, req, res, user, challenge, status);
         }
         
         function next(err) {
@@ -2084,33 +3494,54 @@ vows.describe('authenticate').addBatch({
       'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res) {
+      'should not set user on request' : function(err, req, res, user, challenge, status) {
         assert.isUndefined(req.user);
       },
-      'should set status code to unauthorized' : function(err, req, res) {
-        assert.equal(res.statusCode, 403);
+      'should pass user to callback as false' : function(err, req, res, user, challenge, status) {
+        assert.isFalse(user);
       },
-      'should set WWW-Authenticate to challenge' : function(err, req, res) {
-        assert.equal(res._headers['WWW-Authenticate'], 'Mock challenge');
+      'should pass challenges callback' : function(err, req, res, user, challenge, status) {
+        assert.isArray(challenge);
+        assert.lengthOf(challenge, 3);
+        assert.equal(challenge[0], 'Basic foo');
+        assert.isUndefined(challenge[1]);
+        assert.equal(challenge[2], 'Digest foo');
+      },
+      'should pass statuses callback' : function(err, req, res, user, challenge, status) {
+        assert.isArray(status);
+        assert.lengthOf(status, 3);
+        assert.isUndefined(status[0]);
+        assert.isUndefined(status[1]);
+        assert.isUndefined(status[2]);
       },
     },
   },
   
-  'with a failed authentication due to bad request': {
+  'with a multiple API strategies failing with different status using custom callback': {
     topic: function() {
       var self = this;
       var passport = new Passport();
-      passport.use('bad-request', new MockBadRequestStrategy());
-      return passport.authenticate('bad-request');
+      passport.use('basic', new MockBasicStrategy({ fail: true, statusCode: 400 }));
+      passport.use('digest', new MockDigestStrategy({ fail: true, statusCode: 403 }));
+      passport.use('nc', new MockNoChallengeStrategy({ fail: true, statusCode: 402 }));
+      var callback = function(err, user, challenge, status) {
+        this.done(err, user, challenge, status);
+      }
+      var context = {};
+      
+      var authenticate = passport.authenticate(['basic', 'nc', 'digest'], callback.bind(context));
+      process.nextTick(function () {
+        self.callback(null, authenticate, context);
+      });
     },
     
     'when handling a request': {
-      topic: function(authenticate) {
+      topic: function(authenticate, context) {
         var self = this;
         var req = new MockRequest();
         var res = new MockResponse();
-        res.end = function() {
-          self.callback(null, req, res)
+        context.done = function(err, user, challenge, status) {
+          self.callback(err, req, res, user, challenge, status);
         }
         
         function next(err) {
@@ -2124,14 +3555,25 @@ vows.describe('authenticate').addBatch({
       'should not generate an error' : function(err, req, res) {
         assert.isNull(err);
       },
-      'should not set user on request' : function(err, req, res) {
+      'should not set user on request' : function(err, req, res, user, challenge, status) {
         assert.isUndefined(req.user);
       },
-      'should set status code to bad request' : function(err, req, res) {
-        assert.equal(res.statusCode, 400);
+      'should pass user to callback as false' : function(err, req, res, user, challenge, status) {
+        assert.isFalse(user);
       },
-      'should not set WWW-Authenticate header' : function(err, req, res) {
-        assert.isUndefined(res._headers['WWW-Authenticate']);
+      'should pass challenges callback' : function(err, req, res, user, challenge, status) {
+        assert.isArray(challenge);
+        assert.lengthOf(challenge, 3);
+        assert.equal(challenge[0], 'Basic foo');
+        assert.equal(challenge[1], 402);
+        assert.equal(challenge[2], 'Digest foo');
+      },
+      'should pass statuses callback' : function(err, req, res, user, challenge, status) {
+        assert.isArray(status);
+        assert.lengthOf(status, 3);
+        assert.equal(status[0], 400);
+        assert.isUndefined(status[1]);
+        assert.equal(status[2], 403);
       },
     },
   },
diff --git a/test/strategies/session-test.js b/test/strategies/session-test.js
index 4eda55d..5a38186 100644
--- a/test/strategies/session-test.js
+++ b/test/strategies/session-test.js
@@ -92,6 +92,49 @@ vows.describe('SessionStrategy').addBatch({
     },
   },
   
+  'strategy handling a request with a login session using user ID 0': {
+    topic: function() {
+      return new SessionStrategy();
+    },
+    
+    'after augmenting with actions': {
+      topic: function(strategy) {
+        var self = this;
+        var req = {};
+        strategy.pass = function() {
+          self.callback(null, req);
+        }
+        strategy.error = function(err) {
+          self.callback(err, req);
+        }
+        
+        req._passport = {};
+        req._passport.instance = {};
+        req._passport.instance.deserializeUser = function(user, done) {
+          done(null, { id: user });
+        }
+        req._passport.session = {};
+        req._passport.session.user = 0;
+        
+        process.nextTick(function () {
+          strategy.authenticate(req);
+        });
+      },
+      
+      'should not generate an error' : function(err, req) {
+        assert.isNull(err);
+      },
+      'should set a user on the request' : function(err, req) {
+        assert.isObject(req.user);
+        assert.equal(req.user.id, 0);
+      },
+      'should maintain the session' : function(err, req) {
+        assert.isObject(req._passport.session);
+        assert.equal(req._passport.session.user, 0);
+      },
+    },
+  },
+  
   'strategy handling a request with a login session that has been invalidated': {
     topic: function() {
       return new SessionStrategy();

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/passportjs.git



More information about the Pkg-javascript-commits mailing list