Volto SAML authentication

Hi everybody,

I have been looking for solid documentation on configuring SAML based authentication (azure) with Volto. There are backend solutions, but they require users to logon to the classic ui. Please correct me if I am wrong.

The goal is to have a closed down site (intranet) and users must be automatically logged on based on SAML. When they hit the Volto UI and aren't logged on, the SAML authentication must be started automatically.

Is there an existing Volto add-on that I haven't found yet that does the trick? Am I right that the requirements would require a Volto add-on solution?

I am willing to document my findings and installation setup in training / plone docs. Currently working with latest Plone/Volto.

Best,
Nils

Progress has been made. I would like to invite interested community members to work on a solid volto/plone saml2 integration. From what I've read so far, is that @riker11451 has managed to get it to work with Volto. So it would be extremely helpful if he could chime in on this.

Currently I am at the stage that:

For more background on the setup and nginx configuration, please read this post: Accessing the classic ui on a deployed CookiePlone instance - #4 by ghnire

The next steps include:

  1. Create a button in a Volto add-on to kick off the saml2 authentication
  2. Make use of the saml2 backend integration and somehow let Volto know the user is authenticated (or not)
  3. Replace the button for an automatic redirect for anonymous users

If anyone has any experience in this area, please provide some guidance. I am merely an IT manager trying to make sense of it all. However, if we can come up with a solid solution, I will put in the time to document and test everything. Having Saml2 as an integrated add-on using the preferred Volto/Plone combination, will lower the threshold for adopting this wonderful CMS.

Here are some of my experiences with trying to to implement SSO on Plone6 (Classic) two years ago.

Also possibly interesting, in case you haven't already seen...

check the code on pas.plugins.oidc to check how this addon creates a restapi ticket in order to authenticate Volto users.

You can also check voltoko-authomatic to see how the addon uses the rest api endpoints exposed by pas.plugins.authomatic.

@mtrebron and @erral , thanks for chiming in.
That was exactly what i am doing right now. Diving into volto-authomatic and pas.plugins.oidc code.

And Norbert, I have read most of the resources you posted. Unfortunately, not one of them is describing an exact "how to". So, diving into the code right now.

Progress was made after analyzing the volto-authomatic, pas.plugins.authomatic and wcs.samlauth code. I did a wireshark trace to follow the mechanism of volto-authomatic <-> pas.plugins.authomatic and tracked it in the code. Very good learning experience.

The funny thing is however, I was able to solve my problem using rewrite rules in nginx. I still need to tweak and optimize the rules, but they currently work.

So, in short, what I did was:

  • install Nginx
  • install wcs.samlauth as plugin for your backend
  • setup Azure enterprise application
  • set Identifiers:
    https://[your fqdn]/acl_users/[your wcs.samlauth app name]/metadata
  • set Reply URLs:
    https://[your fqdn]/acl_users/[your wcs.samlauth app name]/acs
  • set Sign On URL and Relay State to:
    https://[your fqdn]
  • set Logout URL:
    https://www.example.com/acl_users/[your wcs.samlauth app name]/logout
  • Download the metadata file from the azure app and import it into wcs.samlauth configuration
  • Be sure to enable the "Create API Session" option in your wcs.samlauth app
  • Apply rewrite rules in nginx

Users can now access the site and automatically logon using saml by pointing their browser to:
https://www.example.com/start

This is the configuration in nginx:

upstream backend {
    server localhost:8080;
}
upstream frontend {
    server localhost:3000;
}

