首页 > 解决方案 > 部署到生产后的 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)

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

标签: javascriptexpressoauthoauth-2.0passport.js

解决方案


使用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


推荐阅读