Nest.js集成Google OAuth遇重定向CORS错误问题及解决
Nest.js + Next.js Google OAuth 跨域问题及解决方案
问题场景
- Nest.js 后端运行在 5000 端口,Next.js 前端运行在 3000 端口
- Google Cloud Console 已配置源 URI 和重定向 URI 为
http://localhost:3000
前端错误
- 从
http://localhost:3000访问经http://localhost:5000/auth/google重定向的 Google OAuth 地址时,触发 CORS 策略拦截,提示缺少Access-Control-Allow-Origin标头 - Google OAuth 地址的 GET 请求返回
net::ERR_FAILED 302 (Found)
相关 Nest.js 代码片段
Google OAuth 策略
export class GoogleOAuthStrategy extends PassportStrategy(Strategy, 'google') { constructor(private readonly configService: ConfigService) { super({ clientID: configService.get('googleOAuthClientId'), clientSecret: configService.get('googleOAuthClientSecret'), callbackURL: configService.get('googleOAuthCallbackURL'), scope: ['email', 'profile'], }); } async validate( accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback, ): Promise<any> { const { id, name, emails, photos } = profile; const user: User = { type: 'individual', email: emails[0].value, firstName: name.givenName, lastName: name.familyName, picture: photos[0].value, authenticationProviders: [{ name: 'google', id }], }; done(null, user); } }
Google OAuth 守卫
export class GoogleOAuthGuard extends AuthGuard('google') { constructor() { super({ accessType: 'offline', }); } }
Auth 控制器
export class AuthController { constructor(private readonly authService: AuthService) {} @Get('google') @UseGuards(GoogleOAuthGuard) async googleAuth() { return HttpStatus.OK; } @Get('google-redirect') @UseGuards(GoogleOAuthGuard) googleAuthRedirect( @Req() req: Request, @Res({ passthrough: true }) res: Response, ) { return this.authService.login(req, res, 'google'); } }
Auth 服务
export class AuthService { constructor( private readonly configService: ConfigService, private readonly usersService: UsersService, private readonly jwtService: JwtService, ) {} async login(req: Request, res: Response, provider: Provider): Promise<void> { const user = req.user as User; if (!user) { throw new NotFoundException(`No user from ${provider}`); } let userPayload: User; let accessTokenPayload: string; const foundUser = await this.usersService.findByEmail(user.email); if (foundUser) { const providerExists = foundUser.authenticationProviders.some( (provider) => provider.name === user.authenticationProviders[0].name, ); // 用户已存在但未绑定当前登录方式 if (!providerExists) { foundUser.authenticationProviders.push(user.authenticationProviders[0]); await foundUser.save(); } userPayload = foundUser; accessTokenPayload = foundUser._id.toString(); } else { // 用户不存在则创建新用户 const newUser = await this.usersService.create(user); userPayload = newUser; accessTokenPayload = newUser._id.toString(); } const accessToken = this.jwtService.sign({ id: accessTokenPayload }); res.cookie('CARDTRIKA_ACCESS_TOKEN', accessToken, { maxAge: this.configService.get('cookieMaxAge'), httpOnly: true, }); res.status(HttpStatus.OK).json({ statusCode: HttpStatus.OK, success: true, message: 'User information', data: userPayload, }); } }
补充说明
直接在浏览器访问 http://localhost:5000/auth/google 可正常完成授权并获取用户信息,但通过 Next.js 发起请求时立即触发 CORS 错误。
解决方案
调整 Google 控制台配置
- 源 URI 保持为
http://localhost:3000 - 重定向 URI 修改为
http://localhost:3000/auth/google-redirect(可根据实际路由调整)
- 源 URI 保持为
前端登录逻辑修改
放弃 AJAX 请求方式,直接通过页面跳转发起授权请求,代码示例:const handleLogin = async (method: string) => { window.location.replace(`${apiBaseUrl}/auth/${method.toLowerCase()}`); };完整流程说明
- 调用上述方法后,页面跳转到 Google 授权界面
- 用户完成登录授权后,Google 会将页面重定向到
http://localhost:3000/auth/google-redirect?code=<授权码> - 前端在该页面中,携带 URL 查询参数请求后端的
http://localhost:5000/auth/google-redirect接口 - 后端通过授权码完成身份验证,返回用户详情并设置登录 Cookie
内容的提问来源于stack exchange,提问作者Prashant Patel
相关产品推荐
相关产品推荐

