0

I am successfully authenticating and logging in with the google OAuth API.

const passport       = require('passport');
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const passportInit  = require('./app/routes/auth')
const session = require('express-session');

module.exports = (app, db, passport) => {

    app.use(session({secret: "ahskfjdhkjshadkjfhlajsdhlfkj"}));

    passportInit(passport)

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

    app.get('/', (req, res) => {
        if (req.session.token) {
            res.cookie('token', req.session.token);
            res.json({
                status: 'session cookie set'
            });
            console.log(req.session.token);
            console.log(JSON.stringify(req.user))
        } else {
            res.cookie('token', '')
            res.json({
                status: 'session cookie not set'
            });
        }
    });

    app.get('/auth/google', passport.authenticate('google', {
        scope: ['https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/youtube']
    }));

    app.get('/auth/google/callback',
        passport.authenticate('google', {
            failureRedirect: '/' // Fail: To err Page
        }),
        (req, res) => {
            req.session.token = req.user.token;
            userString = JSON.stringify(req.user);
            userObjectValue = JSON.parse(userString);

            userId = userObjectValue['profile'].id;
            userName = userObjectValue['profile'].name;
            userGivenName = userName.givenName;

            const details = {'userId': userId};

            db.collection('users').findOne(details, (err, item) => {

                if (item == null) {
                    res.redirect('http://localhost:80/Register.html');
                } else {
                    if(item['rolle'] == 'yt') {
                        res.redirect('http://localhost:80/YT_Welcome.html');
                    } else {
                        res.redirect('http://localhost:80/WT_Welcome.html');
                    }

                }
            });
        }
    );

    app.get('/logout', (req, res) => {
        req.logout();
        req.session.token = null;
        res.redirect('/');
    });

}

Now I want to make a POST request from my frontend to my NodeJS backend.

Frontend-Request:

function registerWT() {

    console.log('registerWT started...')

    var rolle = 'wt';

    var json = {"rolle": rolle};

    $.ajax({
        url: 'http://localhost:8000/user',
        type: 'POST',
        data: JSON.stringify(json),
        contentType: 'application/json; charset=utf-8',
        dataType: 'JSON',
        async: false,
        success: function (msg) {
            var js = JSON.stringify(msg);

            var state = msg['state']; 

            if (state == true) {
                console.log('successfully created new user')

            } else {

                console.log('failed to create new user')

            }

        }
    });

Backend-API:

var ObjectID = require('mongodb').ObjectID;
const passport       = require('passport');
const passportInit  = require('./auth');

module.exports = (app, db) => {

    app.post('/user', (req, res) => {

        console.log("POST USER REACHED!"); // This is printed out...
        console.log(req.body.rolle); //is printed out correctly
        console.log(req.user); // Is undefined...

        if (!req.isAuthenticated()) { return res.send({'error':'unauthorized'}) } //Authentication fails...

        console.log(req.user.userId);
        console.log(req.userGivenName);
        console.log(req.body.rolle);

        userId = req.user.userId;
        userGivenName = req.user.userGivenName;
        userRolle= req.body.rolle;

        const details = { userId: userId, userGivenName: userGivenName, rolle: userRolle };

        db.collection('users').insert(details, (err, result) => {
          if (err) { 
            res.send({ 'error': 'An error has occurred' }); 
          } else {
            res.send(result.ops[0]);
          }
        });
    });

}

From my understanding the user authentication data should be send automaticly with every request I am doing from my frontend to my backend, since I logged in via google before. Is this correct or do I miss to include something in my frontend JS request code?

What is interessting is that after I logged in, I have to trouble with navigating to /user. So there is no problem with manualy doing a get request to this API, where I am also checking for authentication.

app.get('/user', (req, res) => {
       if (!req.isAuthenticated()) { return res.send({'error':'unauthorized'}) }
        db.collection('users').find({}).toArray((err, item) => {
            if (err) {
                res.send({'error':'An error has occurred'});
            } else {
                res.json(item)
            }
        });
    });

But when I am making a Get request with JavaScript, the authentication fails again...

JavaScript get request:

function getTest() {
    console.log('YT');

        $.ajax({
            url: 'http://localhost:8000/user',
            type: 'GET',
            async: false,
            success: function (msg) {
                var state = msg['state']; //nehmen den Wert von state aus der JSON

                if (state == true) {
                    console.log('successfully created new user')

                } else {

                    console.log('failed to create new user')
                }
            }

        });
}

Does someone know what I am doing wrong here?

Edit: My passportInit:

const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const keys = require('../../config/keys')

module.exports = (passport) => {
    passport.serializeUser((user, done) => {
        done(null, user);
    });
    passport.deserializeUser((user, done) => {
        done(null, user);
    });
    passport.use(new GoogleStrategy({
        clientID: keys.google.clientID,
        clientSecret: keys.google.cientSecret,
        callbackURL: keys.google.callback
    },
    (token, refreshToken, profile, done) => {
        return done(null, {
            profile: profile,
            token: token
        });
    }));
};

Edit2: Added cors package:

    const MongoClient    = require('mongodb').MongoClient;
    const bodyParser     = require('body-parser');
    const db             = require('./config/db');
    const keys           = require('./config/keys')
    const passport       = require('passport');
    const express        = require('express');
    const app            = express();
    const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
    const cors           = require('cors')

    const port = 8000;

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors({ origin: 'http://localhost' })); //configuring cors

