reactjs - בעיית CORs עם אימות/הרשאה של Google בעזרת React/Nodejs/Passport


אני נתקל באותה בעיה כמו בעיית CORs Error: Google Oauth מ-React ל-Express (אימות PassportJs). אבל אני לא מצליח להפעיל את הפתרון שהוצע על ידי @Yazmin.

אני מנסה ליצור סטאק של React, Express/Nodejs, MongoDB עם אימות והרשאה של Google. אני כרגע מפתח את הסטאק על Windows 10, משתמש ב-Vs Code (React על 'localhost:3000, Nodejs על localhost:5000 ו-MongoDB על localhost:27017).

המטרה של האפליקציה היא להציג סקיצות עירוניות (תמונות) על מפה באמצעות Google Maps, Google Photos API ו-Google Gmail API. ייתכן שבעתיד אצטרך גם גישה דומה לקבוצות Facebook כדי לגשת לסקיצות עירוניות. אבל כרגע כללתי רק את ההרשאות לפרופיל ודוא"ל לצורך הרשאה.

אני רוצה לשמור את כל הבקשות למשאבים של צד שלישי בצד השרת, משום שבאופן ארכיטקטוני אני מבין שזו הפרקטיקה הטובה ביותר.

תהליך האישור של גוגל מהמקור http://localhost:5000 עובד מצוין ומחזיר את התוצאות הצפויות. עם זאת, כאשר אני מנסה לעשות את אותו הדבר מהלקוח - המקור Http://localhost:3000, מתקבלת השגיאה הבאה בכלי המפתחים בקונסול לאחר הניסיון הראשון לגשת ל-API של google auth2. למרות שהתוכנית והדומיין זהים, הפורט שונה, ולכן ההודעה מהצד השלישי (Https://account.google.com) נדחתה על ידי הדפדפן.

גישה ל-fetch ב-‘https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email%20https%3A%2F%2Fmail.google.com%2F&client_id=‘ (הועברה מ-‘http://localhost:3000/auth/google’) מהמקור ‘http://localhost:3000’ נחסמה על ידי מדיניות CORS: אין כותרת ‘Access-Control-Allow-Origin’ במקור המבוקש. אם תגובה אטומה מספקת את הצרכים שלך, הגדר את מצב הבקשה ל-‘no-cors’ כדי לבצע את הבקשה עם CORS מושבת.

לא משנה מה אני מנסה, הודעת השגיאה היא אותה אחת.

אני חושב שגוגל שולחת את התשובה ללקוח (localhost:3000) במקום לשרת.

בין פתרונות אחרים, ניסיתי ליישם את הפתרון של יילמאז על ידי ציטוט: "צור קובץ setupProxy.js בתיקיה client/src. אין צורך לייבא את זה בשום מקום. create-react-app יחפש את התיקיה הזו". כבר יצרתי את הלקוח שלי על ידי הפעלת create-react-app בעבר. לכן הוספתי את setupProxy.js בתוך תיקיית src שלי.

שאלה: אני מניח שאני צודק שהקובץ החדש setupProxy.cjs המכיל את ההגדרות שלי ייכלל על ידי webpack לאחר שאאתחול את הלקוח.

נראה לי שהזרימה שאני מקבל היא לא BROWSER ==> EXPRESS ==> GOOGLE-SERVER אלא BROWSER ==> EXPRESS ==> GOOGLE-SERVER ==> BROWSER שם היא נעצרת עם שגיאת ה-CORS כפי שהוצג למעלה.

כדי לבדוק את התיאוריה הזו, הנחתי כמה הודעות console log בפונקציות "shouldProxy" ו-"middleware" בקובץ client\node_modules\http-proxy-middleware\lib\index.js, אך לא הצלחתי לזהות שום פעילות מהנקודה auth/google מתוך תגובת שרת האישור של גוגל (https://accounts.google.com/o/oauth2/v2/auth).

אז אני חושב שהתיאוריה שלי שגויה ואני לא יודע איך אני אצליח לגרום לזה לעבוד.

הודעות יומן הקונסולה שמוצגות במסוף של VsCode לאחר בקשה ל-/auth/google מהלקוח React הן כדלקמן...

http-proxy-middleware - 92 HttpProxyMiddleware - shouldProxy
  context [Function: context]
  req.url /auth/google
  req.originalUrl /auth/google
  Trace
      at shouldProxy (C:\Users\User\github\GiveMeHopev2\client\node_modules\http-proxy-middleware\lib\index.js:96:13)
      at middleware (C:\Users\User\github\GiveMeHopev2\client\node_modules\http-proxy-middleware\lib\index.js:49:9)
      at handle (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-server\lib\Server.js:322:18)
      at Layer.handle [as handle_request] (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\layer.js:95:5)
      at trim_prefix (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:317:13)
      at C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:284:7
      at Function.process_params (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:335:12)
      at next (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:275:10)
      at goNext (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-middleware\lib\middleware.js:28:16)
      at processRequest (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-middleware\lib\middleware.js:92:26)
http-proxy-middleware - 15 HttpProxyMiddleware - prepareProxyRequest
req localhost

The Google callback uri is http://localhost:5000/auth/google/callback

זוהי רשימה של קוד השרת שלי ב-Node.js.

dotenv.config();
// express
const app = express();
// cors
app.use(cors())

// passport config
require ('./config/passport')(passport)

// logging
if( process.env.NODE_ENV! !== 'production') {
    app.use(morgan('dev'))
}
const conn = process.env.MONGODB_LOCAL_URL!

/**
 * dbConnection and http port initialisation
 */

const dbConnnect = async (conn: string, port: number) => {

    try {
        let connected = false;
        await mongoose.connect(conn, { useNewUrlParser: true, useUnifiedTopology: true })
        app.listen(port, () => console.log(`listening on port ${port}`))
        return connected;
    } catch (error) {
        console.log(error)
        exit(1)
    }
}

const port = process.env.SERVERPORT as unknown as number
dbConnnect(conn, port)
//index 02
// Pre Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }))


const mongoStoreOptions = {
    mongoUrl: conn,
    collectionName: 'sessions'
}

app.use(
    session({
        secret: process.env.SESSIONKEY as string,
        resave: false,
        saveUninitialized: false,
        store: MongoStore.create(mongoStoreOptions),
    })
)

app.use(passport.initialize())
app.use(passport.session())

// Authentication and Authorisation

const emailScope: string = process.env.GOOGLE_EMAIL_SCOPE as string
//GOOGLE_EMAIL_SCOPE=https://www.googleapis.com/auth/gmail/gmail.compose

const scopes = [
    'profile',
     emailScope

].join(" ")


app.get('/auth/google', passport.authenticate('google', {
    scope: scopes
}));

app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/'}),
    (req, res) => {
        res.send('Google Login Successful ')
    }
)

app.get('/', (req, res) => {
    res.send('Hello World');
})

קובץ http-proxy-middleware setupProxy.cjs. שים לב לסיומת cjs. אני מניח שזה בגלל שאני משתמש ב-Typescript. הוא נמצא בתיקיית src של הלקוח

const  createProxyMiddleware   = require('http-proxy-middleware');
module.exports = function (app) {
  app.use(createProxyMiddleware('/auth', {target: 'http://localhost:5000'}))
}

ולבסוף הפקודה fetch מהלקוח

async function http(request: RequestInfo): Promise<any> {
    try {
      const response = await fetch('/auth/google') 
      const body = await response.json();
      return body
    } catch (err) { console.log(`Err SignInGoogle`) }
  };

והתצורה של הדרכון...

import { PassportStatic} from 'passport';
import {format, addDays} from 'date-fns'
import { IUserDB, IUserWithRefreshToken, ProfileWithJson} from '../interfaces/clientServer'

 
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const User = require('../models/User')



module.exports   = function (passport:PassportStatic) {

    const clientID: string = process.env.GOOGLE_CLIENTID as string
    const clientSecret: string = process.env.GOOGLE_SECRET as string
    const callbackURL: string = process.env.GOOGLE_AUTH_CALLBACK as string

    const strategy = new GoogleStrategy(
        {
            clientID: clientID,
            clientSecret: clientSecret,
            callbackURL: callbackURL,
            proxy: true
            
        },
        async (_accesstoken: string, _refreshtoken: string,
            profile: ProfileWithJson,
etc

תשובה #1

אי אפשר לבצע קריאת fetch לנתיב /auth/google!
הנה הפתרון שלי ב-JavaScript...

// step 1:
// handler function should use window.open instead of fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")

//step 2: 
// on the server's redirect route add this successRedirect object with correct url. 
// Remember! it's your clients root url!!! 
router.get(
    '/google/redirect', 
    passport.authenticate('google',{
        successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
    })
)

// step 3:
// create a new server route that will send back the user info when called after the authentication 
// is completed. you can use a custom authenticate middleware to make sure that user has indeed 
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))

// here is an example of a custom authenticate express middleware 
const authenticated = (req,res,next)=>{
    const customError = new Error('you are not logged in');
    customError.statusCode = 401;
    (!req.user) ? next(customError) : next()
}
// step 4: 
// on your client's app.js component make the axios or fetch call to get the user from the 
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
    axios.get('http://[server:port]/getUser',{withCredentials : true})
    .then(response => response.data && setUser(response.data) )
},[])

הסבר....
שלב 1 יטען את כתובת ה-URL של האימות של השרת בדפדפן שלך ויבצע את בקשת האימות.
שלב 2 לאחר מכן טען מחדש את כתובת ה-URL של הלקוח בדפדפן כאשר האימות הושלם.
שלב 3 עושה קצה API זמין לאיסוף מידע על המשתמש כדי לעדכן את ה-state של React
שלב 4 מבצע קריאה לקצה, מבקש נתונים ומעדכן את ה-state של המשתמשים.