我希望用户能够使用Discord登录并将其保存到使用express-session
的会话中。当我在本地运行前端和后端时,它可以很好地工作,但是当我使用NGINX将其部署到我的Digitalocean服务器时,该会话将不会持续。客户端永远不会收到会话持续所需的cookie。
这是使用express-session
,passport
和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数据库中。
答案 0 :(得分:0)
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成功进行身份验证之后,您还必须调用login
和logout
函数。请参见上面发布的github examples文件夹中的工作示例。