Overview
Passport is authentication middleware for Node.js. It is designed to serve a singular purpose: authenticate requests. Passport cleanly encapsulates this functionality, while delegating unrelated details such as data access to the application. Whether you are building a new application or working on an existing one, this separation of concerns makes Passport extremely easy to integrate.
In modern web applications, authentication can be performed in a variety of ways. Traditionally, users log in by providing a username and password. Social networks, along with the billions of people that have joined them, have made single sign-on (SSO) using Facebook or Google a popular option. Recent innovations, encompassed by Web Authentication (WebAuthn), allow people to log in using fingerprint or facial recognition.
Application architectures also impact how authentication is achieved. To support web applications as well as native mobile and desktop applications, server-side logic can be exposed as an API which is invoked by applications running on a desktop, mobile device, or within a browser executing client-side JavaScript. Access to APIs is protected by token-based credentials, typically issued via OAuth.
Passport provides a flexible framework which allows an application to make use of any of these authentication mechanisms. Passport reduces the complexity of authenticating a request to a simple statement:
app.post('/login/password', passport.authenticate('local'));
Hidden behind that simple statement are three fundamental concepts:
- Middleware
- Strategies
- Sessions
This guide provides an overview of these concepts, explaining how they fit together within Passport. Some of the most commonly used authentication mechanisms will be explored in detail, to illustrate how they are integrated. After reading this guide, you will have an understanding of how Passport works when authenticating requests to your application.
Middleware
Passport is used as middleware within a web application to authenticate requests. Middleware was popularized in Node.js by Express and its even more minimalist sibling Connect. Given its popularity, middleware is easily adaptable to other web frameworks.
The following code is an example of a route that authenticates a user with a username and password:
app.post('/login/password',
passport.authenticate('local', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/~' + req.user.username);
});
In this route, passport.authenticate()
is middleware
which will authenticate the request. By default, when authentication succeeds,
the req.user
property is set to the authenticated user, a login session is
established, and the next function in the stack is called. This next function
is typically application-specific logic which will process the request on behalf
of the user.
When authentication fails, an HTTP 401 Unauthorized
response will be sent and
the request-response cycle will end. Any additional functions in the stack will
not be called. This default behavior is suitable for APIs obeying representational state transfer
(REST) constaints, and can be modified using options.
In traditional web applications, which interact with the user via HTML pages,
forms, and redirects, the failureRedirect
option is commonly used. Instead
of responding with 401 Unauthorized
, the browser will be redirected to the
given location with a 302 Found
response. This location is typically the
login page, which gives the user another attempt to log in after an
authentication failure. This is often paired with the failureMessage
option,
which will add an informative message to the session about why authentication
failed which can then be displayed to the user.
The mechanism used to authenticate the request is implemented by a strategy.
Authenticating a user with a username and password entails a different set of
operations than authenticating a user via OpenID Connect. As such, those two
mechanisms are implemented by two different strategies. In the route above, the
local
strategy is used to verify a username and password.
Strategies
Strategies are responsible for authenticating requests, which they accomplish by implementing an authentication mechanism. Authentication mechanisms define how to encode a credential, such as a password or an assertion from an identity provider (IdP), in a request. They also specify the procedure necessary to verify that credential. If the credential is successfully verified, the request is authenticated.
There are a wide variety of authentication mechanisms, and a corresponding variety of strategies. Strategies are distributed in separate packages which must be installed, configured, and registered.
Install
Strategies are published to the npm registry, and installed using a package manager.
For example, the following command will install passport-local
,
a package which provides a strategy for authenticating with a username and
password:
$ npm install passport-local
And the following command will install passport-openidconnect
,
a package which implements support for OpenID Connect:
$ npm install passport-openidconnect
Developers only need to install the packages which provide authentication mechanisms required by the application. These packages are then plugged into Passport. This reduces overall application size by avoiding unnecessary dependencies.
Configure
Once a package has been installed, the strategy needs to be configured. The configuration varies with each authentication mechanism, so strategy-specific documentation should be consulted. That being said, there are common patterns that are encountered across many strategies.
The following code is an example that configures the LocalStrategy
:
var LocalStrategy = require('passport-local');
var strategy = new LocalStrategy(function verify(username, password, cb) {
db.get('SELECT * FROM users WHERE username = ?', [ username ], function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false, { message: 'Incorrect username or password.' }); }
crypto.pbkdf2(password, user.salt, 310000, 32, 'sha256', function(err, hashedPassword) {
if (err) { return cb(err); }
if (!crypto.timingSafeEqual(user.hashed_password, hashedPassword)) {
return cb(null, false, { message: 'Incorrect username or password.' });
}
return cb(null, user);
});
});
});
Verify Function
The LocalStrategy
constructor takes a function as an argument. This function
is known as a verify
function, and is a common pattern in many strategies.
When authenticating a request, a strategy parses the credential contained in the
request. A verify
function is then called, which is responsible for
determining the user to which that credential belongs. This allows data access
to be delegated to the application.
In this particular example, the verify
function is executing a SQL query to
obtain a user record from the database and, after verifying the password,
yielding the record back to the strategy, thus authenticating the user and
establishing a login session.
Because a verify
function is supplied by the application itself, access to
persistent storage is not constrained in any way. The application is free to
use any data storage system, including relational databases, graph databases,
or document stores, and structure data within that database according to any
schema.
A verify
function is strategy-specific, and the exact arguments it receives
and parameters it yields will depend on the underlying authentication mechanism.
For authentication mechanisms involving shared secrets, such as a password, a
verify
function is responsible for verifying the credential and yielding a
user. For mechanisms that provide cryptographic authentication, a verify
function will typically yield a user and a key, the later of which the strategy
will use to cryptographically verify the credential.
A verify
function yields under one of three conditions: success, failure, or
an error.
If the verify
function finds a user to which the credential belongs, and that
credential is valid, it calls the callback with the authenticating user:
return cb(null, user);
If the credential does not belong to a known user, or is not valid, the verify
function calls the callback with false
to indicate an authentication failure:
return cb(null, false);
If an error occurs, such as the database not being available, the callback is called with an error, in idiomatic Node.js style:
return cb(err);
It is important to distinguish between the two failure cases that can occur.
Authentication failures are expected conditions, in which the server is
operating normally, even though invalid credentials are being received from the
user (or a malicious adversary attempting to authenticate as the user). Only
when the server is operating abnormally should err
be set, to indicate an
internal error.
Register
With the strategy configured, it is then registered by calling .use()
:
var passport = require('passport');
passport.use(strategy);
All strategies have a name which, by convention, corresponds to the package
name according to the pattern passport-{name}
. For instance, the
LocalStrategy
configured above is named local
as it is distributed in the
passport-local
package.
Once registered, the strategy can be employed to authenticate a request by
passing the name of the strategy as the first argument to passport.authenticate()
middleware:
app.post('/login/password',
passport.authenticate('local', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/~' + req.user.username);
});
In cases where there is a naming conflict, or the default name is not
sufficiently descriptive, the name can be overridden when registering the
strategy by passing a name as the first argument to .use()
:
var passport = require('passport');
passport.use('password', strategy);
That name is then specified to passport.authenticate()
middleware:
app.post('/login/password',
passport.authenticate('password', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/~' + req.user.username);
});
For brevity, strategies are often configured and registered in a single statement:
var passport = require('passport');
var LocalStrategy = require('passport-local');
passport.use(new LocalStrategy(function verify(username, password, cb) {
// ...
});
Sessions
A web application needs the ability to identify users as they browse from page to page. This series of requests and responses, each associated with the same user, is known as a session.
HTTP is a stateless protocol, meaning that each request to an application can be understood in isolation - without any context from previous requests. This poses a challenge for web applications with logged in users, as the authenticated user needs to be remembered across subsequent requests as they navigate the application.
To solve this challenge, web applications make use of sessions, which allow state to be maintained between the application server and the user's browser. A session is established by setting an HTTP cookie in the browser, which the browser then transmits to the server on every request. The server uses the value of the cookie to retrieve information it needs across multiple requests. In effect, this creates a stateful protocol on top of HTTP.
While sessions are used to maintain authentication state, they can also be used by applications to maintain other state unrelated to authentication. Passport is carefully designed to isolate authentication state, referred to as a login session, from other state that may be stored in the session.
Applications must initialize session support in order to make use of login
sessions. In an Express app, session support is added
by using express-session
middleware.
var session = require('express-session');
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
cookie: { secure: true }
}));
To maintain a login session, Passport serializes and deserializes user
information to and from the session. The information that is stored is
determined by the application, which supplies a serializeUser
and a
deserializeUser
function.
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, {
id: user.id,
username: user.username,
picture: user.picture
});
});
});
passport.deserializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user);
});
});
A login session is established upon a user successfully authenticating using a
credential. The following route will authenticate a user using a username and
password. If successfully verified, Passport will call the serializeUser
function, which in the above example is storing the user's ID, username, and
picture. Any other properties of the user, such as an address or birthday, are
not stored.
app.post('/login/password',
passport.authenticate('local', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/~' + req.user.username);
});
As the user navigates from page to page, the session itself can be authenticated
using the built-in session
strategy. Because an authenticated session is
typically needed for the majority of routes in an application, it is common to
use this as application-level middleware,
after session
middleware.
app.use(session(/* ... */);
app.use(passport.authenticate('session'));
This can also be accomplished, more succinctly, using the passport.session()
alias.
app.use(session(/* ... */);
app.use(passport.session());
When the session is authenticated, Passport will call the deserializeUser
function, which in the above example is yielding the previously stored user ID,
username, and picture. The req.user
property is then set to the yielded
information.
There is an inherent tradeoff between the amount of data stored in a session and
database load incurred when authenticating a session. This tradeoff is
particularly pertinent when session data is stored on the client, rather than
the server, using a package such as cookie-session
.
Storing less data in the session will require heavier queries to a database to
obtain that information. Conversely, storing more data in the session reduces
database queries while potentially exceeding the maximum amount of data that can
be stored in a cookie.
This tradeoff is controlled by the application and the serializeUser
and
deserializeUser
functions it supplies. In contrast to the above example, the
following example minimizes the data stored in the session at the expense of
querying the database for every request in which the session is authenticated.
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user.id);
});
});
passport.deserializeUser(function(id, cb) {
db.get('SELECT * FROM users WHERE id = ?', [ id ], function(err, user) {
if (err) { return cb(err); }
return cb(null, user);
});
});
To balance this tradeoff, it is recommended that any user information needed on every request to the application be stored in the session. For example, if the application displays a user element containing the user's name, email address, and photo on every page, that information should be stored in the session to eliminate what would otherwise be frequent database queries. Specific routes, such as a checkout page, that need additional information such as a shipping address, can query the database for that data.
Username & Password
A username and password is the traditional, and still most widely used, way for
users to authenticate to a website. Support for this mechanism is provided by
the passport-local
package.
Install
To install passport-local
, execute the following command:
$ npm install passport-local
Configure
The following code is an example that configures and registers the
LocalStrategy
:
var passport = require('passport');
var LocalStrategy = require('passport-local');
var crypto = require('crypto');
passport.use(new LocalStrategy(function verify(username, password, cb) {
db.get('SELECT * FROM users WHERE username = ?', [ username ], function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false, { message: 'Incorrect username or password.' }); }
crypto.pbkdf2(password, user.salt, 310000, 32, 'sha256', function(err, hashedPassword) {
if (err) { return cb(err); }
if (!crypto.timingSafeEqual(user.hashed_password, hashedPassword)) {
return cb(null, false, { message: 'Incorrect username or password.' });
}
return cb(null, user);
});
});
});
The LocalStrategy
constructor takes a verify
function as an argument, which
accepts username
and password
as arguments. When authenticating a request,
the strategy parses a username and password, which are submitted via an HTML
form to the web application. The strategy then calls the verify
function with
those credentials.
The verify
function is responsible for determining the user to which the
username belongs, as well as verifying the password. Because the verify
function is supplied by the application, the application is free to use a
database and schema of its choosing. The example above illustrates usage of a
SQL database.
Similarly, the application is free to determine its password storage format. The example above illustrates usage of PBKDF2 when comparing the user-supplied password with the hashed password stored in the database.
In case of authentication failure, the verify
callback supplies a message, via
the message
option, describing why authentication failed. This will be
displayed to the user when they are re-prompted to sign in, informing them of
what went wrong.
Prompt
The user is prompted to sign in with their username and password by rendering a form. This is accomplished by defining a route:
app.get('/login',
function(req, res, next) {
res.render('login');
});
The following form is an example which uses best practices:
<form action="/login/password" method="post">
<div>
<label for="username">Username</label>
<input id="username" name="username" type="text" autocomplete="username" required />
</div>
<div>
<label for="current-password">Password</label>
<input id="current-password" name="password" type="password" autocomplete="current-password" required />
</div>
<div>
<button type="submit">Sign in</button>
</div>
</form>
Authenticate
When the user submits the form, it is processed by a route that authenticates the user using the username and password they entered.
app.post('/login/password',
passport.authenticate('local', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/~' + req.user.username);
});
If authentication succeeds, passport.authenticate()
middleware calls the next
function in the stack. In this example, the function is redirecting the
authenticated user to their profile page.
When authentication fails, the user is re-prompted to sign in and informed that
their initial attempt was not successful. This is accomplished by using the
failureRedirect
option, which will redirect the user to the login page, along
with the failureMessage
option which will add the message to
req.session.messages
.
Facebook Login allows
users to sign in using their Facebook account. Support for Faceboook Login is
provided by the passport-facebook
package.
Install
To install passport-facebook
, execute the following command:
$ npm install passport-facebook
Configure
Before your application can make use of Facebook Login, you must register your app with Facebook. This can be done in the App dashboard at Facebook for Developers. Once registered, your app will be issued an app ID and secret which will be used in the strategy configuration.
The following code is an example that configures and registers the
FacebookStrategy
:
var passport = require('passport');
var FacebookStrategy = require('passport-facebook');
passport.use(new FacebookStrategy({
clientID: process.env['FACEBOOK_APP_ID'],
clientSecret: process.env['FACEBOOK_APP_SECRET'],
callbackURL: 'https://www.example.com/oauth2/redirect/facebook'
},
function(accessToken, refreshToken, profile, cb) {
db.get('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [
'https://www.facebook.com',
profile.id
], function(err, cred) {
if (err) { return cb(err); }
if (!cred) {
// The Facebook account has not logged in to this app before. Create a
// new user record and link it to the Facebook account.
db.run('INSERT INTO users (name) VALUES (?)', [
profile.displayName
], function(err) {
if (err) { return cb(err); }
var id = this.lastID;
db.run('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [
id,
'https://www.facebook.com',
profile.id
], function(err) {
if (err) { return cb(err); }
var user = {
id: id.toString(),
name: profile.displayName
};
return cb(null, user);
});
});
} else {
// The Facebook account has previously logged in to the app. Get the
// user record linked to the Facebook account and log the user in.
db.get('SELECT * FROM users WHERE id = ?', [ cred.user_id ], function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
return cb(null, user);
});
}
};
}
));
The options to the FacebookStrategy
constructor must include a clientID
and
clientSecret
, the values of which are set to the app ID and secret that were
obtained when registering your application. A callbackURL
must also be
included. Facebook will redirect users to this location after they have
authenticated. The path of this URL must match the route defined below.
The verify
function accepts an accessToken
, refreshToken
and profile
as
arguments. accessToken
and refreshToken
are used for API access, and are
not needed for authentication. profile
is a normalized
profile containing information provided by Facebook about the user who is
signing in.
The verify
function is responsible for determining the user to which the
Facebook account belongs. The first time that account is used to sign in, a new
user record is typically created automatically using profile information
supplied by Facebook, and that record is then linked to the Facebook account.
On subsequent signins, the existing user record will be found via its relation
to the Facebook account.
Linking social accounts to a user record is recommended, as it allows users to link multiple social accounts from other providers in the event that they stop using Facebook. Alternatively, the user could set up a credential, such as a password, for their user account at your app. Either feature allows the user to continue to sign in to your application independent of their Facebook account.
The example above illustrates usage of a SQL database to find or create a user
record and link it to a Facebook account. However, because the verify
function is supplied by the application, the application is free to use a
database and schema of its choosing.
Internally, Facebook Login is implemented using OAuth 2.0. As such, the strategy configuration is able to make use of additional options and functionality provided by the base OAuth 2.0 strategy.
Prompt
Place a button on the application's login page, prompting the user to sign in with Facebook.
<a href="/login/facebook" class="button">Log In With Facebook</a>
Define a route that, when the button is clicked, will redirect the user to Facebook, where they will authenticate.
app.get('/login/facebook', passport.authenticate('facebook'));
If your application needs additional permissions from the user, they can be
requested with the scope
option:
app.get('/login/facebook', passport.authenticate('facebook', {
scope: [ 'email', 'user_location' ]
}));
Authenticate
After the user has authenticated with Facebook, they will be redirected back to your application. Define a route which will handle this redirect.
app.get('/oauth2/redirect/facebook',
passport.authenticate('facebook', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/');
});
When a request to this route is processed, the strategy will authenticate the
fact that the user signed in with Facebook and obtain that user's profile
information. If authentication succeeds, passport.authenticate()
middleware
calls the next function in the stack. In this example, the function is
redirecting the authenticated user to the home page.
When authentication fails, the user is re-prompted to sign in and informed that
their initial attempt was not successful. This is accomplished by using the
failureRedirect
option, which will redirect the user to the login page, along
with the failureMessage
option which will add the message to
req.session.messages
.
The path of this route should be the value supplied for the callbackURL
option
in the strategy configuration above.
Sign In With Google allows users to
sign in using their Google account. Support for Sign In With Google is
provided by the passport-google-oidc
package.
Install
To install passport-google-oidc
, execute the following command:
$ npm install passport-google-oidc
Configure
Before your application can make use of Sign In With Google, you must register your app with Google. This can be done in the APIs & Services page of the Google Cloud Platform console. Once registered, your app will be issued a client ID and secret which will be used in the strategy configuration.
The following code is an example that configures and registers the
GoogleStrategy
:
var passport = require('passport');
var GoogleStrategy = require('passport-google-oidc');
passport.use(new GoogleStrategy({
clientID: process.env['GOOGLE_CLIENT_ID'],
clientSecret: process.env['GOOGLE_CLIENT_SECRET'],
callbackURL: 'https://www.example.com/oauth2/redirect/google'
},
function(issuer, profile, cb) {
db.get('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [
issuer,
profile.id
], function(err, cred) {
if (err) { return cb(err); }
if (!cred) {
// The Google account has not logged in to this app before. Create a
// new user record and link it to the Google account.
db.run('INSERT INTO users (name) VALUES (?)', [
profile.displayName
], function(err) {
if (err) { return cb(err); }
var id = this.lastID;
db.run('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [
id,
issuer,
profile.id
], function(err) {
if (err) { return cb(err); }
var user = {
id: id.toString(),
name: profile.displayName
};
return cb(null, user);
});
});
} else {
// The Google account has previously logged in to the app. Get the
// user record linked to the Google account and log the user in.
db.get('SELECT * FROM users WHERE id = ?', [ cred.user_id ], function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
return cb(null, user);
});
}
};
}
));
The options to the GoogleStrategy
constructor must include a clientID
and
clientSecret
, the values of which are set to the client ID and secret that
were obtained when registering your application. A callbackURL
must also be
included. Google will redirect users to this location after they have
authenticated. The path of this URL must match the route defined below.
The verify
function accepts issuer
and profile
as arguments. issuer
is
set to "https://accounts.google.com"
, indicating that the user signed in with
Google. profile
is a normalized profile containing
information provided by Google about the user who is signing in.
The verify
function is responsible for determining the user to which the
Google account belongs. The first time that account is used to sign in, a new
user record is typically created automatically using profile information
supplied by Google, and that record is then linked to the Google account. On
subsequent signins, the existing user record will be found via its relation to
the Google account.
Linking social accounts to a user record is recommended, as it allows users to link multiple social accounts from other providers in the event that they stop using Google. Alternatively, the user could set up a credential, such as a password, for their user account at your app. Either feature allows the user to continue to sign in to your application independent of their Google account.
The example above illustrates usage of a SQL database to find or create a user
record and link it to a Google account. However, because the verify
function
is supplied by the application, the application is free to use a database and
schema of its choosing.
Internally, Sign In With Google is implemented using OpenID Connect. As such, the strategy configuration is able to make use of additional options and functionality provided by the base OpenID Connect strategy.
Prompt
Place a button on the application's login page, prompting the user to sign in with Google.
<a href="/login/google" class="button">Sign in with Google</a>
Define a route that, when the button is clicked, will redirect the user to Google, where they will authenticate.
app.get('/login/google', passport.authenticate('google'));
If your application needs additional information about the user, that can be
requested with the scope
option:
app.get('/login/google', passport.authenticate('google', {
scope: [ 'email' ]
}));
Authenticate
After the user has authenticated with Google, they will be redirected back to your application. Define a route which will handle this redirect.
app.get('/oauth2/redirect/google',
passport.authenticate('google', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/');
});
When a request to this route is processed, the strategy will authenticate the
fact that the user signed in with Google and obtain that user's profile
information. If authentication succeeds, passport.authenticate()
middleware
calls the next function in the stack. In this example, the function is
redirecting the authenticated user to the home page.
When authentication fails, the user is re-prompted to sign in and informed that
their initial attempt was not successful. This is accomplished by using the
failureRedirect
option, which will redirect the user to the login page, along
with the failureMessage
option which will add the message to
req.session.messages
.
The path of this route should be the value supplied for the callbackURL
option
in the strategy configuration above.
Log in with Twitter
allows users to sign in using their Twitter account. Support for Log in with
Twitter is provided by the passport-twitter
package.
Install
To install passport-twitter
, execute the following command:
$ npm install passport-twitter
Configure
Before your application can make use of Log in with Twitter, you must register your app with Twitter. This can be done in the Apps dashboard at Twitter Developer Platform. Once registered, your app will be issued an API key and secret which will be used in the strategy configuration.
The following code is an example that configures and registers the
TwitterStrategy
:
var passport = require('passport')
var TwitterStrategy = require('passport-twitter');
passport.use(new TwitterStrategy({
consumerKey: process.env['TWITTER_API_KEY'],
consumerSecret: process.env['TWITTER_API_SECRET'],
callbackURL: 'http://www.example.com/oauth/callback/twitter'
},
function(token, tokenSecret, profile, done) {
db.get('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [
'https://twitter.com',
profile.id
], function(err, cred) {
if (err) { return cb(err); }
if (!cred) {
// The Twitter account has not logged in to this app before. Create
// new user record and link it to the Twitter account.
db.run('INSERT INTO users (name) VALUES (?)', [
profile.displayName
], function(err) {
if (err) { return cb(err); }
var id = this.lastID;
db.run('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [
id,
'https://twitter.com',
profile.id
], function(err) {
if (err) { return cb(err); }
var user = {
id: id.toString(),
name: profile.displayName
};
return cb(null, user);
});
});
} else {
// The Twitter account has previously logged in to the app. Get the
// user record linked to the Twitter account and log the user in.
db.get('SELECT * FROM users WHERE id = ?', [ cred.user_id ], function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
return cb(null, user);
});
}
};
}
));
The options to the TwitterStrategy
constructor must include a consumerKey
and consumerSecret
, the values of which are set to the API key and secret that
were obtained when registering your application. A callbackURL
must also be
included. Twitter will redirect users to this location after they have
authenticated. The path of this URL must match the route defined below.
The verify
function accepts a token
, tokenSecret
and profile
as
arguments. token
and tokenSecret
are used for API access, and are not
needed for authentication. profile
is a normalized profile
containing information provided by Twitter about the user who is signing in.
The verify
function is responsible for determining the user to which the
Twitter account belongs. The first time that account is used to sign in, a new
user record is typically created automatically using profile information
supplied by Twitter, and that record is then linked to the Twitter account. On
subsequent signins, the existing user record will be found via its relation to
the Twitter account.
Linking social accounts to a user record is recommended, as it allows users to link multiple social accounts from other providers in the event that they stop using Twitter. Alternatively, the user could set up a credential, such as a password, for their user account at your app. Either feature allows the user to continue to sign in to your application independent of their Twitter account.
The example above illustrates usage of a SQL database to find or create a user
record and link it to a Twitter account. However, because the verify
function
is supplied by the application, the application is free to use a database and
schema of its choosing.
Internally, Log in with Twitter is implemented using OAuth 1.0a. As such, the strategy configuration is able to make use of additional options and functionality provided by the base OAuth strategy.
Prompt
Place a button on the application's login page, prompting the user to sign in with Twitter.
<a href="/login/twitter" class="button">Sign in with Twitter</a>
Define a route that, when the button is clicked, will redirect the user to Twitter, where they will authenticate.
app.get('/login/twitter', passport.authenticate('twitter'));
Authenticate
After the user has authenticated with Twitter, they will be redirected back to your application. Define a route which will handle this redirect.
app.get('/oauth/callback/twitter',
passport.authenticate('twitter', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/');
});
When a request to this route is processed, the strategy will authenticate the
fact that the user signed in with Twitter and obtain that user's profile
information. If authentication succeeds, passport.authenticate()
middleware
calls the next function in the stack. In this example, the function is
redirecting the authenticated user to the home page.
When authentication fails, the user is re-prompted to sign in and informed that
their initial attempt was not successful. This is accomplished by using the
failureRedirect
option, which will redirect the user to the login page, along
with the failureMessage
option which will add the message to
req.session.messages
.
The path of this route should be the value supplied for the callbackURL
option
in the strategy configuration above.
OAuth
OAuth is a standard protocol that allows users to authorize API access to web and desktop or mobile applications. Once access has been granted, the authorized application can utilize the API on behalf of the user. OAuth has also emerged as a popular mechanism for delegated authentication.
OAuth comes in two primary flavors, both of which are widely deployed.
The initial version of OAuth was developed as an open standard by a loosely organized collective of web developers. Their work resulted in OAuth 1.0, which was superseded by OAuth 1.0a. This work has now been standardized by the IETF as RFC 5849.
Recent efforts undertaken by the Web Authorization Protocol Working Group have focused on defining OAuth 2.0. Due to the lengthy standardization effort, providers have proceeded to deploy implementations conforming to various drafts, each with slightly different semantics.
Thankfully, Passport shields an application from the complexities of dealing with OAuth variants. In many cases, a provider-specific strategy can be used instead of the generic OAuth strategies described below. This cuts down on the necessary configuration, and accommodates any provider-specific quirks. See Facebook, Twitter or the list of providers for preferred usage.
Support for OAuth is provided by the passport-oauth module.
Install
$ npm install passport-oauth
OAuth 1.0
OAuth 1.0 is a delegated authentication strategy that involves multiple steps. First, a request token must be obtained. Next, the user is redirected to the service provider to authorize access. Finally, after authorization has been granted, the user is redirected back to the application and the request token can be exchanged for an access token. The application requesting access, known as a consumer, is identified by a consumer key and consumer secret.
Configuration
When using the generic OAuth strategy, the key, secret, and endpoints are specified as options.
var passport = require('passport')
, OAuthStrategy = require('passport-oauth').OAuthStrategy;
passport.use('provider', new OAuthStrategy({
requestTokenURL: 'https://www.provider.com/oauth/request_token',
accessTokenURL: 'https://www.provider.com/oauth/access_token',
userAuthorizationURL: 'https://www.provider.com/oauth/authorize',
consumerKey: '123-456-789',
consumerSecret: 'shhh-its-a-secret',
callbackURL: 'https://www.example.com/auth/provider/callback'
},
function(token, tokenSecret, profile, done) {
User.findOrCreate(..., function(err, user) {
done(err, user);
});
}
));
The verify callback for OAuth-based strategies accepts token
, tokenSecret
,
and profile
arguments. token
is the access token and tokenSecret
is its
corresponding secret. profile
will contain user profile information provided
by the service provider; refer to User Profile for
additional information.
Routes
Two routes are required for OAuth authentication. The first route initiates an OAuth transaction and redirects the user to the service provider. The second route is the URL to which the user will be redirected after authenticating with the provider.
// Redirect the user to the OAuth provider for authentication. When
// complete, the provider will redirect the user back to the application at
// /auth/provider/callback
app.get('/auth/provider', passport.authenticate('provider'));
// The OAuth provider has redirected the user back to the application.
// Finish the authentication process by attempting to obtain an access
// token. If authorization was granted, the user will be logged in.
// Otherwise, authentication has failed.
app.get('/auth/provider/callback',
passport.authenticate('provider', { successRedirect: '/',
failureRedirect: '/login' }));
Link
A link or button can be placed on a web page, which will start the authentication process when clicked.
<a href="/auth/provider">Log In with OAuth Provider</a>
OAuth 2.0
OAuth 2.0 is the successor to OAuth 1.0, and is designed to overcome perceived shortcomings in the earlier version. The authentication flow is essentially the same. The user is first redirected to the service provider to authorize access. After authorization has been granted, the user is redirected back to the application with a code that can be exchanged for an access token. The application requesting access, known as a client, is identified by an ID and secret.
Configuration
When using the generic OAuth 2.0 strategy, the client ID, client secret, and endpoints are specified as options.
var passport = require('passport')
, OAuth2Strategy = require('passport-oauth').OAuth2Strategy;
passport.use('provider', new OAuth2Strategy({
authorizationURL: 'https://www.provider.com/oauth2/authorize',
tokenURL: 'https://www.provider.com/oauth2/token',
clientID: '123-456-789',
clientSecret: 'shhh-its-a-secret'
callbackURL: 'https://www.example.com/auth/provider/callback'
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate(..., function(err, user) {
done(err, user);
});
}
));
The verify callback for OAuth 2.0-based strategies accepts accessToken
,
refreshToken
, and profile
arguments. refreshToken
can be used to obtain
new access tokens, and may be undefined
if the provider does not issue refresh
tokens. profile
will contain user profile information provided by the service
provider; refer to User Profile for additional information.
Routes
Two routes are required for OAuth 2.0 authentication. The first route redirects the user to the service provider. The second route is the URL to which the user will be redirected after authenticating with the provider.
// Redirect the user to the OAuth 2.0 provider for authentication. When
// complete, the provider will redirect the user back to the application at
// /auth/provider/callback
app.get('/auth/provider', passport.authenticate('provider'));
// The OAuth 2.0 provider has redirected the user back to the application.
// Finish the authentication process by attempting to obtain an access
// token. If authorization was granted, the user will be logged in.
// Otherwise, authentication has failed.
app.get('/auth/provider/callback',
passport.authenticate('provider', { successRedirect: '/',
failureRedirect: '/login' }));
Scope
When requesting access using OAuth 2.0, the scope of access is controlled by the scope option.
app.get('/auth/provider',
passport.authenticate('provider', { scope: 'email' })
);
Multiple scopes can be specified as an array.
app.get('/auth/provider',
passport.authenticate('provider', { scope: ['email', 'sms'] })
);
Values for the scope
option are provider-specific. Consult the provider's
documentation for details regarding supported scopes.
Link
A link or button can be placed on a web page, which will start the authentication process when clicked.
<a href="/auth/provider">Log In with OAuth 2.0 Provider</a>
OpenID
OpenID is an open standard for federated authentication. When visiting a website, users present their OpenID to sign in. The user then authenticates with their chosen OpenID provider, which issues an assertion to confirm the user's identity. The website verifies this assertion in order to sign the user in.
Support for OpenID is provided by the passport-openid module.
Install
$ npm install passport-openid
Configuration
When using OpenID, a return URL and realm must be specified. The returnURL
is
the URL to which the user will be redirected after authenticating with their
OpenID provider. realm
indicates the part of URL-space for which
authentication is valid. Typically this will be the root URL of the website.
var passport = require('passport')
, OpenIDStrategy = require('passport-openid').Strategy;
passport.use(new OpenIDStrategy({
returnURL: 'http://www.example.com/auth/openid/return',
realm: 'http://www.example.com/'
},
function(identifier, done) {
User.findOrCreate({ openId: identifier }, function(err, user) {
done(err, user);
});
}
));
The verify callback for OpenID authentication accepts an identifier
argument
containing the user's claimed identifier.
Form
A form is placed on a web page, allowing the user to enter their OpenID and sign in.
<form action="/auth/openid" method="post">
<div>
<label>OpenID:</label>
<input type="text" name="openid_identifier"/><br/>
</div>
<div>
<input type="submit" value="Sign In"/>
</div>
</form>
Routes
Two routes are required for OpenID authentication. The first route accepts the form submission containing an OpenID identifier. During authentication, the user will be redirected to their OpenID provider. The second route is the URL to which the user will be returned after authenticating with their OpenID provider.
// Accept the OpenID identifier and redirect the user to their OpenID
// provider for authentication. When complete, the provider will redirect
// the user back to the application at:
// /auth/openid/return
app.post('/auth/openid', passport.authenticate('openid'));
// The OpenID provider has redirected the user back to the application.
// Finish the authentication process by verifying the assertion. If valid,
// the user will be logged in. Otherwise, authentication has failed.
app.get('/auth/openid/return',
passport.authenticate('openid', { successRedirect: '/',
failureRedirect: '/login' }));
Profile Exchange
OpenID can optionally be configured to retrieve profile information about the
user being authenticated. Profile exchange is enabled by setting the profile
option to true
.
passport.use(new OpenIDStrategy({
returnURL: 'http://www.example.com/auth/openid/return',
realm: 'http://www.example.com/',
profile: true
},
function(identifier, profile, done) {
// ...
}
));
When profile exchange is enabled, the function signature of the verify callback
accepts an additional profile
argument containing user profile information
provided by the OpenID provider; refer to User Profile for
further information.
Log In
Passport exposes a login()
function on req
(also aliased as logIn()
) that
can be used to establish a login session.
req.login(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + req.user.username);
});
When the login operation completes, user
will be assigned to req.user
.
Note: passport.authenticate()
middleware invokes req.login()
automatically.
This function is primarily used when users sign up, during which req.login()
can be invoked to automatically log in the newly registered user.
Log Out
Passport exposes a logout()
function on req
(also aliased as logOut()
)
that can be called from any route handler which needs to terminate a login
session. Invoking logout()
will remove the req.user
property and clear the
login session (if any).
It is a good idea to use POST or DELETE requests instead of GET requests for the logout endpoints, in order to prevent accidental or malicious logouts.
app.post('/logout', function(req, res, next){
req.logout(function(err) {
if (err) { return next(err); }
res.redirect('/');
});
});
OAuth 2.0
OAuth 2.0 (formally specified by RFC 6749) provides an authorization framework which allows users to authorize access to third-party applications. When authorized, the application is issued a token to use as an authentication credential. This has two primary security benefits:
- The application does not need to store the user's username and password.
- The token can have a restricted scope (for example: read-only access).
These benefits are particularly important for ensuring the security of web applications, making OAuth 2.0 the predominant standard for API authentication.
When using OAuth 2.0 to protect API endpoints, there are three distinct steps that must be performed:
- The application requests permission from the user for access to protected resources.
- A token is issued to the application, if permission is granted by the user.
- The application authenticates using the token to access protected resources.
Issuing Tokens
OAuth2orize, a sibling project to Passport, provides a toolkit for implementing OAuth 2.0 authorization servers.
The authorization process is a complex sequence that involves authenticating both the requesting application and the user, as well as prompting the user for permission, ensuring that enough detail is provided for the user to make an informed decision.
Additionally, it is up to the implementor to determine what limits can be placed on the application regarding scope of access, as well as subsequently enforcing those limits.
As a toolkit, OAuth2orize does not attempt to make implementation decisions. This guide does not cover these issues, but does highly recommend that services deploying OAuth 2.0 have a complete understanding of the security considerations involved.
Authenticating Tokens
OAuth 2.0 provides a framework, in which an arbitrarily extensible set of token types can be issued. In practice, only specific token types have gained widespread use.
Bearer Tokens
Bearer tokens are the most widely issued type of token in OAuth 2.0. So much so, in fact, that many implementations assume that bearer tokens are the only type of token issued.
Bearer tokens can be authenticated using the passport-http-bearer module.
Install
$ npm install passport-http-bearer
Configuration
passport.use(new BearerStrategy(
function(token, done) {
User.findOne({ token: token }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user, { scope: 'read' });
});
}
));
The verify callback for bearer tokens accepts the token
as an argument.
When invoking done
, optional info
can be passed, which will be set by
Passport at req.authInfo
. This is typically used to convey the scope of the
token, and can be used when making access control checks.
Protect Endpoints
app.get('/api/me',
passport.authenticate('bearer', { session: false }),
function(req, res) {
res.json(req.user);
});
Specify passport.authenticate()
with the bearer
strategy to protect API
endpoints. Sessions are not typically needed by APIs, so they can be disabled.
OAuth
OAuth (formally specified by RFC 5849) provides a means for users to grant third-party applications access to their data without exposing their password to those applications.
The protocol greatly improves the security of web applications, in particular, and OAuth has been important in bringing attention to the potential dangers of exposing passwords to external services.
While OAuth 1.0 is still widely used, it has been superseded by OAuth 2.0. It is recommended to base new implementations on OAuth 2.0.
When using OAuth to protect API endpoints, there are three distinct steps that that must be performed:
- The application requests permission from the user for access to protected resources.
- A token is issued to the application, if permission is granted by the user.
- The application authenticates using the token to access protected resources.
Issuing Tokens
OAuthorize, a sibling project to Passport, provides a toolkit for implementing OAuth service providers.
The authorization process is a complex sequence that involves authenticating both the requesting application and the user, as well as prompting the user for permission, ensuring that enough detail is provided for the user to make an informed decision.
Additionally, it is up to the implementor to determine what limits can be placed on the application regarding scope of access, as well as subsequently enforcing those limits.
As a toolkit, OAuthorize does not attempt to make implementation decisions. This guide does not cover these issues, but does highly recommend that services deploying OAuth have a complete understanding of the security considerations involved.
Authenticating Tokens
Once issued, OAuth tokens can be authenticated using the passport-http-oauth module.
Install
$ npm install passport-http-oauth
Configuration
passport.use('token', new TokenStrategy(
function(consumerKey, done) {
Consumer.findOne({ key: consumerKey }, function (err, consumer) {
if (err) { return done(err); }
if (!consumer) { return done(null, false); }
return done(null, consumer, consumer.secret);
});
},
function(accessToken, done) {
AccessToken.findOne({ token: accessToken }, function (err, token) {
if (err) { return done(err); }
if (!token) { return done(null, false); }
Users.findById(token.userId, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
// fourth argument is optional info. typically used to pass
// details needed to authorize the request (ex: `scope`)
return done(null, user, token.secret, { scope: token.scope });
});
});
},
function(timestamp, nonce, done) {
// validate the timestamp and nonce as necessary
done(null, true)
}
));
In contrast to other strategies, there are two callbacks required by OAuth. In OAuth, both an identifier for the requesting application and the user-specific token are encoded as credentials.
The first callback is known as the "consumer callback", and is used to find the application making the request, including the secret assigned to it. The second callback is the "token callback", which is used to identify the user as well as the token's corresponding secret. The secrets supplied by the consumer and token callbacks are used to compute a signature, and authentication fails if it does not match the request signature.
A final "validate callback" is optional, which can be used to prevent replay attacks by checking the timestamp and nonce used in the request.
Protect Endpoints
app.get('/api/me',
passport.authenticate('token', { session: false }),
function(req, res) {
res.json(req.user);
});
Specify passport.authenticate()
with the token
strategy to protect API
endpoints. Sessions are not typically needed by APIs, so they can be disabled.
Basic & Digest
Along with defining HTTP's authentication framework, RFC 2617 also defined the Basic and Digest authentications schemes. These two schemes both use usernames and passwords as credentials to authenticate users, and are often used to protect API endpoints.
It should be noted that relying on username and password credentials can have adverse security impacts, especially in scenarios where there is not a high degree of trust between the server and client. In these situations, it is recommended to use an authorization framework such as OAuth 2.0.
Support for Basic and Digest schemes is provided by the passport-http module.
Install
$ npm install passport-http
Basic
The Basic scheme uses a username and password to authenticate a user. These credentials are transported in plain text, so it is advised to use HTTPS when implementing this scheme.
Configuration
passport.use(new BasicStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.validPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));
The verify callback for Basic authentication accepts username
and password
arguments.
Protect Endpoints
app.get('/api/me',
passport.authenticate('basic', { session: false }),
function(req, res) {
res.json(req.user);
});
Specify passport.authenticate()
with the basic
strategy to protect API
endpoints. Sessions are not typically needed by APIs, so they can be disabled.
Digest
The Digest scheme uses a username and password to authenticate a user. Its primary benefit over Basic is that it uses a challenge-response paradigm to avoid sending the password in the clear.
Configuration
passport.use(new DigestStrategy({ qop: 'auth' },
function(username, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user, user.password);
});
},
function(params, done) {
// validate nonces as necessary
done(null, true)
}
));
The Digest strategy utilizes two callbacks, the second of which is optional.
The first callback, known as the "secret callback" accepts the username and
calls done
supplying a user and the corresponding secret password. The
password is used to compute a hash, and authentication fails if it does not
match that contained in the request.
The second "validate callback" accepts nonce related params, which can be checked to avoid replay attacks.
Protect Endpoints
app.get('/api/me',
passport.authenticate('digest', { session: false }),
function(req, res) {
res.json(req.user);
});
Specify passport.authenticate()
with the digest
strategy to protect API
endpoints. Sessions are not typically needed by APIs, so they can be disabled.