You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用passport-spotify实现Spotify OAuth2登录时遇两类错误求助

问题描述

在Node.js项目中使用passport-spotify集成Spotify OAuth2登录,已在Spotify后台正确配置客户端凭证、密钥和回调URI,但出现以下问题:

  • 首次登录触发错误:
InternalOAuthError: failed to fetch user profile
    at C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-spotify\lib\passport-spotify\strategy.js:151:19
    at passBackControl (C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:132:9)
    at IncomingMessage.<anonymous> (C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:157:7)
    at IncomingMessage.emit (node:events:531:35)
    at endReadableNT (node:internal/streams/readable:1696:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
  • 刷新页面后错误变为:
TokenError: Invalid authorization code
    at OAuth2Strategy.parseErrorResponse (C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-oauth2\lib\strategy.js:373:12)
    at OAuth2Strategy._createOAuthError (C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-oauth2\lib\strategy.js:420:16)
    at C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-oauth2\lib\strategy.js:177:45
    at C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-spotify\lib\passport-spotify\strategy.js:81:20
    at passBackControl (C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:132:9)
    at IncomingMessage.<anonymous> (C:\Users\PAKSHAL\Documents\Pakshal\Projects\Saarang\backend\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:157:7)
    at IncomingMessage.emit (node:events:531:35)
    at endReadableNT (node:internal/streams/readable:1696:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
  • 仅部分用户出现问题:测试3个用户,2个报错,1个正常
  • 预期流程:访问/auth/spotify→输入凭证→回调后跳转到/dashboard显示「Welcome, username!」,实际触发上述错误

核心代码

Index.js

const express = require('express');
const mongoose = require('mongoose');
const passport = require('passport');
const session = require('express-session');
const authRoutes = require('./routes/auth');
const groupRoutes = require('./routes/grouproutes');
require('dotenv').config();
require('./config/passport');  // Initialize passport strategy
const cors = require('cors');

const app = express();

app.use(cors());
app.use(express.json());
// Middleware for session management
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true
}));

// Passport middleware
app.use(passport.initialize());
app.use(passport.session());

// MongoDB connection
mongoose.connect(process.env.MONGO_URI)
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error('MongoDB connection error:', err));

// Auth routes
app.use('/auth', authRoutes);
app.use('/group',groupRoutes);

app.listen(process.env.PORT || 3000, () => {
  console.log(`Server running on port ${process.env.PORT || 3000}`);
  app.get('/', (req, res) => {
    res.send('Hello, world!');
  });
});


app.get('/dashboard', (req, res) => {
  if (!req.isAuthenticated()) {
    return res.redirect('/auth/spotify');
  }
  res.send(`Welcome, ${req.user.displayName}!`);  // Access user info from Spotify profile
});

Passport.js

const passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
const User = require('../models/User');  // Your User model

passport.use(new SpotifyStrategy({
  clientID: process.env.SPOTIFY_CLIENT_ID,
  clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
  callbackURL: "http://localhost:3000/auth/spotify/callback",
},
  async function(accessToken, refreshToken, expires_in, profile, done) {
    try {
      // Check if the user already exists
      let user = await User.findOne({ spotifyId: profile.id });

      if (!user) {
        // If user doesn't exist, create a new one
        user = new User({
          spotifyId: profile.id,
          displayName: profile.displayName,
          email: profile.emails[0].value,
          profileImage: profile.photos[0]?.value,
          accessToken,
          refreshToken
        });
        await user.save();
      } else {
        // If user exists, update tokens
        user.accessToken = accessToken;
        user.refreshToken = refreshToken;
        await user.save();
      }

      return done(null, user);
    } catch (err) {
      return done(err, null);
    }
  }
));

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

passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (err) {
    done(err, null);
  }
});

authmiddleware.js

const axios = require('axios');
const User = require('../models/User');

