首页 > 解决方案 > 部署到 Heroku 后节点/apollo/express 服务器的 Cookie 问题

问题描述

我正在使用 apollo 构建节点 js 服务器,并使用 next.js/apollo 客户端构建前端。我的问题是,在 localhost 上一切正常,但在部署到 Heroku 后未设置 cookie。我正在从服务器获取数据并将我重定向到主页,但在页面重新加载后它再次将我重定向到登录页面。

这是阿波罗服务器代码

import cors from 'cors'
import Redis from 'ioredis'
import dotenv from 'dotenv'
import express from 'express'
import mongoose from 'mongoose'
import session from 'express-session'
import connectRedis from 'connect-redis'
import { ApolloServer } from 'apollo-server-express'

import typeDefs from './schema'
import resolvers from './resolvers'

dotenv.config({
  path: `.env.${process.env.NODE_ENV}`,
})

const port = process.env.PORT || 4000
const dev = process.env.NODE_ENV !== 'production'

const RedisStore = connectRedis(session)

const startServer = async () => {
  await mongoose
    .connect(process.env.DB_URL, { useNewUrlParser: true })
    .then(() => console.log(`  MongoDB Connected...`))
    .catch(err => console.log(`❌  MongoDB Connection error: ${err}`))

  const app = express()

  const server = new ApolloServer({
    typeDefs,
    resolvers,
    playground: !dev
      ? false
      : {
          settings: {
            'request.credentials': 'include',
          },
        },
    context: ({ req, res }) => ({ req, res }),
  })

  app.disable('x-powered-by')
  app.set('trust proxy', 1)

  app.use(
    cors({
      credentials: true,
      origin:
        process.env.NODE_ENV === 'production'
          ? process.env.FRONT_END_URL
          : 'http://localhost:3000',
    }),
  )

  app.use((req, _, next) => {
    const authorization = req.headers.authorization

    if (authorization) {
      try {
        const cid = authorization.split(' ')[1]
        req.headers.cookie = `cid=${cid}`
        console.log(cid)
      } catch (err) {
        console.log(err)
      }
    }

    return next()
  })

  app.use(
    session({
      store: new RedisStore({
        host: process.env.REDIS_HOST,
        port: process.env.REDIS_PORT,
        pass: process.env.REDIS_PASS,
      }),
      name: process.env.SESS_NAME,
      secret: process.env.SESS_SECRET,
      saveUninitialized: false,
      resave: false,
      cookie: {
        httpOnly: true,
        maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
        secure: false,
      },
    }),
  )

  server.applyMiddleware({ app, cors: false })

  app.listen({ port }, () =>
    console.log(
      `  Server ready at http://localhost:${port}${server.graphqlPath}`,
    ),
  )
}

startServer()

这是解析器

import gravatar from 'gravatar'
import bcrypt from 'bcrypt'

import { User } from '../models'
import { isAuthenticated, signOut } from '../auth'
import { loginSchema, registerSchema } from '../utils'

export default {
  Query: {
    users: (parent, args, { req }, info) => {
      isAuthenticated(req)

      return User.find({})
    },
    user: (parent, { id }, context, info) => {
      isAuthenticated(req)

      return User.findById(id)
    },
    me: (parent, args, { req }, info) => {
      isAuthenticated(req)

      return User.findById(req.session.userId)
    },
  },
  Mutation: {
    signUp: async (parent, args, { req }, info) => {
      // isAuthenticated(req)

      args.email = args.email.toLowerCase()

      try {
        await registerSchema.validate(args, { abortEarly: false })
      } catch (err) {
        return err
      }

      args.avatar = await gravatar.url(
        args.email,
        {
          protocol: 'https',
          s: '200', // Size
          r: 'pg', // Rating
          d: 'mm', // Default
        },
        true,
      )

      args.password = await bcrypt.hash(args.password, 12)
      const user = await User.create(args)

      req.session.userId = user.id

      return user
    },
    signIn: async (parent, args, { req }, info) => {
      // isAuthenticated(req)

      const { email, password } = args

      try {
        await loginSchema.validate(args, { abortEarly: false })
      } catch (err) {
        return err
      }

      const user = await User.findOne({ email })

      if (!user || !(await bcrypt.compare(password, user.password))) {
        throw new Error('Incorrect email or password. Please try again.')
      }

      req.session.userId = user.id

      return user
    },
    signOut: (parent, args, { req, res }, info) => {
      return signOut(req, res)
    },
  },
}

这是前端的 apollo 客户端(我正在使用 next.js 中的 with-apollo-auth 示例)

import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { createHttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context'
import fetch from 'isomorphic-unfetch'

let apolloClient = null

if (!process.browser) {
  global.fetch = fetch
}

function create(initialState, { getToken }) {
  const httpLink = createHttpLink({
    uri:
      process.env.NODE_ENV === 'development'
        ? 'http://localhost:4000/graphql'
        : process.env.BACK_END_URL,
    credentials: 'include',
  })

  const authLink = setContext((_, { headers }) => {
    const token = getToken()
    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : '',
      },
    }
  })

  return new ApolloClient({
    connectToDevTools: process.browser,
    ssrMode: !process.browser,
    link: authLink.concat(httpLink),
    cache: new InMemoryCache().restore(initialState || {}),
  })
}

export default function initApollo(initialState, options) {
  if (!process.browser) {
    return create(initialState, options)
  }

  if (!apolloClient) {
    apolloClient = create(initialState, options)
  }

  return apolloClient
}

提前致谢。

标签: javascriptnode.jsexpressnext.jsapollo-server

解决方案


推荐阅读