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:

  1. Middleware
  2. Strategies
  3. 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

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.

Google

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.

Twitter

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' }));

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.

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:

  1. The application does not need to store the user's username and password.
  2. 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:

  1. The application requests permission from the user for access to protected resources.
  2. A token is issued to the application, if permission is granted by the user.
  3. 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:

  1. The application requests permission from the user for access to protected resources.
  2. A token is issued to the application, if permission is granted by the user.
  3. 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.

SEARCH FOR STRATEGIES

0STRATEGIES