using passportjs passport.authenticate() in Sapper route or sapper middleware - express

I used passportjs in the past with expressjs and currently I'm trying to incorporate it with Sapper app but I'm unable to figure out how to inlcude the passport.authenticate() in my route because it's a sapper route not an express route. Also if I try to run everything in my server.js file I run into the issue of how to integrate it with the sapper middleware.
How do you use passport.authenticate() in/with Sapper middleware or sapper routes js files (which is the front not server routes)?
My server.js is typical:
const sirv = require('sirv');
import express from 'express';
var cookieParser = require('cookie-parser');
import * as sapper from '#sapper/server';
const session = require('express-session');
var passport = require('passport');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/passport', {
useNewUrlParser: true });
const MongoStore = require('connect-mongo')(session);
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
store: new MongoStore({ url: 'mongodb://localhost/passport' }),
cookie: { secure: false, maxAge: 1000 * 60 * 60 * 24 * 7 }
}));
app.use(passport.initialize());
app.use(passport.session());
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
const assets = sirv('static', {
maxAge: 31536000, // 1Y
immutable: true
});
app.use(assets, sapper.middleware({
session: req => ({
user: req.session && req.session.user
})})).listen(process.env.PORT, err => { if (err) console.log('error', err); });
As you can see, Sapper is just a middleware so if I want to authenticate a user and send it to the front/sapper, I need to figure out how to run passport.authenticate() inside the middleware function, right?
If I want to use passport in the route JS file which is sapper front route:
//How to import passport.js here to make passport.authenticate() middleware available?
import passport from './passport';
import User from './mongoso';
export async function post(req, res, next) {
res.setHeader('Content-Type', 'application/json');
/* Retrieve the data */
var data = req.body;
req.session.user = data.email;
console.log("Here's the posted data:", data);
console.log("information in the session is:", req.session);
/* Returns the result */
return res.end(JSON.stringify({ Email: req.session.user }));
//return res.json({ data: data });
}
Any ideas? Greatly appreciated if someone out there could help.

