Как избежать кругового входа в систему с использованием аутентификации по паспорту

#node.js #passport.js #saml-2.0

Вопрос:

Я настраиваю единый вход с паспортом через SAML2 с помощью серверной части nodejs/express. Я оказываюсь в какой-то петле..

Процесс происходит следующим образом:

Когда я захожу в свое приложение, оно вызывает whoami маршрут, определенный ниже. При первом проходе это должно (и возвращает) 401 (несанкционированный). Когда это вернется с 401 (несанкционированным) в мое приложение, мы перенаправим приложение на login . login Маршрут, определенный ниже, запоминает наш исходный URL-адрес (чтобы мы могли вернуться к нему) RelayState и запускается passport.authenticate(....) перед перенаправлением обратно в приложение. Я признаю, что не знаю, как и почему мы перенаправляем обратно в приложение здесь — и удаление его не решает проблему, однако на самом деле оно обращается к поставщику единого входа (определенному в config.saml.options объекте). Поставщик единого входа творит чудеса и возвращается к /login/callback маршруту, определенному ниже. В этом запросе содержится вся необходимая информация. … однако… затем мое приложение перезапускает процесс… создавая цикл: WhoAmI -> Вход ->> Обратный вызов ->>> WhoAmI ->>>> Вход — > > > > Обратный вызов

Насколько я понимаю, в какой-то момент серверная часть должна сохранять пользователя локально, и последующий whoami вызов вернет желаемую информацию.

Как я могу остановить эту циклическую обработку.

Мой passport.js файл:

 import fs from 'fs';
import passport from 'passport';
import { Strategy } from 'passport-saml';
import config from './config.js';

const savedUsers = [];
passport.serializeUser((expressUser, done) => {
    done(null, expressUser)
});
passport.deserializeUser((expressUser, done) => {
    done(null, expressUser)
});

passport.use(
    new Strategy(
        {
            issuer: config.saml.issuer,
            protocol: 'https://',
            path: '/auth/login/callback',
            entryPoint: config.saml.entryPoint,
            cert: fs.readFileSync(config.saml.cert, 'utf-8'),
            authnContext: ["urn:federation:authentication:windows"],
            identifierFormat: null,  //if omitted the identifier format defaults to urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
        },
        (expressUser, done) => {
            if (savedUsers.includes(expressUser)) {
                savedUsers.push(expressUser)
            }
            return done(null, expressUser)
        }
    )
)
 

Мои определения маршрутов для аутентификации SAML следующие:

 import express from 'express';
import config from '../config/config.js';

import '../config/passport.js'
import session from 'express-session'
import passport from 'passport'

const samlRouter = express.Router();

samlRouter.use(session(config.session));
samlRouter.use(passport.initialize());
samlRouter.use(passport.session());

samlRouter.use((req, res, next) => {
    console.log("Use");
    res.header('Access-Control-Allow-Origin', req.header('origin'));
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-Width, Content-Type, Accept, Authorization');
    res.header('Access-Control-Allow-Credentials', 'true'); //SAML

    if (req.method == 'OPTIONS') {
        res.header('Access-Control-Allow-Methods', 'PUT, POST, GET');
        return res.status(200).json({})
    }
    next();
})

samlRouter.get('/whoami', (req, res, next) => {
    console.log('whoami');
    if (!req.isAuthenticated()) {
        return res.status(401).json({
            message: 'Unauthorized',
        });
    }
    return res.status(200).json({ user: req.user });
});


samlRouter.get('/login',

    (req, res, next) => {
        console.log('login');
        req.query.RelayState = req.query.origURL;
        passport.authenticate('saml', config.saml.options)(req, res, next)
    },
    (req, res, next) => {
        return res.redirect(`${config.app.location}:3000`);
    }
); 


samlRouter.post('/login/callback',
(req, res, next) => {
        console.log('login/callback');
        passport.authenticate('saml', config.saml.options)(req, res, next)
    },
    (req, res, next) => {
        if(req.user) {
            console.log('req.user.nameID :>> ', req.user?.nameID);   **// THIS OUTPUTS THE CORRECT INFO**
        } else {
            console.log('req.user was falsey');
        }
        
        console.log(`redirect to Relay state ${req.body.RelayState}`);
        return res.redirect(`${req.body.RelayState}`);
    });


export default samlRouter;
 

Мой код приложения для получения пользователя выглядит следующим образом:

 export function getUser() {  
    console.log('Getting user...');
    const RedirectToLogin = () => {
        console.log("Redirecting to xxxx/login...")
        window.location.assign(`https://xxxx/login?origURL=${encodeURIComponent(window.location.href)}`)
    }

    return axios({
        method: 'GET',
        url: 'https://xxxx/whoami',
        withCredentials: true
    })
        .then(resp => {    // WE NEVER GET HERE...
            if (resp.data.user) {
                console.log(`User Authenticated as ${resp.data.user.nameID}`);
                return Promise.resolve(resp.data.user.nameID)
            }
            else {
                console.warn("Unauthenticated - redirecting to login")
                RedirectToLogin()
            }
        })
        .catch(err => {
            console.log('err :>> ', err);
            console.warn("Unauthenticated -- redirecting to login")
            RedirectToLogin()
        })
}
 

Комментарии:

1. Мне интересно посмотреть, как вы это решили!

2. Я не уверен, что сделал это правильно, но эффективно в приложениях, которые я называю, я включаю отпечаток пальца браузера. Серверная часть ищет в таблице этот отпечаток пальца. Если он найдет его, он просто вернет правильную информацию. Если это не так, он продолжает процесс единого входа. … таким образом, во второй раз приложение отправляет отпечаток пальца, и серверная часть знает, кто этот человек… без необходимости продолжать цикл единого входа.