SSO: SAML and Shibboleth

Hi,
from the Indico documentation, at the end of the production installation section there is a basic section on SSO in Indico: Apache — Indico 3.2 documentation

But then I noticed that there is also a SAML section under the Authentication chapter (without the need to use Shibboleth): Authentication — Indico 3.2 documentation

We have a inhouse SSO system and we would like to see if there is the possibility to link Indico with it.
Which one of the previous guides should we use as starting point?

Thank you in advance for your time.

Best regards,
Cristiano Urban.

First of all, verify if it’s using any standard (SAML, OIDC, OAuth2, etc.). If it’s one of them, you can use it out of the box.

If you really have something completely custom, you’d have to write a flask-multipass auth/identity provider backend that connects flask-multipass (the tool we use for authentication) and your SSO.

Hi,
thank you for your answer.

We have a custom authorization server that receives OAuth2/OICD from a web page and forwards SAML, OAuth2, OICD to provide different login modes.

SAML is used for EduGAIN, since we have a local Shibboleth SP.

I think Indico could be linked to this authorization server using a OAuth2 client and secret pair, but the point is that we would like to add to Indico a login button only for EduGAIN, not for the other login modes.

In this case, my doubt is: should I install another Shibboleth SP on the Indico VM and then ask for SP registration to EduGAIN?

Or, is there a simpler way to link Indico to EduGAIN, just to allow users to login using EduGAIN?

Thank you in advance for your time.

I never used edugain directly, if it does saml you have to use either the saml or shibboleth backend …
If edugain has a OIDC option it’d certainly be easier, but I somehow doubt they have one :wink:

I managed to connect Indico (test installation) to our SSO system, even if I obtain an error in the OAuth2 flow, more specifically when the client tries to retrieve the token.

This i what i do:

  1. Click on SSO login button on Indico
  2. This redirects me to our authentication portal: here I click on eduGAIN button and then I’m redirected to our organization IdP
  3. I insert my credentials in the form and then I’m redirected back to Indico
  4. At this point I obtain the following error: Something went wrong HTTPError: 401 Client Error: Unauthorized for url: https://<base_autorization_server_url>/auth/oauth2/token

From the logs I see the following (I’ve obfuscated some fields):