You don't need to run passport.authenticate() inside the sapper.middleware. You need to add passport-local strategy firstly, then do serializeUser and deserializeUser, then create routes to do passport.authenticate and
after that catch req.session.passport object in sapper.middleware. I don't use passport-local strategy, but here is my working server.js with passport-github strategy.
//server.js
import sirv from 'sirv';
import express from 'express';
import passport from 'passport';
import { Strategy } from 'passport-github';
import bodyParser from 'body-parser';
import session from 'express-session';
import sessionFileStore from 'session-file-store';
import compression from 'compression';
import * as sapper from '#sapper/server';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
const FileStore = sessionFileStore(session);
passport.use(new Strategy({
clientID: 'someClientID',
clientSecret: 'someClientSecret',
callbackURL: 'http://localhost:3000/auth/callback',
}, (accessToken, refreshToken, profile, cb) => {
// console.log('success');
return cb(null, profile);
}));
passport.serializeUser(function (user, cb) {
cb(null, user);
});
passport.deserializeUser(function (obj, cb) {
cb(null, obj);
});
const expressServer = express()
.use(passport.initialize())
.use(bodyParser.json())
.use(session({
secret: 'conduit',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 31536000
},
store: new FileStore({
path: `.sessions`
})
}))
.get('/auth/login',
passport.authenticate('github'))
.get('/auth/callback',
passport.authenticate('github', { failureRedirect: '/auth/login' }),
(req, res) => {
res.redirect('/');
//console.log(req.user.username);
})
.get('/auth/logout', (req, res) => {
req.logout();
req.session.destroy( function (err) {
res.redirect('/');
});
})
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware({
session: req => {
const user = req.session.passport ? req.session.passport.user.username : null;
// console.log(req.session.passport.user.username);
return { user };
}
})
)
if (dev) {
expressServer.listen(PORT, err => {
if (err) console.log('error', err);
});
}
export { expressServer }
Аfter this, you can catch that this { user } object in your client
sapper route component through Stores using const { session } = stores(); console.log($session) or you can get it via special preload function to apply before page is rendered, like this for example in index.svelte
<script context="module">
export function preload(page, { user }) {
return { user };
}
</script>
<script>
import { stores } from "#sapper/app";
import { onMount } from "svelte";
const { session } = stores();
export let user;
onMount(() => {
console.log($session);
});
</script>
<div>
{#if !user}
<p>Not logged in</p>
{:else}
<p>Logged in!</p>
{/if}
</div>
Here i use two approaches same time, but most of time it will be enough to
use preload, no need to direct access to session in stores.
Hope this will help you. Good luck!

I used the answer from DioXine to implement Google Auth.
The cookie is now also http only.
import sirv from "sirv";
import express from "express";
import bodyParser from "body-parser";
import session from "express-session";
import sessionFileStore from "session-file-store";
import compression from "compression";
import * as sapper from "#sapper/server";
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === "development";
passport.use(
new GoogleStrategy(
{
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/callback",
},
function (accessToken, refreshToken, profile, cb) {
// User.findOrCreate({ googleId: profile.id }, function (err, user) {
// return cb(err, user);
// });
return cb(null, profile);
}
)
);
passport.serializeUser(function (user, cb) {
cb(null, user);
});
passport.deserializeUser(function (obj, cb) {
cb(null, obj);
});
const FileStore = sessionFileStore(session);
const sessionConfig = {
secret: "sefmvks4Fgblolf4sdJHBd",
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
maxAge: 31536000,
},
//TODO: redis
store: new FileStore({
path: `.sessions`,
}),
};
express()
.use(passport.initialize())
.use(bodyParser.json())
.use(session(sessionConfig))
.get("/auth/google", passport.authenticate("google", { scope: ["profile"] }))
.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/auth/login" }),
(req, res) => {
res.redirect("/");
}
)
.get("/auth/logout", (req, res) => {
req.logout();
req.session.destroy(function (err) {
res.redirect("/");
});
})
.use(
compression({ threshold: 0 }),
sirv("static", { dev }),
sapper.middleware({
session: (req) => {
const user = req.session.passport ? req.session.passport.user.id : null;
return { user };
},
})
)
.listen(PORT, (err) => {
if (err) console.log("error", err);
});
This is not changed:
<script context="module">
export function preload(page, { user }) {
return { user };
}
</script>
<script>
import { stores } from "#sapper/app";
import { onMount } from "svelte";
const { session } = stores();
export let user;
onMount(() => {
console.log($session);
});
</script>
<div>
{#if !user}
<p>Not logged in</p>
{:else}
<p>Logged in!</p>
{/if}
</div>
If it only works after refresh check this:
https://github.com/sveltejs/sapper/issues/567#issuecomment-542788270

Related

Why can I not set/see the cookie in my browser dev tools?

I am following Ben Awads Reddit Clone Tutorial and have a problem with setting a cookie.
I have an express app and I am trying to set a cookie if the user provides a correct username and password to my GraphQL mutation. When I use Apollo GraphQLs Sandbox to send a login request. I can see in the Network Tab, that there is a Set-Cookie Header with the correct data. But when I send a request to see if there is another query and check for req.session.userId it is undefined.
Here is my index.ts file:
import 'reflect-metadata';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { PrismaClient } from '#prisma/client';
import { buildSchema } from 'type-graphql';
import { PostResolver } from './resolvers/post';
import { UserResolver } from './resolvers/user';
import { __prod__ } from './constants';
import { MyContext } from './types';
import session from 'express-session';
import connectRedis from 'connect-redis';
import { createClient } from 'redis';
const prisma = new PrismaClient();
const main = async () => {
const app = express();
const RedisStore = connectRedis(session);
const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
app.use(
session({
name: 'qid',
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: 'lax', // lax für csrf
secure: __prod__, // cookie only works in https
},
secret: 'keyboard cat',
store: new RedisStore({ client: redisClient }),
saveUninitialized: false,
resave: false,
})
);
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [PostResolver, UserResolver],
validate: false,
}),
context: ({ req, res }): MyContext => ({ prisma, req, res }),
});
await apolloServer.start();
apolloServer.applyMiddleware({ app });
app.listen(4000, () => {
console.log('Server is running on http://localhost:4000');
});
};
And here is my users resolver:
#Resolver()
export class UserResolver {
// create a me query
#Query(() => UserType, { nullable: true })
async me(#Ctx() { prisma, req }: MyContext) {
// you are not logged in
if (!req.session.userId) {
return null;
}
const user = await prisma.user.findUnique({
where: {
id: req.session.userId,
},
});
return user;
}
#Mutation(() => UserResponse)
async login(
#Arg('options') options: UsernamePasswordInput,
#Ctx() { prisma, req }: MyContext
): Promise<UserResponse> {
const user = await prisma.user.findUnique({
where: {
username: options.username,
},
});
if (!user) {
return {
errors: [
{
field: 'username',
message: 'That username does not exist',
},
],
};
}
if (!(await argon2.verify(user.password, options.password))) {
return {
errors: [
{
field: 'password',
message: 'Incorrect password',
},
],
};
}
req.session.userId = user.id;
return {
user,
};
}
}
I tried using different versions of the dependencies "connect-redis", "redis" and "express-session"

