Skip to content
This repository has been archived by the owner on Jun 23, 2023. It is now read-only.

Token Exchange support #162

Open
ctriant opened this issue Nov 26, 2021 · 11 comments
Open

Token Exchange support #162

ctriant opened this issue Nov 26, 2021 · 11 comments

Comments

@ctriant
Copy link
Contributor

ctriant commented Nov 26, 2021

So, we are in the process of adding Token Exchange support on oidc-op as described in RFC-8693 and we need feedback regarding the implementation.

More specifically, we consider the following scenario regarding the exchanging of Access Tokens with Refresh Tokens:

  1. A USER_A accesses CLIENT_A and retrieves an Access Token AT1 with a set of scopes that includes the offline_scope.
  2. CLIENT_A sends AT1 to CLIENT_B.
  3. Then CLIENT_B exchanges AT1 with a new Refresh Token RT1 with the same scope set, but sets the audience parameter of the request to be CLIENT_C and CLIENT_D.
  4. Finally CLIENT_B, CLIENT_C or CLIENT_D may use RT1 to get Access Token AT2 with the same or fewer scopes (and optionally with a different audience) to access protected resource X. Equivalently, AT2 will be owned by the client that issued the new Token Exchange request and every client (if any) that will be stated in the audience parameter will be allowed to use it.

Some observations on the aforementioned scenario:

  • During step 1, the initial access token AT1 belongs to a session USER_A;;CLIENT_A in terms of oidc-op.
  • On the contrary, at step 3 the exchanged refresh token RT1 should be mapped in a different client in order for CLIENT_B to be able to use it. This in terms of oidc-op is interpreted as a new session USER_A;;CLIENT_B where the token should be assigned.
  • In step 4, only the owner and the corresponding audience of token RT1 are allowed to use it. Currently, oidc-op retrieves the session that RT1 is mapped to and checks if the client_id stated in the request matches the client of the session. This check should be modified in order to include a check upon the audience of the used token.
  • In RFC-8693 there is no strict definition of what the audience (or even resource ) parameter should represent. For now, we intend to map the audience parameter with oidc-op client_id.

Some potential conflicts in case of multiple audiences:

  • What happens if we decide to support revocation of token upon usage? The first client, out of the set of the legitimate clients that are allowed to use the token, restricts the others from using it.
@nsklikas
Copy link
Contributor

nsklikas commented Dec 13, 2021

The way I see it a token exchange request is an exchange between a token with a set of token_type/scope/resource/audience to a different token with another set of token_type/scope/resource/audience. The task of the library is to :

  • Allow or disallow the request.
  • Produce the new token if needed.

I see 2 big problems with this:

  1. How will we manage sessions, E.g.:
    • If client_1 gives an access token to client_2 and client_2 exchanges it who will the new token belong to?
    • If client_1 exchanges an access token (A) for a new token (B) and then the grant that created token (A) gets revoked, what happens to (B) and its children-tokens.
  2. How will we decide if a mapping between a set of token_type/scope/resource/audience to another one is allowed? It is a complex problem that is hard to exhaust with simple configurations.

For (1):

  • When a token (A) is exchanged for a token (B). Then a new session should be created. The new session must be somehow connected to the original session only for auditing reasons. IMHO the revocation of the parent session should not revoke the children.
  • If client_1 gives token (A) to client_2 and then client_2 uses token (A) to produce token (B), then token (B) must belong to client_2. This means that the new session/grant must be under client_2, have the claims to scopes mapping defined for client_2 and client_1 must not be able to revoke it.

For (2):

  • Token exchange should define a global and a per client policy.
  • Each policy will define different behaviors based on the provided token type (and the requested token type?).
  • We need to allow the user to provide a callable that will decide whether a mapping is allowed.
  • We should also provide some out of the box behavior for each of scope/resource/audience, e.g.:
    • Allow subset of the originally requested.
    • Allow all.
    • Allow none.
    • Allow subset of a pre-configured set.

Other than that we should add a set of validations based on the subject_token_type. E.g.:

  • if the subject_token_type is refresh_token then we should not allow an audience other than the client_id to be requested, since according to the spec only the owner of the refresh token must have it.

@rohe @peppelinux any thoughts on this approach?

@peppelinux
Copy link
Member

Hi, happy to see this PR indeed

@nsklikas on your thoughts

  1. it depends by STS policy. I'm quite reluctant to share a token between two clients. I'd see token exchange like a way that a Client has to obtain a new access token usable to a RS, without requiring a new authentication (auth code). More like a SSO mechanism

  2. the new token has the power, once exchanged it works with its powers. that's how I used to think Token exchange, just personal approach.

1.1 I'd store only the event of exchange, at STS side, as a log. Yes, the revocation of the parent won't revocke the children
1.2 don't share token between different clients, and also the STS MUST be protected with a client authentication and this MUST match with the client_id/aud of the submitted token. That's how I'd do my implementation.

@rohe
Copy link
Collaborator

rohe commented Dec 13, 2021

@nsklikas My 2c

  • The entity that gets a token from an token exchange point owns the token
  • If the original token gets revoked the all the tokens flowing from it MUST be removed/revoked. Provided the original token and all coming from it is minted by the same client. If you move to another client all bets are off! For that reason I question whether a client should be allowed to mint tokens based on tokens from another client.
  • No suggestion for how to deal with mapping.
  • Presently creating a new token from another token does not lead to a new session being created. Note that session here is based on an authentication session. Meaning that if the same entity uses the exchange to get a new token based on an old one it has it's still within the same authentication session.
  • I can't see every allowing the minting of a refresh token based on an access token.
  • Yes, I think a callable is the way to deal with mapping.
  • The defaults sounds OK

