79154093

Date: 2024-11-04 03:09:21
Score: 1.5
Natty:
Report link

I hope this is helpful, I got stuck on the same problem but with the help of ChatGTP and Claude AI, I was able to come across one possible solution.

I am using localhost in this example and tailwind CSS in a MERN Stack project.

-------------------------------Passport Setup--------------------------------

import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";

import User from "../models/user.model.js";
import dotenv from "dotenv";

dotenv.config();

// Configure Passport with a Google strategy for authentication
passport.use(
  "google",
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: "/api/auth/google/callback",
    },
    /**
     * Verify the user's credentials using Google.
     *
     * This function is called by Passport when a user attempts to log in with their Google account.
     * It:
     * 1. Searches for a user with the provided Google ID.
     * 2. If no user is found, it creates a new user with information from the Google profile.
     * 3. Returns the user object.
     * 4. Passes any errors to the `done` callback.
     *
     * @param {string} accessToken - The access token provided by Google.
     * @param {string} refreshToken - The refresh token provided by Google.
     * @param {Object} profile - The user's profile information from Google.
     * @param {Function} done - The callback to call with the authentication result.
     */
    async (accessToken, refreshToken, profile, done) => {
      try {
        let user = await User.findOne({ googleId: profile.id });
        // Additional check to prevent duplicate accounts if Google email changes
        if (!user) {
          user = await User.findOne({ email: profile._json.email });
        }

        if (!user) {
          // Generate a random password
          const randomPassword = User.prototype.generateRandomPassword();
          // Create a new user
          user = await User.create({
            googleId: profile.id,
            name: profile._json.name,
            email: profile._json.email,
            password: randomPassword, // Set the generated password
            profilePicture: profile._json.picture,
          });
        }
        return done(null, user);
      } catch (error) {
        return done(error, false);
      }
    }
  )
);

/**
 * Serialize the user for the session.
 *
 * This function is called when a user is authenticated. It:
 * 1. Takes the user object and stores the user ID in the session.
 * 2. This ID is used to identify the user in subsequent requests.
 *
 * @param {Object} user - The authenticated user object.
 * @param {Function} done - The callback to call with the serialized user ID.
 */
passport.serializeUser((user, done) => {
  done(null, user.id);
});

/**
 * Deserialize the user from the session.
 *
 * This function is called on each request to retrieve the user object based on the user ID stored in the session. It:
 * 1. Finds the user by their ID.
 * 2. Passes the user object to the `done` callback.
 * 3. Passes any errors to the `done` callback if the user cannot be found.
 *
 * @param {string} id - The user ID stored in the session.
 * @param {Function} done - The callback to call with the user object or an error.
 */
passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (err) {
    done(err);
  }
});

export default passport;

------------------------------- Auth Controller --------------------------------

import passport from "../lib/PassportSetup.js";
import User from "../models/user.model.js";

/**
 * Initiates Google authentication.
 *
 * This function handles initiating the Google OAuth2 authentication process by:
 * 1. Redirecting the user to Google's OAuth2 login page.
 *
 * @param {Object} req - The request object for initiating Google authentication.
 * @param {Object} res - The response object to redirect the user to Google.
 * @param {Function} next - The next middleware function in the stack.
 */
export const googleAuth = passport.authenticate("google", {
  scope: ["profile", "email"],
});

/**
 * Handles the callback from Google OAuth2.
 *
 * This function handles the callback after the user has authenticated with Google. It:
 * 1. Uses Passport's 'google' strategy to authenticate the user.
 * 2. Redirects the user to the home page on successful authentication.
 * 3. Handles authentication errors by redirecting to the login page with an error message.
 *
 * @param {Object} req - The request object containing Google OAuth2 callback data.
 * @param {Object} res - The response object to redirect the user.
 * @param {Function} next - The next middleware function in the stack.
 */
export const googleAuthCallback = (req, res, next) => {
  passport.authenticate("google", {
    successRedirect: `${process.env.CLIENT_URL}/oauth/callback`,
    failureRedirect: `${process.env.CLIENT_URL}/login`,
    failureFlash: true,
  })(req, res, next);
};

/**
 * Handles successful authentication callbacks from OAuth providers.
 *
 * This function is triggered when a user is successfully authenticated via an OAuth provider (e.g., Google, GitHub).
 * It:
 * 1. Checks if a user object is present on the request, which is set by Passport after successful authentication.
 * 2. Responds with a 200 status and user information if authentication is successful.
 * 3. Includes the user's ID, name, email, profile picture, and role in the response.
 *
 * @param {Object} req - The request object, containing authenticated user data.
 * @param {Object} res - The response object used to send back the authentication result.
 * @param {Function} next - The next middleware function in the stack (not used in this function).
 * @returns {Object} JSON object with user data on success, or an error status if authentication fails.
 */
export const authCallbackSuccess = (req, res, next) => {
  return res.status(200).json({
    success: true,
    status: 200,
    user: {
      id: req.user.id,
      name: req.user.name,
      email: req.user.email,
      profilePicture: req.user.profilePicture,
      role: req.user.role,
    },
  });
};

------------------------------- Auth Routes --------------------------------

import express from "express";

import {
  googleAuth,
  googleAuthCallback,
  authCallbackSuccess,
} from "../controllers/auth.controller.js";

const router = express.Router();

// Passport Google OAuth2 login
router.get("/google", googleAuth);

// Handles Passport Google OAuth2 callback
router.get("/google/callback", googleAuthCallback);

// Returns the user object after Passport Google OAuth2, Github, or any other callback
router.get("/callback/success", isAuthenticated, authCallbackSuccess);

