1

I am trying to build google user authentication using passport strategy and express session. To authorize a user, I am trying to pass his data using the context. Unfortunately, when I want to use a context in resolver, req.session.passport and req.user disappear for unknown reasons. Did I do something wrong?

Apollo Server v4.

server.ts

import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer";
import { expressMiddleware } from "@apollo/server/express4";
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";
import { ApolloServer } from "@apollo/server";
import express from "express";
import http from "http";
import cors from "cors";
import "dotenv/config";
import mongoose from "mongoose";
import { schema, setHttpPlugin } from "./serverSettings/config";
import { json } from "body-parser";
import cookieParser from "cookie-parser";
import passport from "passport";
import authRoutes from "./routes/auth";
import "./services/passport";
import { expressSession } from "./services/session";

//config variables
const port = process.env.PORT;
const host = process.env.HOST;
const dbUri = process.env.DB_URI;

//connect to DB
mongoose
  .connect(dbUri)
  .then(() => {
    console.log("DB connected!");
    startApolloServer();
  })
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

const startApolloServer = async () => {
  const app = express();

  const httpServer = http.createServer(app);

  const wsServer = new WebSocketServer({
    server: httpServer,
    path: "/graphql",
  });

  const serverCleanup = useServer({ schema }, wsServer);

  const server = new ApolloServer({
    schema,
    plugins: [
      ApolloServerPluginDrainHttpServer({ httpServer }),
      setHttpPlugin,
      {
        async serverWillStart() {
          return {
            async drainServer() {
              await serverCleanup.dispose();
            },
          };
        },
      },
    ],
  });

  await server.start();

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

  const corsOptions = {
    origin: "https://studio.apollographql.com",
    credentials: true,
  };
  app.use(
    "/graphql",
    // isLoggedIn,
    cors<cors.CorsRequest>(corsOptions),
    json(),
    cookieParser(),
    expressMiddleware(server, {
      context: async ({ req }: any) => {
        // console.log("session --->", req.session);
        // console.log("user --->", req.user);
        return { req };
      },
    })
  );

  //Google Auth
  app.get("/", (req, res) => {
    res.send('<a href="/auth/google">Auth with Google</a>');
  });

  app.use("/auth", authRoutes);

  //check if user is auth
  // function isLoggedIn(req: Request, res: Response, next: NextFunction) {
  //   req.user ? next() : res.sendStatus(401);
  // }

  await new Promise<void>((resolve) => httpServer.listen({ port }, resolve));
  console.log(` Server ready at ${host}:${port}/graphql`);
};

passport.ts

import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import passport from "passport";
import "dotenv/config";
import Settings from "../models/Settings";

const googleClientId = process.env.GOOGLE_CLIENT_ID;
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
const callbackURL = process.env.GOOGLE_OAUTH_REDIRECT_URL;

passport.serializeUser(function (profile: any, done) {
  done(null, profile.id);
});

passport.deserializeUser(function (id: string, done) {
  done(null, id);
});

passport.use(
  new GoogleStrategy(
    {
      clientID: googleClientId,
      clientSecret: googleClientSecret,
      callbackURL,
      passReqToCallback: true,
    },
    async (request, accessToken, refreshToken, profile, done) => {
      await Settings.collection.drop();
      await new Settings({ refreshToken }).save();
      done(null, profile);
    }
  )
);

session.ts

import session from "express-session";
import MongoStore from "connect-mongo";
import "dotenv/config";

const dbUri = process.env.DB_URI;
const sessionSecret = process.env.SESSION_SECRET;

export const expressSession = session({
  name: "mySession",
  secret: sessionSecret,
  store: MongoStore.create({
    mongoUrl: dbUri,
  }),
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24,
  },
});

enter image description here

I will be grateful for your help

Vytautas
  • 21
  • 3
  • Does this answer your question? [Why is my graphql Context returning an empty object](https://stackoverflow.com/questions/74185183/why-is-my-graphql-context-returning-an-empty-object) – Michel Floyd Oct 28 '22 at 22:56
  • @MichelFloyd Thank you for your response. I probably passed context in the right way, in expressMiddleware - I can see whole context in resolvers. In the example you sent, function getSubscriptionContext informs the graphql's resolvers about the current session and user. In my case I can see a session but without passport id and user - it disappears after passing it to the context. Take a look at my pic below the code. Anyway, thanks for trying to help! – Vytautas Oct 29 '22 at 08:36
  • I even tried to cheat it by adding a new "passportId" field to the session but it didn't work. Still undefined in resolver. expressMiddleware(server, { context: async ({ req }: any) => { req.session.passportId = req.session.passport.user; return { req }; }, }) – Vytautas Oct 29 '22 at 08:38
  • Where are you adding `user` and `session` to `req`? Also why are you not also returning `res` from your `context` function? – Michel Floyd Oct 29 '22 at 16:49
  • Passport.js does it for me - by adding user's id to req.session object. If I don't have access to user in session, I won't be able to write permissions for individual mutation or query. – Vytautas Oct 29 '22 at 20:00
  • Sounds like passport may not actually be adding the user to the request object. I also don't see where Passport is getting the request to be able to add something to it. – Michel Floyd Oct 29 '22 at 20:33

1 Answers1

0

For future generations - the problem was with a settings for sending credentials in Apollo's v4 playground. By default, this option is set to "omit" and interestingly it cannot be changed.

  1. The solution is to install the package and import in your server's config file:
import expressPlayground from "graphql-playground-middleware-express";
  1. Put that somewhere at the bottom of the code:
app.get("/playground", expressPlayground({ endpoint: "/graphql" }));
  1. Hit the /playground route and find settings button. Change credentials option:
"request.credentials": "include",
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
Vytautas
  • 21
  • 3