stephenplusplus / google-auto-auth

Making it as easy as possible to authenticate a Google API request
MIT License
34 stars 9 forks source link

Invalid JWT Signature #49

Closed ghost closed 6 years ago

ghost commented 6 years ago

Hey @stephenplusplus

I am hoping you can help me out, I have tried multiple libraries including the official Google API node module but I keep receiving the same error over and over.

I have created a service account with all the possible permissions under my google api console. Everytime I try to get a token I receive the following:

invalid_grant: Invalid JWT Signature.

The code I am using to try and get the token is as follows:

    var googleAuth = require('google-auto-auth');

    var authConfig = {};
    authConfig.keyFilename = './config/keys/CCKey.json';
    authConfig.scopes = 'https://www.googleapis.com/auth/analytics.readonly';

    var auth = googleAuth(authConfig);

    auth.getToken(function (err, token) {
        console.log(err);
    });

I am pulling my hair out with this, maybe I am creating the service account incorrectly? I am not sure at this point anymore, any help will be appreciated.

stephenplusplus commented 6 years ago

These token/key things can be very frustrating! Let's start from the beginning, when the key is created:

https://console.cloud.google.com/iam-admin/serviceaccounts/project

image

image

Clicking "Create" will download the key file that we accept

ghost commented 6 years ago

Hi @stephenplusplus

I gave that a try, still getting the same error. Have tried different combinations of roles to no avail.

I seem to have found the issue. If i place my code directly in the app.js file where the express https server starts, I get a token.

But, if I add this code to the route file and try calling it from there I receive this issue. Problem being, I need the token when that route is accessed, generating it in app.js won't for my use case.

stephenplusplus commented 6 years ago

Could you show the code that doesn't work, so I can understand better?

ghost commented 6 years ago

@stephenplusplus Sure thing, even when the calling code is in my app.js, if the user logs in then out and tries to get the token again it fails. But, this happens if the user logs in and out on one browser and I try to get the token by visiting the page in another browser as well, so it can't be something to do with the cookies, specific user or the browser.

I tried stepping through the code to find the issue but I can't seem to find it. I couldn't see a difference in the requests once the user has logged out.

I have now changed the code to directly use the email and private key instead of the JSON file, but the interesting thing is, when I run the code on my linux VPS I get a different error, even with the key and email. The error on my Windows machine stays the same.

The error I receive there is as follows:

Bad input string

But this error also only happens once a token has been generated, the user logging in then the user logging out and trying to get another token. As I mentioned above, once the user is logged out on one browser, the token cannot be generated from a different browser either.

As you can see below I try to generate the token when the user visits a specific route in my app, but the error still appears when I try it in my app.js file. When the calling code is in the app.js file, I can repeatedly refresh the page and it successfully gets a token every single time, until I log in and then try. I'm not sure if passport.js is interfering with the process in some way, but I am completely lost now, I have no idea what to try anymore.

Anyways, here is the code that I am using, maybe you can spot something wrong in it. Getting the auth token (route.js):

function getGToken() {
    var googleAuth = require('google-auto-auth');
    var models = require('./models');
    var serverlog = models.ServerLog;
    var config = models.Config;

    var authConfig = {};
    authConfig.credentials = {
        client_email: 'XXXXX@XXXX.iam.gserviceaccount.com',
        private_key: '-----BEGIN PRIVATE KEY-----\nXXXXXXX==\n-----END PRIVATE KEY-----\n'
    };
    authConfig.scopes = 'https://www.googleapis.com/auth/analytics.readonly';

    var auth = googleAuth(authConfig);

    auth.getToken(function (err, token) {
        if (err) {
            var newServerLog = new serverlog();
            newServerLog.class = 'GToken';
            newServerLog.exception = err.message;
            newServerLog.save();
        } else {
            config.findOne({ where: { key: 'GToken' } }).then((result) => {
                if (result) {
                    result.value = token;
                    result.save();
                } else {
                    var newConfig = new config();
                    newConfig.key = 'GToken';
                    newConfig.value = token;
                    newConfig.save();
                }
            })
        }
    });
}

The calling code (route.js):

module.exports = function(app, passport) {
    //LOGIN GET
    app.get('/login', function(req, res) {
        getGToken();
        res.render('login', {
            title: 'Car Centered - Login',
            description: 'Login page for the car centered leads portal.'
        });
    });
   //LOGOUT
    app.get('/logout', function(req, res) {
        req.logout();
        res.redirect('/');
    });
};

Relevant app.js code (app.js):

//Passport
require('./config/passport.js')(passport);

//Express
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(cookieParser());

//Static files
app.use(express.static('public', { maxAge: 31557600 }));

app.engine('hbs', handlebars.engine);
app.set('view engine', 'hbs');
app.set('views', path.join(__dirname, "views"));

app.use(session({
    cookie: {
        maxAge: 12*60*60*100,
        httpOnly: true,
        secure: true
    },
    store: new RedisStore({port:process.env.REDIS_PORT, host:process.env.REDIS_HOST, pass:process.env.REDIS_PASS}),
    secret: process.env.SESS_SECRET,
    saveUninitialized: false,
    resave: false
}));

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

app.use(fileUpload({
    limits:{fileSize: 5 * 1024 * 1024}
}));

//Routes
require('./routes.js')(app, passport);

Passport login code(passport.js):

passport.use('login', new LocalStrategy({
        usernameField: 'email',
        passwordField: 'password',
        passReqToCallback: true
    }, function(req, email, password, done) {
        User.findOne({
            where: {
                email: email
            }
        }).then(function(user) {
            if(!user)
                return done(null, false, req.flash('error', 'No user found.'));

            user.validPassword(password).then((correctPass) => {
                if(!correctPass) {
                    return done(null, false, req.flash('error', 'Oops! Wrong password.'));
                } else {
                    user.lastIP = req.connection.remoteAddress;
                    user.save().then(() =>{
                        return done(null, user);
                    });
                }
            });
        });
    }));

App.js trial:

app.use(function (req, res, next) {
    if (!/https/.test(req.protocol)) {
        res.redirect("https://" + req.headers.host + req.url);
    } else {
        getGToken();
        return next();
    }
});
ghost commented 6 years ago

@stephenplusplus I came right by implementing this functionality using request and the REST api's available from Google.