export default router;

------------------------ React OAuthButtons.jsx -------------------------

import React from 'react';
import { useSelector } from 'react-redux';

function OAuthButtons() {
    const { loading } = useSelector((state) => state.user);

    const handleOAuth = (provider) => {
        window.location.href = `http://localhost:4000/api/auth/${provider}`;
    }

    return (
        <div className='flex flex-col gap-3'>
            <button
                className="bg-red-700 text-white rounded-lg p-3 uppercase hover:bg-red-600 disabled:bg-red-400"
                type="button"
                onClick={() => handleOAuth("google")}
                disabled={loading}
            >
                Continue with Google
            </button>
            <button
                className="bg-blue-700 text-white rounded-lg p-3 uppercase hover:bg-blue-600 disabled:bg-blue-400"
                type="button"
                onClick={() => handleOAuth("github")}
                disabled={loading}
            >
                Continue with Github
            </button>
        </div>
    );
}

export default OAuthButtons;

------------------------ React OAuthCallback.jsx -------------------------

import React, { useEffect } from 'react';
import axios from 'axios';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { loginStart, loginSuccess, loginFailure } from '../../redux/user/userSlice.js';

function OAuthCallback() {
    const dispatch = useDispatch();
    const navigate = useNavigate();

    useEffect(() => {
        const handleCallback = async () => {
            try {
                dispatch(loginStart());
                const response = await axios.get(
                    `http://localhost:4000/api/auth/callback/success`,
                    { withCredentials: true }
                );
                dispatch(loginSuccess({ user: response.data.user }));
                navigate('/');
            } catch (error) {
                dispatch(loginFailure({
                    error: error.response?.data?.message || "Login using Google failed! Please try using email and password!"
                }));
                navigate('/login');
            }
        };

        handleCallback();
    }, [dispatch, navigate]);

    return (
        <div className="flex items-center justify-center min-h-screen">
            <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-red-700"></div>
        </div>
    );
}

export default OAuthCallback;

------------------------ React App.jsx -------------------------

import React from 'react';
import { BrowserRouter, Routes, Route } from "react-router-dom";

import NavigationBar from './components/Navigation/NavigationBar.jsx';
import Home from './pages/Static/Home.jsx';
import About from './pages/Static/About.jsx';
import Register from './pages/Auth/Register.jsx';
import Login from './pages/Auth/Login.jsx';
import OAuthCallback from './components/Auth/OAuthCallback.jsx';

function App() {
  return (
    <BrowserRouter>
      <NavigationBar />
      <Routes>
        <Route path='/' element={<Home />} />
        <Route path='/about' element={<About />} />
        <Route path='/register' element={<Register />} />
        <Route path='/login' element={<Login />} />
        <Route path="/oauth/callback" element={<OAuthCallback />} />
      </Routes>
    </BrowserRouter>
  )
}

export default App;

------------------------ React (Sample Implementation) Login.jsx -------------------------

import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import axios from "axios";
import { useDispatch, useSelector } from 'react-redux';
import { loginStart, loginSuccess, loginFailure } from '../../redux/user/userSlice.js';
import OAuthButtons from '../../components/Auth/OAuthButtons.jsx';

function Login() {
  const [formDate, setFormData] = useState({
    email: "",
    password: "",
  });
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const { loading, error } = useSelector((state) => state.user);

  const handleChange = (e) => {
    setFormData({ ...formDate, [e.target.id]: e.target.value });
  }

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      dispatch(loginStart());
      const response = await axios.post("http://localhost:4000/api/auth/login", formDate, { withCredentials: true });
      const data = await response.data;
      dispatch(loginSuccess({ user: data.user }));
      navigate("/profile");
    } catch (error) {
      dispatch(loginFailure({ error: error.response?.data?.message || "An unexpected error occurred. Please try again." }));
    }
  }

  return (
    <div className='p-3 max-w-lg mx-auto'>
      <h1 className='text-3xl text-center font-semibold my-7'>Login</h1>
      <form onSubmit={handleSubmit} className='flex flex-col gap-4'>
        <input type="email" placeholder='Email' id='email' className='bg-slate-100 p-3 rounded-lg' onChange={handleChange} />
        <input type="password" placeholder='Password' id='password' className='bg-slate-100 p-3 rounded-lg' onChange={handleChange} />
        <button type='submit' disabled={loading} className='bg-slate-700 text-white p-3 rounded-lg uppercase hover:opacity-95 disabled:opacity-75 cursor-pointer'>
          {loading ? "Loading..." : "Login"}
        </button>
        <div className='border-b'></div>
        <OAuthButtons />
      </form>
      <div className='flex gap-2 mt-5'>
        <p>Don't an account?</p>
        <span className='text-blue-500'><Link to={"/register"}>Register</Link></span>
      </div>
      <div>
        <p className='text-red-700'>{error}</p>
      </div>
    </div>
  )
}

export default Login;
Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Unregistered user (0.5):
  • No latin characters (1):
  • Filler text (0.5): -------------------------------
  • Filler text (0): --------------------------------
  • Filler text (0): -------------------------------
  • Filler text (0): --------------------------------
  • Filler text (0): -------------------------------
  • Filler text (0): --------------------------------
  • Filler text (0): ------------------------
  • Filler text (0): -------------------------
  • Filler text (0): ------------------------
  • Filler text (0): -------------------------
  • Filler text (0): ------------------------
  • Filler text (0): -------------------------
  • Filler text (0): ------------------------
  • Filler text (0): -------------------------
  • Low reputation (1):
Posted by: Jeel Patel