reactjs - Πρόβλημα CORs με την Πιστοποίηση/Εξουσιοδότηση Google χρησιμοποιώντας React/Nodejs/Passport


Έχω το ίδιο πρόβλημα με το σφάλμα CORs: Google Oauth από React σε Express (επαλήθευση PassportJs). Αλλά δεν μπορώ να κάνω τη λύση που προσέφερε η @Yazmin να λειτουργήσει.

Προσπαθώ να δημιουργήσω ένα stack React, Express/Nodejs, MongoDB με αυθεντικοποίηση και εξουσιοδότηση Google. Επί του παρόντος αναπτύσσω το stack στα Windows 10, χρησιμοποιώντας το Vs Code (React στο 'localhost:3000, Nodejs στο localhost:5000 και MongoDB στο localhost:27017.

Ο σκοπός της εφαρμογής είναι να εμφανίζει Αστικά Σκίτσα (εικόνες) σε έναν χάρτη χρησιμοποιώντας τα Google Maps, το API του Google Photos και το API του Google Gmail. Στο μέλλον ίσως χρειαστώ επίσης παρόμοια πρόσβαση σε Ομάδες Facebook για να αποκτήσω πρόσβαση σε Αστικά Σκίτσα. Αλλά προς το παρόν έχω συμπεριλάβει μόνο τα scopes προφίλ και Email για την εξουσιοδότηση.

Θέλω να διατηρώ όλα τα αιτήματα για πόρους τρίτων στο backend, καθώς αρχιτεκτονικά κατανοώ ότι αυτό είναι βέλτιστη πρακτική.

Η διαδικασία εξουσιοδότησης της Google από την προέλευση http://localhost:5000 λειτουργεί σωστά και επιστρέφει τα αναμενόμενα αποτελέσματα. Ωστόσο, όταν προσπαθώ να κάνω το ίδιο από τον πελάτη - προέλευση Http://localhost:3000 επιστρέφεται το ακόλουθο σφάλμα στην κονσόλα εργαλείων προγραμματιστή μετά την πρώτη προσπάθεια πρόσβασης στο api εξουσιοδότησης google auth2. Παρόλο που το σχήμα και ο τομέας είναι τα ίδια, η θύρα είναι διαφορετική, οπότε το μήνυμα από το τρίτο μέρος (Https://account.google.com) έχει απορριφθεί από το πρόγραμμα περιήγησης.

Η πρόσβαση για λήψη στο '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.

Ό,τι κι αν προσπαθήσω, το μήνυμα σφάλματος είναι το ίδιο.

Νομίζω ότι η Google στέλνει την απάντηση στον πελάτη (localhost:3000) αντί για τον διακομιστή.

Μεταξύ άλλων λύσεων, προσπάθησα να εφαρμόσω τη λύση του Yilmaz με την παραπομπή: «Δημιουργήστε το αρχείο 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, αλλά δεν μπόρεσα να εντοπίσω καμία δραστηριότητα από το endpoint auth/google από την απάντηση του διακομιστή εξουσιοδότησης της Google (https://accounts.google.com/o/oauth2/v2/auth).

Λοιπόν, νομίζω ότι η θεωρία μου είναι λάθος και δεν ξέρω πώς θα το κάνω να λειτουργήσει.

Τα μηνύματα του console log που εμφανίζονται στο τερματικό του VsCode μετά από αίτημα στο endpoint /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 του client

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 για τη συλλογή πληροφοριών χρήστη για την ενημέρωση της κατάστασης του React
βήμα 4 καλεί τη διαδρομή, αντλεί δεδομένα και ενημερώνει την κατάσταση του χρήστη.