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

Nest.js中Passport Google与Apple认证冲突报错求助

问题:同时使用passport-google-oauth20和passport-appleid时Google认证报错

错误信息

[Nest] 987  - 07/22/2022, 9:49:18 AM   ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'callbackURL')
TypeError: Cannot read properties of undefined (reading 'callbackURL')
    at GoogleStrategy.OAuth2Strategy.authenticate (/home/node/app/node_modules/passport-appleid/lib/strategy.js:76:59)
    at attempt (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:369:16)
    at authenticate (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:370:7)
    at /home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:96:3
    at new Promise (<anonymous>)
    at /home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:88:83
    at MixinAuthGuard.<anonymous> (/home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:49:36)
    at Generator.next (<anonymous>)
    at fulfilled (/home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:17:58)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

问题描述

用passport-google-oauth20和passport-appleid分别实现Google与Apple认证功能,单独使用任意一种策略均正常,但同时启用时,调用GET google-sign-in接口会触发上述错误。注释掉UserModule中的AppleStrategy后,Google认证恢复正常。无法理解为何GoogleStrategy的OAuth2Strategy.authenticate会调用passport-appleid包内的逻辑。

相关代码

控制器代码

@UseGuards(AuthGuard('google'))
@ApiOperation({ summary: 'google sign in' })
@Get('google-sign-in')
public async signInWithGoogle(@Req() req: any) {
  return req.user;
}

@UseGuards(AuthGuard('google'))
@ApiOperation({ summary: 'google redirect' })
@Get('auth/google/redirect')
public async signInWithGoogleRedirect(@Req() req: any) {
  return this.userService.socialSignIn(req.user);
}

@UseGuards(AuthGuard('apple'))
@ApiOperation({ summary: 'apple sign in' })
@Get('apple-sign-in')
public async signInWithApple(@Req() req: any) {
  return req.user;
}

@UseGuards(AuthGuard('apple'))
@ApiOperation({ summary: 'apple redirect' })
@Get('apple/redirect')
public async signInWithAppleRedirect(@Req() req: any) {
  return this.userService.socialSignIn(req.user);
}

GoogleStrategy代码

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Profile } from 'passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum';
import { ConfigService } from '../config.services';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor(configService: ConfigService) {
    super({
      clientID: configService.get('GOOGLE_AUTH_CLIENT_ID'),
      clientSecret: configService.get('GOOGLE_AUTH_CLIENT_SECRET'),
      callbackURL: configService.get('GOOGLE_AUTH_CALLBACK_URL'),
      scope: ['email', 'profile'],
    });
  }

  public async validate(
    accessToken: string,
    refreshToken: string,
    profile: Profile,
    done: VerifyCallback,
  ): Promise<any> {
    const { name, emails } = profile;
    const [userEmail] = emails;

    const user = {
      email: userEmail.value,
      name: name.givenName,
      registeredPlatform: RegisteredPlatformsEnum.GOOGLE,
      accessToken,
    };
    done(null, user);
  }
}

AppleStrategy代码

import path from 'path';

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Profile } from 'passport';
import { Strategy, VerifyCallback } from 'passport-appleid';
import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum';
import { ConfigService } from '../config.services';

@Injectable()
export class AppleStrategy extends PassportStrategy(Strategy, 'apple') {
  constructor(configService: ConfigService) {
    super({
      clientID: configService.get('APPLE_AUTH_CLIENT_ID'),
      callbackURL: configService.get('APPLE_AUTH_CALLBACK_URL'),
      teamId: configService.get('APPLE_AUTH_TEAM_ID'),
      keyIdentifier: configService.get('APPLE_AUTH_KEY_IDENTIFIER'),
      privateKeyPath: path.join(
        __dirname,
        `./AuthKey_${configService.get('APPLE_AUTH_KEY_IDENTIFIER')}.p8`,
      ),
    });
  }

  public async validate(
    accessToken: string,
    refreshToken: string,
    profile: Profile,
    done: VerifyCallback,
  ): Promise<any> {
    const { name, emails } = profile;
    const [userEmail] = emails;
    const user = {
      email: userEmail.value,
      name: name.givenName,
      registeredPlatform: RegisteredPlatformsEnum.APPLE,
      accessToken,
    };
    done(null, user);
  }
}

UserModule代码

import { Module, forwardRef, Logger } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { AppModule } from '../../app.module';
import { ConfigService } from '../../services/config.services';
import { UserService } from './services/users.service';
import { UserController } from './user.controller';
import { UserEntity } from '../../model/user.entity';
import { EmailService } from '../../services/email.service';
import { UserRepository } from 'src/repositories/user.repository';
import { GoogleStrategy } from 'src/services/strategy/google-strategy';
import { AppleStrategy } from 'src/services/strategy/apple-strategy';

@Module({
  imports: [
    forwardRef(() => AppModule),
    TypeOrmModule.forFeature([UserEntity, UserRepository]),
  ],
  controllers: [UserController],
  providers: [
    Logger,
    UserService,
    ConfigService,
    EmailService,

    // passport strategies
    AppleStrategy,
    GoogleStrategy,
  ],
  exports: [UserService],
})
export class UserModule {}

解决方法

问题根源是passport-appleid包存在原型污染:它直接修改了passport-oauth2中OAuth2Strategy的原型方法,导致所有继承该基类的策略(包括GoogleStrategy)都被篡改。

方案1:调整策略加载顺序

在UserModule的providers中,将GoogleStrategy放在AppleStrategy前面,让GoogleStrategy先完成原型方法的绑定,避免被后续加载的AppleStrategy覆盖:

providers: [
  Logger,
  UserService,
  ConfigService,
  EmailService,

  // 先加载GoogleStrategy
  GoogleStrategy,
  AppleStrategy,
],

方案2:更换无原型污染的Apple认证包

改用@node-passport/apple这类不修改基类原型的包,替代passport-appleid。

方案3:修复passport-appleid的原型污染问题

如果不想换包,可手动修改passport-appleid/lib/strategy.js代码,将直接修改原型的逻辑改为类继承:

// 原代码(污染原型)
const OAuth2Strategy = require('passport-oauth2').Strategy;
OAuth2Strategy.prototype.authenticate = function() { /* 自定义逻辑 */ }

// 修改为类继承(不污染原型)
const OAuth2Strategy = require('passport-oauth2').Strategy;

class AppleStrategy extends OAuth2Strategy {
  constructor(options, verify) {
    super(options, verify);
    this.name = 'apple';
  }

  authenticate(req, options) {
    // 自定义逻辑
    super.authenticate(req, options);
  }
}

module.exports = AppleStrategy;

内容的提问来源于stack exchange,提问作者Orest Mirko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 16:15:39