Как аутентифицировать приложение React по паспорту? - PullRequest
1 голос
/ 25 апреля 2020

У меня есть приложение React.

У меня также есть сервер Express, с помощью которого passport-saml я могу аутентифицироваться на PingID SSO IdP компании.

Я хотел бы получить приложение React для вызова приложения Express для проверки подлинности.

Сервер Express и приложение 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;

Я могу нажать счастливо на кнопке, и консоль показывает, что сервер GET Express запущен, но клиент React ничего не возвращает.

Проксирует ли путь к go? У меня есть правильный подход? Если да, как мне вернуть результат из приложения Express в приложение React?

1 Ответ

0 голосов
/ 26 апреля 2020

У меня есть решение, но это похоже на ужасный взлом. Тем не менее, это работает, и мне нужно получить это через черту. Если кто-нибудь может предложить улучшение или альтернативный подход, я был бы признателен.

Мы начнем с базового c 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=..., захватывая токен, используя что в качестве значения в любой последующей bearer: аутентификации и перенаправления на основной сайт.

Поток выглядит следующим образом:

  1. Пользователь загружает первую страницу и нажимает <StartBtn/>;
  2. Ссылка перенаправляется благодаря setupProxy.js на сервер Express;
  3. Express сервер делает попытку аутентификации Passport-SAML;
  4. Результат Процесс аутентификации - это POST-вызов от IdP (сервера аутентификации PingID) к * 1 Сервер 048 *, на маршруте /assert.
  5. Результат либо успешен, либо неудачен, но в обоих случаях перенаправляет в приложение React.
  6. В случае успеха данные пользователя возвращаются как JWT; или
  7. В случае сбоя возвращается ошибка.

Я вернусь к этому ответу, если смогу найти способы его улучшить, или расширить на этапе JWT .

Я надеюсь, что кто-то либо (а) сочтет это полезным, либо (б) придумает машину времени, вернется и отправит сообщения за эти 3 недели go, чтобы я мог сэкономить больше своих оставшихся волосяные фолликулы. Или (c) говорит мне, как я должен был это сделать.

...