indigo-iam/iam

Problem with 'preferred_username' Claim Missing in Access Token from Indigo IAM for iRODS HTTP API Authorization

Opened this issue · 16 comments

Hello,
I am attempting (with @sigau) to obtain the access token to be used as a bearer for fetching the iRODS HTTP API endpoints with the 'preferred_username' claim, but without success. Let me simplify the context and the steps:

  • I registered 2 clients in Indigo IAM instance: FITS (the portal) and iRods HTTP API, with exactly the same configuration
  • I set IAM_ACCESS_TOKEN_INCLUDE_AUTHN_INFO=true.
  • The FITS client requests an authentication grant from the Indigo IAM during the login step and exchanges it for the access token.
  • When I test by adding 'scope' => 'openid profile' in this request, the returned access token includes the preferred_username claim.
  • If the user on the FITS client clicks on "storage," a route controller executes a GET request to the iRODS HTTP API authentication endpoint (http://***:9000/irods-http-api/0.2.0/authenticate).
    The instance of the iRODS HTTP API has been configured to know the Indigo IAM (OIDC provider) well-known endpoint:
"openid_connect": {
    "timeout_in_seconds": 3600,
    "provider_url": "https://fits-indigo-iam-test.in2p3.fr",
    "client_id": "<irods http api client_id in Indigo IAM>",
    "redirect_uri": "https://<fits client>/iam/irods/callback",
    "irods_user_claim": "preferred_username",
    "tls_certificates_directory": "/etc/ssl/certs",
    "state_timeout_in_seconds": 600
}
  • Therefore, when the FITS client makes a GET request to http://***:9000/irods-http-api/0.2.0/authenticate, the iRODS HTTP api client can return the location key with its value corresponding to the Indigo IAM path page for the user to authorize iRODS.
  • The FITS client intercepts the location value and redirects the user to it.
  • After authorization, we return to the redirect URI set in the iRODS API conf https://<fits client>/iam/irods/callback, where the FITS client intercepts the authorization grant generated by Indigo IAM and uses it to request the access token to fetch the iRODS HTTP API endpoints.

Here is the problem:

Even when adding 'scope' => 'openid profile' in this request and passing the client_id and secret of the iRODS HTTP API client in the body of the request, the access token in the response doesn't contain the preferred_username needed for mapping the iRODS user with the IAM user.

What am I doing wrong ?
Thanks for your help

Hi,
first of all, your client has the profile scope checked in its configuration? if it is only requested during token request, it does not appear in the AT and consequently you cannot get the preferred_username claim in the AT.

yes both fits and irods_http_api clients are configured in Indigo IAM instance with the scope profile checked.

if it is only requested during token request, it does not appear in the AT and consequently you cannot get the preferred_username claim in the AT.

I'm not sure to understand ...

If I understand correctly, you use the authorization code grant type to obtain the access token, right? or client_credentials flow?

I think the issue lies in the authorization grant that is issued by Indigo IAM following the GET request from the FITS client to the authentication endpoint of the iRODS HTTP API. Even though the irods_http_api client is configured with the "profile" scope in the Indigo IAM instance, Is it possible that the authorization grant issued to the redirect URI specified in the configuration of the iRODS HTTP API does not allow the "profile" scope?

If I understand correctly, you use the authorization code grant type to obtain the access token, right? or client_credentials flow?

yes that's correct. We use the authorization code grant type

I think the issue lies in the authorization grant that is issued by Indigo IAM following the GET request from the FITS client to the authentication endpoint of the iRODS HTTP API. Even though the irods_http_api client is configured with the "profile" scope in the Indigo IAM instance, Is it possible that the authorization grant issued to the redirect URI specified in the configuration of the iRODS HTTP API does not allow the "profile" scope?

It is possible. Could you copy us the url before giving the grant? It should contain the list of the requested scopes

I fixed it but have another problem.

How I fixed it

I checked the source code of the iRODS http api and found that the authenticate endpoint create an url with just the openid scope

body_arguments args{
    {"client_id",
        irods::http::globals::oidc_configuration().at("client_id").get_ref<const std::string&>()},
    {"response_type", "code"},
    {"scope", "openid"}, // HERE !
    {"redirect_uri",
        irods::http::globals::oidc_configuration().at("redirect_uri").get_ref<const std::string&>()},
    {"state", state}};

and since I can't modify the source code I found a way to add the profile scope to the authentication url whan it is intercepted by the FITS client (Symfony portal) before the user is redirect to that url

            $headers = ['Content-Type' => 'application/json'];
            $response = $client->request('GET', 'http://134.158.225.148:9000/irods-http-api/0.2.0/authenticate', [
                'headers' => $headers,
            ]);

            if ($response->getStatusCode() === 302 && $response->getHeaders(false)['location']) {
                $authorizationUrl = $response->getHeaders(false)['location'][0];
                $this->logger->debug('IRODS AUTHORIZATION URL: ' . $authorizationUrl);
                $authorizationUrl = str_replace('scope=openid', 'scope=openid%20profile', $authorizationUrl);
                $this->logger->debug('IRODS AUTHORIZATION UPDATED URL: ' . $authorizationUrl);
                return new RedirectResponse($authorizationUrl);
                

this way the access token includes the preferred_user claim.

Now I have another problem

when I use it as bearer in fetching the iRODS api collection_list endpoint, the token is not recognized.
These are the logs on iRODS http API side (I remove the acces token for security reason but I checked and I can't assure that it contains the preferred_username that matches an existing iRODS user's username):

resolve_client_identity: Authorization value: [Bearer eyJra***]
[2024-02-06 16:00:44.873] [P:1] [debug] [T:1] resolve_client_identity: Bearer token: [eyJra***]
[2024-02-06 16:00:44.873] [P:1] [error] [T:1] resolve_client_identity: Could not find bearer token matching [eyJra***].

here below the bearer access token claims

{
  "sub": "***",
  "iss": "https://fits-indigo-iam-test.in2p3.fr",
  "groups": [],
  "preferred_username": "***",
  "organisation_name": "indigo-IAM-instance-auto",
  "client_id": "<irods_api_client_id>",
  "nbf": 1707235244,
  "scope": "openid profile",
  "name": "...",
  "exp": 1707238844,
  "iat": 1707235244,
  "jti": "***"
}

but on Indigo side I have these logs:

 : {"@type":"IamAuthenticationSuccessEvent","timestamp":1707235244751,"category":"AUTHENTICATION","principal":"<irods_api-client_id> ,"message":"<irods_api-client_id> authenticated succesfully","sourceEvent":{"principal":"<irods_api_client_id>","type":"InteractiveAuthenticationSuccessEvent"},"source":"UsernamePasswordAuthenticationToken"}

what the problem could be related to?

I just found out while checking checking the AT that it seems that the signature is not valid (testing with JWT).
I paste the token here, since the client_id of the irods api is just a test and there is no security problem so that you can verify on your side:

eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI3M2YxNmQ5My0yNDQxLTRhNTAtODhmZi04NTM2MGQ3OGM2YjUiLCJpc3MiOiJodHRwczpcL1wvZml0cy1pbmRpZ28taWFtLXRlc3QuaW4ycDMuZnIiLCJncm91cHMiOltdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsIm9yZ2FuaXNhdGlvbl9uYW1lIjoiaW5kaWdvLUlBTS1pbnN0YW5jZS1hdXRvIiwiY2xpZW50X2lkIjoiZTRjZWZhNGYtZDc0YS00NGViLWJhZTktMjNiNGFkMjQzNGYyIiwibmJmIjoxNzA3MjM1MTQ0LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIiwibmFtZSI6IkFkbWluIFVzZXIiLCJleHAiOjE3MDcyMzg3NDQsImlhdCI6MTcwNzIzNTE0NCwianRpIjoiYTI0MTM1NmQtNDRkNy00NzgyLWI5ZDYtNTUyYTQzMjgzMjUyIn0.h4HPnq8BWiWY7B5qFQ95U16PVzXz95vTVmGz80F-ydvthR8pMmKu09hh34HpPPcgHt9TpgQRrMifpZ_99uymp1y_yVOX_Hb3BTndhhfUEoqOk1aPILHOw8-yLHMcnPSEPZoJphI7d4wDzexVTSH6HWrFdB0Onx3ptJvMkiFPzUs

do you have an idea of why the signature could be not valid in this case?
Thanks for your help

Hi, I've just checked with this online tool https://jwt.davetonge.co.uk/ and the signature of the token is valid. I think the problem lies elsewhere!

Thanks for confirming that.
I got this answer back from iRODS team:

This is because the iRODS HTTP API does not act as an OAuth protected resource at this moment.
It is being worked on in #155. Once it's done, you should be able to use the HTTP API as you are trying to here.
Currently, the HTTP API needs the authorization code, since we assume we get it back after the user authenticates.
From there, we extract the irods_user_claim, and then give back an 'iRODS HTTP API' token, which is in the format of a UUID.
All further requests to the other iRODS HTTP API endpoints, like collections, require this UUID token at the moment.

At this point I'm a bit confused. Do you think that there is a way to let the thing work with this limit on irods api side?
Here the full exchange

Hi,

from our point of view the next implementation is the correct OAuth flow to be used

  • FITS interface to log-in with IAM and get token
  • iRODS storage system that acts as an OAuth resource, meaning it has to validate the token and give access according with some policy (here, claim mapping).
    In the second case, another client wouldn't even be needed, since iRODS can validate the token offline, once known the issuer public key. In case of online validation, instead the client is needed to access to the introspection endpoint of the issuer (IAM).

So, if the PR do not take long to be included, I would wait for iRODS release.

Anyway, we noticed here that if the claim used for mapping is not contained in the token iRODS looks for preferred_username, so what happens if you set by chance a random claim string?

I'm not sure I understand the whole architecture of your application (FITS + iRODS), but one thing that you can try as a short-term workaround is to manage the OAuth "front channel" (i.e. the interaction with the user, up to obtaining the authorizazion code) in FITS and the "back channel" in iRODS, with FITS passing the authorization code to iRODS, which then goes to the /token endpoint to get the needed tokens. Of course, for this to possibly work, the two need to share the same client id and client secret.

trel commented

The link quoted above points to this repository rather than the correct location...
irods/irods_client_http_api#155

We welcome input and use cases as we continue to build out our API.

I'm not sure I understand the whole architecture of your application (FITS + iRODS), but one thing that you can try as a short-term workaround is to manage the OAuth "front channel" (i.e. the interaction with the user, up to obtaining the authorizazion code) in FITS and the "back channel" in iRODS, with FITS passing the authorization code to iRODS, which then goes to the /token endpoint to get the needed tokens. Of course, for this to possibly work, the two need to share the same client id and client secret.

@giacomini Your idea was excellent, and I've been testing it for several hours. However, it appears that the /authenticate endpoint in the irods_http_api does not handle POST requests containing the authorization code in the body. It seems to parse the URL of the callback after authorization, but only if the /authenticate endpoint is set as the redirect_uri in the irods_api config. Therefore, there is no way to intercept the redirect URL, add the "profile" scope, retrieve the authorization code, and send it back to the /authenticate endpoint. This is because we need to set a FITS route as the redirect_uri for this flow, a condition that prevents the authorization code from being sent back to the /authenticate endpoint. It's a kind of logical short circuit. Thanks for your help anyway!

Hi,

from our point of view the next implementation is the correct OAuth flow to be used

  • FITS interface to log-in with IAM and get token
  • iRODS storage system that acts as an OAuth resource, meaning it has to validate the token and give access according with some policy (here, claim mapping).
    In the second case, another client wouldn't even be needed, since iRODS can validate the token offline, once known the issuer public key. In case of online validation, instead the client is needed to access to the introspection endpoint of the issuer (IAM).

So, if the PR do not take long to be included, I would wait for iRODS release.

Anyway, we noticed here that if the claim used for mapping is not contained in the token iRODS looks for preferred_username, so what happens if you set by chance a random claim string?

@federicaagostini thank you Federica. I agree with you. It doesn't seem to be any other possibility now...

The link quoted above points to this repository rather than the correct location... irods/irods_client_http_api#155

We welcome input and use cases as we continue to build out our API.

Thank you, @trel, for being attentive. We will definitely do that.