node.jsreactjssaml-2.0passport-saml

SAML2.0 Authentication with Node.js and SPA


I've been scratching my head for about 2 days on how to solve what seemed to be a simple task, but it's starting to drive me crazy.

I have an application where users will use SAML 2.0 to authenticate. I have a react-application set up for the front-end, and was thinking I was gonna use JWT to secure the rest-api communication between front-end and backend.

When a user signs in, the flow is the following:

  1. User accesses www.server.com/ and gets served the static HTML with react-application
  2. User clicks 'Sign in' and accesses www.server.com/login
  3. passport-saml redirects user to saml identity provider. User logs in.
  4. User calls back to www.server.com/callback with a SamlResponse in the req.body which is decoded by passport-saml and put into req.user.
  5. If the user doesn't already exist, I create the user in the database.
  6. I create a JWT.

What should I do next? The problem is that the user is not in the react-application when calling back from the identity provider, so I've lost all state in the application, so whatever I reply with will get sent to the browser.

Is there any way I could force the browser to give me the SamlResponse which the identityprovider is calling back with? Then I could send it to the server as a http-request from the react-application.


Solution

  • After some thinking, I came up with the following solution which worked quite nicely for me.

    SAML has something called RelayState which is a property that the Service Provider has to respond with. So now the process looks like this:

    1. User accesses http://frontendserver.com and gets server the static page with the React application (not signed in.).
    2. User clicks 'Login' and gets redirected to http://backendserver.com/login/?RelayState=http://frontendserver.com which authenticates via passport-saml and redirects user to SP. So I pass the origin of the request in RelayState.
    3. User calls back to http://backendserver.com/callback with the SamlResponse, which includes the RelayState.
    4. I create a token, and redirect the user to RelayState/#token.
    5. I can then parse the url in the React application, and add the token as a header for any further requests.

    This might've seemed like the obvious way to do it, but it took me quite a while to figure out that this would work.