bookmark_borderHow to setup Google OAuth2 login with Express

I was recently working on SubScrubber, and I had to allow users to log into Google and request permissions in order to access their YouTube subscriptions information. The Google documentation for their server-side APIs does not include a code sample or an example for Node.JS at all. They have an example on their Github, but it uses the plain Node.JS standard http library which adds so much boilerplate code that it’s difficult to discern the parts that are Google specific. To fill this void, here is how to setup Google OAuth2 login with Express. Note that this guide assumes you know how to set up a Node.JS project and install dependencies, and have created the project in the Google Developer Console.

If you just want to look at the code, a sample project is available on Github.

1. Set up your Node.JS project, and install the following dependencies:

  • cookie-parser
  • ejs
  • express
  • google-auth-library
  • googleapis
  • jsonwebtoken

2. In the Credentials section of the Google Developer Console, create an OAuth Client ID credential of type Web Application.

3. Create a file named config.js with the following contents,

const port = 3002;
const baseURL = `http://localhost:${port}`;

module.exports = {
  // The secret for the encryption of the jsonwebtoken
  JWTsecret: 'mysecret',

  baseURL: baseURL,
  port: port,

  // The credentials and information for OAuth2
  oauth2Credentials: {
    client_id: "",
    project_id: "", // The name of your project
    auth_uri: "https://accounts.google.com/o/oauth2/auth",
    token_uri: "https://oauth2.googleapis.com/token",
    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
    client_secret: "",
    redirect_uris: [
      `${baseURL}/auth_callback`
    ],
    scopes: [
      'https://www.googleapis.com/auth/youtube.readonly'
    ]
  }
};

4. Fill in the client_id, project_id, and client_secret properties with the information for your project.

5. Create a main.js. I’ve included all of the includes and boilerplate stuff below as that is outside the scope of this article.

const express = require('express');
const google = require('googleapis').google;
const jwt = require('jsonwebtoken');

// Google's OAuth2 client
const OAuth2 = google.auth.OAuth2;

// Including our config file
const CONFIG = require('./config');

// Creating our express application
const app = express();

// Allowing ourselves to use cookies
const cookieParser = require('cookie-parser');
app.use(cookieParser());

// Setting up EJS Views
app.set('view engine', 'ejs');
app.set('views', __dirname);

// Listen on the port defined in the config file
app.listen(CONFIG.port, function () {
  console.log(`Listening on port ${CONFIG.port}`);
});

Note that I am using EJS for templating, as it is really close to html.

6. Create a GET route for /. This is where we’ll put our link to log in with google.

app.get('/', function (req, res) {

});

6a. The OAuth2 class we included on line 6 of our main.js is from the google-auth-library module. It is just an object for our OAuth2 client. In this route, we want to create an instance of the OAuth2 client so that we can use it to authenticate our requests to the Google API.

// Create an OAuth2 client object from the credentials in our config file
const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);

6b. Now, we want to obtain the link to which we’ll send the user when they click the Login button. To do this, we need to call the generateAuthUrl method on our OAuth2 Client, passing it the access type and what access scopes we need. Access scopes tell google what exactly it needs to ask the user consent for. For example, if we want access to a user’s YouTube data, we would need to ask for the scope https://www.googleapis.com/auth/youtube.readonly, so that Google will ask them if they want to share their YouTube data with us.

// Obtain the google login link to which we'll send our users to give us access
const loginLink = oauth2Client.generateAuthUrl({
  access_type: 'offline', // Indicates that we need to be able to access data continously without the user constantly giving us consent
  scope: CONFIG.oauth2Credentials.scopes // Using the access scopes from our config file
});

6c. Finally, we need to render our index template(we’ll create it in 6d) with the login link.

return res.render("index", { loginLink: loginLink });

At this point, this should be your / route:

app.get('/', function (req, res) {
  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);

  // Obtain the google login link to which we'll send our users to give us access
  const loginLink = oauth2Client.generateAuthUrl({
    access_type: 'offline', // Indicates that we need to be able to access data continously without the user constantly giving us consent
    scope: CONFIG.oauth2Credentials.scopes // Using the access scopes from our config file
  });
  return res.render("index", { loginLink: loginLink });
});