MongoClient.connect(db.url, (err, database) => {
    if (err) return console.log(err)

    app.options('*', cors()) // enable pre-flight across-the-board
    require('./authenticate')(app, database,passport);
    require('./app/routes')(app, database, passport);

    app.listen(port, () => {
    console.log('We are live on ' + port);
    });
});  
Leonard Michalas
  • 1,130
  • 1
  • 11
  • 26
  • 1
    Can you add the code in `passportInit()`? – Vasan May 19 '18 at 16:43
  • 1
    Also, check whether your JS requests are sending the session cookie properly, using browser's developer tools. – Vasan May 19 '18 at 16:52
  • @Vasan Thank you very much for taking a look at this! I just added the `passportInit()` under the tab 'network' I am seeing a response cookie with the name: SIDCC from the domain google, which will expire in August 2018. So I think this is okay. – Leonard Michalas May 19 '18 at 17:02
  • 1
    So you have your frontend running at port 80 and node running at port 8000? – Vasan May 19 '18 at 17:59
  • Yes, exactly! But I am allowing ‘access-control-allow-origin’ via a chrome plugin and since I am getting the body of the request in the backenend this should work. – Leonard Michalas May 19 '18 at 18:02
  • 1
    Right. In addition to that, you also need to set withCredentials true on your AJAX request. See [this answer](https://stackoverflow.com/a/29138912) (or other answers in that question) for that. – Vasan May 19 '18 at 18:06
  • 1
    Your express session cookie name is `connect.sid` with localhost as domain, which should be sent in every AJAX request. Check if this happens (through dev tools) once you've done the above change. – Vasan May 19 '18 at 18:13
  • Thanks for this! But now I am getting: `Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.` --- I already tried to add the cors package to my backend (see edited question), but that did not help... Do you have any idea where this comes from? – Leonard Michalas May 19 '18 at 18:33
  • 1
    See [this](https://www.npmjs.com/package/cors#user-content-enabling-cors-pre-flight). Also, make sure to remove your chrome plugin which adds the access control header, or it might override what's set by Node. And finally, try including the request port explicitly in your origin. – Vasan May 19 '18 at 18:41
  • Thank you @Vasan for your patients! However, this does not work for me. I disabled the CORS plug-in in chrome. I added the the code from the link you provided (see edited question), I cleared cache and checked multiple browsers, but the problem is still there. When I am adding `:80` to the origin I get a new error btw, so I removed it. The error was: `Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://localhost:80' that is not equal to the supplied origin. Origin 'http://localhost' is therefore not allowed access.` – Leonard Michalas May 19 '18 at 19:12
  • 1
    Strange. Ok, hopefully someone else has an answer. Do update here (preferably post an answer) when you finally resolve your issue. – Vasan May 19 '18 at 19:15
  • I will! Thank you very much for your help! If you come up with any other idea, please get back to me :) – Leonard Michalas May 19 '18 at 19:18
  • @Vasan One last question... Do you think this problem would disappear, if I would just host my frontend and backend somewhere in the cloud? This problem just appears, because they are both on localhost on different ports correct? – Leonard Michalas May 19 '18 at 19:20
  • 1
    If your problem was indeed _only_ CORS-related then yes, that would solve it. But we don't know for sure if that's the case. If the express cookies are passed correctly in the request your problem would be solved for sure. – Vasan May 19 '18 at 19:30
  • @Vasan After 11 hours working on this god damn problem I finally solved it. Thank you very much for your support on this! You led me in the right direction and made me keep going! – Leonard Michalas May 19 '18 at 22:54

1 Answers1

0

I finally was able to solve the problem. No, Browser Plug-in or something like this is needed! Please see my Code below.

Frontend-Request:

function register() {

    var role = 'wt';

    var json = {"role": role};

    $.ajax({
        url: 'http://localhost:8000/user',
        type: 'POST',
        data: JSON.stringify(json),
        contentType: 'application/json; charset=utf-8',
        dataType: 'JSON',
        xhrFields: {
            withCredentials: true
       },
       crossDomain: true,
       async: false,
       success: function (msg) {

            var state = msg['state']; 

            if (state == true) {
                console.log('successfully created new WT')
                location.href = 'http://localhost:80/WT_Welcome.html'

            } else {

                console.log('failed to create new WT')
                location.href = 'http://localhost:80/index.html'

            }

        }
    });
}    

Backend Server.js

const MongoClient    = require('mongodb').MongoClient;
const bodyParser     = require('body-parser');
const db             = require('./config/db');
const keys           = require('./config/keys')
const passport       = require('passport');
const express        = require('express');
const app            = express();
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;

const port = 8000;

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

MongoClient.connect(db.url, (err, database) => {
    if (err) return console.log(err)

    app.use(function(req, res, next) {
        res.header('Access-Control-Allow-Origin', "http://localhost");
        res.header('Access-Control-Allow-Methods','GET,PUT,POST,DELETE');
        res.header('Access-Control-Allow-Credentials', true)
        res.header('Access-Control-Allow-Headers', 'Content-Type');
        next();
    })

    require('./authenticate')(app, database,passport);
    require('./app/routes')(app, database, passport);

    app.listen(port, () => {
    console.log('We are live on ' + port);
    });
});   

Backend API:

app.post('/user', (req, res) => {

        if (!req.isAuthenticated()) { return res.send({'error':'unauthorized'}) } //Authentication fails...

        userString = JSON.stringify(req.user);
        userObject = JSON.parse(userString)

        userId = userObjectValue['profile'].id;
        userName = userObjectValue['profile'].name; //not used
        userGivenName = userName.givenName;
        userRolle= req.body.rolle;

        const details = { userId: userId, userGivenName: userGivenName, rolle: userRolle };

        console.log(details);

        db.collection('users').insert(details, (err, result) => {
          if (err) { 
            res.send({ 'state': false }); 
          } else {
            res.send({'state' : true});
          }
        });
    }); 
Leonard Michalas
  • 1,130
  • 1
  • 11
  • 26