server {
  listen              80;
  listen              443 ssl;
  server_name         www.example.com;

  ssl_certificate     /etc/ssl/certs/mycert.crt;
  ssl_certificate_key /etc/ssl/private/mykey.key;
  ssl_protocols       TLSv1.3;
  ssl_ciphers         HIGH:!aNULL:!MD5;
  keepalive_timeout   70;

  client_max_body_size 1G;

  access_log /var/log/nginx/plone_access.log;
  error_log /var/log/nginx/plone_error.log;

  # [seamless mode] Recommended as default configuration, using seamless mode new plone.rest traversal
  # pnpm build && pnpm start:prod
  location ~ /\+\+api\+\+($|/.*) {
      rewrite ^/\+\+api\+\+($|/.*) /VirtualHostBase/https/www.example.com/Plone/++api++/VirtualHostRoot/$1 break;
      proxy_pass http://backend;
  }
  location ~ /acl_users/[your wcs.samlauth app name]($|/.*) {
      rewrite ^/acl_users/[your wcs.samlauth app name]($|/.*) /VirtualHostBase/https/www.example.com/Plone/VirtualHostRoot/acl_users/[your wcs.samlauth app name]/$1 break;
      proxy_pass http://backend;
  }
  location ~ /start {
      rewrite ^/start /VirtualHostBase/https/www.example.com/Plone/VirtualHostRoot/acl_users/[your wcs.samlauth app name]/sls break;
      proxy_pass http://backend;
  }

  # Legacy deployment example, using RAZZLE_LEGACY_TRAVERSE Volto won't append ++api++ automatically
  # Recommended only if you can't upgrade to latest `plone.restapi` and `plone.rest`
  # pnpm build && RAZZLE_API_PATH=http://myservername.org/api RAZZLE_LEGACY_TRAVERSE=true pnpm start:prod
  # location ~ /api($|/.*) {
  #     rewrite ^/api($|/.*) /VirtualHostBase/http/myservername.org/Plone/VirtualHostRoot/_vh_api$1 break;
  #    proxy_pass http://backend;
  # }

  location ~ / {
      location ~* \.(js|jsx|css|less|swf|eot|ttf|otf|woff|woff2)$ {
          add_header Cache-Control "public";
          expires +1m;
          proxy_pass http://frontend;
      }
      location ~* static.*\.(ico|jpg|jpeg|png|gif|svg)$ {
          add_header Cache-Control "public";
          expires +1m;
          proxy_pass http://frontend;
      }

      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;
      proxy_redirect http:// https://;
      proxy_pass http://frontend;
  }
}
3 Likes

@ghnire instead of using rewrite rules in nginix you can use the proxy in volto like volto-authomatic does. That way the solution is more out of the box.

Btw, one thing to note is currently having more than one backend SSO provider doesn't seem to work well. At least you can't easily have authomatic and oidc installed at the same time. Because they use the same adapter interface.
There really needs to be a standard restapi to list auth endpoints so they appear in the login dialog and don't clash on the backend.

Hi Dylan,
Thanks for the feedback.
I am currently working on a volto addon for saml that mimics volto-authomatic. The rewrite rules work, but having the functionality as plugin seems more sustainable and expandable to me.

However, I am new to react so it will take me some time.

It is true you have to choose between pas.plugins.oidc and pas.plugins.authomatic, they cannot co-exist.

For my use case however, I only need to support Saml with 1 identity provider. All users are on the same provider (azure).

Once I have a better understanding of the mechanics in place, we could work on a more robust solution for Saml, if you're interested.

@ghnire my recommendation would be to add functionality to volto-authomatic rather than create a new plugin.
That is what GitHub - collective/pas.plugins.oidc: PAS plugin for OpenID Connect authentication did.
It means volto-authomatic needs to be renamed since it's name doesn't imply a frontend for multiple backend auth apis but I think it makes sense.
The volto part for all these plugins is roughly the same. Override the login to push a button that takes you to another site that then redirects you back again. + plus a little bit of middleware to proxy some auth specific requests.
The first part is all done for you with volto-authomatic so I think there would be less react needed to reuse it and add to it. and it's done in a generic way where it gets a list of auth descriptions and presents a button for each one. So it would not be hard to add to it.

It is true you have to choose between pas.plugins.oidc and pas.plugins.authomatic, they cannot co-exist.

We have a PR to fix that somewhere but looks like this idea is a much better way to do it - add new @login endpoint to return available external login options by erral · Pull Request #1757 · plone/plone.restapi · GitHub

you are right that most people don't have the case of needing to support more than one way to auth. but some do so it would be nice when this is supported.