@nsklikas
Copy link
Contributor

nsklikas commented Dec 15, 2021

it depends by STS policy. I'm quite reluctant to share a token between two clients. I'd see token exchange like a way that a Client has to obtain a new access token usable to a RS, without requiring a new authentication (auth code). More like a SSO mechanism

@peppelinux But wouldn't that be like using a refresh token? I understand your doubts about sharing tokens between clients, but I think that sometimes it is okay. If the user has given his consent, giving an access token to a trusted client may be okay (although I still don't like this approach). Perhaps this should be configurable as well.

If the original token gets revoked the all the tokens flowing from it MUST be removed/revoked. Provided the original token and all coming from it is minted by the same client. If you move to another client all bets are off! For that reason I question whether a client should be allowed to mint tokens based on tokens from another client.

@rohe Sure, I agree if the token comes from the same client it should be revoked as well.

@nsklikas
Copy link
Contributor

@peppelinux something went wrong and you edited my comment instead of writing an answer. I will paste it here for now:

Yes, I seen the british ehalthy system that adopts this kind of sharing between clients.
Regarding refresh token, no because of this use case:

The RP needs to exchange an acquired access_token (from ISS1) to a third-party RS. This RS have two way to handle this request:

acts like a RP to reauthenticate the user again (a kind of proxy, AA to RP side, RP to OP side)
expose a STS endpoint that validate the access_token issued by ISS1 and exchange it with an access_token issued by itself

regarding point 2, we have some singolatirites.
2.1 We use an access_token issued for another scopes, as a auth mechanism to release another access_token. But it's resonable to have STS for that!
2.2 the RP could exchange a token by itsself, without any use interaction. Yes it MUST have the consent of the user but we now that token exchange is a machine-to-machine flow

Do you think to give the access token to the user-agent and have it submitted by the user?
Yes, we can but also the RP could do something similar, with a procedura user-agent, isn't so?

@nsklikas
Copy link
Contributor

nsklikas commented Dec 15, 2021

Do you think to give the access token to the user-agent and have it submitted by the user?

No but issuing a refresh token requires the user 's consent, likewise giving an access token to another client and exchanging it for a refresh token must have the consent of the user

Also it's not clear to me what an STS is, is there some oauth2 spec describing it?

@peppelinux
Copy link
Member

STS is here
https://datatracker.ietf.org/doc/html/rfc8693

what's the specs you're considering for this token endpoint?

@nsklikas
Copy link
Contributor

Ok, I'm blind. Thanks.
With:

expose a STS endpoint that validate the access_token issued by ISS1 and exchange it with an access_token issued by itself

You mean that the RS should expose an STS endpoint? I'm not really sure what you mean.

@peppelinux
Copy link
Member

The STS is only an endpoint, it can be hosted anywhere

@ctriant
Copy link
Contributor Author

ctriant commented Dec 27, 2021

In #165 Token Exchange support is introduced based on the discussion here. I'm considering the following format for the token exchange related configurations.

"token": {
  "path": "token",
  "class": "oidcop.oidc.token.Token",
  "kwargs": {
    "token_exchange": {
      "subject_token_types_supported": [
        "urn:ietf:params:oauth:token-type:access_token",
        "urn:ietf:params:oauth:token-type:refresh_token",
        "urn:ietf:params:oauth:token-type:id_token"
      ],
      "requested_token_types_supported": [
        "urn:ietf:params:oauth:token-type:access_token",
        "urn:ietf:params:oauth:token-type:refresh_token",
        "urn:ietf:params:oauth:token-type:id_token"
      ],
      "policy": {
        "urn:ietf:params:oauth:token-type:refresh_token": {
          "callable": "/path/to/callable",
          "kwargs": {
            "audience": ["https://example.com"],
            "resource": [],
            "scopes": ["abc", "def"],
          }
       },
       "": {
         "callable": "/path/to/callable",
         "kwargs": {
           "audience": ["https://example.com"],
           "resource": [],
           "scopes": ["abc", "def"],
           "requested_token_types_supported": [
             "urn:ietf:params:oauth:token-type:access_token",
             "urn:ietf:params:oauth:token-type:refresh_token",
             "urn:ietf:params:oauth:token-type:id_token"
           ],
        }
      }
    }
  }
},

Any configuration under the token_exchange refers to the general configurations regarding the behavior of token exchange handler, i.e the supported subject token types.

Under the policy key exists any subject token specific policy, that is handled by a callable that accepts a set of arguments. If no specific subject token policy is defined then the default callable defined under "" is used.

Any comments?

@nsklikas
Copy link
Contributor

nsklikas commented Dec 28, 2021

So it will be

{
"token": {
  "path": "token",
  "class": "oidcop.oidc.token.Token",
  "kwargs": {
    "token_exchange": {
      "subject_token_types_supported": []  # A list of supported subject_token_types, if not defined then all token_types are allowed
      "requested_token_types_supported": []  # A list of supported requested_token_types, if not defined then all token_types are allowed
      "policy": {
       "token_type":  # If {token_type} is not in subject_token_types_supported, then this is ignored
       "token_type_2": {
         "callable":  # A string path to a callable or a callable object
         "kwargs":  # A dict with extra params that will be passed to the callable in addition to request, token, context, etc
       "": {}  # The default policy which we will fall back to in case a token_type is supported, but not defined in the policy dict
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants