For our portal, i integrated my express server with passport to authenticate user. Once user authenticated need to fetch details for user from our api using same session from client. passport authentication working for my portal and able to see identity provider data for user logged in. but when any GraphQL call executed from react side, Apollo servers don't have user session in its request but express had.
Followed the steps mentioned here https://www.apollographql.com/docs/apollo-server/essentials/server.html#ssl but when setting up context for ApolloServer, req don't have passport in its session.
const express = require('express');
const session = require('express-session');
const { ApolloServer } = require('apollo-server-express');
const addResolveFunctionsToSchema = require('graphql-tools').addResolveFunctionsToSchema;
const mongooseConnection = require('./src/persistence/mongooseConnection');
const MongoSession = require('connect-mongo')(session);
const config = require('config');
const mongo = config.get('mongo');
const fs = require('fs');
const https = require('https');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const configSession = config.get('session');
const configPassport = config.get('passport');
const configCommon = config.get('common');
const resolvers = require('./src/resolvers');
const schema = require('./src/schema');
const uri = `${mongo.uri}/${mongo.database}`;
const app = express();
var passport = require('passport');
const SamlStrategy = require('passport-saml').Strategy;
const authRoute = require('./routes/auth');
const apiRoute = require('./routes/userDetails');
const Helmet = require('helmet');
require('reflect-metadata');
mongooseConnection.create();
let mongoOptions = {};
const mongoSessionStore = new MongoSession({
url: `${uri}`,
mongoOptions: mongoOptions,
collection: configSession.mongo.collection
});
addResolveFunctionsToSchema(schema, resolvers);
passport.use(
new SamlStrategy(
{
entryPoint: configPassport.entry_point,
// issuer: 'passport-saml',
protocol: 'https',
callbackURL: `${configCommon.dns}/${configPassport.callback_url}`,
// cert: adfsTokenSigningCert,
identifierFormat: null,
forceAuthn: true,
signatureAlgorithm: 'sha256',
acceptedClockSkewMs: -1
},
(profile, cb) => {
cb(null, profile);
}
)
);
passport.serializeUser(function(user, done) {
done(null, userData);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
app.use(cookieParser());
app.use(
session({
secret: 'project',
secret: configSession.config.secret,
resave: false,
saveUninitialized: false,
store: mongoSessionStore,
sameSite: true,
rolling: true,
name: 'project',
cookie: {
secure: true,
domain: configCommon.cookie_domain
}
})
);
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.json());
app.use(
bodyParser.urlencoded({
extended: false
})
);
// setting up security headers
app.use(Helmet());
app.get('/s/health-check', (req, res) => {
res.send(`I am alive!!!!`);
});
app.use('/s/api', apiRoute);
app.use('/s/auth', authRoute); // API integration
const isDev = process.env.NODE_ENV !== 'prod';
const apolloServer = new ApolloServer({
schema,
resolvers,
context: ({ req }) => {
console.log(req) //req.session don't have passport in it
return {
req
};
}
});
apolloServer.applyMiddleware({
app,
path: '/graphql'
});
apolloServer.installSubscriptionHandlers(app);
https
.createServer(
{
key: fs.readFileSync(`./keys/server.key`),
cert: fs.readFileSync(`./keys/server.crt`)
},
app
)
.listen(process.env.PORT, () => {
console.log('Express server is running on port 9091');
});
// Handle uncaughtException
process.on('uncaughtException', err => {
console.log(err.message);
process.exit(1);
});
Double check that the request is sending the session cookies. I had the same problem, and I was using Graphql Playground as the client, and it disables sending the cookies by default. You can fix it by going to the settings in the top right of the playground, and setting "request.credentials": "same-origin". After I did that I saw the passport session initialized properly.
Related
I have my express server on a different port than my client-side nextjs project.
I know when you have a server on the same port you can use getRequestHandler with next that passes the user object to be accessible with getInitialProps in the client-side.
const express = require("express");
const next = require("next");
const app = next({ dev: true });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
// adds passport session
require("./middlewares").init(server);
const apolloServer = require("./graphql").createApolloServer();
apolloServer.applyMiddleware({ app: server });
server.all("*", (req, res) => {
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
});
});
My passport implementation is as follows
const config = require("../config");
const session = require("express-session");
const passport = require("passport");
exports.init = (server, db) => {
require("./passport").init(passport);
const sess = {
name: "pid",
secret: config.SESSION_SECRET,
cookie: { maxAge: 2 * 60 * 60 * 1000 },
resave: false,
saveUninitialized: false,
store: db.initSessionStore(),
};
if (process.env.NODE_ENV === "production") {
server.set("trust proxy", 1);
sess.cookie.secure = true;
sess.cookie.httpOnly = true;
sess.cookie.sameSite = 'none';
sess.cookie.domain = process.env.DOMAIN;
}
server.use(session(sess));
server.use(passport.initialize());
server.use(passport.session());
};
And running the following on the express server, I can see req.user returning the user object.
app.use((req, res, next) => {
console.log(req.user);
next();
});
In a page in my nextjs app, in getInitialProps req.user is undefined
Home.getInitialProps = async (ctx) => {
const { req } = ctx;
const { user } = req;
console.log(user);
..........
};
Is there a way to either access the passport user object via SSR in nextjs or a different method to authorize and user on a page?
I do have a Github Repo with instructions on how to run the app in the README.md
Passport auth doesn't seems work across port. The solution is put a ngnix in front.
Local passport authorization on different ports
index.js
const express = require('express')
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const MongoStore = connectMongo(session);
const app = express()
const sessionStore = new MongoStore({
mongooseConnection: connection,
collection: 'sessions'
})
app.use(session({
secret: 'mysecret',
resave: false,
saveUninitialized: true,
store: sessionStore,
cookie: {
maxAge: (1000 * 60)*10
},
httpOnly: true,
secure: false
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => {
console.log(user._id: " + user._id);
done(null, user._id)
})
const GoogleStrategy = passportGoogle.OAuth2Strategy
const strategy = app => {
const strategyOptions = {
clientID: GOOGLE_ID,
clientSecret: GOOGLE_SECRET,
callbackURL: `/auth/google/callback`
}
const verifyCallback = async (accessToken, refreshToken, profile, done) => {
let [err, user] = await to(getUserByProviderId(profile.id))
if (err || user) {
return done(err, user)
}
const [createdError, createdUser] = await to(
createUser({
provider: profile.provider,
...
})
)
return done(createdError, createdUser)
}
passport.use(new GoogleStrategy(strategyOptions, verifyCallback))
app.get('/auth/google/callback',
passport.authenticate('google'),
(req, res) => {
res.cookie('???what is default key set by PassportJS???', req.user._id);
res.redirect(303, `/loggedByGoogle.html`);
}
I succesfully
- logged in with Google,
- passport.serializeUser was invoked
- profile data was saved in MongoDB,
- redirection happened to `/loggedByGoogle.html`
but no cookies were sent to client
and there is no entry in "sessions" data store after login.
Do I have to explicitly set and send cookies myself?
Only after I added res.cookie('foo', 'bar');
ahead of
res.redirect(303, /loggedByGoogle.html);
I got some cookie on client side.
I though cookies were set internally by Passport after "serializeUser"
invocation.
Is there a default cookie name (key) set by Passport?
What am I missing?
Below you can see I have an Apollo server (using express). In lines 33-39 I use an express middleware to check an auth token and (if the token is valid) set the users _id onto the request object.
In my server constructor, I set the context to return the req, as well as the _id as the userId. I have also tried just doing ({req}) => {..req}.
No matter what I am trying, I am not getting acces to the userId in the apollo context. I've made a simple gql query resolver in Apollo to just log out the value, and it's always undefined.
Furthermore, I have a simple REST route (lines 43-49) that seem to attach the userId to every request just fine, so something isn't connecting, but I'm not sure where.
At the end of the day my goal is to receive a JWT token from a cookie sent client side, verify it, and add it the context so I can access the values in my graphQL resolvers. Any thoughts?
index.js
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const bodyParser = require('body-parser');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const db = require('./db');
const User = require('./models/User');
const typeDefs = require('./gqlSchema');
const queries = require('./resolvers/queries');
const mutations = require('./resolvers/mutations');
const resolvers = {
Mutation: mutations,
Query: queries,
}
const app = express();
const corsOptions = {
origin: process.env.FRONTEND_URL,
credentials: true,
};
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use((req, res, next) => {
const { token } = req.cookies;
if (token) {
const tokenData = jwt.verify(token, process.env.SECRET);
req._id = tokenData._id;
}
next();
})
//// TEMP REST LOGIN ////
const login = require('./controllers/login');
app.post('/auth/google', login);
app.post('/token', (req, res) => {
console.log('/token middlware req: ', req._id);
res.send('test token route')
})
///////////////////
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({
...req,
userId: req._id,
})
});
server.applyMiddleware({
app,
path: '/graphql',
cors: false,
});
app.listen({ port: 4000 }, () => {
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
})
I'm new to NUXT and SSR and I've been researching this for a few hours now and I can't seem to figure it out. I'm using JWT to authenticate users in my Nuxt app with a Bearer Token, which is working great until I hit refresh and lose my session.
Now I'm looking to persist sessions using express-session and connect-mongo. I can't get the cookie to set on the client to be included on future requests.
When a user is authenticated:
router.post('/login', function(req, res) {
User.findOne({
username: req.body.username
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(401).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
// check if password matches
user.comparePassword(req.body.password, function (err, isMatch) {
if (isMatch && !err) {
// if user is found and password is right create a token
var token = jwt.sign(user.toJSON(), config.secret, { expiresIn: 604800 });
req.session.authUser = { 'user': 'Test User' }
return res.json({success: true, token: token, user: user});
} else {
res.status(401).send({success: false, msg: 'Authentication failed. Wrong password.'});
}
});
}
The console.log above shows the authUser in the session.
Session {
cookie:
{ path: '/',
_expires: 2018-04-03T18:13:53.209Z,
originalMaxAge: 60000,
httpOnly: true },
authUser: { user: 'Test User' } }
When I look at my chrome devtools application cookies a connect.ssid hasn't been set and when I console.log(req.session) on future requests the authUser is missing.
My server code is:
// Passport
var passport = require('passport');
var passportJWT = require("passport-jwt");
var ExtractJwt = passportJWT.ExtractJwt;
var JwtStrategy = passportJWT.Strategy;
// Config File
let config = require('./config/settings.js')
// Initialize Express
var app = express();
// CORS-ENABLE
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:1337");
res.header("Access-Control-Allow-Credentials", 'true');
next();
});
app.use(cors())
const dbPath = 'mongodb://blogUser:blogUserPassword#localhost:27017/blog'
// Express Session
app.use(session({
secret: 'super-secret-key',
resave: false,
saveUninitialized: false,
store: new MongoStore({ url: dbPath }),
cookie: { maxAge: 60000 }
}))
// File Upload
app.use(fileUpload());
// view engine setup
// app.set('views', path.join(__dirname, 'views'));
// app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Routes
var index = require('./routes/index');
var users = require('./routes/users');
app.use('/api', index);
app.use('/users', users);
// Passport Config
app.use(passport.initialize());
app.use(passport.session())
// mongoose
const options = {
autoIndex: true, // Don't build indexes
reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
reconnectInterval: 500, // Reconnect every 500ms
poolSize: 10, // Maintain up to 10 socket connections
// If not connected, return errors immediately rather than waiting for reconnect
bufferMaxEntries: 0
};
console.log(options);
// Localhost Connect
mongoose.connect(dbPath, options).then(
() => { console.log("connected !!!"); },
err => { console.log(err); }
);
Any and all help is appreciated.
If you want to use the server you create the problem with the session is the express router, because change res and req vars so like recommend in nuxt use this.
const express = require('express');
// Create express router
const router = express.Router()
// Transform req & res to have the same API as express
const app = express()
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request)
Object.setPrototypeOf(res, app.response)
req.res = res
res.req = req
next()
})
You are missing this step
// Create express router
const router = express.Router()
// Transform req & res to have the same API as express
// So we can use res.status() & res.json()
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request)
Object.setPrototypeOf(res, app.response)
req.res = res
res.req = req
next()
})
The req, res parameters need to be interchanged on the client side
Once you do router.post('/login') and logout
app.use('/api', router)
And that will work perfectly
I'm trying to build a small nodejs, express, mongodb app with authentication using passport-local and passport-local-mongoose, however for some reason when entering the middleware for passport in the main app.js file I get this error "TypeError: User.authenticate is not a function"
I tried the authentication by itself in a test project and it worked just fine, so maybe it's something to do with the order of my code but I can't get to what's causing this issue. I've tried several refactors but to no avail.
Below is the code for the app.js and the User Schema , but Here's a link to the project files if needed.
app.js
const express = require('express'),
app = express(),
path = require('path'),
exphbs = require('express-handlebars'),
methodOverride = require('method-override'),
flash = require('connect-flash'),
session = require('express-session'),
bodyParser = require('body-parser'),
mongoose = require('mongoose'),
LocalStrategy = require("passport-local"),
passport = require("passport"),
mongodb = require("mongodb"),
User = require("./models/User");
// Load routes
const ideas = require('./routes/ideas');
const users = require('./routes/users');
// Connect to mongoose
mongoose.connect('mongodb://localhost/diaries');
mongoose.Promise = Promise;
// Handlebars Middleware
app.engine('handlebars', exphbs({
defaultLayout: 'main'
}));
app.set('view engine', 'handlebars');
// Body parser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Static folder
app.use(express.static(path.join(__dirname, 'public')));
// Method override middleware
app.use(methodOverride('_method'));
// Express session midleware
//PASSPORT CONFIGURATION
app.use(require("express-session")({
secret: "secret",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
app.use(flash());
// Global variables
app.use(function(req, res, next) {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
next();
});
// Index Route
app.get('/', (req, res) => {
const title = 'Welcome';
res.render('index', {
title: title
});
});
// About Route
app.get('/about', (req, res) => {
res.render('about');
});
// Use routes
app.use('/ideas', ideas);
app.use('/users', users);
const port = process.env.PORT;
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
User Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const passportLocalMongoose = require("passport-local-mongoose");
// Create Schema
const UserSchema = new Schema({
name:{
type: String,
required: true
},
email:{
type: String,
required: true
},
password:{
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
UserSchema.plugin(passportLocalMongoose, {usernameField: "email"});
mongoose.model('users', UserSchema);
try importing LocalStrategy from require('passport-local').Strategy
like
const LocalStrategy = require('passport-local').Strategy