6d. Create a base html(ish) file named index.ejs with a login link to the page we passed to the file.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Express Google OAuth2 Tutorial by Aidan Lovelace</title>
</head>
<body>
  <a href="<%= loginLink %>">Login</a>
</body>
</html>

7. At this point, you can run node main.js and visit http://localhost:3002/ and see a little Login button that links to a Google error. This error is due to the fact that we didn’t tell Google that we want it to redirect the user back to us at http://localhost:3002/auth_callback, so now we need to do that. In the Google Developer Console, click on the Web Application credential you created for this project, and add http://localhost:3002/auth_callback to the list of Authorized Redirect URLs. Now, you should be able to login with your Google Account and get redirected to a 404 error.

8. This 404 error is due to us not having implemented the auth_callback route, so we should probably do that now. When Google redirects the user, it’s either going to redirect with a code that we can use to obtain permanent credentials or an error if the user decided not to give us access. This data will be included in the GET parameters. We need an OAuth2 client here, so add that first thing. we also need to check for the error parameter. If there is one, let’s redirect the user to the homepage. Otherwise, we need to get the permanent user credentials and store them in a cookie so we can use them later. The code below redirects the user to /get_some_data, a page we have not yet created but will show some data about the user.

app.get('/auth_callback', function (req, res) {
  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);

  if (req.query.error) {
    // The user did not give us permission.
    return res.redirect('/');
  } else {
    oauth2Client.getToken(req.query.code, function(err, token) {
      if (err)
        return res.redirect('/');
      
      // Store the credentials given by google into a jsonwebtoken in a cookie called 'jwt'
      res.cookie('jwt', jwt.sign(token, CONFIG.JWTsecret));
      return res.redirect('/get_some_data');
    });
  }
});

9. Let’s create the/get_some_data page. In my example, it’ll display 5 channels to which the user is subscribed. It’ll need to create an OAuth2 client and add the user’s credentials to it in order to access anything. Then, it’ll get the subscriptions and send them to the template.

app.get('/get_some_data', function (req, res) {
  if (!req.cookies.jwt) {
    // We haven't logged in
    return res.redirect('/');
  }

  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);

  // Add this specific user's credentials to our OAuth2 client
  oauth2Client.credentials = jwt.verify(req.cookies.jwt, CONFIG.JWTsecret);

  // Get the youtube service
  const service = google.youtube('v3');

  // Get five of the user's subscriptions (the channels they're subscribed to)
  service.subscriptions.list({
    auth: oauth2Client,
    mine: true,
    part: 'snippet,contentDetails',
    maxResults: 5
  }).then(response => {
    // Render the data view, passing the subscriptions to it
    return res.render('data', { subscriptions: response.data.items });
  });
});

Lastly, we need to create the data.ejs template in order to display the data.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Express Google OAuth2 Tutorial by Aidan Lovelace</title>
</head>
<body>
  <ul>
    <% subscriptions.forEach(function (subscription) { %>
      <li><%= subscription.snippet.title %></li>
    <% }) %>
  </ul>
</body>
</html>

Thanks for reading! If I made any mistakes, please don’t hesitate to let me know in the comments.

bookmark_borderHow to use ES6+ with Node.JS

I have seriously come to really like the new features that come with ES6+ Javascript such as async/await, the new class syntax, let and const (screw you hoisting!), arrow functions, destructuring, the new import syntax, and many more. These features are seriously useful. I think that the import syntax in particular makes Node.JS feel so much more like it was made for Javascript.

Ok. Ok. I’ll get on with it—How can I use ES6+ with Node.JS? It’s super simple.

1. cd to your project in your Terminal

2. Run npm install --save-dev esm

3. Run your main Javascript file with node -r esm {THE FILE}

If you are using nodemon, you can just run nodemon -r esm {THE FILE}.