简体   繁体   English

为什么 Express Session 变量 (userID) 不在浏览器中创建 cookie?

[英]Why does Express Session variable (userID) not create a cookie in the browser?

I created a simple server with Express Session and Redis.我用 Express Session 和 Redis 创建了一个简单的服务器。 The Redis server is running (I received 'PONG' when I typed 'redis-cli ping'), and I declared the namespace and exported the interface SessionData to allow me to store userID on req.session (using a basic index.d.ts). Redis 服务器正在运行(当我输入“redis-cli ping”时收到“PONG”),我声明了命名空间并导出了接口 SessionData 以允许我将用户 ID 存储在 req.session 上(使用基本索引。 ts)。 However, when I make the login request, the variable userID is stored on req.session, but since the cookie is not set to the browser, it is immediately forgotten/erased.但是,当我发出登录请求时,变量 userID 存储在 req.session 上,但由于 cookie 未设置到浏览器,因此立即被遗忘/删除。 It seems like every request produces a new session and the cookie never saves.似乎每个请求都会产生一个新的 session 并且 cookie 永远不会保存。

App, Redis, and Session Cookie setup:应用程序、Redis 和 Session Cookie 设置:

// ...
const app = express();

const RedisStore = connectRedis(session);
const redis = new Redis();

app.use(
    session({
      name: 'testcookie',
      store: new RedisStore({
        client: redis,
        disableTouch: true,
        port: 6379,
        host: 'localhost',
      }),
      cookie: {
        maxAge: 36000000 * 24 * 365,
        httpOnly: true,
        sameSite: 'lax',
        secure: false,
      },
      saveUninitialized: false,
      secret: 'secret',
      resave: false,
    })
  );
// ...

Login mutation:登录突变:

@Mutation(() => UserResponse)
  async login(
    @Arg("usernameOrEmail") usernameOrEmail: string,
    @Arg("password") password: string,
    @Ctx() { req }: MyContext
  ): Promise<UserResponse> {
    // gets user via inputs (everything works here)
    // ...

    req.session.userID = user.id;
    // userID is set to be a number, as is user.id
    // logging req.session.userID works perfectly if done right here

    return { user };
  }

Query to check if logged in:查询是否已登录:

@Query(() => User, { nullable: true })
  async me(@Ctx() { req }: MyContext): Promise<User | undefined> {
    // logging req.session.userID shows undefined

    return (req.session.userID)
      ? await User.findOne({ id: req.session.userID })
      : undefined;
  }

UPDATE (SOLUTION): This was resolved by going into GraphQL's settings and changing the "request.credentials" property to "include."更新(解决方案):这已通过进入 GraphQL 的设置并将“request.credentials”属性更改为“包含”来解决。

