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

Token exchange support #165

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 124 additions & 1 deletion docs/source/contents/conf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ An example::
- implicit
- urn:ietf:params:oauth:grant-type:jwt-bearer
- refresh_token
- urn:ietf:params:oauth:grant-type:token-exchange
claim_types_supported:
- normal
- aggregated
Expand Down Expand Up @@ -486,7 +487,8 @@ An example::
"supports_minting": ["access_token", "refresh_token"]
}
},
"expires_in": 43200
"expires_in": 43200,
"audience": ['https://www.example.com']
}
}
},
Expand Down Expand Up @@ -661,6 +663,127 @@ the following::
}
}

==============
Token exchange
==============
There are two possible ways to configure Token Exchange in OIDC-OP, globally and per-client.
For the first case the configuration is passed in the Token Exchange handler throught the
`urn:ietf:params:oauth:grant-type:token-exchange` dictionary in token's `grant_types_supported`.

If present, the token exchange configuration may contain a `policy` dictionary
that defines the behaviour for each subject token type. Each subject token type
is mapped to a dictionary with the keys `callable` (mandatory), which must be a
python callable or a string that represents the path to a python callable, and
`kwargs` (optional), which must be a dict of key-value arguments that will be
passed to the callable.

The key `""` represents a fallback policy that will be used if the subject token
type can't be found. If a subject token type is defined in the `policy` but is
not in the `subject_token_types_supported` list then it is ignored.

```
"grant_types_supported":{
"urn:ietf:params:oauth:grant-type:token-exchange": {
"class": "oidcop.oauth2.token.TokenExchangeHelper",
"kwargs": {
"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:access_token": {
"callable": "/path/to/callable",
"kwargs": {
"audience": ["https://example.com"],
"scopes": ["openid"]
}
},
"urn:ietf:params:oauth:token-type:refresh_token": {
"callable": "/path/to/callable",
"kwargs": {
"resource": ["https://example.com"],
"scopes": ["openid"]
}
},
"": {
"callable": "/path/to/callable",
"kwargs": {
"scopes": ["openid"]
}
}
}
}
}
}
```

For the per-client configuration a similar configuration scheme should be present in the client's
metadata under the `token_exchange` key.

For example:

```
"token_exchange":{
"urn:ietf:params:oauth:grant-type:token-exchange": {
"class": "oidcop.oidc.token.TokenExchangeHelper",
"kwargs": {
"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:access_token": {
"callable": "/path/to/callable",
"kwargs": {
"audience": ["https://example.com"],
"scopes": ["openid"]
}
},
"urn:ietf:params:oauth:token-type:refresh_token": {
"callable": "/path/to/callable",
"kwargs": {
"resource": ["https://example.com"],
"scopes": ["openid"]
}
},
"": {
"callable": "/path/to/callable",
"kwargs": {
"scopes": ["openid"]
}
}
}
}
}
}
```

The policy callable accepts a specific argument list and must return the altered token exchange
request or raise an exception.

For example:

```
def custom_token_exchange_policy(request, context, subject_token, **kwargs):
if some_condition in request:
return TokenErrorResponse(
error="invalid_request", error_description="Some error occured"
)

return request
```

=======
Clients
Expand Down
41 changes: 41 additions & 0 deletions docs/source/contents/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,44 @@ oidc-op will return a json response like this::
"oLyRj7sJJ3XvAYjeDCe8rQ"
]
}

Token exchange
-------------

Here an example about how to exchange an access token for a new access token.

import requests

CLIENT_ID=""
CLIENT_SECRET=""
SUBJECT_TOKEN=""
REQUESTED_TOKEN_TYPE="urn:ietf:params:oauth:token-type:access_token"

data = {
"grant_type" : "urn:ietf:params:oauth:grant-type:token-exchange",
"requested_token_type" : f"{REQUESTED_TOKEN_TYPE}",
"client_id" : f"{CLIENT_ID}",
"client_secret" : f"{CLIENT_SECRET}",
"subject_token" : f"{SUBJECT_TOKEN}"
}
headers = {'Content-Type': "application/x-www-form-urlencoded" }
response = requests.post(
'https://example.com/OIDC/token', verify=False, data=data, headers=headers
)

oidc-op will return a json response like this::

{
"access_token": "eyJhbGciOiJFUzI1NiIsI...Bo6aQcOKEN-1U88jjKxLb-9Q",
"scope": "openid email",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"expires_in": 86400
}

In order to request a refresh token the value of `requested_token_type` should be set to
`urn:ietf:params:oauth:token-type:refresh_token`.

The [RFC-8693](https://datatracker.ietf.org/doc/html/rfc8693) describes the `audience` parameter that
defines the authorized targets of a token exchange request.
If `subject_token = urn:ietf:params:oauth:token-type:refresh_token` then `audience` should not be
included in the token exchange request.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Idpy OIDC-op implements the following standards:
* `OpenID Connect Back-Channel Logout 1.0 <https://openid.net/specs/openid-connect-backchannel-1_0.html>`_
* `OpenID Connect Front-Channel Logout 1.0 <https://openid.net/specs/openid-connect-frontchannel-1_0.html>`_
* `OAuth2 Token introspection <https://tools.ietf.org/html/rfc7662>`_
* `OAuth2 Token exchange <https://datatracker.ietf.org/doc/html/rfc8693>`_


It also comes with the following `add_on` modules.
Expand Down
2 changes: 1 addition & 1 deletion src/oidcop/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ def __init__(
"client_secret_basic",
"client_secret_jwt",
"private_key_jwt",
]
],
},
},
"userinfo": {
Expand Down
5 changes: 4 additions & 1 deletion src/oidcop/oauth2/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ def process_request(self, request=None, release: Optional[list] = None, **kwargs
_resp.update(_info)
_resp.weed()

_claims_restriction = grant.claims.get("introspection")
_claims_restriction = _context.claims_interface.get_claims(
_session_info["session_id"], scopes=_token.scope, claims_release_point="introspection"
)

if _claims_restriction:
user_info = _context.claims_interface.get_user_claims(
_session_info["user_id"], _claims_restriction
Expand Down
Loading