const ensureAuthenticated = async (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ message: 'User not authenticated' });
  }

  const token = authHeader.split(' ')[1];

  try {
    // Validate the token by making a request to Spotify API
    const response = await axios.get('https://api.spotify.com/v1/me', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    const spotifyUserData = response.data;

    // Find the user in your MongoDB by spotifyId
    const user = await User.findOne({ spotifyId: spotifyUserData.id });

    if (!user) {
      return res.status(401).json({ message: 'User not found in the system' });
    }

    // Attach MongoDB user object to req.user
    req.user = user;

    next(); // Continue to the next middleware or route handler
  } catch (error) {
    return res.status(401).json({ message: 'Invalid token or session expired' });
  }
};

module.exports = ensureAuthenticated;

User.js (model)

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  spotifyId: { type: String, required: true, unique: true }, // Spotify ID
  displayName: { type: String },  // Spotify username
  email: { type: String },        // Spotify email
  profileImage: { type: String }, // Spotify profile picture
  accessToken: { type: String },  // Spotify OAuth access token
  refreshToken: { type: String }, // Spotify OAuth refresh token
  friends: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // Friends in your app
  friendRequests: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // Pending friend requests
  activeGroup: { type: mongoose.Schema.Types.ObjectId, ref: 'Group', default: null },  // Active group, null if not in any
  createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('User', userSchema);

问题排查与解决方案

1. 「failed to fetch user profile」错误修复

原因1:缺少必要授权Scope

部分用户未授予user-read-private或user-read-email权限,导致passport-spotify无法获取完整profile信息。
修复:在SpotifyStrategy中添加明确Scope:

passport.use(new SpotifyStrategy({
  clientID: process.env.SPOTIFY_CLIENT_ID,
  clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
  callbackURL: "http://localhost:3000/auth/spotify/callback",
  scope: ['user-read-private', 'user-read-email'] // 新增此行
},
// ... 原有代码
));

同时在认证路由中传递Scope参数:

// authRoutes.js 示例
router.get('/spotify', passport.authenticate('spotify', {
  scope: ['user-read-private', 'user-read-email'],
  showDialog: true // 强制用户确认权限,避免缓存旧权限
}));

原因2:用户Profile字段为空报错

部分用户Spotify账号未设置邮箱或头像,直接访问profile.emails[0].value会触发未定义错误。
修复:修改用户创建逻辑,添加空值判断:

user = new User({
  spotifyId: profile.id,
  displayName: profile.displayName || 'Unknown User',
  email: profile.emails?.[0]?.value || null,
  profileImage: profile.photos?.[0]?.value || null,
  accessToken,
  refreshToken
});

2. 「Invalid authorization code」错误修复

原因1:Session配置不严谨

saveUninitialized: true会保存未初始化Session,导致授权码被重复提交。
修复:调整Session配置:

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false, // 仅保存已初始化的Session
  cookie: {
    secure: process.env.NODE_ENV === 'production', // 生产环境启用HTTPS Cookie
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000 // Cookie有效期1天
  }
}));

原因2:回调路由未及时重定向

认证成功后未立即重定向,用户刷新页面会重复提交已失效的授权码。
修复:确保回调路由认证成功后立即跳转:

// authRoutes.js 示例
router.get('/spotify/callback', 
  passport.authenticate('spotify', { failureRedirect: '/' }),
  (req, res) => {
    res.redirect('/dashboard'); // 立即重定向,避免重复提交授权码
  }
);

3. 部分用户正常的原因

正常用户已授予足够Scope,账号信息完整(有邮箱、头像),且Session流程无异常,因此能顺利完成登录。

4. 额外优化建议

  • 在Passport的verify回调中添加错误日志,便于排查具体问题:
async function(accessToken, refreshToken, expires_in, profile, done) {
  try {
    // ... 原有代码
  } catch (err) {
    console.error('Passport verify error:', err); // 新增日志
    return done(err, null);
  }
}
  • 生产环境必须使用HTTPS回调URI,Spotify对此有强制要求;
  • 实现token自动刷新逻辑,避免过期token导致的认证失败。

内容的提问来源于stack exchange,提问作者Pakshal Shah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 22:47:04