Unable to exchange token #36392
-
|
Hey everyone, I am utilizing the following authentication flow to retrieve a token on behalf of a user in my backend, utilizing my BACKEND client. Afterwards, I attempt to exchange this token for another one for my FRONTEND client, thereby enabling my FRONTEND client to refresh the session from my frontend. I am using the token exchange feature to retrieve an active session using this request: curl -X POST "http://localhost:8090/realms/usp/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=BACKEND \
-d "client_secret=BACKENDSECRET" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "requested_subject=userId"Which returns a new session like that: {
"access_token": "<access_token_value>",
"refresh_token": "<refresh_token_value>",
"expires_in": <expires_in_value>,
"refresh_expires_in": <refresh_expires_in_value>
}If I now use that access token and try to exchange the token for one for my FRONTEND client using the following request: curl -X POST \
-d "client_id=FRONTEND" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=<access_token_value>" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
--data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:refresh_token" \
http://localhost:8090/realms/usp/protocol/openid-connect/tokenI receive the following error: {
"error": "access_denied",
"error_description": "Client is not the holder of the token"
}If alternatively I use this request instead of the last one: curl -X POST \
-d "client_id=BACKEND" \
-d "client_secret=BACKENDSECRET" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=<access_token_value>" \
--data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:refresh_token" \
-d "audience=FRONTEND" \
http://localhost:8090/realms/usp/protocol/openid-connect/tokenI receive a session {
"access_token":"<access_token_value2>",
"expires_in":300,
"refresh_expires_in":1800,
"refresh_token":"",
"token_type":"Bearer",
"not-before-policy":0,
"session_state":"a7c5268b-7a70-4420-9351-831e335bf145",
"scope":"profile email",
"issued_token_type":"urn:ietf:params:oauth:token-type:refresh_token"
}And decoding the access token looks similar to this: {
"exp": 1736778127,
"iat": 1736777827,
"jti": "ae4be389-deb1-4bb4-ad3b-9e8c3a5b636",
"iss": "http://localhost:8090/realms/usp",
"aud": [
"account",
"FRONTEND"
],
"sub": "b66f2a43-66a8-1097-a0c9-de627ec58690",
"typ": "Bearer",
"azp": "BACKEND",
"sid": "a7c5268b-7a70-1344-9351-831e4356bf145",
"acr": "1",
"allowed-origins": [
"http://localhost",
"http://localhost:4200"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"USER",
"default-roles-usp"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
...
}As we can see the FRONTEND client is now listed in the audience array. If I now try again with this request curl -X POST "http://localhost:8090/realms/usp/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=<access_token_value2>" \
-d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
-d "client_id=FRONTEND"I receive the same error as before. {
"error": "access_denied",
"error_description": "Client is not the holder of the token"
}
I am using Keycloak 26.0.7 I enabled the token exchange feature in keycloak, defined the token exchange permission, created a new policy, and configured my client IDs as described here https://www.keycloak.org/securing-apps/token-exchange and I tried everything another user tried here https://stackoverflow.com/questions/63948830/keycloak-reauthenticate-an-authenticated-user-with-a-different-client Does anyone have an idea on how I can retrieve a session on behalf of a user and another client so that the user is able to refresh the session using the other client? Thanks in advance! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
|
If you can solve this I would love to know how! I have been battling this for two weeks, with every variation of settings and so far nothing works as it should. |
Beta Was this translation helpful? Give feedback.
-
|
@c0ball What you are trying to achieve is not possible. The holder of the token is your backend client. When you pass that token to your Frontend, which seems to be a public client, the frontend won't be able to exchange that token again, as it is not the holder. That is a limitation (more a security feature): public clients can only exchange on to themselves if they are the token holder |
Beta Was this translation helpful? Give feedback.
@c0ball What you are trying to achieve is not possible. The holder of the token is your backend client. When you pass that token to your Frontend, which seems to be a public client, the frontend won't be able to exchange that token again, as it is not the holder.
That is a limitation (more a security feature): public clients can only exchange on to themselves if they are the token holder