node.js - 如何使用 Passport 验证 React 应用程序?

标签 node.js reactjs express proxy passport.js

我有一个 React 应用程序。

我还有一个 Express 服务器,使用 passport-saml 我可以根据公司的 PingID SSO IdP 进行身份验证。

我想要获取 React 应用程序,以某种方式调用 Express 应用程序,进行身份验证。

Express Server 和 React 应用程序在同一主机上运行。

这是我所拥有的:

// Express App - rendering code pulled out
const express = require('express');
var passport = require('passport');
var Strategy = require('passport-saml').Strategy;
var fs = require('fs')
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const app = express();
const port = process.env.PORT || 4005;

passport.use('saml2', new Strategy({
    path: 'http://MYSERVER:4005/assert',
    entryPoint: 'https://sso.connect.pingidentity.com/sso/idp/SSO.saml2?XXXXXXXX',
    issuer: 'MYAPP',
    audience: 'MYAPP',
  },
  function(profile, cb) {
    return cb(null, profile);
  }));

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(expressSession({
    secret: '123xyz',
    resave: true,
    saveUninitialized: true 
}));

// Initialize Passport and restore authentication state, if any, from the session.
app.use(passport.initialize());
app.use(passport.session());

app.get('/login/idp', () =>{
    passport.authenticate('saml2')
    console.log('Authentication called');
});

app.get('/login', () =>{
    console.log('Authentication failed, try again');
});

app.post('/assert', 
  passport.authenticate('saml2', { failureRedirect: '/login' }),
  function(req, res) {
    console.log('Authentication succeeded');
    console.log(req.user)
    res.redirect('/');
  });

app.listen(port, () => console.log(`Listening on port ${port}`));

在我的 React 应用程序的 package.json 中,我有:

{
  ...
  "proxy": "http://localhost:4005/",
  ...
}

Create React App 玩具的轮廓是:

// Create React App
import React, { useState } from 'react';
import './App.css';

function App() {

  const handleLogin = async e => {
    e.preventDefault();
    const response = await fetch('/login/idp', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      }
    });
  };

  return (
    <div className="App">
      <form onSubmit={handleLogin}>
        <button type="submit">Login</button>
      </form>
    </div>
  );
}

export default App;

我可以愉快地点击按钮,控制台显示 Express 服务器的 GET 被触发,但没有任何内容返回到 React 客户端。

代理是可行的方法吗?我有正确的方法吗?如果是这样,我如何将结果从 Express 应用程序返回到 React 应用程序?

最佳答案

我有一个解决方案,但它看起来像是一个糟糕的黑客。然而,它确实有效,我需要把它过线。如果有人可以提出改进或替代方法,我将不胜感激。

我们从一个基本的 Express 服务器(托管在 4005)开始,它可以通过 Passport-SAML SSO 验证用户:

const express = require('express');
const jwt = require('jsonwebtoken')
const passport = require('passport');
const Strategy = require('passport-saml').Strategy;

require('dotenv').config()
const signature = process.env.SIGNATURE
const expiresIn = process.env.EXPIRESIN

// Simplification: actually there's a db look-up here
// based on req.user in order to get just the id
// but you get the idea
const createToken = user =>
    jwt.sign({ user.email }, signature, { expiresIn: expiresIn })

passport.use('saml2', new Strategy({
    path: 'http://localhost:4005/assert',
    entryPoint: 'https://sso.connect.pingidentity.com/sso/idp/SSO.saml2?idpid=XXXX_YOURVALUEHERE_XXXX',
    issuer: 'XXXX_YOURIDHERE_XXXX',
    audience: 'XXXX_YOURIDHERE_XXXX',
  },
  function(profile, cb) {
    return cb(null, profile);
  }));

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

// Create a new Express application.
var app = express();
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));

// Initialize Passport and restore authentication state, if any, from the
// session.
app.use(passport.initialize());

app.get('/login/idp', passport.authenticate('saml2'));

app.post('/assert', 
  passport.authenticate('saml2', 
    { failureRedirect: `http://localhost:3000/?error=unauthenticated` } ),
    function(req, res) {
      const token = createToken(req.user)
      res.redirect(`http://localhost:3000/signinOK?token=${token}`);
    });

app.listen(4005);

然后在React中src文件夹中,添加所需的setupProxy.js:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
    app.use(
      '/login',
      createProxyMiddleware({
        target: 'http://localhost:4005',
        headers: {
            "Connection": "keep-alive"
        }
      })
    );
  };

然后在 React 应用程序(托管在端口 3000)中,我们为首页创建一个简单的按钮组件:

import React from 'react'
import { Button } from '@material-ui/core'

function StartBtn() {
  return (
    <Button type="submit" variant="contained" color="primary" >
      <a href="/login/idp">Login</a>
    </Button>
  )
}

export default StartBtn

此时,我们粘贴 <StartBtn />在首页上,并设置一条响应 http://localhost:3000/signinOK?token=... 的路线通过获取 token ,并将其用作任何后续 bearer: 中的值身份验证,并重定向到主站点。

流程如下:

  1. 用户加载首页,然后单击 <StartBtn/> ;
  2. 链接已重定向,感谢 setupProxy.js到 Express 服务器;
  3. Express 服务器尝试 Passport-SAML身份验证;
  4. 身份验证过程的结果是从 IdP(PingID 身份验证服务器)到 /assert 上的 Express 服务器的 POST 调用。路线。
  5. 结果要么成功,要么失败,但在这两种情况下都会重定向到 React 应用。
  6. 如果成功,用户详细信息将以 JWT 形式返回;或
  7. 如果失败,则会返回错误。

如果我能找到改进它的方法,或者在 JWT 阶段进行扩展,我会回到这个答案。

我希望有人(a)发现这很有用,或者(b)发明了一台时间机器,回去并在三周前发布了这篇文章,这样我就可以保存更多剩余的毛囊。或者 (c) 告诉我应该如何做。

关于node.js - 如何使用 Passport 验证 React 应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61431902/

相关文章:

javascript - ExpressJS 后数据验证和强制数据类型

javascript - Node.js - 无法运行项目

javascript - 如何将数组复制到从某个单元格开始的列的每个单元格,而不是复制到一行的每个单元格?

javascript - 这个 useReduxState hook 和 useSelector 在性能方面的区别?

javascript - 将 html 从变量返回到 React &lt;input/> 的值中

angularjs - Angular 路由不适用于 Express

angularjs - Node jS API 和 Angular jS 应该在同一个项目中吗?

node.js - Node js 上的 Heroku 登录问题

javascript - react 应用程序,根据下拉选项加载组件

node.js - 与 socket.io 一起使用时,express-session 未设置 session cookie