繁体   English   中英

Express会话在NGINX反向代理上不持久

[英]Express-session is not persistent on NGINX reverse proxy

我希望用户能够使用Discord登录并将其保存到带有express-session 当我在本地运行前端和后端时,它可以很好地工作,但是当我使用NGINX将其部署到我的Digitalocean服务器时,该会话将不会持续。 客户端永远不会收到会话持续所需的cookie。

这是使用express-sessionpassport和MySQL存储会话的会话登录设置。

import express, { Request, NextFunction, Response } from 'express';
import cors from 'cors';
import session, { SessionOptions } from 'express-session';
import DiscordStrategy from 'passport-discord';
import passport from 'passport';
import mysqlSession from 'express-mysql-session';
import secretConfig from 'config/secret';
import discordConfig from 'config/discord';
import apiConfig from 'config/apiconfig';
import { RESPONSE_CODE } from './helpers';

const isProd = process.env.NODE_ENV === 'production';

const app = express();

if (isProd) {
  app.set('trust proxy', 1); // Trust first proxy
  app.disable('x-powered-by'); // Hide information about the server
}

// Enable CORS
app.use(cors({
  credentials: true,
  origin: (origin, callback) => {
    const sameServer = !origin;

    if (sameServer || apiConfig.CORSWhitelist.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
}));

passport.serializeUser((user, done) => {
  done(null, user);
});
passport.deserializeUser((user, done) => {
  done(null, user);
});

passport.use(new DiscordStrategy(
  {
    clientID: secretConfig.discord.publicKey,
    clientSecret: secretConfig.discord.privateKey,
    callbackURL: discordConfig.callbackUrl,
    scope: discordConfig.scopes,
  },
  (accessToken, refreshToken, user, done) => {
    process.nextTick(() => {
      return done(null, user);
    });
  }
));

const MysqlStore = mysqlSession(session);
const mysqlCfg = {
  host: 'localhost',
  port: 3306,
  user: 'user',
  password: 'password',
  database: 'database',
};

const sessionCfg: SessionOptions = {
  secret: secretConfig.sessionSecret,
  name: 'plan-b-auth',
  resave: false,
  saveUninitialized: false,
  proxy: isProd,
  cookie: {
    secure: isProd,
  },
  store: new MysqlStore(mysqlCfg),
};

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

const checkAuth = (req: Request, res: Response, next: NextFunction) => {
  if (req.isAuthenticated()) return next();

  res.status(RESPONSE_CODE.UNAUTHORIZED).json('Unauthorized');
};

app.get(
  '/discord/auth',
  passport.authenticate('discord', { scope: discordConfig.scopes }),
);

app.get(
  '/discord/auth/callback',
  passport.authenticate('discord', { failureRedirect: apiConfig.websiteDomain }),
  (req, res) => {
    res.redirect(apiConfig.websiteDomain);
  }
);

app.get(
  '/discord/auth/logout',
  (req, res) => {
    req.logout();
    res.redirect('/');
  }
);

app.get(
  '/discord/auth/me',
  checkAuth,
  (req, res) => {
    res.json(req.user);
  }
);

app.listen(apiConfig.port, (err) => {
  if (err) return console.info(err);
  console.info('Listening at http://localhost:8080/');
});

在前端(使用Next.js),我通过此访存来获取用户数据

fetch('https://myapi.com/discord/auth/me', {
    headers: {
      cookie: req.headers.cookie,
    },
    credentials: 'include',
  })

这是我的api服务器块(与客户端相似)

server {
        server_name myapi.com;

        location / {
                proxy_pass http://127.0.0.1:3002;
                proxy_http_version 1.1;
                proxy_cache_bypass $http_upgrade;
                proxy_redirect off;

                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
}

再次:当我在本地运行代码并登录时,它可以工作。 当我将其部署到Digitalocean服务器时,它不再起作用。 在部署的网站上的客户端上没有存储cookie,但是当我在本地运行该cookie时仍然存在。 但是,会话在部署后存储在我的MySQL数据库中。

Next.js是服务器端呈现的应用程序。 您将无法从浏览器访问请求标头。

因此,您的问题是关于如何为Next.js设置cookie。

您必须使用next-cookies来访问它。

请参阅示例目录以获取cookie-auth

您必须实现cookie-auth示例,才能在客户端和服务器上访问cookie。 以下帮助器功能为经过身份验证的用户存储和检索Cookie:

import { Component } from 'react'
import Router from 'next/router'
import nextCookie from 'next-cookies'
import cookie from 'js-cookie'

export const login = async ({ token }) => {
  cookie.set('token', token, { expires: 1 })
  Router.push('/profile')
}

export const logout = () => {
  cookie.remove('token')
  // to support logging out from all windows
  window.localStorage.setItem('logout', Date.now())
  Router.push('/login')
}

// Gets the display name of a JSX component for dev tools
const getDisplayName = Component =>
  Component.displayName || Component.name || 'Component'

export const withAuthSync = WrappedComponent =>
  class extends Component {
    static displayName = `withAuthSync(${getDisplayName(WrappedComponent)})`

    static async getInitialProps (ctx) {
      const token = auth(ctx)

      const componentProps =
        WrappedComponent.getInitialProps &&
        (await WrappedComponent.getInitialProps(ctx))

      return { ...componentProps, token }
    }

    constructor (props) {
      super(props)

      this.syncLogout = this.syncLogout.bind(this)
    }

    componentDidMount () {
      window.addEventListener('storage', this.syncLogout)
    }

    componentWillUnmount () {
      window.removeEventListener('storage', this.syncLogout)
      window.localStorage.removeItem('logout')
    }

    syncLogout (event) {
      if (event.key === 'logout') {
        console.log('logged out from storage!')
        Router.push('/login')
      }
    }

    render () {
      return <WrappedComponent {...this.props} />
    }
  }

export const auth = ctx => {
  const { token } = nextCookie(ctx)

  /*
   * This happens on server only, ctx.req is available means it's being
   * rendered on server. If we are on server and token is not available,
   * means user is not logged in.
   */
  if (ctx.req && !token) {
    ctx.res.writeHead(302, { Location: '/login' })
    ctx.res.end()
    return
  }

  // We already checked for server. This should only happen on client.
  if (!token) {
    Router.push('/login')
  }

  return token
}

您必须使用withAuthSync包装页面,并使用cookie来访问经过身份验证的数据。

class YourPage extends Component {
...
}
YourPage.getInitialProps = async ctx => {
  const { token } = nextCookie(ctx)
  const url = `${process.env.API_URL}/discord/auth/me.js`

  const redirectOnError = () =>
    typeof window !== 'undefined'
      ? Router.push('/login')
      : ctx.res.writeHead(302, { Location: '/login' }).end()

  try {
    const response = await fetch(url, {
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json',
        cookie: token
      }
    })

    if (response.ok) {
      return await response.json()
    }

    return redirectOnError()
  } catch (error) {
    // Implementation or Network error
    return redirectOnError()
  }
}
export default withAuthSync(YourPage)

此外,通过discord API成功进行身份验证后,您还必须调用loginlogout功能。 请参阅上面发布的github examples文件夹中的工作示例。

暂无
暂无

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

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