Introduction

We will build a todo list app in this tutorial, complete with functionality that allows users to sign in with Google. In this tutorial, you will learn how to use Passport for authentication.

If you want to see where we are headed, here's an example of the final result: https://github.com/passport/todos-express-google

Before we dive in, you'll need a working development environment with Node.js and Git, as well as an editor and terminal of your choosing. Take a moment to set up these tools if you have not already done so.

Let's get started!

We are going to start with a starter app, which has all the scaffolding needed to build a todo list. Let's clone the app:

$ git clone https://github.com/passport/todos-express-starter.git google-tutorial

You now have a directory named 'google-tutorial'. Let's cd into it:

$ cd google-tutorial

Take a moment browse through the files in the starter app. As we work through this tutorial, we'll be using Express as our web framework, along with EJS as our template engine and CSS for styling. We will use SQLite as our database for storing data. Don't worry if you are not familiar with these technologies -- the necessary code will be provided at each step.

Now, let's install the dependencies:

$ npm install

And start the server:

$ npm start

Let's check to see if its working. Open http://localhost:3000 in your browser. You should be greeted with a page explaining how todos help you get things done.

Next, we will add a login page to the app.

Login Prompt

We want to let users sign in with Google. For that, we need a login page that prompts the user to do that. Let's add that now.

Let's create a file that will contain authentication-related routes:

$ touch routes/auth.js

Add the following code to that file, which creates a login route that will render the login page.

var express = require('express');

var router = express.Router();

router.get('/login', function(req, res, next) {
  res.render('login');
});

module.exports = router;

Next, we need to add this route to the app. Open 'app.js' and require the newly created auth routes at line 10, below where 'routes/index' is require'd:

var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');

Continuing within 'app.js', use the newly require'd authRouter at line 27, below where indexRouter is use'd.

app.use('/', indexRouter);
app.use('/', authRouter);

The login page has been added to our app! Let's see how it looks.

Start the server:

$ npm start

And open http://localhost:3000 and click "Sign in." We are prompted to sign in, but there's no place to choose Google.

For that we need a button. Let's add that now.

Open 'views/login.ejs' and add the form at line 15, below the "Sign in" heading:

<h1>Sign in</h1>
<a class="button google" href="/login/federated/google">Sign in with Google</a>

Refresh the page. We've now got a login page that prompts the user to sign in with Google. Next, we will redirect the user to Google.

Redirect to Google

Now that we are prompting the user to sign in with Google, the next step is to redirect the user to Google.

To do that, we are going to use Passport and the passport-google-oidc strategy. Install both as dependencies:

$ npm install passport
$ npm install passport-google-oidc

Open 'routes/auth.js' and require the newly installed packages, as well as the app's database, at line 2, below where express is require'd:

var express = require('express');
var passport = require('passport');
var GoogleStrategy = require('passport-google-oidc');
var db = require('../db');

Next, we need to add a route that will redirect the user when they click "Sign in with Google".

Continuing within 'routes/auth.js', add this route at line 12, below the '/login' route:

router.get('/login/federated/google', passport.authenticate('google'));

We've now got a route that will redirect the user to Google! Let's test it out to see what happens.

Start the server:

$ npm start

Open http://localhost:3000, click "Sign in" and then click "Sign in with Google".

Uh oh... we are informed the Google authentication strategy is unknown. We will fix that by configuring the strategy. But first, we need to register our app with Google.

Register App

Before we can let users sign in to our app with Google, we need to register the app with Google.

Go to the Google Cloud Platform console.

From the projects list, select a project or create a new one.

Navigate to the APIs & Services page and select Credentials.

If you have an existing application, it will be listed under OAuth 2.0 Client IDs. Click Edit OAuth client to obtain the client ID and secret, and proceed to configure the strategy. Otherwise, continue.

If you have not already done so, configure the OAuth consent screen. Select External to make your application available to any user with a Google account. Complete the app registration process by entering the app name, support email, and developer contact information.

Click Create Credentials, then select OAuth client ID.

Select Web application as Application type.

Click Add URI under Authorized Redirect URIs. Enter http://localhost:3000/oauth2/redirect/google.

Click Create to create the OAuth client. The following screen will display your client ID and secret. Proceed to configure the strategy.

Configure Strategy

Now that we've registered our app with Google, we can configure Passport.

First, let's create a '.env' file to store the client ID and secret we just obtained from Google.

$ touch .env

Then, add the client ID and secret. The contents of the file should look something like this:

GOOGLE_CLIENT_ID=__INSERT_CLIENT_ID_HERE__
GOOGLE_CLIENT_SECRET=__INSERT_CLIENT_SECRET_HERE__

Of course, your client ID and secret should be inserted where noted.

Open 'routes/auth.js' and add the following code at line 6 to configure the GoogleStrategy.

passport.use(new GoogleStrategy({
  clientID: process.env['GOOGLE_CLIENT_ID'],
  clientSecret: process.env['GOOGLE_CLIENT_SECRET'],
  callbackURL: '/oauth2/redirect/google',
  scope: [ 'profile' ]
}, function verify(issuer, profile, cb) {
  db.get('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [
    issuer,
    profile.id
  ], function(err, row) {
    if (err) { return cb(err); }
    if (!row) {
      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,
            name: profile.displayName
          };
          return cb(null, user);
        });
      });
    } else {
      db.get('SELECT * FROM users WHERE id = ?', [ row.user_id ], function(err, row) {
        if (err) { return cb(err); }
        if (!row) { return cb(null, false); }
        return cb(null, row);
      });
    }
  });
}));

This configures the GoogleStrategy to fetch the user record associated with the Google account. If a user record does not exist, one is created the first time someone signs in.

The strategy is now configured. Next we need to maintain state when redirecting to Google.

Maintain State

When a user signs in to our app with Google, they are redirected to Google. Google takes care of authenticating the user and then redirects them back to our app.

For security, state needs to be maintained between these two redirects. Passport does this automatically, but the app first needs session support. Let's add that now.

Begin by installing the necessary dependencies:

$ npm install express-session
$ npm install connect-sqlite3

Open 'app.js' and require the additional dependencies at line 8, below where 'morgan' is require'd:

var logger = require('morgan');
var session = require('express-session');

var SQLiteStore = require('connect-sqlite3')(session);

Add the following code at line 28, after express.static middleware, to add sessions to the application.

app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false,
  store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' })
}));

Now that the app has session support, we are ready to handle the redirect back to our app.

Redirect Back to App

When a user signs in to our app with Google, they are redirected to Google. Google takes care of authenticating the user and then redirects them back to our app.

Let's close the loop by adding a route that will handle this redirect.

Open 'routes/auth.js' and add this route at line 55, below the '/login/federated/google' route:

router.get('/oauth2/redirect/google', passport.authenticate('google', {
  successRedirect: '/',
  failureRedirect: '/login'
}));

We've now got routes that will redirect the user to Google and handle the redirect back to our app! Let's test it out to see what happens.

Start the server:

$ npm start

Open http://localhost:3000, click "Sign in" and then click "Sign in with Google".

Uh oh... we are informed that there's an error related to sessions. Next, we will fix that by establishing a login session.

Establish Session

Once the user has signed in with Google, we need to a login session to remember the fact that the user has authenticated as they navigate the app.

Open 'app.js' and require Passport at line 9, below where 'express-session' is require'd:

var session = require('express-session');
var passport = require('passport');

Add the following code at line 35, after session middleware, to authenticate the session.

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false,
  store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' })
}));
app.use(passport.authenticate('session'));

Finally, we need to configure Passport to manage the login session. Open 'routes/auth.js' and add the following code at line 47:

passport.serializeUser(function(user, cb) {
  process.nextTick(function() {
    cb(null, { id: user.id, username: user.username, name: user.name });
  });
});

passport.deserializeUser(function(user, cb) {
  process.nextTick(function() {
    return cb(null, user);
  });
});

Now, let's retry signing in. Open http://localhost:3000, click "Sign in" and then click "Sign in with Google".

We are logged in! Go ahead and enter some tasks you've been needing to get done.

At this point, users can sign in with Google! Next, we will add the ability to sign out.

Log Out

Now that users can sign in, they'll need a way to sign out.

Open 'routes/auth.js' and add this route at line 72, below the '/oauth2/redirect/google' route:

router.post('/logout', function(req, res, next) {
  req.logout(function(err) {
    if (err) { return next(err); }
    res.redirect('/');
  });
});

Return to the app, where you should already be signed in, and click "Sign out."

We've now got a working app where users can sign in and sign out!

SEARCH FOR STRATEGIES

0STRATEGIES