Shibboleth and Volto?

Can i use Shibboleth with Plone 6 Volto? For a Classic UI Installation i know what i should do, but if i decide me for a Volto Frontend, there are some Problems?

For a Classic UI Installation i know what i should do,

That's interesting to know, how would you add Shibboleth support to Classic UI? If you provide that information, maybe other community members who know more about Volto can assist you with providing information on how to amend / support also the Volto Frontend.

I use the AutoUserMakerPASPlugin. The saml2 part happen in an apache plugin :wink:

2 Likes

We use Shibboleth and Volto with following setup:

  • Backend: pas.plugins.header to use request headers set by Shibboleth to do the authentication
  • nginx*/shibd:
    • Configure /login to enforce Shibboleth login
    • Configure /++api++/@login-renew to provide Shibboleth headers
  • Frontend (Volto): Custom login component that performs loginRenew action to retrieve and store login token in the frontend state

So the login flow is:

  1. User visits secured page
  2. Is redirect to /login
  3. Is forced to do Shibboleth login
  4. Redirected to our custom login component ...
  5. which calls @login-renew, and sets retrieved auth token
  6. and redirects to requested secured page

Changes in Volto:

config.js
config.addonRoutes.push(
    /* replace original login by our Shibboleth component */
    {
      path: ['/login', '/**/login'],
      component: Shibboleth,
    },
    /* but keep local login available */
    {
      path: '**/local-login',
      component: Login,
    },
  );
Shibboleth.jsx
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { useDispatch, useSelector } from 'react-redux';
import { Container, Dimmer, Loader } from 'semantic-ui-react';
import { loginRenew } from '@plone/volto/actions';
import { ContentPlaceholder } from '@package/components';

/* This component calls @login-renew endpoint.
   When user is authenticated via Shibboleth and
   backend is configured properly, client receives
   a login token and user will be logged in.
*/
const Shibboleth = () => {
  const token = useSelector((state) => state.userSession?.token || false);
  if (token && __CLIENT__)
    window.location.href = window.location.href.replace('/login', '');

  const dispatch = useDispatch();
  useEffect(() => {
    if (__CLIENT__) dispatch(loginRenew());
  }, [dispatch]);

  return (
    <Container className="view-wrapper">
      <Dimmer.Dimmable as={Container} dimmed={true}>
        <ContentPlaceholder />
        <Dimmer active={true} className="embedded">
          <Loader size="large" inline="centered" active>
            <FormattedMessage
              id="Login is performed"
              defaultMessage="Login is performed"
            />
          </Loader>
        </Dimmer>
      </Dimmer.Dimmable>
    </Container>
  );
};

export default Shibboleth;

* dont do it with nginx if you can avoid it :wink:

6 Likes

This is incredibly helpful! I was able to get this to work in our setup but I do have a few questions:

  1. Are the Shibboleth headers only sent if you call the /login view or do they come through in every request? If the latter, I am interested in knowing how you make it use the JWT plugin after this point and not the pas.plugins.header plugin - maybe just give it a higher priority in authentication plugins?
  2. Instead of calling @login-renew could you write a new plugin that creates the token? (updateCredentials maybe?) The "benefit" there would be it pushes this off to the plugin manager, instead of explicitly doing it in /login, but admittedly this isn't much and may not be worth it. EDIT - it looks like this isn't practical at all, because you need something to explicitly call updateCredentials. It is not called on every request, as I thought it would be.

We are looking at updating the SSO plugin we use for Classic UI, so that the login process is roughly the same for our Volto and Classic UI sites. Currently our plugin (not pas.plugins.header) just reads the Shibb headers on every request and does not use sessions. I am thinking we should change this to a similar method where some @@login view page authenticates with Shibboleth and then sets a JWT.

To my understanding, the headers generally are in every request but they are sent in the context of the shibboleth session, so the shib IDP send them once and your shibd create the headers at each request, usually in your httpd server that reads them from shibd. So they're not updated "live", you need to recreate a session to get them updated. I think login-renew logs out and logs in again to renew the headers values.

1 Like