2022-10-12 10:19:24,388  INFO     594cf5d2c5f64e93  -       indico.rh                 GET /multipass/authlib/rap-sso?code=<the_code>=&scope=openid%20email%20read:gms%20read:rap&state=<the_state_string> [IP=XX.YYY.ZZ.K] [PID=2014]
2022-10-12 10:19:24,451  ERROR    594cf5d2c5f64e93  -       indico.flask              401 Client Error: Unauthorized for url: https://<base_autorization_server_url>/auth/oauth2/token
Traceback (most recent call last):
  File "/opt/indico/.venv/lib/python3.9/site-packages/flask/app.py", line 1517, in full_dispatch_request
    rv = self.dispatch_request()
  File "/opt/indico/.venv/lib/python3.9/site-packages/flask/app.py", line 1503, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/web/rh.py", line 333, in wrapper
    return rh.process()
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/web/rh.py", line 281, in process
    res = self._do_process()
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/web/rh.py", line 252, in _do_process
    rv = self._process()
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/web/rh.py", line 322, in _process
    rv = self.func()
  File "/opt/indico/.venv/lib/python3.9/site-packages/flask_multipass/util.py", line 119, in decorator
    return func(*args, **kwargs)
  File "/opt/indico/.venv/lib/python3.9/site-packages/flask_multipass/providers/authlib.py", line 119, in _authorize_callback
    token_data = self.authlib_client.authorize_access_token()
  File "/opt/indico/.venv/lib/python3.9/site-packages/authlib/integrations/flask_client/apps.py", line 103, in authorize_access_token
    token = self.fetch_access_token(**params, **kwargs)
  File "/opt/indico/.venv/lib/python3.9/site-packages/authlib/integrations/base_client/sync_app.py", line 341, in fetch_access_token
    token = client.fetch_token(token_endpoint, **params)
  File "/opt/indico/.venv/lib/python3.9/site-packages/authlib/oauth2/client.py", line 202, in fetch_token
    return self._fetch_token(
  File "/opt/indico/.venv/lib/python3.9/site-packages/authlib/oauth2/client.py", line 353, in _fetch_token
    resp.raise_for_status()
  File "/opt/indico/.venv/lib/python3.9/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://<base_autorization_server_url>/auth/oauth2/token

It seems the client receives the code (I can see it on the URL bar of the browser), but for some reason it cannot exchange it for the token.

My configuration is the following:

AUTH_PROVIDERS = {
    'rap-sso': {
        'type': 'authlib',
        'title': 'SSO',
        'authlib_args': {
            'client_id': 'my_client_id',
            'client_secret': 'xxxxxxxxx',
            'client_kwargs': {'scope': 'openid email read:gms read:rap'},
            'authorize_url': 'https://<base_autorization_server_url>/auth/oauth2/authorize',
            'access_token_url': 'https://<base_autorization_server_url>/auth/oauth2/token',
            'userinfo_endpoint': 'https://<base_autorization_server_url>/ws/user',
            'callback_uri': 'https://<IP_of_Indico_test_installation>/multipass/authlib/rap-sso'
        }
    }

IDENTITY_PROVIDERS = {
    'rap-sso': {
        'type': 'authlib',
        'title': 'SSO',
        'identifier_field': 'email',
        'mapping': {
            'first_name': 'given_name',
            'last_name': 'family_name'
        },
        'trusted_email': True,
        'synced_fields': {'first_name', 'last_name'}
    }
}

MULTIPASS_PROVIDER_MAP = {
    'rap-sso': 'rap-sso'
}

A similar issue was encountered by a developer that tried to communicate with our authorization portal via OAuth2.
At that time he obtained an unauthorized error because he was sending client_id and secret into the body of the HTTP request and not in the header. As in this case, he was able to receive the code, but not to obtain the token.

At that time he solved by setting the following header:

"Authorization": "Basic " + b64encode(client_id:client_secret)

Is this modification possible in some way?

Thank you in advance,
Cristiano.

Try adding 'token_endpoint_auth_method': 'client_secret_basic' to authlib_args, even though that should already be the default according to the authlib code…

        if token_endpoint_auth_method is None:
            if client_secret:
                token_endpoint_auth_method = 'client_secret_basic'
            else:
                token_endpoint_auth_method = 'none'

Just added and reloaded the services.
Nothing seems to change, I obtain the same error…

In that case I recommend adding some debug print statements to the locally-installed versions of these files to see what’s actually being sent:

Note: Do this on a dev server, not your prod setup. Getting print output is much easier there.

Oook. So, the problem was related to some special characters in the client secret.
I changed the secret and redeployed the configuration of our authentication portal.

Then Indico complained about “Missing jwks_uri”, so I fixed by adding the following entry to the ‘authlib_args’:

'jwks_uri': 'https://<base_autorization_server_url>/auth/oidc/jwks'

Another way is to achieve the same result is to add the following entry in place of ‘jwks_uri’:

'server_metadata_url': 'https://<base_autorization_server_url>/.well-known/openid-configuration'

Choosing one of the options here above seems to be equivalent.

Now I’m stuck on another error and trying to investigate what is happening under the hood.

At the login Indico says: Login failed: invalid_claim: Invalid claim “iss”.
The authlib used by Indico is the latest version v1.0.1, so this should not be the problem.

From the logs I get the following:

2022-10-14 08:26:09,265  INFO     00b0f02a903840fc  -       indico.rh                 /multipass/authlib/rap-sso?code=<the_code>=&scope=openid%20email%20read:gms%20read:rap&state=<the_state_string> [IP=XX.YYY.ZZ.K] [PID=11723]
2022-10-14 08:26:09,495  ERROR    00b0f02a903840fc  -       indico.auth               Authentication via rap-sso failed: invalid_claim: Invalid claim "iss" (None)
2022-10-14 08:26:09,655  INFO     2012da9b8bc0422f  -       indico.rh                 GET /login/ [IP=XX.YYY.ZZ.K] [PID=11723]

If I figure out what’s the issue I will report it here.

So, with the same configuration, then I received a similar error: Authentication via rap-sso failed: missing_claim: Missing " nonce" claim (None).

It seems that nonce claim is not supported by our authentication/authorization server.

I removed the code within the validate_nonce function in /opt/indico/.venv/lib/python3.9/site-packages/authlib/oidc/core/claims:

def validate_nonce(self):
    pass

and restarted Indico services.

At that point I was able to login!

TBH, removing checks in a security-related library sounds dangerous and a good way to compromise security. I don’t know by heart what exactly the nonce in OIDC does, but when removing such checks you could expose yourself e.g. to CSRF attacks.

Do you have any details on what backend your SSO uses? Is it something custom or something standard (Google, Microsoft, Keycloak, etc.)?

PS: If you have a server_metadata_url always use that and omit the other URLs from the config. The server metadata contains all the relevant information, so metadata uri, client id, client secret and possibly scopes are all you need.

TBH, removing checks in a security-related library sounds dangerous and a good way to compromise security. I don’t know by heart what exactly the nonce in OIDC does, but when removing such checks you could expose yourself e.g. to CSRF attacks.

Yes, maybe this is not a very good idea, in fact I’m doing these tests on a clean test installation behind a firewall.

Do you have any details on what backend your SSO uses? Is it something custom or something standard (Google, Microsoft, Keycloak, etc.)?

It is a custom software written by two developers who no longer work with us. It seems that the nonce claim is not supported. If I check the “server_metadata_url” I don’t see the nonce in the list of supported claims.

I also tried the following configuration:

'rap-sso': {
        'type': 'authlib',
        'title': 'SSO',
        'callback_uri': '/multipass/authlib/rap-sso', 
        'authlib_args': {
            'server_metadata_url': 'https://<base_url>/.well-known/openid-configuration',
            'client_id': 'my_client_id',
            'client_secret': 'xxxxxxxxx',
            'client_kwargs': {'scope': 'openid email profile read:gms read:rap'}
        }
    }

But then, again, I receive the error: Login failed: invalid_claim: Invalid claim “iss”
The iss seems to be ‘None’ from the Indico logs.

I need to investigate more on this point.