quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.41k stars 2.57k forks source link

OidcClientImpl should support JsonPath for extracting the Access Token #40258

Open TheHandOfNOD opened 2 months ago

TheHandOfNOD commented 2 months ago

Description

Currently it is only possible to configure a property which directly access the access token from the response. It is not possible to define nested properties.

If I get a response for example from Personio Auth API then the response structure would look like:

{
  "success": true,
  "data": {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vYXBpLmRldi5wZXJzb25pby5kZTozMDAwMS92MS9hdXRoIiwiaWF0IjoxNDg5MDkxMzA2LCJleHAiOjE0ODkxNzc3MDYsIm5iZiI6MTQ4OTA5MTMwNiwianRpIjoiZmU1ZjkxOGY2MDZjOWI4OGMwMzM0ZmJkZjkyYzkwMzgiLCJzdWIiOiJPR014TVdRd1kySmxZbVF6Tm1RNVpqQmxOell6WmpsaSJ9.QZZCdlDjmL-LYdoDx2XLUfhwTdcjDgm9h4t-6JoACiM",
    "expires_in": 86400,
    "scope": "employees:read absences:read"
  }
}

As you see it would be necessary to define a JsonPath to be able to access the "token" since it's wrapped inside the "data" object. See the code from OidcClientImpl below, and you will see that currently it's not possible to define a value in the configuration property oidcConfig.grant.accessTokenProperty which would be able to handle such a response structure. Same goes for all other attributes. So I would suggest to implement or add a JsonPath functionality to be able to define something like $.data.token as value for the properties.

if (resp.statusCode() == 200) {
            LOG.debugf("%s OidcClient has %s the tokens", oidcConfig.getId().get(), (refresh ? "refreshed" : "acquired"));
            JsonObject json = resp.bodyAsJsonObject();
            // access token
            final String accessToken = json.getString(oidcConfig.grant.accessTokenProperty);
            final Long accessTokenExpiresAt = getExpiresAtValue(accessToken, json.getValue(oidcConfig.grant.expiresInProperty));

            final String refreshToken = json.getString(oidcConfig.grant.refreshTokenProperty);
            final Long refreshTokenExpiresAt = getExpiresAtValue(refreshToken,
                    json.getValue(oidcConfig.grant.refreshExpiresInProperty));

            return new Tokens(accessToken, accessTokenExpiresAt, oidcConfig.refreshTokenTimeSkew.orElse(null), refreshToken,
                    refreshTokenExpiresAt, json);

Implementation ideas

Add or implement a JsonPath functionality to the OidcClientImpl, or at least make the part of the extraction able to be overwritten, so that it it's possible to create a custom OidcClientImpl which only has to implement this specific part.

quarkus-bot[bot] commented 2 months ago

/cc @pedroigor (oidc), @sberyozkin (oidc)

sberyozkin commented 2 months ago

@TheHandOfNOD I've looked at it more carefully, Personio does return an access token in a custom format which we could've handled, but I don't think it is related to OIDC at all, the authentication request does not represent any OIDC grant type. Looks like you need to simply use for example Quarkus REST client to acquire this token and then sent to the API. I'm not sure how this custom authentication flow can be wired in into the OIDC/OAuth2 flows supported by quarkus-oidc and quarkus-oidc-client extensions

sberyozkin commented 2 months ago

@TheHandOfNOD I'd like to get a bit more info about what exactly you are trying to achieve.

According to https://support.personio.de/hc/en-us/articles/360000019129-Choose-the-right-authentication-method-for-your-company, Personio does support OAuth2 and users can be authenticated via Google etc. This case though is about using quarkus-oidc, quarkus.oidc.applicatiion-type=web-app, to have Quarkus users authenticated to Personio and that Quarkus application using the access token to access some Personio API.

Can you confirm please this is the case you'd like to support - and that the only problem is that Personio OAuth2 authorization code response is not a standard one ? I'd be prepared to support this case, we already handle custom claim paths in JSON easily with expressions like a/b.

I do not see any indication in their docs they support other token grants, their v1 Auth API is not OAuth2 aware, this is why I suggest we don't deal with the OIDC client to avoid the confusion.

So my proposal to you is to confirm you can set up Quarkus to get users authenticated to Personio as per their docs, and if necessary we can tweak the code a bit to get the tokens

TheHandOfNOD commented 2 months ago

I am sorry but unfortunately I got suprisingly sick and wasn't able to respond in time. You are right that it might not be really an OIDC concern but since I am using theio.quarkus.oidc.client.runtime.TokensHelper to retrieve the AccessToken I am able to just use the configuration for handling everything I need.

The TokensHelper Class is part of the quarkus-oidc-client library which is why I opened the issue here. The TokensHelper also uses the OidcClient and therefore also the OidcClientImpl and I find it convenient because I do not have to obtain the tokens manually.

Unfortunately Personio uses a nested response structure which is not a standard way to provide the access token and therefore I would need to be able to use a JsonPath expression like mentioned above to just be able to extract the access token via a configured property.

Thus, I only ask for an extension of the OidcClientImpl to support JsonPath which shouldn't do any harm even if it's not required for a standard OIDC response.