Implementing Social Authentication in Node.js with OAuth and Passport.js

November 2, 2024 (2w ago)

Implementing Social Authentication in Node.js with OAuth and Passport.js

Social authentication enables users to log in using accounts from external providers like Google or Facebook, streamlining the authentication process and improving user experience. In this guide, we’ll implement social login in a Node.js application using Passport.js and OAuth to authenticate users with Google and Facebook.


What is Social Authentication?

Social authentication allows users to log in with third-party accounts, eliminating the need to remember additional usernames and passwords. By using OAuth 2.0, users can securely authenticate with providers such as Google, Facebook, Twitter, or GitHub, and gain access to your application without creating a new account.

Benefits of Social Authentication

  1. Improved User Experience: Users can log in quickly with familiar credentials.
  2. Increased Security: OAuth tokens reduce the need for storing sensitive data like passwords.
  3. Higher Conversion Rates: Social login often leads to higher registration and retention rates.

Setting Up the Project

This guide assumes a basic Node.js, Express, and Mongoose setup with Passport.js for authentication.

Step 1: Install the Required Dependencies

Initialize the project if you haven’t already, and install the necessary dependencies.

mkdir social-auth
cd social-auth
npm init -y
npm install express mongoose passport passport-google-oauth20 passport-facebook express-session dotenv

Step 2: Configure Environment Variables

Create a .env file to store configuration details, including OAuth client IDs and secrets from Google and Facebook.

MONGODB_URI=mongodb://localhost:27017/social_auth
PORT=5000
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
FACEBOOK_CLIENT_ID=your_facebook_client_id
FACEBOOK_CLIENT_SECRET=your_facebook_client_secret
SESSION_SECRET=your_session_secret

Note: To obtain OAuth client IDs and secrets, create a project on the Google Developer Console and Facebook for Developers.


Setting Up the User Model

Define a User model in Mongoose to store user information, including fields for each social provider.

models/User.js

const mongoose = require("mongoose");
 
const userSchema = new mongoose.Schema({
  username: { type: String },
  email: { type: String, unique: true, sparse: true },
  googleId: { type: String, unique: true, sparse: true },
  facebookId: { type: String, unique: true, sparse: true },
});
 
module.exports = mongoose.model("User", userSchema);

In this schema:


Configuring Passport for Google and Facebook OAuth

Next, configure Passport to use Google and Facebook OAuth strategies.

Step 1: Configuring Passport with Google Strategy

In the config folder, create passport.js to handle Passport configuration.

config/passport.js

const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const FacebookStrategy = require("passport-facebook").Strategy;
const User = require("../models/User");
 
passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: "/auth/google/callback",
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        // Check if user exists
        let user = await User.findOne({ googleId: profile.id });
        if (!user) {
          // Create new user if not found
          user = new User({
            googleId: profile.id,
            username: profile.displayName,
            email: profile.emails[0].value,
          });
          await user.save();
        }
        return done(null, user);
      } catch (error) {
        return done(error, false);
      }
    }
  )
);

Step 2: Configuring Passport with Facebook Strategy

Add Facebook OAuth to the same file.

config/passport.js

passport.use(
  new FacebookStrategy(
    {
      clientID: process.env.FACEBOOK_CLIENT_ID,
      clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
      callbackURL: "/auth/facebook/callback",
      profileFields: ["id", "displayName", "emails"],
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        // Check if user exists
        let user = await User.findOne({ facebookId: profile.id });
        if (!user) {
          // Create new user if not found
          user = new User({
            facebookId: profile.id,
            username: profile.displayName,
            email: profile.emails ? profile.emails[0].value : undefined,
          });
          await user.save();
        }
        return done(null, user);
      } catch (error) {
        return done(error, false);
      }
    }
  )
);

Step 3: Setting Up Serialization

Configure passport.serializeUser and passport.deserializeUser to manage sessions.

config/passport.js

passport.serializeUser((user, done) => {
  done(null, user.id);
});
 
passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (error) {
    done(error, null);
  }
});
 
module.exports = passport;

This setup allows Passport to store the user ID in the session and retrieve it on subsequent requests.


Setting Up Express and Middleware

In server.js, set up Express, session management, and Passport middleware.

server.js

require("dotenv").config();
const express = require("express");
const mongoose = require("mongoose");
const session = require("express-session");
const passport = require("./config/passport");
 
const app = express();
 
// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true });
 
// Set up session middleware
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
  })
);
 
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
 
// Routes
app.use("/auth", require("./routes/auth"));
 
const port = process.env.PORT || 5000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Creating OAuth Routes for Google and Facebook

Create routes to handle Google and Facebook login.

routes/auth.js

const express = require("express");
const passport = require("passport");
 
const router = express.Router();
 
// Google Authentication
router.get("/google", passport.authenticate("google", { scope: ["profile", "email"] }));
 
router.get(
  "/google/callback",
  passport.authenticate("google", { failureRedirect: "/" }),
  (req, res) => {
    res.redirect("/profile");
  }
);
 
// Facebook Authentication
router.get("/facebook", passport.authenticate("facebook", { scope: ["email"] }));
 
router.get(
  "/facebook/callback",
  passport.authenticate("facebook", { failureRedirect: "/" }),
  (req, res) => {
    res.redirect("/profile");
  }
);
 
// Logout Route
router.get("/logout", (req, res) => {
  req.logout();
  res.redirect("/");
});
 
module.exports = router;

In this code:

  1. Google: The /google route initiates authentication, while /google/callback handles the response.
  2. Facebook: The /facebook route initiates authentication, while /facebook/callback handles the response.
  3. Logout: The /logout route clears the user session.

Protecting Routes with Middleware

To restrict certain routes to authenticated users, use a middleware that checks for the user’s authentication status.

middleware/authMiddleware.js

const ensureAuthenticated = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect("/");
};
 
module.exports = ensureAuthenticated;

Apply this middleware to routes that require authentication.

routes/profile.js

const express = require("express");
const ensureAuthenticated = require("../middleware/authMiddleware");
 
const router = express.Router();
 
// Profile Route (requires authentication)
router.get("/", ensureAuthenticated, (req, res) => {
  res.json({ message: `Welcome, ${req.user.username}!` });
});
 
module.exports = router;

Testing the Social Authentication Process

  1. Start the Server: Run the application with node server.js.

  2. Google and Facebook Login: Access the /auth/google and /auth/facebook routes to authenticate with social accounts.

  3. Check Profile Route: Verify that authenticated users can access the /profile route.

  4. Logout: Use the /auth/logout route to end the session.


Best Practices for Social Authentication

  1. Restrict Sensitive Data: Only request necessary scopes to minimize data access.
  2. Handle Missing Email: Some providers (e.g., Facebook) may not provide emails. Implement a fallback mechanism.
  3. Use HTTPS: Ensure OAuth redirection URIs use HTTPS in production.
  4. Session Management: Implement session expiration policies to enhance security.

Conclusion

Implementing social authentication in a Node.js application using Passport.js and OAuth enables users to log in securely and easily with their social accounts. By configuring strategies for Google and Facebook, setting up routes, and managing sessions, you can offer a convenient and secure authentication option that enhances user experience.

Integrate this setup into your application to allow social logins, improving user accessibility while maintaining robust security.