Authentication and authorization (password, facebook, & more) for your node.js Connect and Express apps.
So far, everyauth enables you to login via:
passwordfacebooktwittergithubinstagramfoursquarelinkedin
everyauth is:
- Modular - We have you covered with Facebook and Twitter OAuth logins, basic login/password support, and modules coming soon for beta invitation support and more.
- Easily Configurable - everyauth was built with powerful configuration needs in mind. Configure an authorization strategy in a straightforward, easy-to-read & easy-to-write approach, with as much granularity as you want over the steps and logic of your authorization strategy.
- Idiomatic - The syntax for configuring and extending your authorization strategies are idiomatic and chainable.
$ npm install everyauth
Using everyauth comes down to just 2 simple steps if using Connect or 3 simple steps if using Express:
-
Choose and Configure Auth Strategies - Find the authentication strategy you desire in one of the sections below. Follow the configuration instructions.
-
Add the Middleware to Connect
var everyauth = require('everyauth'); // Step 1 code goes here // Step 2 code var connect = require('connect'); var app = connect( connect.bodyParser() , connect.cookieParser() , connect.session({secret: 'mr ripley'}) , everyauth.middleware() , connect.router(routes) );
-
Add View Helpers to Express
// Step 1 code // ... // Step 2 code // ... // Step 3 code everyauth.helpExpress(app); app.listen(3000);
For more about what view helpers
everyauthadds to your app, see the section titled "Express Helpers" near the bottom of this README.
There is an example application at ./example
To run it:
$ cd example
$ node server.js
Some OAuth Providers do not allow callbacks to localhost, so you will need to create a localhost
alias called local.host. Make sure you set up your /etc/hosts so that 127.0.0.1 is also
associated with 'local.host'. So inside your /etc/hosts file, one of the lines will look like:
127.0.0.1 localhost local.host
Then point your browser to http://local.host:3000
If you integrate everyauth with connect, then everyauth automatically
sets up a logoutPath at GET /logout for your app. It also
sets a default handler for your logout route that clears your session
of auth information and redirects them to '/'.
To over-write the logout path:
everyauth.everymodule.logoutPath('/bye');To over-write the logout redirect path:
everyauth.everymodule.logoutRedirectPath('/navigate/to/after/logout');To over-write the logout handler:
everyauth.everymodule.handleLogout( function (req, res) {
// Put you extra logic here
req.logout(); // The logout method is added for you by everyauth, too
// And/or put your extra logic here
res.writeHead(303, { 'Location': this.logoutRedirectPath() });
res.end();
});var everyauth = require('everyauth')
, connect = require('connect');
everyauth.facebook
.myHostname('http://localhost:3000')
.appId('YOUR APP ID HERE')
.appSecret('YOUR APP SECRET HERE')
.handleAuthCallbackError( function (req, res) {
// If a user denies your app, Facebook will redirect the user to
// /auth/facebook/callback?error_reason=user_denied&error=access_denied&error_description=The+user+denied+your+request.
// This configurable route handler defines how you want to respond to
// that.
// If you do not configure this, everyauth renders a default fallback
// view notifying the user that their authentication failed and why.
})
.findOrCreateUser( function (session, accessToken, fbUserMetadata) {
// find or create user logic goes here
})
.redirectPath('/');
var routes = function (app) {
// Define your routes here
};
connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);You can also configure more parameters (most are set to defaults) via the same chainable API:
everyauth.facebook
.entryPath('/auth/facebook')
.callbackPath('/auth/facebook/callback')
.scope('email') // Defaults to undefinedIf you want to see what the current value of a configured parameter is, you can do so via:
everyauth.facebook.scope(); // undefined
everyauth.facebook.entryPath(); // '/auth/facebook'To see all parameters that are configurable, the following will return an object whose parameter name keys map to description values:
everyauth.facebook.configurable();Facebook provides many different
permissions
for which your app can ask your user. This is bundled up in the scope query
paremter sent with the oauth request to Facebook. While your app may require
several different permissions from Facebook, Facebook recommends that you only
ask for these permissions incrementally, as you need them. For example, you might
want to only ask for the "email" scope upon registration. At the same time, for
another user, you may want to ask for "user_status" permissions because they
have progressed further along in your application.
everyauth enables you to specify the "scope" dynamically with a second
variation of the configurable scope. In addition to the first variation
that looks like:
everyauth.facebook
.scope('email,user_status');you can have greater dynamic control over "scope" via the second variation of scope:
everyauth.facebook
.scope( function (req, res) {
var session = req.session;
switch (session.userPhase) {
case 'registration':
return 'email';
case 'share-media':
return 'email,user_status';
}
});var everyauth = require('everyauth')
, connect = require('connect');
everyauth.twitter
.myHostname('http://localhost:3000')
.consumerKey('YOUR CONSUMER ID HERE')
.consumerSecret('YOUR CONSUMER SECRET HERE')
.findOrCreateUser( function (session, accessToken, accessTokenSecret, twitterUserMetadata) {
// find or create user logic goes here
})
.redirectPath('/');
var routes = function (app) {
// Define your routes here
};
connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);var everyauth = require('everyauth')
, connect = require('connect');
everyauth.password
.getLoginPath('/login') // Uri path to the login page
.postLoginPath('/login') // Uri path that your login form POSTs to
.loginView('a string of html; OR the name of the jade/etc-view-engine view')
.authenticate( function (login, password) {
// Either, we return a user or an array of errors if doing sync auth.
// Or, we return a Promise that can fulfill to promise.fulfill(user) or promise.fulfill(errors)
// `errors` is an array of error message strings
//
// e.g.,
// Example 1 - Sync Example
// if (usersByLogin[login] && usersByLogin[login].password === password) {
// return usersByLogin[login];
// } else {
// return ['Login failed'];
// }
//
// Example 2 - Async Example
// var promise = this.Promise()
// YourUserModel.find({ login: login}, function (err, user) {
// if (err) return promise.fulfill([err]);
// promise.fulfill(user);
// }
// return promise;
})
.loginSuccessRedirect('/') // Where to redirect to after a login
// If login fails, we render the errors via the login view template,
// so just make sure your loginView() template incorporates an `errors` local.
// See './example/views/login.jade'
.getRegisterPath('/register') // Uri path to the registration page
.postRegisterPath('/register') // The Uri path that your registration form POSTs to
.registerView('a string of html; OR the name of the jade/etc-view-engine view')
.validateRegistration( function (newUserAttributes) {
// Validate the registration input
// Return undefined, null, or [] if validation succeeds
// Return an array of error messages (or Promise promising this array)
// if validation fails
//
// e.g., assuming you define validate with the following signature
// var errors = validate(login, password, extraParams);
// return errors;
//
// The `errors` you return show up as an `errors` local in your jade template
})
.registerUser( function (newUserAttributes) {
// This step is only executed if we pass the validateRegistration step without
// any errors.
//
// Returns a user (or a Promise that promises a user) after adding it to
// some user store.
//
// As an edge case, sometimes your database may make you aware of violation
// of the unique login index, so if this error is sent back in an async
// callback, then you can just return that error as a single element array
// containing just that error message, and everyauth will automatically handle
// that as a failed registration. Again, you will have access to this error via
// the `errors` local in your register view jade template.
// e.g.,
// var promise = this.Promise();
// User.create(newUserAttributes, function (err, user) {
// if (err) return promise.fulfill([err]);
// promise.fulfill(user);
// });
// return promise;
//
// Note: Index and db-driven validations are the only validations that occur
// here; all other validations occur in the `validateRegistration` step documented above.
})
.registerSuccessRedirect('/'); // Where to redirect to after a successful registration
var routes = function (app) {
// Define your routes here
};
connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);You can also configure more parameters (most are set to defaults) via the same chainable API:
everyauth.password
.loginFormFieldName('login') // Defaults to 'login'
.passwordFormFieldName('password') // Defaults to 'password'If you want to see what the current value of a configured parameter is, you can do so via:
everyauth.password.loginFormFieldName(); // 'login'
everyauth.password.passwordFormFieldName(); // 'password'To see all parameters that are configurable, the following will return an object whose parameter name keys map to description values:
everyauth.password.configurable();Sometimes your registration will ask for more information from the user besides the login and password.
For this particular scenario, you can configure the optional step, extractExtraRegistrationParams.
everyauth.password.extractExtraRegistrationParams( function (req) {
return {
phone: req.body.phone
, name: {
first: req.body.first_name
, last: req.body.last_name
}
};
});By default, everyauth uses the field and user key name login during the
registration and login process.
Sometimes, you want to use email or phone instead of login. Moreover,
you also want to validate email and phone fields upon registration.
everyauth provides an easy way to do this:
everyauth.password.loginWith('email');
// OR
everyauth.password.loginWith('phone');With simple login configuration like this, you get email (or phone) validation in addition to renaming of the form field and user key corresponding to what otherwise would typically be referred to as 'login'.
var everyauth = require('everyauth')
, connect = require('connect');
everyauth.github
.myHostname('http://localhost:3000')
.appId('YOUR CLIENT ID HERE')
.appSecret('YOUR CLIENT SECRET HERE')
.findOrCreateUser( function (session, accessToken, githubUserMetadata) {
// find or create user logic goes here
})
.redirectPath('/');
var routes = function (app) {
// Define your routes here
};
connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);You can also configure more parameters (most are set to defaults) via the same chainable API:
everyauth.github
.entryPath('/auth/github')
.callbackPath('/auth/github/callback')
.scope('repo'); // Defaults to undefined
// Can be set to a combination of: 'user', 'public_repo', 'repo', 'gist'
// For more details, see http://develop.github.com/p/oauth.htmlIf you want to see what the current value of a configured parameter is, you can do so via:
everyauth.github.scope(); // undefined
everyauth.github.entryPath(); // '/auth/github'To see all parameters that are configurable, the following will return an object whose parameter name keys map to description values:
everyauth.github.configurable();var everyauth = require('everyauth')
, connect = require('connect');
everyauth.instagram
.myHostname('http://localhost:3000')
.appId('YOUR CLIENT ID HERE')
.appSecret('YOUR CLIENT SECRET HERE')
.findOrCreateUser( function (session, accessToken, instagramUserMetadata) {
// find or create user logic goes here
})
.redirectPath('/');
var routes = function (app) {
// Define your routes here
};
connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);You can also configure more parameters (most are set to defaults) via the same chainable API:
everyauth.instagram
.entryPath('/auth/instagram')
.callbackPath('/auth/instagram/callback')
.scope('basic') // Defaults to 'basic'
// Can be set to a combination of: 'basic', 'comments', 'relationships', 'likes'
// For more details, see http://instagram.com/developer/auth/#scope
.display(undefined); // Defaults to undefined; Set to 'touch' to see a mobile optimized version
// of the instagram auth pageIf you want to see what the current value of a configured parameter is, you can do so via:
everyauth.instagram.callbackPath(); // '/auth/instagram/callback'
everyauth.instagram.entryPath(); // '/auth/instagram'To see all parameters that are configurable, the following will return an object whose parameter name keys map to description values:
everyauth.instagram.configurable();var everyauth = require('everyauth')
, connect = require('connect');
everyauth.foursquare
.myHostname('http://localhost:3000')
.appId('YOUR CLIENT ID HERE')
.appSecret('YOUR CLIENT SECRET HERE')
.findOrCreateUser( function (session, accessToken, foursquareUserMetadata) {
// find or create user logic goes here
})
.redirectPath('/');
var routes = function (app) {
// Define your routes here
};
connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);You can also configure more parameters (most are set to defaults) via the same chainable API:
everyauth.foursquare
.entryPath('/auth/foursquare')
.callbackPath('/auth/foursquare/callback');If you want to see what the current value of a configured parameter is, you can do so via:
everyauth.foursquare.callbackPath(); // '/auth/foursquare/callback'
everyauth.foursquare.entryPath(); // '/auth/foursquare'To see all parameters that are configurable, the following will return an object whose parameter name keys map to description values:
everyauth.foursquare.configurable();var everyauth = require('everyauth')
, connect = require('connect');
everyauth.linkedin
.myHostname('http://localhost:3000')
.consumerKey('YOUR CONSUMER ID HERE')
.consumerSecret('YOUR CONSUMER SECRET HERE')
.findOrCreateUser( function (session, accessToken, accessTokenSecret, linkedinUserMetadata) {
// find or create user logic goes here
})
.redirectPath('/');
var routes = function (app) {
// Define your routes here
};
connect(
connect.bodyParser()
, connect.cookieParser()
, connect.session({secret: 'whodunnit'})
, everyauth.middleware()
, connect.router(routes);
).listen(3000);You can also configure more parameters (most are set to defaults) via the same chainable API:
everyauth.linkedin
.entryPath('/auth/linkedin')
.callbackPath('/auth/linkedin/callback');If you want to see what the current value of a configured parameter is, you can do so via:
everyauth.linkedin.callbackPath(); // '/auth/linkedin/callback'
everyauth.linkedin.entryPath(); // '/auth/linkedin'To see all parameters that are configurable, the following will return an object whose parameter name keys map to description values:
everyauth.linkedin.configurable();If you are using express or connect, then everyauth
provides an easy way to access the user as:
req.userfrom your app servereveryauth.uservia theeveryauthhelper accessible from yourexpressviews.useras a helper accessible from yourexpressviews
To access the user, configure everyauth.everymodule.findUserById.
For example, using mongoose:
everyauth.everymodule.findUserById( function (userId, callback) {
User.findById(userId, callback);
// callback has the signature, function (err, user) {...}
});Once you have configured this method, you now have access to the user object
that was fetched anywhere in your server app code as req.user. For instance:
var app = require('express').createServer()
// Configure your app
app.get('/', function (req, res) {
console.log(req.user); // FTW!
res.render('home');
});Moreover, you can access the user in your views as everyauth.user or as user.
//- Inside ./views/home.jade
span.user-id= everyauth.user.name
#user-id= user.id
If you are using express, everyauth comes with some useful dynamic helpers. To enable them:
var express = require('express')
, everyauth = require('everyauth')
, app = express.createServer();
everyauth.helpExpress(app);Then, from within your views, you will have access to the following helpers methods
attached to the helper, everyauth:
everyauth.loggedIneveryauth.user- the mongoose User document associated with the sessioneveryauth.facebook- The is equivalent to what is stored atreq.session.auth.facebook, so you can do things like ...everyauth.facebook.user- returns the user json provided from the OAuth provider.everyauth.facebook.accessToken- returns the access_token provided from the OAuth provider for authorized API calls on behalf of the user.- And you also get this pattern for other modules - e.g.,
everyauth.twitter.user,everyauth.github.user, etc.
You also get access to the view helper
user- the same aseveryauth.userabove
As an example of how you would use these, consider the following ./views/user.jade jade template:
.user-id
.label User Id
.value #{user.id}
.facebook-id
.label User Facebook Id
.value #{everyauth.facebook.user.id}
mongoose-auth also provides convenience methods on the ServerRequest instance req.
From any scope that has access to req, you get the following convenience getters and methods:
req.loggedIn- a Boolean getter that tells you if the request is by a logged in userreq.user- the mongoose User document associated with the sessionreq.logout()- clears the sesion of your auth data
everyauth was built with powerful configuration needs in mind.
Every module comes with a set of parameters that you can configure
directly. To see a list of those parameters on a per module basis,
with descriptions about what they do, enter the following into the
node REPL (to access the REPL, just type node at the command line)
> var ea = require('everyauth');
> ea.facebook.configurable();
For example, you will see that one of the configuration parameters is
moduleTimeout, which is described to be how long to wait per step before timing out and invoking any timeout callbacks
Every configuration parameter corresponds to a method of the same name
on the auth module under consideration (i.e., in this case
ea.facebook). To create or over-write that parameter, just
call that method with the new value as the argument:
ea.facebook
.moduleTimeout( 4000 ); // Wait 4 seconds before timing out any step
// involved in the facebook auth processConfiguration parameters can be scalars. But they can be anything. For
example, they can also be functions, too. The facebook module has a
configurable step named findOrCreateUser that is described as
"STEP FN [findOrCreateUser] function encapsulating the logic for the step
fetchOAuthUser.". What this means is that this configures the
function (i.e., "FN") that encapsulates the logic of this step.
ea.facebook
.findOrCreateUser( function (session, accessToken, extra, oauthUser) {
// find or create user logic goes here
});How do we know what arguments the function takes?
We elaborate more about step function configuration in our
Introspection section below.
everyauth provides convenient methods and getters for finding out about any module.
Show all configurable parameters with their descriptions:
everyauth.facebook.configurable();Show the value of a single configurable parameter:
// Get the value of the configurable callbackPath parameter
everyauth.facebook.callbackPath(); // => '/auth/facebook/callback'Show the declared routes (pretty printed):
everyauth.facebook.routes;Show the steps initiated by a given route:
everyauth.facebook.route.get.entryPath.steps;
everyauth.facebook.route.get.callbackPath.steps;Sometimes you need to set up additional steps for a given auth
module, by defining that step in your app. For example, the
set of steps triggered when someone requests the facebook
module's callbackPath contains a step that you must define
in your app. To see what that step is, you can introspect
the callbackPath route with the facebook module.
everyauth.facebook.route.get.callbackPath.steps.incomplete;
// => [ { name: 'findOrCreateUser',
// error: 'is missing: its function' } ]This tells you that you must define the function that defines the
logic for the findOrCreateUser step. To see what the function
signature looks like for this step:
var matchingStep =
everyauth.facebook.route.get.callbackPath.steps.filter( function (step) {
return step.name === 'findOrCreateUser';
})[0];
// { name: 'findOrCreateUser',
// accepts: [ 'session', 'accessToken', 'extra', 'oauthUser' ],
// promises: [ 'user' ] }This tells you that the function should take the following 4 arguments:
function (session, accessToken, extra, oauthUser) {
...
}And that the function should return a user that is a user object or
a Promise that promises a user object.
// For synchronous lookup situations, you can return a user
function (session, accessToken, extra, oauthUser) {
...
return { id: 'some user id', username: 'some user name' };
}
// OR
// For asynchronous lookup situations, you must return a Promise that
// will be fulfilled with a user later on
function (session, accessToken, extra, oauthUser) {
var promise = this.Promise();
asyncFindUser( function (err, user) {
if (err) return promise.fail(err);
promise.fulfill(user);
});
return promise;
}You add this function as the block for the step findOrCreateUser just like
you configure any other configurable parameter in your auth module:
everyauth.facebook
.findOrCreateUser( function (session, accessToken, extra, oauthUser) {
// Logic goes here
});There are also several other introspection tools at your disposal:
For example, to show the submodules of an auth module by name:
everyauth.oauth2.submodules;Other introspection tools to describe (explanations coming soon):
-
Invalid Steps
everyauth.facebook.routes.get.callbackPath.steps.invalid
Currently, the following module uses everyauth. If you are using everyauth in a project, app, or module, get in touch to get added to the list below:
- mongoose-auth Authorization plugin for use with the node.js MongoDB orm.
MIT License
Brian Noguchi