skip to Main Content

I’m following along with Ben Awad’s 13-hour Fullstack React GraphQL TypeScript Tutorial and encountered a wall during the login cookie setting (aprx at 1:50:00).

I think I successfully connected to redis, set express-session and set req type but in graphql sandbox I don’t see my cookie (named ‘qid’) at Inspect->Application.

index.ts

import { MikroORM } from "@mikro-orm/core";
import { __prod__ } from "./constants";
import microConfig from "./mikro-orm.config";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { buildSchema } from "type-graphql";
import { HelloResolver } from "./resolvers/hello";
import { PostResolver } from "./resolvers/post";
import { UserResolver } from "./resolvers/user";
import redis from "redis";
import session from "express-session";
import connectRedis from "connect-redis";

const main = async () => {
  const orm = await MikroORM.init(microConfig);
  await orm.getMigrator().up();

  const app = express();

  const RedisStore = connectRedis(session);
  const redisClient = redis.createClient();

  app.use(
    session({
      name: "qid",
      store: new RedisStore({
        client: redisClient,
        disableTouch: true,
      }),
      cookie: {
        maxAge: 1000 * 60 * 60 * 24 * 365 * 10,
        httpOnly: true,
        sameSite: "none",
        // secure: __prod__,
      },
      saveUninitialized: false,
      secret: "dfhfdjkgfkbjktzkzf",
      resave: false,
    })
  );

  app.use(function (req, res, next) {
    res.header(
      "Access-Control-Allow-Origin",
      "https://studio.apollographql.com"
    );
    res.header("Access-Control-Allow-Credentials", "true");
    next();
  });

  const apolloServer = new ApolloServer({
    schema: await buildSchema({
      resolvers: [HelloResolver, PostResolver, UserResolver],
      validate: false,
    }),
    context: ({ req, res }) => ({ em: orm.em, req, res }),
  });

  await apolloServer.start();
  apolloServer.applyMiddleware({
    app,
    cors: {
      credentials: true,
      origin: new RegExp("/*/"),
    },
  });

  app.listen(4000, () => {
    console.log("server started on port 4000");
  });
};

main();

types.ts

import { EntityManager, IDatabaseDriver, Connection } from "@mikro-orm/core";
import { Request, Response } from "express";
import { Session, SessionData } from "express-session";

export type MyContext = {
  em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
  req: Request & {
    session: Session & Partial<SessionData> & { userId: number };
  };
  res: Response;
};

and my userResolver (user.ts)

import { User } from "../entities/User";
import { MyContext } from "../types";
import {
  Arg,
  Ctx,
  Field,
  InputType,
  Mutation,
  ObjectType,
  Query,
  Resolver,
} from "type-graphql";
import argon2 from "argon2";

@InputType()
class UsernamePasswordInput {
  @Field()
  username: string;

  @Field()
  password: string;
}

@ObjectType()
class FieldError {
  @Field()
  field: string;

  @Field()
  message: string;
}

@ObjectType()
class UserResponse {
  @Field(() => [FieldError], { nullable: true })
  errors?: FieldError[];

  @Field(() => User, { nullable: true })
  user?: User;
}

@Resolver()
export class UserResolver {


  @Mutation(() => UserResponse)
  async login(
    @Arg("options", () => UsernamePasswordInput) options: UsernamePasswordInput,
    @Ctx() { em, req }: MyContext
  ): Promise<UserResponse> {
    const user = await em.findOne(User, { username: options.username });
    if (!user) {
      return {
        errors: [
          {
            field: "username",
            message: "username does not exist",
          },
        ],
      };
    }
    const valid = await argon2.verify(user.password, options.password);
    if (!valid) {
      return {
        errors: [
          {
            field: "password",
            message: "incorrect password",
          },
        ],
      };
    }

    req.session.userId = user.id;

    return {
      user,
    };
  }
}

I tried setting up res.headers as graphql sandbox is asking but still to no avail. Would appreciate any help, thank you!

3

