Nest.js Google Login(passport.js) 与 SPA 前端(React)

2024-04-30

在我的全栈项目(Nest.js + React)中,我使用护照谷歌实现了谷歌登录,如下所示。

import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {
    constructor(
        private authService: AuthService,
    ) {}

    ...

    @Get('google')
    @UseGuards(GoogleAuthGuard)
    async googleAuth(@Req() req) {}
  
    @Get('google/callback')
    @UseGuards(GoogleAuthGuard)
    async googleAuthRedirect(@Req() req) {
        return this.authService.socialLogin(req);
    }
}

当我使用 Chrome 浏览器访问 http://localhost:3000/auth/google 时,所有流程(包括新会员的登录和注册)都运行良好。

但在前端项目(React)上,下面的代码不起作用。

axios.get('http://localhost:3000/auth/google')

所以我尝试使用“react-google-login”,如下所示:

<GoogleLogin 
    clientId={GOOGLE_OAUTH.GOOGLE_CLIENT_ID}
    buttonText="Log In with Google Account"
    onSuccess={result => onGoogleLogin(result)}
    onFailure={result => console.log(result)}
    cookiePolicy={'single_host_origin'}
/>

但我无法理解Google登录在前端和后端的流程,也不知道如何实现“onGoogleLogin”功能。

我该如何修复它?


你好,将你的前端更改为:

<Button
   color="secondary"
   startIcon={<GoogleIcon />}
   onClick={googleLogin}
   fullWidth
   variant="outlined"
   size="large"
>
     Login with Google
</Button>

并添加 googleLogin 函数如下:

  const googleLogin = async () => {
    try {
      window.open(`http://localhost:3001/auth/google-logins/${from.replaceAll('/', '@')}`, "_self");
    } catch (ex) {
      console.log(ex)
    }
  }

像这样更改您的路线文件:

<Route path="google-oauth-success-redirect">
     <Route path=":accessToken/:refreshToken/:from" element={<GoogleOAuthSuccessRedirect />} />
</Route>

并添加 GoogleOAuthSuccessRedirect 组件:

import React, { useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom';
import { setAuthTokens } from 'redux/features/auth/authSlice';
import { useAppDispatch } from 'redux/hooks';

type Props = {}

const GoogleOAuthSuccessRedirect = (props: Props) => {

    let { accessToken, refreshToken, from } = useParams();
    const navigate = useNavigate();
    const dispatch = useAppDispatch()

    useEffect(() => {
        if (from && accessToken && refreshToken) {
            dispatch(setAuthTokens({ accessToken, refreshToken }))
            navigate('/' + from, { replace: true });
        }
    }, [accessToken, dispatch, from, navigate, refreshToken])


    return (
        <div>Loading...</div>
    )
}

export default GoogleOAuthSuccessRedirect

转到应用程序的后端并进行如下更改:

在控制器上:

@Get('google-logins/:from')
  @UseGuards(GoogleOauthGuard)
  async googleLogin(@Req() req: Request) {
  }

  @Get('google/callback')
  @UseGuards(GoogleOauthGuard)
  async googleLoginCallback(
    @Req() req: Request,
    @Res() res: Response,
  ) {
    const auth = await this.authService.login(req.user);
    res.redirect(`http://localhost:3000/google-oauth-success-redirect/${auth.accessToken}/${auth.refreshToken}${req.params.from}`)
  }

关于谷歌策略:

authenticate(req: any, options: any) {

    if (!options?.state) {
      options = { ...options, state: req.params.from }
    } 
    
    return super.authenticate(req, options)
  }

和谷歌oauth守卫如下:

import {
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AuthGuard, IAuthModuleOptions } from '@nestjs/passport';
import { Request, Response } from 'express';

@Injectable()
export class GoogleOauthGuard extends AuthGuard('google') {
  constructor(private configService: ConfigService) {
    super({
      // accessType: 'offline',
      // response_type: "code",
      // display: 'popup',
      // approvalPrompt: 'auto',
      prompt: 'select_account', //"consent"
    });
  }

  async canActivate(context: ExecutionContext) {
    try {
      const request = context.switchToHttp().getRequest() as Request;
      const from = (request.query.state as string)?.replace(/\@/g, '/')
      // response.setHeader('X-Frame-Options', 'SAMEORIGIN');
      // await super.logIn(request) //to enabling session / we dont need it  

      const activate = (await super.canActivate(context)) as boolean;
      request.params.from = from
      return activate;
    } catch (ex) {
      throw ex
    }
  }
}

最后,您可以访问我的应用程序的完整源代码,该应用程序是使用 React TypeScript(redux 工具包 rtk 查询)和 Nestjs 实现的,其中包含 google oauth2 流和 Passport.js。resource https://github.com/mehdiparastar/MUITNT_boilerplate/commit/114d6d77af65dac3288760506403f527fe08418e

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nest.js Google Login(passport.js) 与 SPA 前端(React) 的相关文章

随机推荐

  • mypy 如何忽略源文件中的一行?

    我在用着mypy http mypy lang org 在我的 python 项目中进行类型检查 我还使用 PyYAML 来读取和写入项目配置文件 不幸的是 当使用PyYAML 文档中推荐的导入机制 http pyyaml org wiki
  • Peek/Pop 预览忽略集合视图中的单元格角半径

    我已将 3D Touch Peek Pop 功能添加到我的集合视图单元格中 效果很好 但我注意到预览框架不考虑单元格的角半径 这是我的预览功能 func previewingContext previewingContext UIViewC
  • 将 OneClassSVM 与 GridSearchCV 结合使用

    我正在尝试在 OneClassSVM 上执行 GridSearchCV 函数 但我似乎无法找到 OCSVM 的正确评分方法 根据我收集的信息 像 OneClassSVM score 这样的东西不存在 因此 GridSearchCV 中没有所
  • 我在“宝石列表”中看到宝石,但“没有要加载的文件”

    我是Ubuntu10 sudo apt get install ruby1 9 1 full 然后下载 ruby gem 1 3 7 的源并安装它 sudo ruby setup rb 然后 例如 安装 sinatra sudo gem i
  • ActiveRecord 上的 setter 覆盖问题

    这不完全是一个问题 而是关于我如何解决问题的报告write attribute当属性是一个对象时 在 Rails 上Active Record 我希望这对面临同样问题的其他人有用 让我用一个例子来解释一下 假设你有两个班级 Book and
  • 支持引号的 XML 转义字符串的静态 python 方法

    我有一个同时包含 XML 转义字符和非转义字符的字符串 并且我需要它 100 XML 有效 例如 gt gt gt s lt lt 我希望这是 gt gt gt s lt lt 我已经尝试了多种方法 例如 lxml cgi 等 但它们都希望
  • 为什么Java中的实例变量总是私有的?

    我是 Java 新手 正在学习封装 并看到了一个实例变量在类中声明为私有的示例 http www tutorialspoint com java java encapsulation htm http www tutorialspoint
  • 推送到 Git (GitLab) 时出现错误 401

    我收到错误 Total 4 delta 2 reused 0 delta 0 error RPC failed result 22 HTTP code 401 当推送到 GitLab 上的 git 存储库时 它曾经有效 我遇到了同样的问题
  • 获取facebook更改后的facebook用户个人资料图片(10月24日)

    所以facebook改变了网站获取用户个人资料图片的方式 所有细节都在这里 https developers facebook com docs graph api reference user picture https develope
  • Gearman 工作状态问题

    我有一台 Gearman 服务器正在运行一个需要几分钟才能完成的进程 我正在运行一个进度条来显示完成情况 并尝试使用 Gearman PHP 扩展和 jobStatus 函数获取进度条的百分比 该作业肯定处于活动状态并已找到 因为前两个字段
  • 如何获取 docker run -i 的输出

    docker run通常返回其运行的命令的输出 我需要将一些数据传递给 docker 运行处理数据的命令 然后返回输出 当我使用 i选项 不返回任何输出 考虑这个简单的例子 echo hello docker run i base wc 它
  • 即使通过Javascript代码检查,如何触发复选框点击事件?

    我的页面中有很多复选框 并且有一个全选复选框可以检查所有复选框 不知何故 我想模拟复选框的单击事件 即使它是通过全选按钮选中 取消选中的 我该怎么做 您可以使用 jQuery trigger 方法 看http api jquery com
  • 下拉列表文本在 IE8 中不可见

    由于某种原因 我的 DDL 选项在 IE8 中都是不可见的 它们显然在那里 因为列表有 127 个选项 但文本是不可见的 在 Firefox 中一切都显示良好 我什至尝试在选择上放置内联样式 并将颜色设置为黑色 重要 当我使用 Firebu
  • 如何修复 git 中的“警告:忽略名称损坏的引用”?

    我正在 OSX 上工作 在特定的存储库中 每当我在输入 git 命令后按 Tab 键自动完成 git diff clicks tab 我现在看到大量警告 warning ignoring ref with broken name refs
  • Python,Tkinter:如何使用线程防止 tkinter gui 主循环崩溃

    嗨 我有一个小的 python gui 界面 有两个按钮 开始 启动计数器 和停止 假设停止计数器 计数器是一个无限循环 因为我不希望它结束 除非第二个按钮是点击 问题是当第一个按钮的功能仍在运行时无法单击第二个按钮 我读到我需要使用线程并
  • 有没有办法设置 iOS Safari 过度滚动/弹性滚动区域的样式?

    在 iOS Safari 中 当您滚动到网页底部时 您可以通过尝试再次滚动来将页面 抬起 我认为这是为了向用户保证他们已经到达页面末尾 默认情况下 该区域是空的 白色的 有没有办法用 CSS 来设置这个区域的样式 我想添加背景图片 只是为了
  • 防止系统字体缩放 - Jetpack Compose

    我正在尝试限制应用程序免受系统字体缩放的影响 我已经尝试了很多解决方案 但没有一个有帮助 他们中的大多数人都告诉我们使用 dp 而不是 sp 来确定文本大小 但在撰写中 如果我是正确的 我们只能使用 sp 因为它需要一个文本单元 有没有正确
  • Airbrake 抛出错误“pybrake - 错误 - strconv.ParseInt:解析“None”:无效语法”

    我正在尝试按照中描述的步骤在 Django 项目中使用 Airbrake 记录器https github com airbrake pybrake django integration https github com airbrake p
  • 如何配置 Webpack 5 包以使用全局 jQuery?

    我有一个正在加载 jQuery 的网页 其中有一个指向 CDN 的脚本标记 我正在将 jQuery 加载到全局范围内 并且在整个站点中都有少量 JS 使用它 这一切都工作正常 我想继续以这种方式加载 jQuery 除此之外 我使用 Webp
  • Nest.js Google Login(passport.js) 与 SPA 前端(React)

    在我的全栈项目 Nest js React 中 我使用护照谷歌实现了谷歌登录 如下所示 import Controller Get Req UseGuards from nestjs common import AppService fro