skip to Main Content

I have a question regarding cookies. Here I’m using apollo-server-express, express-session and redis for all of the authentication process. My problem with it is that in apollo studio my cookie, which is created inside the UserResolver under the mutation login, isn’t shown there. Hence why the query me returns null. Is also worth mentioning I’m not getting any errors while doing all of this.

For better understanding I’ll leave some screenshots below.

I hope someone can help me out here. Thanks in advance.

index.ts(server setup)

import {MikroORM} from "@mikro-orm/core";
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";
const { createClient } = require("redis")
import session from "express-session";
import connectRedis from "connect-redis";
import {__prod__} from "./constants";
import {MyContext} from "./types";

const main=async () => {
    const orm=await MikroORM.init(microConfig)
    await  orm.getMigrator().up()
    const app=express();
    const RedisStore = connectRedis(session);
    const redisClient = createClient({
        legacyMode: true
    });
    redisClient.connect().catch(console.error)
    app.set('trust proxy', !__prod__)
    app.use(
        session({
            name: "qid",
            store: new RedisStore({
                client: redisClient,
                disableTouch: true,
            }),
            cookie: {
                maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
                httpOnly: true,
                sameSite: "none", // csrf
                secure: __prod__, // cookie only works in https
            },
            saveUninitialized: false,
            secret: "qowiueojwojfalksdjoqiwueo",
            resave: false,
        })
    );
    const apolloServer =new ApolloServer({
        schema:await buildSchema({
            resolvers:[HelloResolver,PostResolver,UserResolver],
            validate:false
        }),
        context:({req,res}):MyContext => ({em:orm.em,req,res})
    })
    await apolloServer.start();
    const corsOptions={origin:["https://studio.apollographql.com", "http://localhost:4000"],credentials:true}
     apolloServer.applyMiddleware({app,cors:corsOptions})

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

User.ts(my resolver)

import {
    Resolver,
    Mutation,
    Arg,
    InputType,
    Field,
    Ctx,
    ObjectType,
} from "type-graphql";
import { MyContext } from "../types";
import { User } from "../entities/User";
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 register(
        @Arg("options") options: UsernamePasswordInput,
        @Ctx() { em }: MyContext
    ): Promise<UserResponse> {
        if (options.username.length <= 2) {
            return {
                errors: [
                    {
                        field: "username",
                        message: "length must be greater than 2",
                    },
                ],
            };
        }

        if (options.password.length <= 2) {
            return {
                errors: [
                    {
                        field: "password",
                        message: "length must be greater than 2",
                    },
                ],
            };
        }

        const hashedPassword = await argon2.hash(options.password);
        const user = em.create(User, {
            username: options.username,
            password: hashedPassword,
        });
        try {
            await em.persistAndFlush(user);
        } catch (err) {
            //|| err.detail.includes("already exists")) {
            // duplicate username error
            if (err.code === "23505") {
                return {
                    errors: [
                        {
                            field: "username",
                            message: "username already taken",
                        },
                    ],
                };
            }
        }
        return { user };
    }

    @Mutation(() => UserResponse)
    async login(
        @Arg("options") options: UsernamePasswordInput,
        @Ctx() { em,req }: MyContext
    ): Promise<UserResponse> {
        const user = await em.findOne(User, { username: options.username });
        if (!user) {
            return {
                errors: [
                    {
                        field: "username",
                        message: "that username doesn't 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,
        };
    }
}

3

Answers


  1. Chosen as BEST ANSWER

    I have changed the secure key to be true and it works

     cookie: {
                    maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
                    httpOnly: true,
                    sameSite: "none", // csrf
                    secure: true, // changed to true from __prod_ 
                },
    

  2. First of all, you have to add two lines in your index.ts

    app.set("Access-Control-Allow-Origin", "https://studio.apollographql.com");
    app.set("Access-Control-Allow-Credentials", true);
    

    And configure Apollo graphql studio-> go to Connection Settings -> Edit -> Include Cookies
    Make sure to add this header : x-forwarded-proto: https

    Login or Signup to reply.
  3. I looked into their docs and its recommended to use Apollo 4+
    Setup below will probably help you setup endpoint to http://localhost:4000/graphql instead of https://studio.apollographql.com/

      // Apollo Recommended Plugin
      let plugins: any = [];
      if (process.env.NODE_ENV === "production") {
        plugins = [
          ApolloServerPluginLandingPageProductionDefault({
            embed: true,
            graphRef: "myGraph@prod",
            includeCookies: true,
          }),
        ];
      } else {
        plugins = [
          ApolloServerPluginLandingPageLocalDefault({
            embed: true,
            includeCookies: true, // very important
          }),
        ];
      }
    

    Pass it to plugins in new ApolloServer({})

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