Answers


  1. Chosen as BEST ANSWER

    Okay I'm not sure what is happening, but I seemingly solved the issue.

    My idea is that: GraphQL Playground is retired and localhost:port/graphql now redirects to Apollo GraphQL Sandbox to a different url and my guess is that the cookies do not get transfered to this location but the cookie is set at localhost.

    So there is a way you can force Apollo to still use the Playground by adding:

    import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
    
    
      const apolloServer = new ApolloServer({
        ...,
        plugins: [
          ApolloServerPluginLandingPageGraphQLPlayground({
            // options
          }),
        ],
      });
    

    And this way Playground shows up and you can set

      "request.credentials": "include",
    

    in the settings and voila the cookie shows up at localhost:port.

    I hope this helps anyone with this issue - however I'm still not exactly sure that this is a right solution.


  2. Adding the old playground as a plugin probably works but, since they say it is being deprecated, if you want to make it work with the new Apollo Studio, here is how I managed to do it:

    I added these three lines right after initializing the app:

      app.set("trust proxy", !process.env.NODE_ENV === "production");
      app.set("Access-Control-Allow-Origin", "https://studio.apollographql.com");
      app.set("Access-Control-Allow-Credentials", true);
    

    Here is how the configuration of my session looks like:

    const RedisStore = connectRedis(session);
    const redisClient = redis.createClient();
    
    app.use(
      session({
        saveUninitialized: false,
        store: new RedisStore({ client: redisClient }),
        cookie: {
          maxAge: 1000 * 60 * 60 * 24 * 365 * 1, // 1 year
          httpOnly: true,
          sameSite: "none",
          secure: true, // if true, studio works, postman doesn't; if false its the other way around
        },
        name: "qid",
        secret: "keyboard cat",
        resave: false,
      }),
    );
    

    Then, over to Apollo Studio, go to Connection Settings -> Edit -> Include Cookies (this one was really hard to find):

    enter image description here

    Make sure to send this header with every request to login: x-forwarded-proto: https

    Login or Signup to reply.
  3. spent some time on this one. Try this combined solution:

    import { MikroORM } from "@mikro-orm/core";
    import { __prod__ } from "./constants";
    import microConfig from "./mikro-orm.config";
    import express from "express";
    import { ApolloServer } from "apollo-server-express";
    import { buildSchema } from "type-graphql";
    import { PostResolver } from "./resolvers/Post";
    import { UserResolver } from "./resolvers/User";
    import session from "express-session";
    import connectRedis from "connect-redis";
    import { createClient } from "redis";
    
    const main = async () => {
      try {
        const orm = await MikroORM.init(microConfig);
        orm.getMigrator().up();
        const app = express();
        app.set("trust proxy", process.env.NODE_ENV !== "production"); //a little fix here from another users codes--- actually think this made it works
        app.set("Access-Control-Allow-Origin", "https://studio.apollographql.com");
        app.set("Access-Control-Allow-Credentials", true);
        let redisClient = createClient({ legacyMode: true });
        redisClient.connect().catch(console.error);
        let RedisStore = connectRedis(session);
        const cors = {
          credentials: true,
          origin: "https://studio.apollographql.com",
        };
        app.use(
          session({
            name: "qid",
            store: new RedisStore({ client: redisClient as any, disableTTL: true }),
            cookie: {
              maxAge: 1000 * 60 * 60 * 24 * 365 * 10,
              httpOnly: true,
              secure: true,
              sameSite: "none",
            },
            saveUninitialized: false,
            secret: "keyboardcaxt",
            resave: false,
          })
        );
        const apolloServer = new ApolloServer({
          schema: await buildSchema({
            resolvers: [PostResolver, UserResolver],
            validate: false,
          }),
          context: ({ req, res }) => ({ em: orm.em, req, res }),
        });
    
        await apolloServer.start();
        apolloServer.applyMiddleware({ app, cors });
        app.listen(4000, () => {
          console.log("EXPRESS SERVER IS RUNNINGG");
        });
      } catch (error) {
        console.log(error, "ERRR");
      }
    };
    
    main();
    

    Also dont forget to this
    setup and hard-reset your https://studio.apollographql.com/sandbox/.
    And this: add ENV to your root

    Then you should be ready to go.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search