Heroku to netlify session wont store session values

This works fine when I am running it on Localhost3000(client) and localhost:3005(server). However once I publish my app to Heroku(server) and netlify(client) it for some reason tells me the req.session.steamuser when accessing /user is null even after it has been set in /api/auth/steam/return and I have tested that the req.session.steamuser=req.user accutally work.
Server.js
var express = require('express');
var passport = require('passport');
var session = require('express-session');
var passportSteam = require('passport-steam');
const cors = require("cors");
var SteamStrategy = passportSteam.Strategy;
var app = express();
const corsOptions = {
origin: ["https://stunning-bavarois-0eef55.netlify.app"],
credentials: true, //access-control-allow-credentials:true
methods: ["GET", "POST"],
};
app.use(cors(corsOptions));
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
passport.use(new SteamStrategy({
returnURL: 'https://temtestt.herokuapp.com/api/auth/steam/return',
realm: 'https://temtestt.herokuapp.com/',
apiKey: 'MY SECRET API KEY'
}, function (identifier, profile, done) {
process.nextTick(function () {
profile.identifier = identifier;
return done(null, profile);
});
}
));
app.use(session({
secret: 'db5910cc8b9dcec166fda1d2c34860b6f8cd932cea641ea39924ed18fe6fc863',
resave: true,
saveUninitialized: true,
cookie: {
SameSite:"none",
maxAge: 3600000,
secure:true
}
}))
// Initiate Strategy
app.use(passport.initialize());
app.use(passport.session());
app.get('/', (req, res) => {
res.status(200);
res.send("Welcome to root URL of Server");
});
app.get("/user", (req, res) => {
if (req.session.steamuser) {
res.status(200).send(req.session.steamuser)
}
else {
res.send(false)
}
})
app.get('/api/auth/steam', passport.authenticate('steam', { failureRedirect: '/' }), function (req, res) {
res.redirect('/')
});
app.get('/api/auth/steam/return', passport.authenticate('steam', { failureRedirect: '/' }), function (req, res) {
req.session.steamuser = req.user;
res.redirect('https://stunning-bavarois-0eef55.netlify.app/')
});
app.listen(process.env.PORT || 3005);
Client
import { useEffect, useState } from 'react';
import './App.css';
import axios from 'axios';
function App() {
const [user,setUser]=useState(null);
useEffect(()=>{
async function getUser(){
const data = await axios.get("https://temtestt.herokuapp.com/user",{withCredentials:true});
setUser(data.data);
}
getUser();
},[]);
return (
<div className="App">
<h1>Hello</h1>
{(user===false||user===null)?<><p>Please log in</p>Login</>:<p>{user.displayName}</p>}
</div>
);
}
export default App;
As mentioned already it works fine when I do with localhost and returns correct values. But when I try with netlify and heroku it almost seems like it doesn't recognize the session key or something.

