Setting up Social Logins(Google + Microsoft) with Node.js and Passport.js

Before diving into code, let's first briefly discuss what the heck is Oauth?

What is Oauth:

OAuth(or Open Authorization) is one of the approaches for authenticating a user in an application. It makes it much easier and faster for an end-user to choose a social login(Google, Outlook, or Twitter, etc) to signup on an application rather than the traditional (email/password) signup form.

Simply, it is a way to provide third-party websites or apps access to user's data(name, email, etc.) without requiring them to share their credentials.

Oauth Abstract View

oauth-basic

A lot of things are happening behind the scenes and a detailed explanation is provided in the below image as to how we are going to set this up in our Node app.

Oauth Developer's View

oauth complex

Let's now set up the above login flow by first setting up our app on Google and Microsoft Console.

Step 1: Google - Create client ID and client secret

  1. Head to Google API Console and sign in using your email id.

  2. From the project, drop-down create a new project by filling in the project name and organization(optional).

  3. In the sidebar under "APIs & Services", select OAuth Consent Screen, choose the appropriate User Type basis requirement. For public-facing apps select external.

  4. Fill in the application name, logo(optional), support email(optional), and hit Save.

  5. Switch to the Credentials tab from the sidebar and from the Create credentials drop-down list, choose OAuth client ID.

  6. Under Application type, select Web application.

  7. In Authorized redirect, URI add for dev env, for production env, this will be the server IP address or domain name followed by /auth/google/redirect

  8. Press the Create button and copy the generated client ID and client secret. This will be used later in the Node app

Step 2: Microsoft - Create client ID and client secret

  1. Head to the Microsoft Azure portal and sign in using your email id.

  2. Search for App registrations from the search bar.

  3. Select New registration from the top and fill in your application name.

  4. Choose the Account type basis your requirement. For our application, it will be personal accounts + organizational directory.

  5. In Redirect URI add http://localhost:5500/auth/microsoft/redirect.

  6. Press the Register button to register your app.

  7. From the sidebar select the Overview tab and copy the Application (client) ID. To generate the client secret head to Certificates & secrets from the sidebar and click on New Client Secret from the Client secrets section. Copy the generated secret for future use.

Step 3: Passport Setup

It is an authentication middleware and can be easily configured with express. It provides a comprehensive set of strategies supporting authentication using a username and password, Google, Facebook, Twitter, and many more.

Install the following packages:

npm install passport passport-google-oauth20 passport-microsoft --save

Step 4: Routes Setup

We will be setting up token-based redirection basis social login in our app. This flow is helpful when we have an existing app with email, password setup, and social logins are being added as an enhancement.

Use express-generator to set up the basic boilerplate for the express app.

Setup the following routes in index.js:

app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'], session: false }));
app.get('/auth/google/redirect', passport.authenticate('google', { session: false, failureRedirect: `https://localhost:3000/login` }), (req, res) => {
  res.redirect(req.user); //req.user has the redirection_url});

// Microsoft Routes
app.get('/auth/microsoft', passport.authenticate('microsoft', { session: false }));
app.get('/auth/microsoft/redirect', passport.authenticate('microsoft', { session: false, failureRedirect: `https://localhost:3000/login` }), (req, res) => {
  res.redirect(req.user);});

Over here the route /auth/google or /auth/microsoft is called when the user clicks Log in with Google or Microsoft in the browser. Behind the scenes, passport communicates with Google/Microsoft and directs the user to their respective consent screen.

The consent screen tells users who is requesting access to their data and what kind of data the app is asking to access. The latter part of the statement comes under scope. In our app, we need access to the user's Google profile and email address, thus added it to the scope object. Redirect routes will be discussed later.

Step 5: Google and Microsoft Strategies Setup

Create a new file(google_oauth.js) in the project root directory and add the following code.

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const jwt = require('jsonwebtoken');
passport.use(new GoogleStrategy({
  callbackURL: `http://localhost:5500/auth/google/redirect`,  //same URI as registered in Google console portal
  clientID: process.env.GOOGLE_CLIENT_ID, //replace with copied value from Google console
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,},
  async (accessToken, refreshToken, profile, done) => {
    try {
      let user_email = profile.emails && profile.emails[0].value; //profile object has the user info
      let [user] = await db('users').select(['id', 'name', 'email']).where('email', user_email); //check whether user exist in database
      let redirect_url = "";
      if (user) {
        const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: '1h' }); //generating token
        redirect_url = `http://localhost:3000/${token}` //registered on FE for auto-login
        return done(null, redirect_url);  //redirect_url will get appended to req.user object : passport.js in action
      } else {
        redirect_url = `http://localhost:3000/user-not-found/`;  // fallback page
        return done(null, redirect_url);
      }
    } catch (error) {
      done(error)
    }
  }));

In a similar fashion, create a new file(microsoft_oauth.js) and copy-paste the above code. Just make the following changes to it:

const MicrosoftStrategy = require('passport-microsoft').Strategy;
passport.use(new MicrosoftStrategy({  
callbackURL: `http://localhost:5500/auth/microsoft/redirect`,  
clientID: process.env.MICROSOFT_CLIENT_ID,  
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,  
scope: ['openid', 'profile', 'email']  
}  

A brief explanation of the arguments in the callback function:

  1. accessToken is used to make API requests on behalf of a user. Not required in our app.

  2. Generally, access tokens have a limited lifetime so a refreshToken can be used to obtain new access tokens.

  3. profile will contain user profile information provided by the service provider.

  4. done the callback function which gets invoked upon successful look-up and supplies passport with the user that gets authenticated. The first argument to done is the error object which is null in our code thus telling passport that things are fine and there is no error.

When an end-user approves our app in the consent screen, the redirect api's(/auth/google/redirect or /auth/microsoft/redirect) are invoked, req.user object is already set to the appropriate redirection URL through passport and the user lands on a page basis that.

Since we are not maintaining sessions through cookies thus an additional session: false key was passed. If we have to set up an app using cookies, then this is not passed. Furthermore, we need to require cookie-session and use passport serializeUser and deserializeUser functions to effectively manage the saved cookie basis google/microsoft unique profile id. Though, it is recommended using the user id present in the database. Require the above two files in index.js and test the application locally.

Optional: App Verification

Though it is not mandatory if sensitive scopes are not accessed. For production apps, this should be done irrespective of the scope requested.

For Microsoft, it is pretty easy to get our app verified, head to the Azure portal, and from the sidebar to the branding section.

Over here, you can upload the app logo. Add in Terms of Service, Privacy statement link if needed. To verify the publisher domain just upload the microsoft-identity-association.json file in .well-known folder on your website s3 bucket.

For Google, things are a bit tricky. Though some explanation is provided here. We can Submit our app for Verification from the OAuth Consent Screen(Google Console Portal) after providing the appropriate Authorized domain, Homepage, Term of Service, and privacy policy links. The process takes around 4-5 days if no sensitive scope is being requested. A few rounds of follow-up emails from the google team if needed and that's it.

Apart from getting verified, the benefit of this is that a user can see our App logo and App name on the consent screen which makes it more professional.

Thanks for reading and congrats on making it till the end.

Cheers!!

© 2021 Asim Ansari | Software Engineer at Simpplr