Hello,
I'm unsure whether I'm misunderstanding something, making an error, or encountering a bug.
I'm trying to use OAuth Token Exchange with CAS, but CAS returns an incorrect audience value.
I'm using CAS in version 7.3.4 and i have two services: oidc-001 (localhost:5001) and oidc-002 (localhost:5002).
oidc-001 needs to perform a token exchange to obtain an AccessToken for oidc-002.
Here's my current process:
I perform an authorization code flow for oidc-001 and receive a JWT AccessToken from CAS.
I use this AccessToken as a bearer token to access the oidc-001 API.
The API extracts the JTI and initiates an OAuth token exchange with the JTI,
the audience of oidc-002, and a resource URL that matches the serviceId of oidc-002.
The exchange completes successfully,
but it returns an AccessToken with an audience of oidc-001.
I would expect the audience to be oidc-002 instead.
The request to oidc-001:
GET /proxy HTTP/1.1
Host: localhost:5001
User-Agent: curl/8.7.1
Accept: */*
Authorization: Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImNhcy1haFdMYUxqTSIsIm9yZy5hcGVyZW8uY2FzLnNlcnZpY2VzLlJlZ2lzdGVyZWRTZXJ2aWNlIjoiNTAwMSJ9.eyJhdWQiOiJvaWRjLTAwMSIsInN1YiI6InRyaWxsaWFuIiwiZ3JhbnRfdHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA5MC9jYXMvb2lkYyIsInJlc3BvbnNlX3R5cGUiOiJub25lIiwiZXhwIjoxNzcyNTYxNzgzLCJpYXQiOjE3NzI1MzI5ODMsImp0aSI6IkFULTE0LUpmbi1QRUI0Ujdzano5TnJKR1BJZnAxZHNRdnZqb21yLW9yYnN0YWNrIn0.MG6D5aqbSyGb-FOaSVfQOgVJI54tpc9j_dHdkspMhsyg9MRyZDWA6c0lh9zI-H-Ze12u9cOqMexYFF4CU6EQykx5TISz61UU0C2GZJizrA7c1z-_GrdozbAs1a02mVHCjZoeS-GAr2DIY8mAD_Guykt623ykIW7ML1Dya6rKLKCJayigYOut8vXRMTne50DwOB0gHHnKrHuoGf7YCO-1QUZbYQOhe021nvx2cXiSyOF1rYMOdhNRco23ceQonC3DhzIvn_BpnRqSQunjE_pV0BzEJAQzBa3KZE6Uylii1y7SAOV3R_jkUtKycbC2_h7N6Z_LMu2lW3eH1VaoYWOK2Q
The decoded AccessToken looks like this:
{
"aud": "oidc-001",
"sub": "trillian",
"grant_type": "authorization_code",
"iss": "
http://localhost:8090/cas/oidc",
"response_type": "none",
"exp": 1772561783,
"iat": 1772532983,
"jti": "AT-14-Jfn-PEB4R7sjz9NrJGPIfp1dsQvvjomr-orbstack"
}
The service oidc-001 makes now the request for the token exchange:
POST /cas/oidc/oidcAccessToken HTTP/1.1
Host: localhost:8090
User-Agent: Go-http-client/1.1
Content-Length: 319
Content-Type: application/x-www-form-urlencoded
audience=service-d&client_id=oidc-001&client_secret=secret&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=http%3A%2F%2Flocalhost%3A5002%2F&scope=openid&subject_token=AT-14-Jfn-PEB4R7sjz9NrJGPIfp1dsQvvjomr-orbstack&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token
The fields better formatted:
audience: service-d
client_id: oidc-001
client_secret:secret
grant_type: urn:ietf:params:oauth:grant-type:token-exchange
resource:
http://localhost:5002/scope: openid
subject_token: AT-14-Jfn-PEB4R7sjz9NrJGPIfp1dsQvvjomr-orbstack
subject_token_type: urn:ietf:params:oauth:token-type:access_token
Now we get the response from CAS:
HTTP/1.1 200
Server: Apereo CAS.
{"access_token":"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImNhcy1haFdMYUxqTSIsIm9yZy5hcGVyZW8uY2FzLnNlcnZpY2VzLlJlZ2lzdGVyZWRTZXJ2aWNlIjoiNTAwMSJ9.eyJhdWQiOiJvaWRjLTAwMSIsInN1YiI6InRyaWxsaWFuIiwiZ3JhbnRfdHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA5MC9jYXMvb2lkYyIsInJlc3BvbnNlX3R5cGUiOiJub25lIiwiZXhwIjoxNzcyNTYyMzI1LCJpYXQiOjE3NzI1MzM1MjUsImp0aSI6IkFULTIyLWhiVG0yTkg2MTdKQ3MzUEdSSXNYYTNDZTNFMkphcUlvLW9yYnN0YWNrIn0.h4OvaThjPalkDTfE3WehbXJ4fzjpKSIX_I949j-pRIt71l9Knm_uYgOzcLZSR-n7pWzmquNlqNY9UOQBcO9TqNV2dC9od-eJTTMBUo2q1WVMiRqWBWLpK8-mR3SIZBjxiE5tY-es2LMyd3h_CH6646xqM98DIersYG6oYygg4FFslnagVDcdJlR8vouxDU0pEkBXyTOSPTXubdVvyYRM79fMiqgYPZmsMfRL9MqrB91vcljAN84bXJxwhCdtbnlLoTjtvEwW_6xKJv9XaBM97AJa9BUt5KdUlDqHCwVhQPzal4WHGm2a8osu1JPrm5zLQ_0xBxhzgB_hFEaMya37tg","token_type":"Bearer","expires_in":28800,"issued_token_type":"urn:ietf:params:oauth:token-type:access_token","scope":"openid"}.
The decoded AccessToken:
{
"aud": "oidc-001",
"sub": "trillian",
"grant_type": "authorization_code",
"iss": "
http://localhost:8090/cas/oidc",
"response_type": "none",
"exp": 1772562325,
"iat": 1772533525,
"jti": "AT-22-hbTm2NH617JCs3PGRIsXa3Ce3E2JaqIo-orbstack"
}
As we can see the access token has oidc-001 as audience instead of oidc-002.
The configuration of service oidc-001:
{
"@class": "org.apereo.cas.services.OidcRegisteredService",
"clientId": "oidc-001",
"clientSecret": "secret",
"serviceId": "^
http://localhost:5001/.*",
"name": "oidc-001",
"id": 5001,
"jwtAccessToken": true,
"audience": [
"java.util.HashSet",
[
"oidc-001"
]
],
"bypassApprovalPrompt": true,
"supportedResponseTypes": [
"java.util.HashSet",
[
"code"
]
],
"supportedGrantTypes": [
"java.util.HashSet",
[
"authorization_code",
"urn:ietf:params:oauth:grant-type:token-exchange"
]
],
"scopes": [
"java.util.HashSet",
[
"profile",
"openid",
"email"
]
],
"tokenExchangePolicy": {
"@class": "org.apereo.cas.support.oauth.services.DefaultRegisteredServiceOAuthTokenExchangePolicy",
"allowedActorTokenTypes": [
"java.util.HashSet",
[
"urn:ietf:params:oauth:token-type:access_token",
"urn:ietf:params:oauth:token-type:jwt"
]
],
"allowedResources": [
"java.util.HashSet",
[
"
http://localhost:5002/.*"
]
]
},
"usernameAttributeProvider": {
"@class": "org.apereo.cas.services.DefaultRegisteredServiceUsernameProvider"
},
"attributeReleasePolicy": {
"@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
}
}
The configuration of oidc-002:
{
"@class": "org.apereo.cas.services.OidcRegisteredService",
"clientId": "oidc-002",
"clientSecret": "secret",
"serviceId": "^
http://localhost:5002/.*",
"name": "oidc-002",
"id": 5002,
"jwtAccessToken": true,
"audience": [
"java.util.HashSet",
[
"oidc-002"
]
],
"bypassApprovalPrompt": true,
"supportedResponseTypes": [
"java.util.HashSet",
[
"code"
]
],
"supportedGrantTypes": [
"java.util.HashSet",
[
"authorization_code",
"urn:ietf:params:oauth:grant-type:token-exchange"
]
],
"scopes": [
"java.util.HashSet",
[
"profile",
"openid",
"email"
]
],
"tokenExchangePolicy": {
"@class": "org.apereo.cas.support.oauth.services.DefaultRegisteredServiceOAuthTokenExchangePolicy",
"allowedActorTokenTypes": [
"java.util.HashSet",
[
"urn:ietf:params:oauth:token-type:access_token",
"urn:ietf:params:oauth:token-type:jwt"
]
],
"allowedResources": [
"java.util.HashSet",
[
"
http://localhost:5001/.*"
]
]
},
"usernameAttributeProvider": {
"@class": "org.apereo.cas.services.DefaultRegisteredServiceUsernameProvider"
},
"attributeReleasePolicy": {
"@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
}
}
Is this the expected behavior? Is my request or configuration incorrect? Or is this a bug?