javascript - 部署到生产后的 Passport.js TokenError
问题描述
我有一个 MERN 堆栈应用程序,它使用 Passport.js 进行 Facebook/Twitter/Google 身份验证。在开发环境中,一切正常,我们能够对用户进行身份验证并将他们登录到应用程序中。
在开发环境中确认功能后,我们部署到 Heroku,但相同的功能在生产环境中不起作用,并且 Passport.js 失败并出现“TokenError”,表明域 URL 不包含在应用程序的域中(即使我们确认它已列在应用程序的域中)。
下面列出了代码以及确认 URL 位于应用程序域中的屏幕截图。您还可以在此录音中看到从 Facebook 成功调用的回调的实时行为 - https://thiag0.tinytake.com/tt/MzcxMjIyNV8xMTI5MTA3MA
非常感谢任何为我指明正确方向的帮助!
服务器.js
const express = require('express');
const connectDB = require('./config/db');
const configurePassport = require('./config/passport');
const path = require('path');
const fs = require('fs');
const https = require('https');
const app = express();
// Connect Database
connectDB();
// Init Middleware
app.use(express.json({ extended: false }));
// Passport Middleware
configurePassport(app);
// Define Routes
app.use('/api/users', require('./routes/api/users'));
app.use('/api/auth', require('./routes/api/auth'));
app.use('/api/profile', require('./routes/api/profile'));
app.use('/api/posts', require('./routes/api/posts'));
// Serve static assets in production
if (process.env.NODE_ENV === 'production') {
// Set static folder
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
const PORT = process.env.PORT || 5000;
if (process.env.NODE_ENV === 'production') {
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
} else {
https
.createServer(
{
key: fs.readFileSync('config/certificate/server.key'),
cert: fs.readFileSync('config/certificate/server.cert')
},
app
)
.listen(PORT, () => console.log(`Server started on port ${PORT}`));
}
Passport.js
const passport = require('passport');
const config = require('config');
const session = require('express-session');
const jwt = require('jsonwebtoken');
const gravatar = require('gravatar');
const bcrypt = require('bcryptjs');
const uuid = require('uuid');
const FacebookStrategy = require('passport-facebook').Strategy;
const TwitterStrategy = require('passport-twitter').Strategy;
const GoogleStrategy = require('passport-google-oauth2').Strategy;
const User = require('../models/User');
const webHost = config.get('webHost');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
const configurePassport = app => {
app.use(
session({
secret: config.get('jwtSecret'),
resave: false,
saveUninitialized: true
})
);
app.use(passport.initialize());
app.use(passport.session());
passport.use(
new FacebookStrategy(
{
clientID: config.get('facebook_app_id'),
clientSecret: config.get('facebook_app_secret'),
callbackURL: config.get('facebook_callback_url'),
profileFields: ['email', 'name']
},
async (accessToken, refreshToken, profile, done) => {
const { email, last_name, first_name, id } = profile._json;
let user = await User.findOne({ facebook_id: id });
if (!user) {
// Create user if they don't exist
user = new User({
...
});
await user.save();
}
done(null, user);
}
)
);
passport.use(
new TwitterStrategy(
{
consumerKey: config.get('twitter_consumer_key'),
consumerSecret: config.get('twitter_consumer_secret'),
callbackURL: config.get('twitter_callback_url'),
userProfileURL:
'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true'
},
async function(token, tokenSecret, profile, done) {
const { email, name, id } = profile._json;
let user = await User.findOne({ twitter_id: id });
if (!user) {
// Create user if they don't exist
user = new User({
...
});
await user.save();
}
done(null, user);
}
)
);
// Use the GoogleStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Google
// profile), and invoke a callback with a user object.
passport.use(
new GoogleStrategy(
{
clientID: config.get('google_app_id'),
clientSecret: config.get('google_app_secret'),
callbackURL: config.get('google_callback_url')
},
async function(accessToken, refreshToken, profile, done) {
const { email, name, sub } = profile._json;
let user = await User.findOne({ google_id: sub });
if (!user) {
// Create user if they don't exist
user = new User({
...
});
await user.save();
}
done(null, user);
}
)
);
app.get(
'/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
failureRedirect: webHost + '/login'
}),
function(req, res) {
const payload = {
user: {
id: req.user._id
}
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: 36000 },
(err, token) => {
if (err) throw err;
return res.redirect(webHost + '/social/facebook/' + token);
}
);
}
);
app.get(
'/auth/facebook',
passport.authenticate('facebook', {
session: false,
scope: ['email', 'user_posts']
})
);
// Redirect the user to Twitter for authentication. When complete, Twitter
// will redirect the user back to the application at /auth/twitter/callback
app.get(
'/auth/twitter',
passport.authenticate('twitter', {
session: false
})
);
// Twitter will redirect the user to this URL after approval. Finish the
// authentication process by attempting to obtain an access token. If
// access was granted, the user will be logged in. Otherwise, authentication has failed.
app.get(
'/auth/twitter/callback',
passport.authenticate('twitter', {
session: false,
failureRedirect: webHost + '/login'
}),
function(req, res) {
const payload = {
user: {
id: req.user._id
}
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: 36000 },
(err, token) => {
if (err) throw err;
return res.redirect(webHost + '/social/twitter/' + token);
}
);
}
);
// GET /auth/google
app.get(
'/auth/google',
passport.authenticate('google', {
session: false,
scope: [
'https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/userinfo.email'
]
})
);
// GET /auth/google/callback
app.get(
'/auth/google/callback',
passport.authenticate('google', {
session: false,
failureRedirect: webHost + '/login'
}),
function(req, res) {
const payload = {
user: {
id: req.user._id
}
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: 36000 },
(err, token) => {
if (err) throw err;
return res.redirect(webHost + '/social/google/' + token);
}
);
}
);
};
module.exports = configurePassport;
Heroku 日志
2019-08-20T20:04:52.264433+00:00 app[web.1]: FacebookTokenError: Can't Load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and subdomains of your app to the App Domains field in your app settings.
2019-08-20T20:04:52.264453+00:00 app[web.1]: at Strategy.parseErrorResponse (/app/node_modules/passport-facebook/lib/strategy.js:198:12)
2019-08-20T20:04:52.264456+00:00 app[web.1]: at Strategy.OAuth2Strategy._createOAuthError (/app/node_modules/passport-oauth2/lib/strategy.js:405:16)
2019-08-20T20:04:52.264460+00:00 app[web.1]: at /app/node_modules/passport-oauth2/lib/strategy.js:175:45
2019-08-20T20:04:52.264462+00:00 app[web.1]: at /app/node_modules/oauth/lib/oauth2.js:191:18
2019-08-20T20:04:52.264465+00:00 app[web.1]: at passBackControl (/app/node_modules/oauth/lib/oauth2.js:132:9)
2019-08-20T20:04:52.264466+00:00 app[web.1]: at IncomingMessage.<anonymous> (/app/node_modules/oauth/lib/oauth2.js:157:7)
2019-08-20T20:04:52.264469+00:00 app[web.1]: at IncomingMessage.emit (events.js:203:15)
2019-08-20T20:04:52.264470+00:00 app[web.1]: at endReadableNT (_stream_readable.js:1145:12)
2019-08-20T20:04:52.264472+00:00 app[web.1]: at process._tickCallback (internal/process/next_tick.js:63:19)
解决方案
使用passport-twitter-token代替包含 Twitter 身份验证策略的 passport-twitter 库,但该库不适合 RESTful API。它更适合与某些服务器渲染一起使用的 Express.js 应用程序使用 Twitter 策略初始化 Passport 的代码如下所示:
'use strict';
var passport = require('passport'),
TwitterTokenStrategy = require('passport-twitter-token'),
User = require('mongoose').model('User');
module.exports = function () {
passport.use(new TwitterTokenStrategy({
consumerKey: 'KEY',
consumerSecret: 'SECRET',
includeEmail: true
},
function (token, tokenSecret, profile, done) {
User.upsertTwitterUser(token, tokenSecret, profile,
function(err, user) {
return done(err, user);
});
}));
};
我在这里使用了解决方案https://www.soccermass.com你可以在这里阅读更多关于解决方案的信息https://medium.com/@robince885/how-to-do-twitter-authentication-with-react-and-restful -api-e525f30c62bb
推荐阅读
- javascript - 如何摆脱部分CSS动画在Vuejs中更新v-for渲染列表顺序后重新启动
- regex - 如何在正则表达式中取消捕获字符串?
- python - 无论如何使用matplotlib在饼图中指定标题的位置?
- r - PieDonut 如何改变馅饼和甜甜圈的颜色
- python - 如何将for循环替换为numpy?
- aws-glue - 为什么 Glue 作业会在 240 分钟后超时,即使我已将其设置为更大的值?
- google-sheets - 如何在 Google Sheet 的一维数组中搜索多个值的位置?
- blogger - 如何在 Blogger 的静态页面上显示标签列表?
- react-native - 使用 React Native 和 Tensorflow.js 对实时视频进行预测
- c# - 如何使用linq检查字符串是否包含特定字符串?