6

so i'm trying to build an application which uses the sso to authenticate users. here is the workflow :

  • start the app on localhost:3000 (i'm using a react single web application)
  • a popup will be shown (actually the popup will call my node js authentication route localhost:4000/authenticate which redirects the user to the sso authentication page)
  • after authentication the sso server will redirect the user to the node callback route (http://localhost:4000/authenticate/callback)
  • node checks if this is a valid user and returns a success message (actually node will send an html + javascript code to close the popup).
  • if the received message is success we gonna let the user load the app

here's some code :

App.js

 handleLogIn() {
    const msg = loginTab('http://localhost:4000/authenticate');
    msg.then(response => {
      console.log(response)
    });
  }

  render() {


    let loginButton = (<button onClick={this.handleLogIn.bind(this)}>Sign in</button>)

    return (
      <div>
        {loginButton}
      </div>
    )
  }

loginTab.js

const loginTab = (myUrl) => {
  const windowArea = {
    width: Math.floor(window.outerWidth * 0.8),
    height: Math.floor(window.outerHeight * 0.5),
  };

  if (windowArea.width < 1000) { windowArea.width = 1000; }
  if (windowArea.height < 630) { windowArea.height = 630; }
  windowArea.left = Math.floor(window.screenX + ((window.outerWidth - windowArea.width) / 2));
  windowArea.top = Math.floor(window.screenY + ((window.outerHeight - windowArea.height) / 8));

  const sep = (myUrl.indexOf('?') !== -1) ? '&' : '?';
  const url = `${myUrl}${sep}`;
  const windowOpts = `toolbar=0,scrollbars=1,status=1,resizable=1,location=1,menuBar=0,
    width=${windowArea.width},height=${windowArea.height},
    left=${windowArea.left},top=${windowArea.top}`;

  const authWindow = window.open(url, '_blank', windowOpts);
  // Create IE + others compatible event handler
  const eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent';
  const eventer = window[eventMethod];
  const messageEvent = eventMethod === 'attachEvent' ? 'onmessage' : 'message';

  // Listen to message from child window
  const authPromise = new Promise((resolve, reject) => {
    eventer(messageEvent, (msg) => {
      if (!~msg.origin.indexOf(`${window.location.protocol}//${window.location.host}`)) {
        authWindow.close();
        reject('Not allowed');
      }

      if (msg.data.payload) {
        try {
          resolve(JSON.parse(msg.data.payload));
        }
        catch(e) {
          resolve(msg.data.payload);
        }
        finally {
          authWindow.close();
        }
      } else {
        authWindow.close();
        reject('Unauthorised');
      }
    }, false);
  });

  return authPromise;
};

export default loginTab;

And this the node response :

Authentication.js

router.post(process.env.SAML_CALLBACK_PATH,
    function (req, res, next) {
        winston.debug('/Start authenticate callback ');
        next();
    },
    passport.authenticate('samlStrategy'),
    function (req, res, next) {

        winston.debug('Gsuite user successfully authenticated , email : %s', req.user.email)
        return res.sendFile(path.join(__dirname + '/success.html'));

    }
);

success.html

<!doctype html>
<html lang="fr">
<head>
  <title>Login successful</title>
</head>
<body>
  <h1>Success</h1>
  <p>You are authenticated...</p>
</body>
<script>
  document.body.onload = function() {

    console.log( window.opener.location)
    window.opener.postMessage(
      {
        status: 'success'
      },
      window.opener.location
    );
  };
</script>
</html>

The problem is that after the authentication i couldn't close the popup because of this error :

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://localhost:4000') does not match the recipient window's origin ('http://localhost:3000').

I tried to change the window.opener.location in success.html to 'localhost:3000' and it works perfectly but it's not a good idea for a production environement.

Amdouni Mohamed Ali
  • 410
  • 1
  • 4
  • 18
  • Sounds like a CSP error. Maybe check this out. https://ponyfoo.com/articles/content-security-policy-in-express-apps – Mark Oct 04 '19 at 18:54
  • Thanks for your help Mark but it doesn't seem for me to be a security issue beacause i'm disabling the chorme security ( using this option --disable-web-security) and it works fine if i change window.opener.location to localhost:3000 – Amdouni Mohamed Ali Oct 05 '19 at 13:37
  • https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options That error is definitely from a frame ancestor issue. I just finished a project using the PostMessage API and was throwing that error. This is how I fixed it. – Mark Oct 05 '19 at 15:31
  • Thanks for your comment Mark. I tried to add the Header to my response using frameguard [now i'm receiving this X-Frame-Options: ALLOW-FROM http://localhost:3000 ] but unfortunately i still have the same error. – Amdouni Mohamed Ali Oct 07 '19 at 07:14
  • https://stackoverflow.com/questions/10205192/x-frame-options-allow-from-multiple-domains Read this. This should solve your issue (I hope) =) – Mark Oct 07 '19 at 16:51
  • ```Content-Security-Policy: frame-ancestors 'self' localhost:3000;``` – Mark Oct 07 '19 at 18:10
  • Any luck with this? – Mark Oct 08 '19 at 22:12
  • Hello Mark, I explianed how i solved my issue in the comment below. i don't know if it's the best solution or not. thnks a lot for your time. – Amdouni Mohamed Ali Oct 10 '19 at 15:15

1 Answers1

8

Well, i tried many things and i solved my issue using this technic. In the development environement, i used a star to get it working (it's not a good practice).

window.opener.postMessage(
      {
        status: 'success'
      },
      '*'
    );

And in production, i used the real domain name and not localhost like this :

window.opener.postMessage(
      {
        status: 'success'
      },
      'http://my-server-domain:3000'
    );

Hope this will help someone.

Amdouni Mohamed Ali
  • 410
  • 1
  • 4
  • 18