threehappypenguins
threehappypenguins

Reputation: 138

Google OAuth Refresh Token Undefined

I realize that this has come up several times, but I have tried every solution I could find, and my refreshToken is still coming up as undefined.

  1. I make sure that I delete my app's 3rd party access.
  2. I make sure I delete my mongoDB entry.
  3. I have accessType: 'offline'
  4. I have tried prompt: 'consent', and approvalPrompt: 'force'
  5. I have tried passport.authorize instead of passport.authenticate.
  6. I have tried removing offline access and instead changed the callback url to http://localhost:4000/auth/google/callback?access_type=offline

Some clarification; my app is in testing in the Google Developer Console. It is not published. When I am asked for what scopes I will authorize in the oauth flow, I am not asked to authorize offline_access.

What I am trying to do is create a MERN stack app that allows users to sign into their Google ("connect" their account), and then use a feature in the app that will let them use added metadata to schedule a livestream video to YouTube. Right now, my focus is on the Node.js backend.

Here is oauth/passportConfig.js:

require('dotenv').config();

const passport = require("passport");
const axios = require("axios");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const GoogleUser = require("../models/usergoogleModel");

// Initialize Passport.js
passport.initialize();

// Configure Passport.js with Google OAuth 2.0 Strategy
passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: process.env.GOOGLE_CALLBACK_URL,
      scope: [
        "https://www.googleapis.com/auth/youtube",
        "https://www.googleapis.com/auth/youtube.force-ssl",
        "https://www.googleapis.com/auth/youtube.upload",
      ],
      skipUserProfile: true,
      accessType: 'offline'
    },
    async (accessToken, refreshToken, _profile, done) => {
      try {
        console.log("Refresh Token:", refreshToken);

        // Make a request to the YouTube API to get the user's profile information
        const response = await axios.get(
          "https://www.googleapis.com/youtube/v3/channels",
          {
            params: {
              part: "snippet,contentDetails,statistics",
              mine: "true",
            },
            headers: {
              Authorization: `Bearer ${accessToken}`,
            },
          }
        );

        const youtubeProfile = response.data.items[0];
        const googleId = youtubeProfile.id;

        // Find or create the user based on googleId
        let user = await GoogleUser.findOne({ googleId });
        if (!user) {
          user = new GoogleUser({
            googleId,
            accessToken,
            refreshToken,
          });
        } else {
          // Update user's access token and refresh token
          user.accessToken = accessToken;
          user.refreshToken = refreshToken;
        }
        await user.save();
        done(null, user);
        
      } catch (err) {
        console.error(
          "Error in OAuth callback:",
          err.response ? err.response.data : err.message
        );
        done(err);
      }
    }
  )
);

// Implement Passport.js Serialization and Deserialization
passport.serializeUser((user, done) => {
  done(null, user);
});
passport.deserializeUser(async (id, done) => {
  try {
    const user = await GoogleUser.findById(id);
    if (!user) {
      // If user is not found, pass null as the user
      return done(null, null);
    }
    done(null, user);
  } catch (err) {
    done(err);
  }
});

module.exports = passport;

Here is controllers/authgoogleController.js:

const passport = require("passport");

// Controller function to initiate Google OAuth authentication
exports.googleAuth = passport.authenticate("google", {
});

// Callback function for Google OAuth authentication
exports.googleAuthCallback = (req, res, next) => {
  passport.authenticate("google", (err, user, info) => {
    if (err) {
      return next(err);
    }
    if (!user) {
      // Handle authentication failure
      //  return res.redirect("/login"); // Redirect to the login page or handle appropriately
      return res.send("Login failed.");
    }
    // Handle authentication success
    res.send("Google OAuth authentication successful!"); // Send a response indicating successful authentication
  })(req, res, next);
};

// Controller function for user logout
exports.logout = (req, res) => {
  req.logout(() => {
    //    res.redirect("/"); // Redirect to the home page after logout
    res.send("Logout successful!");
  });
};

Here is routes/authgoogle.js:

const express = require("express");
const router = express.Router();
const authgoogleController = require("../controllers/authgoogleController");

// Route for initiating Google OAuth authentication
router.get("/", authgoogleController.googleAuth);

// Route for handling the OAuth callback
router.get("/callback", authgoogleController.googleAuthCallback);

// Route for user logout
router.get("/logout", authgoogleController.logout);

module.exports = router;

And here is models/usergoogleModel.js:

const mongoose = require("mongoose");
const usergoogleSchema = new mongoose.Schema({
  googleId: String,
  accessToken: String,
  refreshToken: String,
});

module.exports = mongoose.model("GoogleUser", usergoogleSchema);

And in server.js:

require("dotenv").config();

const express = require("express");
const session = require("express-session");
const mongoose = require("mongoose");
const passport = require("./oauth/passportConfig")
const authgoogleRoutes = require("./routes/authgoogle");

// Express app
const app = express();

// Middleware to initialize Passport and configure session
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: true,
  })
);

app.use(passport.session());

//  Routes
app.use("/auth/google", authgoogleRoutes);

// Connect to db
mongoose
  .connect(process.env.MONGO_URI)
  .then(() => {
    // Listen for requests
    app.listen(process.env.PORT, () => {
      console.log("Connected to the db & listening on port", process.env.PORT);
    });
  })
  .catch((error) => {
    console.log(error);
  });

Upvotes: 0

Views: 209

Answers (1)

Muhammad Raheel
Muhammad Raheel

Reputation: 1

Make sure your app is published, probably production. It worked for me

Upvotes: 0

Related Questions