req.user undefined (Vuex + Express + Passport Local Mongoose)

So i am using passport local mongoose for authentication purposes and vuejs for frontend.what i had to do always is store the user info in localstorage and then access that user in express by req.body.user whie what i want to achieve is req.user. Can someone help me what should i include so that req.user will no longer be undefined.. I am new to vue.
this is configuration from app.js
const app = express();
app.use(morgan("combined"));
//app.use(cors(corsOption));
const allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', '*');
res.header('Access-Control-Allow-Headers', '*');
next();
}
app.use(allowCrossDomain)
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser('form based'));
app.use(require('express-session')({
secret: 'form based',
resave: false,
saveUninitialized: false,
}))
// app.use(
// cookieSession({
// maxAge: 24 * 60 * 60 * 1000,
// keys: ["qwefgfds"]
// })
// );
app.use(passport.initialize())
app.use(passport.session())
passport.use('local',new passportLocal(Client.authenticate()))
passport.serializeUser(Client.serializeUser())
passport.deserializeUser(Client.deserializeUser())
This is what i am doing when logging in..
methods: {
async login() {
const response = await AuthenticationService.login({
username: this.username,
password: this.password
});
console.log("logininfo", response.data);
this.res = response.data.msg;
if (response.data.status == 200) {
let userID = response.data.userID;
//console.log(userID, response.data.userID);
localStorage.setItem("user", JSON.stringify(response.data.user));
console.log(
"from localstorage",
JSON.parse(localStorage.getItem("user"))
);
this.$store.dispatch("setUser", response.data.user);
router.push({ name: "HelloWorld", query: { userID: userID } });
}
}
}
This is the store.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import createPersistedState from "vuex-persistedstate";
Vue.use(Vuex)
export default new Vuex.Store({
strict: true,
plugins: [createPersistedState({
storage: window.sessionStorage,
})],
state: {
user: JSON.parse(localStorage.getItem('user')) || null,
isUserLoggedIn: false
},
mutations: {
setUser (state, user) {
state.user = user
if (user) {
//localStorage.setItem('user',user);
state.isUserLoggedIn = true
} else {
state.isUserLoggedIn = false
}
}
},
actions: {
setUser ({commit}, user) {
console.log('commited',user);
axios.defaults.headers.common['Authorization'] = user;
commit('setUser', user);
}
}
})
I am trying to achieve this from so long, i hope someone helps. Thanks a lot in advance.

passport.js + express + apollo-server, req.user is undefined

I tried to record sessions in the database through sequelize, to make it serverless, but req.user is always undefined, I tried every manual which I found on the internet, I do not understand why it not working.
I tried passport.js manual, express manuals, github gists.
Records in the database are successfully created, on successful authentication, but when I try hit /graphql endpoint, it does not fill req.user with user.
req.user should be restored based on session hash which is stored in database.
#!/usr/bin/env node
import express from 'express';
import session from 'express-session';
import { ApolloServer } from 'apollo-server-express';
import { typeDefs, resolvers } from './graphql';
import orm from './orm';
import compose from './dataloader/status.dataloader';
import passport from 'passport';
import { Strategy as GitHubStrategy } from 'passport-github';
import cors from 'cors';
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const user = req.user;
console.log({ user });
return {
user,
orm,
dataloader: compose(orm),
};
},
});
passport.use(
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: `/auth/github/callback`,
},
async (accessToken, refreshToken, profile, done) => {
const { provider, id: externalId, profileUrl, username, displayName, photos } = profile;
const photo = photos && photos[0] && photos[0].value;
const user = await orm.User.findOne({
include: [
{
attributes: [],
model: orm.UserProvider,
where: {
provider,
externalId,
},
},
],
raw: true,
}).then(async (v) => {
if (null !== v) {
return v;
}
v = await orm.User.create({
displayName,
photo,
});
await orm.UserProvider.create({
provider,
internalId: v.id,
externalId,
username,
profileUrl,
});
return v;
})
const session = await orm.UserSession.create({
internalId: user.id,
hash: accessToken,
});
return done(null, session);
}
)
);
passport.serializeUser(({ hash }, done) => {
console.log({ hash });
done(null, hash);
});
passport.deserializeUser(async (hash, done) => {
console.log({ hash });
const user = await orm.User.findOne({
include: [
{
attributes: [],
model: orm.UserSession,
where: {
hash,
},
},
],
raw: true,
});
done(null, user);
});
app.use(
cors({
origin: "*",
methods: "GET,POST",
preflightContinue: false,
optionsSuccessStatus: 204,
credentials: true,
})
);
app.use(session({ secret: 'test' }));
app.use(passport.initialize());
app.use(passport.session());
app.get(
'/auth/github',
passport.authenticate('github', { session: true })
);
app.get(
'/auth/github/callback',
passport.authenticate('github', { session: true }),
(req, res) => res.redirect('/')
);
app.use('/graphql', passport.authenticate('session', { session: true }));
// (req, res, next) => {
// debugger;
// // passport.
// console.log({
// req,
// session: JSON.stringify(req.session, ',', 4),
// cookie: JSON.stringify(req.cookie),
// user: req.user,
// });
// return next();
// });
server.applyMiddleware({ app, path: '/graphql' });
app
.listen(process.env.PORT, () => {
console.log(`GraphQL ready on: http://localhost:${process.env.PORT}/graphql`);
});

CloudFlare set-cookie not passing through to browser

I have an express application behind CloudFlare. In development, the Set-Cookie header gets passed on login, but behind CloudFlare, the Set-Cookie header is not passed. Any suggestions? My config is:
```
import passport from 'passport';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import { Strategy as LocalStrategy } from 'passport-local';
import localPassport from '../../db/sequelize/passport';
const secret = 'foo';
const authenticationMiddleware = (req, res, next) => {
console.log('authenticated', req.isAuthenticated());
if (req.isAuthenticated()) {
next();
} else if (req.url.includes('/rest/')) {
res.status(401).send('Unauthorized');
} else {
res.status(302).redirect('/admin/login');
}
};
const sessionSecurity = (app) => {
app.set('trust proxy', 1);
app.use(cookieParser(secret));
app.use(
session({
secret,
proxy: true,
saveUninitialized: false,
resave: false,
maxAge: null,
cookie: {
path: '/admin',
secure: process.env.NODE_ENV === 'production',
},
}),
); // session secret
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(localPassport.local));
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(localPassport.deserializeUser);
app.post('/admin/login', (req, res, next) => {
passport.authenticate('local', (authErr, user) => {
if (authErr) return next(new Error(authErr));
if (!user) {
return res.sendStatus(401);
}
return req.logIn(user, (loginErr) => {
if (loginErr) return res.sendStatus(401);
return res.sendStatus(200);
});
})(req, res, next);
});
app.post('/admin/logout', (req, res) => {
req.logOut();
req.session.destroy(() => {
res
.clearCookie('connect.sid', { path: '/admin' })
.sendStatus(200);
});
});
app.get('/admin/rest/*', authenticationMiddleware);
app.get('/admin/rest/status', (req, res) => {
res.sendStatus(200);
});
};
```
This was caused because the server settings where set to cookie secure and the connection between CloudFlare and my server were not.
In setting of cloudflare (crypto tab), I turned on "Authenticated Origin Pulls" and in "app.js" (from my app) add this app.set('trust proxy', 1);
before app.use(session({...}) and my problem solved.