I am following the same tutorial Fullstack React GraphQL TypeScript Tutorial in Jun 2022. Since 2021, Apollo's GraphQL Playground does not exist- in it's place there is Apollo studio sandbox ( https://studio.apollographql.com/sandbox/explorer ) I am following the same tutorial Fullstack React GraphQL TypeScript Tutorial in Jun 2022. Since 2021, Apollo's GraphQL Playground does not exist- in it's place there is Apollo studio sandbox ( https://studio.apollographql.com/sandbox/explorer )

There is no way I could find to set request.credentials to include in the new apollo studio.我无法将request.credentials设置为include在新的 apollo 工作室中。

After following these threads:关注这些主题后:

https://community.apollographql.com/t/allow-cookies-to-be-sent-alongside-request/920

and

https://github.com/apollographql/apollo-server/issues/5775

I came to a not-so-great solution, but that works for what I need.我找到了一个不太好的解决方案,但这可以满足我的需要。

Essentially, in my setup it seems like the session.cookie parameters 'sameSite' and 'secure' need to be different values depending on if you want your front end to add the cookie vs the apollo studio to add the cookie.本质上,在我的设置中,session.cookie 参数“sameSite”和“安全”似乎需要不同的值,具体取决于您是否希望前端添加 cookie 与 apollo 工作室添加 cookie。 This is NOT IDEAL - but I could not find any other combinations of parameters that worked for both.这不是理想的 - 但我找不到任何其他适用于两者的参数组合。 So far I've only found mutually exclusive settings.到目前为止,我只发现了互斥设置。

On my server's index.ts在我服务器的 index.ts 上

I use this session setup when I want to set cookie from front-end localhost:3000当我想从前端 localhost:3000 设置 cookie 时,我使用这个 session 设置


  app.use(
    session({
      name: COOKIE_NAME,
      store: new RedisStore({ client: redis, disableTouch: true }),
      saveUninitialized: false,
      cookie: {
        maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
        httpOnly: true,
        sameSite: "lax", // sets cookie from frontend localhost:3000
        secure: false, // sets cookie from frontend localhost:3000
      },
      secret: "shhhhdonttell",
      resave: false,
    })
  );



I actually change the session setup if I want to set the cookie and session userId from the apollo studio如果我想从阿波罗工作室设置 cookie 和 session userId,我实际上会更改 session 设置

I use this session setup when I want to set cookie from backend-end localhost:4000/graphql当我想从后端 localhost:4000/graphql 设置 cookie 时,我使用这个 session 设置


  app.use(
    session({
      name: COOKIE_NAME,
      store: new RedisStore({ client: redis, disableTouch: true }),
      saveUninitialized: false,
      cookie: {
        maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
        httpOnly: true,
        sameSite: "none", // sets cookie from apollo studio
        secure: true, // sets cookie from apollo studio
      },
      secret: "shhhhdonttell",
      resave: false,
    })
  );

Please comment if you know of a way to use the same settings to allow cookies from both front and backend.如果您知道如何使用相同的设置来允许来自前端和后端的 cookies,请发表评论。

For those of you who are following the tutorial and want more details , here are the other important parts of the setup.对于那些正在关注本教程并想要更多详细信息的人,这里是设置的其他重要部分。

This is my entire index.ts file from backend.这是我来自后端的整个 index.ts 文件。 Note - I am 8.5 hours into the tutorial - so don't worry if you don't recognize some parts.注意 - 我已经学习了 8.5 个小时的教程 - 所以如果您不认识某些部分,请不要担心。


import "reflect-metadata";
import { COOKIE_NAME, __prod__ } from "./constants";
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 ioredis from "ioredis";
import session from "express-session";
import connectRedis from "connect-redis";
import { MyContext } from "./types";
import cors from "cors";
import { getDataSource } from "./data-source";
import { Post } from "./entities/Post";

const PORT = 4000;

const main = async () => {
  const dbconn = await getDataSource()

  if (typeof dbconn === "boolean") return

  console.log('starting migrations')
  dbconn.runMigrations()
  // await Post.delete({})

  // const orm = await MikroORM.init(microConfig);
  // orm.getMigrator().up();
  console.log('migrations finished')

  const app = express();

  const RedisStore = connectRedis(session);
  const redis = new ioredis();
  // const redisClient = new redis({ legacyMode: true });
  redis.connect().catch((err) => `RedisClient Connect error: ${err}`);

  !__prod__ && app.set("trust proxy", 1);

  app.use(
    cors({
      origin: ["http://localhost:3000", "http://localhost:4000/graphql", "https://studio.apollographql.com",],
      credentials: true,
    })
  );

  app.use(
    session({
      name: COOKIE_NAME,
      store: new RedisStore({ client: redis, disableTouch: true }),
      saveUninitialized: false,
      cookie: {
        maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
        httpOnly: true,
        sameSite: "lax", 
        secure: __prod__,
      },
      secret: "shhhhdonttell",
      resave: false,
    })
  );

  // app.use(
  //   session({
  //     name: COOKIE_NAME,
  //     store: new RedisStore({ client: redis, disableTouch: true }),
  //     saveUninitialized: false,
  //     cookie: {
  //       maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
  //       httpOnly: true,
  //       // sameSite: "lax", // front end 
  //       // secure: __prod__, //true in production, is can be false for frontend
  //       sameSite: "none", //csrf //must be nne for apollo sandbox
  //       secure: true, //true in production, false on localhost // must be true for apollo sandbox even in dev
  //     },
  //     secret: "shhhhdonttell",
  //     resave: false,
  //   })
  // );

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

  await apolloServer.start();
  apolloServer.applyMiddleware({
    app,
    cors: false,
    // cors: {
    //   // origin: "http://localhost:3000",
    //   origin: [
    //     "http://localhost:3000",
    //     "https://studio.apollographql.com",
    //     "http://localhost:4000/graphql",
    //   ],
    //   credentials: true,
    // },
  });

  app.listen(PORT, () => {
    console.log(`server started on localhost:${PORT}`);
  });
};

main().catch((err) => {
  console.error(err);
});

Here is my data-source (the new way to make typeORM connection!)这是我的数据源(建立 typeORM 连接的新方法!)

import { Post } from "./entities/Post";
import { Users } from "./entities/Users";
import { DataSource } from "typeorm";
import path from "path"
import "reflect-metadata";
import { Upvote } from "./entities/Upvote";

export async function getDataSource() {

    const typeormDataSource = new DataSource({
        type: "postgres",
        host: "localhost",
        port: 5432,
        username: "nicole",
        password: "yourpassword",
        database: "bendb2", //your dbname
        logging: false,
        synchronize: true,
        entities: [Post, Users, Upvote],//take out entites you have't made yet.
        subscribers: [],
        migrations: [path.join(__dirname, "./migrations/*")],

    })
    let datasource = await typeormDataSource.initialize().catch(err => {
        console.error(err)
        console.log("Database connection NOT initialized")
        return false
    })
    return datasource
}


in the createUrqlClient.tsx front end file, I have added在 createUrqlClient.tsx 前端文件中,我添加了

export const createUrqlClient = (ssrExchange: any) => ({
  url: "http://localhost:4000/graphql",
  fetchOptions: {
    credentials: "include" as const, 
  },
  exchanges: [...]

Here is a snapshot of the settings needed in apollo studio.这是 apollo studio 中所需设置的快照。 To open these settings, click on the settings/gear icon at the top left inside the SANDBOX input.要打开这些设置,请单击 SANDBOX 输入内左上角的设置/齿轮图标。

阿波罗工作室设置

make sure to add 'x-forwarded-proto' 'https' to the Shared Headers.确保将“x-forwarded-proto”“https”添加到共享标头。

The answer form @NicoWheat is partial right (I guess, correct me if I am wrong). @NicoWheat 的答案部分正确(我想,如果我错了,请纠正我)。 It worked when I send the request with apollo studio (with sameSite:"none" and secure: true) but regardless what are the options the cookies still has not been set for me when I do the mutation through localhost:3000.当我使用 apollo studio 发送请求时它起作用了(使用sameSite:“none”和secure:true),但无论有什么选项,当我通过localhost:3000 进行突变时,cookies 仍然没有为我设置。

Edit: I was wrong, after following the option in create urql client in frontend directory, it worked for me, thanks and appreciate it a lot.编辑:我错了,按照在前端目录中创建 urql 客户端中的选项后,它对我有用,非常感谢和感谢。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM