irods / irods_client_http_api

An iRODS C++ HTTP API using Boost.Beast
BSD 3-Clause "New" or "Revised" License
0 stars 8 forks source link

Optional ID Token Validation Requirements #361

Open MartinFlores751 opened 2 weeks ago

MartinFlores751 commented 2 weeks ago

ID Token validation reference: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation

  1. The iat Claim can be used to reject tokens that were issued too far away from the current time, limiting the amount of time that nonces need to be stored to prevent attacks. The acceptable range is Client specific.

Should we reject tokens with a distant iat?

  1. If a nonce value was sent in the Authentication Request, a nonce Claim MUST be present and its value checked to verify that it is the same value as the one that was sent in the Authentication Request. The Client SHOULD check the nonce value for replay attacks. The precise method for detecting replay attacks is Client specific.

If we add a nonce value, consider the above?

  1. If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate. The meaning and processing of acr Claim Values is out of scope for this specification.

If acr is used, consider this.

  1. If the auth_time Claim was requested, either through a specific request for this Claim or by using the max_age parameter, the Client SHOULD check the auth_time Claim value and request re-authentication if it determines too much time has elapsed since the last End-User authentication.

If auth_time is requested.

ll4strw commented 1 week ago

Hi @MartinFlores751 , I am wondering if you really mean ID Token or Access Token here, because they are different in both definition and use. Considering this is essentially a reseource server, the appropriate token to use would be an access token and ID tokens should not be let in. Or am I mistaken? If we are checking an access token via introspection from a server as in

https://github.com/irods/irods_client_http_api/blob/3824a09215a4b9b7e934178f2a7aa77b9654aa87/core/src/openid.cpp#L92

then the possible json-response members are defined here https://datatracker.ietf.org/doc/html/rfc7662#section-2.2. In this case,
it would be nice to check the token_type to make sure that we are actually dealing with an access token and not a different one. Similarly, it is also good idea to reject access tokens deemed too old by checking the iat. One problem that could arise when checking time-based values in the introspection response json members, including exp and nbf, is clock synchronizations. Usually it is not a bad idea to allow a clock skew of a couple of seconds.

When validating access tokens locally as in

https://github.com/irods/irods_client_http_api/blob/3824a09215a4b9b7e934178f2a7aa77b9654aa87/core/src/openid.cpp#L513

then the verifications steps are outlined here https://datatracker.ietf.org/doc/html/rfc9068#section-4. Most importantly, in both cases (local or remote introspection), an access token might present some scopes. These scopes will have to be somehow taken into account because this is an important feature of the concept of access tokens. I will elaborate more on this in #248.

MartinFlores751 commented 1 week ago

Hi @MartinFlores751 , I am wondering if you really mean ID Token or Access Token here, because they are different in both definition and use. Considering this is essentially a reseource server, the appropriate token to use would be an access token and ID tokens should not be let in. Or am I mistaken?

Yes, ID Tokens and Access Tokens do serve different purposes, and when operating as a protected resource, we should only accept access tokens.

However, as an artifact of our initial implementation, we do have the option to run as an OpenID Client:

https://github.com/irods/irods_client_http_api/blob/3824a09215a4b9b7e934178f2a7aa77b9654aa87/README.md?plain=1#L260-L264

In client mode, we do utilize ID Tokens, in both the Authorization Code Grant:

https://github.com/irods/irods_client_http_api/blob/3824a09215a4b9b7e934178f2a7aa77b9654aa87/endpoints/authentication/src/main.cpp#L311-L333

And the Resource Owner Password Credentials Grant (which is planned to be removed #363):

https://github.com/irods/irods_client_http_api/blob/3824a09215a4b9b7e934178f2a7aa77b9654aa87/endpoints/authentication/src/main.cpp#L570-L594

This issue is to address these use cases.

Of note, I plan on creating an issue on considering the removal of this mode. I'll link back to it when I do create it.

If we are checking an access token via introspection from a server as in

https://github.com/irods/irods_client_http_api/blob/3824a09215a4b9b7e934178f2a7aa77b9654aa87/core/src/openid.cpp#L92

then the possible json-response members are defined here https://datatracker.ietf.org/doc/html/rfc7662#section-2.2. In this case, it would be nice to check the token_type to make sure that we are actually dealing with an access token and not a different one.

This check was initially left out as it seems the only token_types defined are bearer or mac. If a token type is mac, I don't believe the HTTP API even processes this, as we only expect bearer type tokens. It certainly wouldn't hurt to check regardless. I'll check to see what passing an ID token to the introspection endpoint does too.

Similarly, it is also good idea to reject access tokens deemed too old by checking the iat.

Yes, I'm not sure if this is the only place this is mentioned, but I recall seeing rejecting based on old iat here: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation

Specifically:

  1. The iat Claim can be used to reject tokens that were issued too far away from the current time, limiting the amount of time that nonces need to be stored to prevent attacks. The acceptable range is Client specific.

While this does refer to ID Tokens, it wouldn't be a surprise to see another RFC or document talking about applying this to access tokens.

One problem that could arise when checking time-based values in the introspection response json members, including exp and nbf, is clock synchronizations. Usually it is not a bad idea to allow a clock skew of a couple of seconds.

We certainly do want to consider the option for leeway/skew for the validate_using_local_validation(...) function, as well as the iat based rejection.

For the introspection endpoint, based on this section here: https://datatracker.ietf.org/doc/html/rfc7662#section-4 I believe that the iat, exp, and nbf claims are handled by the authorization server. Unless the leeway the endpoint has differs from the expected, it might be better to let the authorization server handle the claims.

If we can find a document talking about rejecting Access Tokens based on the iat claim, we can certainly add it.

When validating access tokens locally as in

https://github.com/irods/irods_client_http_api/blob/3824a09215a4b9b7e934178f2a7aa77b9654aa87/core/src/openid.cpp#L513

then the verifications steps are outlined here https://datatracker.ietf.org/doc/html/rfc9068#section-4. Most importantly, in both cases (local or remote introspection), an access token might present some scopes. These scopes will have to be somehow taken into account because this is an important feature of the concept of access tokens. I will elaborate more on this in #248.

Yes, I believe this also shows up in the security best practices draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#name-access-token-privilege-rest

ll4strw commented 6 days ago

Apologies for the confusion. The authorization code grant type should exchange a code for an access token. Usually both an access token and an ID token are returned, optionally a refresh token. The API docs in this project state that at the end of the code grant flow an access token will be returned and made visible in the user browser. At what stage is the ID token verified in this scenario and why if the purpose was just to get an access token? I am sorry if I am mistakenly making wrong assumptions and thanks in advance.

MartinFlores751 commented 4 days ago

Apologies for the confusion. ... I am sorry if I am mistakenly making wrong assumptions and thanks in advance.

No problem at all! All questions are very much welcome.

The authorization code grant type should exchange a code for an access token. Usually both an access token and an ID token are returned, optionally a refresh token. The API docs in this project state that at the end of the code grant flow an access token will be returned and made visible in the user browser.

Yes, this is all correct, though the API documentation should have the phrasing changed, since it's misleading. I'll make an issue for that shortly. (See #374)

While we do return a bearer token, what we make visible in the browser is not an ID Token or Access Token. We give back a token generated by the HTTP API, similar to what is used when you perform basic authentication using your iRODS username and password for the HTTP API.

At what stage is the ID token verified in this scenario and why if the purpose was just to get an access token?

The ID Token is verified before performing the mapping of the OpenID User to an iRODS user. We use the ID Token for the mapping of the user in client mode of the HTTP API, as it should contain more information to map against.

Once the OpenID user is mapped to an iRODS user, we check that the iRODS user exists, and then give back the HTTP API token. The OpenID/OAuth Access Token is not used in client mode.

No further OpenID commutations are made in client mode after the HTTP API token is generated and returned.


Hopes this answers your question on how and why we use ID Tokens in client mode, as well as your questions on the use of access tokens.

If there's any more questions, feel free to ask.

ll4strw commented 4 days ago

Got it. Indeed the docs were misleading.

Let's suppose the IAM server is external and managed separately from the iRODS and the HTTP API servers. What would happen if a user authenticated on the IAM server with username rods? Would this be assigned admin privileges to the underlying iRODS system by the HTTP API server just because the username claim in the ID token happened to match the local iRODS admin username?

Is the ID token then essentially (and mistakenly) performing authorization?

Hence my interest in how the HTTP API server utilizes ID and access tokens and the introduction of scopes in the authorization workflow.

trel commented 4 days ago

No - we would actively map information from the IAM to an iRODS username. We’ll get the multiple scenarios laid out better in the documentation.

Until then - this 2x2 is the best we have so far…

https://slides.com/irods/sc24-irods-http-api-and-openid-connect#/9