passport google : why sessionID is different in the callback? - node.js

Dears,
there are a lot of examples around passport google, but there should be something I missed. I know my question might be marked as "possible duplicate", but sorry I can't find the answer, or none of them are fixing my issue.
the user clicks a href link on the client (running on https://localhost:3000), calling the googleauth route on the server (running on https://localhost).
On server side, passport process is running well, the callbackUrl is on the server side at /googleauth/redirect.
Then in this callback, I can hardcoded the redirect to 'https://localhost:3000'. that works fine.
But I don't want this. I want the server to catch the initial url from the client (so https://localhost:3000), keep it in the session and then use this information in the redirect.
In the google auth route, I set the toRedirect variable into the session thanks a middleware, before calling the passport.authenticate.
googleRouter.get('/', (req, res, next) => {
if (!req.session.toRedirect) req.session.toRedirect = req.socket.remoteAddress;
next();
}, Auth.passport.authenticate('google', {
scope: ['profile', 'email', 'https://mail.google.com/'],
accessType: 'offline',
prompt: 'consent',
session: false // <-- whatever true or false, same issue
})
);
However once the google process is complete (choose the google account and confirm the acces to user data), the sessionID is different in the callback, therefore the url to redirect is undefined... And of course the remote address is no longer the client one, but the google authentification APIs one...
googleRouter.get('/redirect', Auth.passport.authenticate('google', { session: false }), (req, res) => {
console.log(req.session.toRedirect, req.sessionID, req.socket.remoteAddress) // <--- sessionID is different so req.session.toRedirect is undefined
res.redirect('https://localhost:3000'); /<--- hardocded here but I want res.redirect(req.session.toRedirect).
});
So guys, how did you send back the google auth results to your clients ?
Here is the code
apps.js
const passport = Auth.passport;
app.use(passport.initialize());
app.use(passport.session()); <-- whatever set or not, same issue
auth.js
const googleOptions = {
clientID: sds,
clientSecret: dsadd,
callbackURL: '/googleauth/redirect',
passReqToCallback: true
};
passport.use(new GoogleStrategy(googleOptions,
async (req, token, refreshToken, profile, done) => {
data = await Auth.helpers.signIn(req, null, 'google');
if (data && data.erreur == authErrors.NONE) {
// MAJ de la session et cookie
req.session.userId = data.user.id;
done(null, data.user);
};
));
googleroutes
googleRouter.get('/', (req, res, next) => {
if (!req.session.toRedirect) req.session.toRedirect = req.socket.remoteAddress;
next();
}, Auth.passport.authenticate('google', {
scope: ['profile', 'email', 'https://mail.google.com/'],
accessType: 'offline',
prompt: 'consent',
session: false
})
);
// callback route pour redirect google
googleRouter.get('/redirect', Auth.passport.authenticate('google', { session: false }), (req, res) => {
console.log(req.session.toRedirect, req.sessionID, req.socket.remoteAddress)
res.redirect('https://localhost:3000');
});

Related

Google APi + Passport + React : Authentication flow

I'm using passport to authenticate using Google API, I'm sending a token by URL to the client (React app) which saves it in the localStorage.
I want to use that token : With every API call (get, post, put) I want to send that token to the server , but I didn't know how to verify that token on the server side.
Passport Startegy :
app.use(passport.initialize()); // Used to initialize passport
app.use(passport.session()); // Used to persist login sessions
passport.use(new GoogleStrategy({
clientID: 'IDxxxxx',
clientSecret: 'SecreXXX',
callbackURL: 'http://localhost:3000/callback'
},
(accessToken, refreshToken, profile, done) => {
// Directory API here
var userData = {
name: profile.displayName,
token: accessToken
};
done(null, userData);
Authentication :
app.get('/auth/google', passport.authenticate('google', {
scope: ['profile'] // Used to specify the required data
}));
// The middleware receives the data from Google and runs the function on Strategy config
app.get('/callback', passport.authenticate('google'), (req, res) => {
var token = req.user.token;
res.redirect("http://localhost:8000?token=" + token);
});
API in express (which contains CRUD methods) :
app.use('/api', movieRouter)
In react side : Getting the token
componentWillMount() {
var query = queryString.parse(this.props.location.search);
if (query.token) {
window.localStorage.setItem("jwt", query.token);
// appel a directory api (avec token) puis sauvergarder dans redux puis redirection vers liste demandes
this.props.history.push("/");
}
}
Doing API calls :
import axios from 'axios'
const api = axios.create({
baseURL: 'http://localhost:3000/api',
})
export const insertMovie = payload => api.post(`/movie`, payload)
I just need to send the token in every call and check it in the server side.
Thanks
You want to set the token in a header most likely, try changing your axios client to something like
const api = axios.create({
baseURL: 'http://localhost:3000/api',
headers: {
Authorization: `Bearer ${your_token_here}`
}
})
I'm not 100% sure if this is the correct header form that passport will be expecting, but it's the general idea you need to do.
If the token is correctly set in the header, session, or cookie by the client as noted by Bill Metcalf, then express is able to authenticate a route/endpoint by adding the passport.authenticate middleware function to the route, like so
app.use('/api', passport.authenticate('google', {failureRedirect:'/login'}), movieRouter)
Refer to http://www.passportjs.org/docs/google/ for more information
For every API that you want to verify the token, you can pass a verify token function (which I call 'isCorrectToken') before taking action like this:
router.get("/api", isCorrectToken, (req, res) => {
// your api content })
And then, this is our isCorrectToken function:
const isCorrectToken = (req, res, next) => {
const token = req.headers.authorization;
if(token){
const onlyToken = token.slice(7, token.length);
jwt.verify(onlyToken, accessToken, (err, decode) => {
if(err) return res.status(401).send({ msg: 'Invalid Token' });
req.user = decode;
next();
return;
});
}
else return res.status(401).send({ msg: 'Token is not supplied'});}
The number 7 is the length of 'Bearer ' (from Bill Metcalf's answer above).

Why request user is undefined in Sapper App

I cannot set a req.user in my Sapper app, what am I doing wrong
express()
.use(
bodyParser.urlencoded({
extended: true
}),
bodyParser.json(),
compression({
threshold: 0
}),
sirv('static', {
dev
}),
cookieParser(),
session({
store: store,
secret: 'somesecret',
resave: false,
saveUninitialized: true,
cookie: {
secure: true,
maxAge: 604800000,
}
}),
passport.initialize(),
passport.session(),
sapper.middleware({
session: (req, res) => {
let user = req.session.user
return {
user
}
}
}),
)
i have a login component from which im using fetching
in login folder i have a login.js file a server side route and i cannot get my req.user not req.session.user , not req.session.passport
I had the same problem.
I installed the module "express-session". (This module works with polka.js too)
And set the sapper's middleware:
sapper.middleware({
session: (req, res) => ({
user: req.session.user
})
I wrote a 'api/login/' to the sever side and a login page.
Once the server recognize the user with his login and pass', I use a goto(".").
The goto()'s behavior should be like a location.href. But this is not.
So I replace goto(".") by location.href("."). With this, I have the session's data from the server.(in the preload and with the stores => $session.user).
I hope that can help you.
I have found partial solution, please correct this if Iam wrong. So entire app is controlled over sapper.middleware and server it self has no automatic effect on it.
So no req.user is set, no session and no cookie value is set, every thing must be manually set from authentication route.
So my solution was to set cookie in login post route, so in login.js file.
import passport from 'passport'
export async function post(req, res, next) {
passport.authenticate('local', (err, user, info) => {
if (err) {
return res.json(err)
}
if (user) {
// console.log(req)
return res.status(201).cookie('user', req.sessionID).json({
email: user.email,
id: user._id
})
}
if (info) {
return res.json(info)
}
})(req, res, next);
}
in login.svelte component i set my session (SAPPER MIDDLEWARE SESSION)
session.set({ user: true, email: user_data.email, id: user_data.id });
So my Further action is to check if sessionid exist in session store on server if yes continue if no set user to null and redirect to login.
is it correct ? Is it safe enough ?
Edit
So My partial "solution" is not right for any one having the same problem with passport authentication solution is
you must define route in server.js prior sapper.middleware
app.httpRequest('/route',YOURAUTHENTICATION MIDDLEWARE)
in your route.js file or in route folder index.js file you will have your req.user
in sapper middleware your session object must look like this
session: (req, res) => ({
user: req.session.passport ? req.session.passport.user : null })
if you will try to pass req.user it will not be serialized by sapper / devalue package and you will keep getting error.
After this in your route.js you can preload user from session. hope this was clear !

Trying to use Google Oauth2 with Passportjs in Graphql-Yoga Server

I thought I followed all of the docs correctly to implement this, however, I am getting a TokenError: Bad Request that has the error message invalid_grant.
My server is super simple:
require('dotenv').config();
import createServer from './createServer';
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const server = createServer();
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:4000/auth/callback',
},
(accessToken, refreshToken, profile, cb) => {
console.log(accessToken);
console.log(refreshToken);
console.log(profile);
cb(null, profile);
},
),
);
server.express.use(
'/auth',
passport.authenticate('google', {
scope: ['email', 'profile'],
session: false,
}),
);
server.express.use(
'/auth/callback',
passport.authenticate('google', {
successRedirect: 'http://localhost:3000/authenticate',
failureRedirect: 'http://localhost:3000/authenticate',
}),
);
server.start(
{
cors: {
credentials: true,
origin: 'http://localhost:3000',
},
},
() => console.log('Server is running on http://localhost:4000'),
);
Is this a problem with the way that I have setup Google in the cloud platform? I can't figure out where I went wrong. My callback is correctly setup. I'm not sure where else to look for a mistake?
Another thing that is confusing is that the GoogleStategy is console logging the user profile and the access token that is returned. I am guessing that the error comes when the callback route tries to verify the code from the URL. Can anyone point me in a direction to look to better troubleshoot this? Thanks in advance.
I found a solution that works for me, but I am still unclear if it is "best-practice" or not. I would love someone with more GraphQL experience to chime in.
I followed the directions in the docs to authenticate in the front-end: https://developers.google.com/identity/sign-in/web/backend-auth
Then I wrote a query called isAuthenticated on the back-end that I can use to verify the token.
async isAuthenticated(_: any, { token }) {
if(!token) {
return null;
}
const ticket = await client.verifyIdToken({
idToken: token,
audience: process.env.GOOGLE_CLIENT_ID,
});
return payload;
},
I use a React component to check the token in localStorage before rendering any protected routes. This is working for me.

OAuth2 using passport-google-oauth: there is no body in GET request but I still recieve some token

I'm trying to implement google oAuth2 for android client. I send empty GET request for google login and receive some token. I can't understand why this happening.
I used my web client id and secret from developer console.
Here is my code:
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
passport.use(new GoogleStrategy({
clientID: 'MY CLIENT ID',
clientSecret: 'MY SECRET',
callbackURL: "http://mysite.io/account/googleLogin/callback"
},
function(accessToken, refreshToken, profile, cb) {
console.log("collection findOrCreate");
console.log("accessToken " + accessToken);
console.log("createIndex" + profile.id);
}
app.get('/googleLogin',
passport.authenticate('google', { scope: ['profile'] }));
app.get('/googleLogin/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
function(req, res) {
// Successful authentication, redirect home.
console.log("Google Login Success!");
res.redirect('/');
});
Logs:
accessToken ya29.tAI7uCiN-JFKWflq4Wm6xbyQjk1S-qdlB6Ks6GTHnNzzr0N_jz8rUVPZLVlvi4aIkF6SGvw
createIndex 102114909694672049994
When I send with the body, effect is the same.
GET http://mysite.io/account/googleLogin/
{ "idToken": "sometoken" }
I've found different approach. First is this part:
router.get('/googleLogin', passport.authenticate('google', { scope : ['profile', 'email'] }));
redirects user to the google authorization page, after success of which called callback method. It's not what I need, because I already have a token on Android side.
For Android client one of the ways is manually check if the google tokenId is valid or not, sending request to the gooogle API. Implementing this part from official source solved my problem:
https://developers.google.com/identity/sign-in/android/backend-auth#verify-the-integrity-of-the-id-token

FacebookTokenError: This authorization code has been used

Ok, so this is a common error with many causes. I am trying to modify an existing Node-Passport-Facebook module to have local images from the desktop uploaded to a users Facebook account after they log in. That is my goal. This is the code module I am extending
https://github.com/passport/express-4.x-local-example
which in turn is based on
https://github.com/jaredhanson/passport-facebook
I never get past console.log('ERROR HERE... with an error of "This authorization code has been used."
What's confusing is that the auth code returned is ALWAYS DIFFERENT! so how could it already have been used when I try and exchange it for an access token?
Can anyone offer some suggestions, and or next steps I might try? My hunch is that there is something about Passport.js that is not implemented properly.
So my question is, how would I modify the code below (based on this passport facebook example) https://github.com/passport/express-4.x-facebook-example/blob/master/server.jsto upload an image after logging in?
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-facebook').Strategy;
var CLIENTSECRET ='<client secret>';
var APPID ='<app id>';
// Configure the Facebook strategy for use by Passport.
//
// OAuth 2.0-based strategies require a `verify` function which receives the
// credential (`accessToken`) for accessing the Facebook API on the user's
// behalf, along with the user's profile. The function must invoke `cb`
// with a user object, which will be set at `req.user` in route handlers after
// authentication.
passport.use(new Strategy({
clientID: APPID,
clientSecret: CLIENTSECRET,
callbackURL: 'http://localhost:3000/login/facebook/return',
enableProof: true
//callbackURL: 'http://localhost:3000/login/facebook/return'
},
function(accessToken, refreshToken, profile, cb) {
// In this example, the user's Facebook profile is supplied as the user
// record. In a production-quality application, the Facebook profile should
// be associated with a user record in the application's database, which
// allows for account linking and authentication with other identity
// providers.
cb(null, profile);
}));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. In a
// production-quality application, this would typically be as simple as
// supplying the user ID when serializing, and querying the user record by ID
// from the database when deserializing. However, due to the fact that this
// example does not have a database, the complete Twitter profile is serialized
// and deserialized.
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
console.log(" ");
console.log("ASSERT passport.deserializeUser being called");
console.log(" ");
cb(null, obj);
});
// Create a new Express application.
var app = express();
// Configure view engine to render EJS templates.
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Use application-level middleware for common functionality, including
// logging, parsing, and session handling.
app.use(require('morgan')('combined'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
// Initialize Passport and restore authentication state, if any, from the
// session.
app.use(passport.initialize());
//app.use(passport.session());
// Define routes.
app.get('/',
function(req, res) {
res.render('home', { user: req.user });
});
app.get('/login',
function(req, res){
res.render('login');
});
app.get('/login/facebook',
passport.authenticate('facebook'));
app.get('/login/facebook/return',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
//my code changes start here!!
var code = req.query.code;
console.log("1 ASSERT after successful login! code="+code);
if(req.query.error) {
// user might have disallowed the app
return res.send('login-error ' + req.query.error_description);
} else if(!code) {
return res.redirect('/');
}
var options={
host:'graph.facebook.com',
path:'/oauth/access_token?client_id='+APPID+'&code='+code +'&client_secret='+CLIENTSECRET+'&redirect_uri=http://localhost:3000/login/faceboo k/return'
}
var https=require('https');
https.get(options,function(res){
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('ERROR HERE'+chunk);
});
});
console.log("2 ASSERT after successful login!")
//my code changes end here!!
});
app.get('/profile',
require('connect-ensure-login').ensureLoggedIn(),
function(req, res){
res.render('profile', { user: req.user });
});
app.listen(3000);
You don't need to make a request to /oauth/access_token at all (well you do, but passport has already handled it for you). That endpoint is for getting an access token when you don't have one, but you already have an access token here:
passport.use(new Strategy({
clientID: APPID,
clientSecret: CLIENTSECRET,
callbackURL: 'http://localhost:3000/login/facebook/return',
enableProof: true
//callbackURL: 'http://localhost:3000/login/facebook/return'
},
function(accessToken, refreshToken, profile, cb) {
// You have the access token here!
cb(null, profile);
}));
You'll need to save that accessToken some way, so that you can use it later when you make requests to the Graph API. You'll probably want to save it to the user's session, but you can also use a strategy like this: https://stackoverflow.com/a/24474900/772035
If you want the user to grant permission to publish (which you will need them to do, to be able to post to their feeds) you also need to replace every call to passport.authenticate with:
passport.authenticate('facebook', { scope: ['publish_actions'] } );
So that the posting permission is requested when the user first adds your app. Then you'll be able to use the /user/photos endpoint to upload a photo, passing the accessToken that you saved earlier in the query string.
You need to encode your query params.
var qs = {
client_id: APPID,
redirect_uri: 'http://localhost:3000/login/facebook/return',
client_secret: CLIENTSECRET,
code: code,
};
options = {
host:'graph.facebook.com',
path:'/oauth/access_token?' + require('querystring').stringify(qs),
};
I think that's your problem. The code itself looks fine apart from that. You'll want the querystring module to parse the results too by the way.
I solved this error. Follow the progress.
Facebook Log in -> Settings -> Apps -> Logged in with Facebook -> Delete Your apps.
After deleting your apps, Try to login with Facebook button.
you need to define profileFields instead of enableProof: true
passport.use(new FacebookStrategy({
clientID:keys.FacebookAppID,
clientSecret:keys.FacebookAppSecret,
callbackURL:'http://localhost:3000/auth/facebook/callback',
profileFields:['email','name','displayName','photos']
},(accessToken,refreshToken,profile,done)=>{
console.log(profile);
}));

Resources